window.__ModuleLoader__.load({ id: "dsh-netxops", factory: (require) => { var module = { exports: {} }; var exports = module.exports; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __hasOwnProp = Object.prototype.hasOwnProperty; function __accessProp(key) { return this[key]; } var __toCommonJS = (from) => { var entry = (__moduleCache ??= new WeakMap).get(from), desc; if (entry) return entry; entry = __defProp({}, "__esModule", { value: true }); if (from && typeof from === "object" || typeof from === "function") { for (var key of __getOwnPropNames(from)) if (!__hasOwnProp.call(entry, key)) __defProp(entry, key, { get: __accessProp.bind(from, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } __moduleCache.set(from, entry); return entry; }; var __moduleCache; var __returnValue = (v) => v; function __exportSetter(name, newValue) { this[name] = __returnValue.bind(null, newValue); } var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true, configurable: true, set: __exportSetter.bind(all, name) }); }; // src/client/index.ts var exports_client = {}; __export(exports_client, { inject: () => inject, apply: () => apply }); module.exports = __toCommonJS(exports_client); // src/client/NetxopsCard.tsx var import_react = require("react"); // src/client/styles.ts var STYLE_ID = "dsh-netxops-card-css"; var CSS = ` .dsh-nx-card{list-style:none;border:.5px solid var(--dsw-alias-border-l4);border-radius:16px;background:var(--dsw-alias-bg-layer-3);transition:border-color .16s,background .16s} .dsh-nx-card:hover{border-color:var(--dsw-alias-label-dimmed)} .dsh-nx-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)} .dsh-nx-header{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px} .dsh-nx-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px} .dsh-nx-headText{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px} .dsh-nx-name{font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)} .dsh-nx-desc{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary)} .dsh-nx-pending{flex:none;border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;font-weight:500;white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)} .dsh-nx-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s} .dsh-nx-chevronOpen{transform:rotate(180deg)} .dsh-nx-body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px} .dsh-nx-readOnly{margin:12px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)} .dsh-nx-field{display:flex;flex-direction:column;gap:6px;padding:12px 0} .dsh-nx-field+.dsh-nx-field{border-top:.5px solid var(--dsw-alias-border-l2)} .dsh-nx-fieldHead{display:flex;align-items:center;gap:8px} .dsh-nx-label{flex:1;min-width:0;font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)} .dsh-nx-badges{display:inline-flex;align-items:center;gap:8px} .dsh-nx-badge{border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;white-space:nowrap;font-weight:500;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)} .dsh-nx-badgeMuted{border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;white-space:nowrap;color:var(--dsw-alias-label-tertiary)} .dsh-nx-reset{border:none;background:none;padding:0;font:inherit;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary);cursor:pointer} .dsh-nx-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)} .dsh-nx-input{height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font:inherit;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary)} .dsh-nx-input:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary)} .dsh-nx-inputInvalid{border-color:var(--dsw-alias-label-error)} .dsh-nx-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)} .dsh-nx-invalid{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-error)} .dsh-nx-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 0 4px;border-top:.5px solid var(--dsw-alias-border-l2)} .dsh-nx-failed{flex:1;min-width:0;margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-error)} .dsh-nx-btn{appearance:none;border:1px solid transparent;border-radius:8px;padding:5px 14px;font:inherit;font-size:13px;line-height:1.5;cursor:pointer} .dsh-nx-discard{border-color:var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-secondary)} .dsh-nx-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)} .dsh-nx-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)} .dsh-nx-btn:disabled{opacity:.4;cursor:default} `; function ensureStyles() { if (typeof document === "undefined") return; if (document.getElementById(STYLE_ID)) return; const tag = document.createElement("style"); tag.id = STYLE_ID; tag.textContent = CSS; document.head.appendChild(tag); } // src/client/NetxopsCard.tsx var jsx_dev_runtime = require("react/jsx-dev-runtime"); function ValueField(props) { return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", { className: "dsh-nx-field", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", { className: "dsh-nx-fieldHead", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("label", { className: "dsh-nx-label", htmlFor: props.id, children: props.label }, undefined, false, undefined, this), props.field.overridden ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-badges", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-badge", children: props.overriddenLabel }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("button", { type: "button", className: "dsh-nx-reset", disabled: props.disabled, onClick: props.onReset, children: props.resetLabel }, undefined, false, undefined, this) ] }, undefined, true, undefined, this) : null ] }, undefined, true, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("input", { id: props.id, className: props.field.invalid ? "dsh-nx-input dsh-nx-inputInvalid" : "dsh-nx-input", type: "text", value: props.field.text, disabled: props.disabled, "aria-invalid": props.field.invalid || undefined, onChange: (event) => { props.onEdit(event.target.value); } }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("p", { className: props.field.invalid ? "dsh-nx-invalid" : "dsh-nx-hint", children: props.field.invalid ? props.invalidLabel : props.hint }, undefined, false, undefined, this) ] }, undefined, true, undefined, this); } function NetxopsCard(props) { ensureStyles(); const { t } = props; const state = props.useNetxopsCard((snapshot) => snapshot); const [open, setOpen] = import_react.useState(false); const saveStarted = import_react.useRef(false); import_react.useEffect(() => { if (state.saving) { saveStarted.current = true; return; } if (!saveStarted.current) return; saveStarted.current = false; if (!state.dirty && !state.failed) setOpen(false); }, [state.dirty, state.failed, state.saving]); if (!state.available) return null; const title = t("title"); const disabled = !state.writable; const blocked = !state.dirty || state.invalid || state.saving; return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("li", { className: open ? "dsh-nx-card dsh-nx-cardOpen" : "dsh-nx-card", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("button", { type: "button", className: "dsh-nx-header", "aria-expanded": open, "aria-label": `${t(open ? "collapse" : "expand")}: ${title}`, onClick: () => { setOpen(!open); }, children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-headText", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-name", children: title }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-desc", children: t("description") }, undefined, false, undefined, this) ] }, undefined, true, undefined, this), state.dirty ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-pending", children: t("unsaved") }, undefined, false, undefined, this) : null, /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: open ? "dsh-nx-chevron dsh-nx-chevronOpen" : "dsh-nx-chevron", "aria-hidden": true, children: "▾" }, undefined, false, undefined, this) ] }, undefined, true, undefined, this), open ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", { className: "dsh-nx-body", children: [ !state.writable ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV("p", { className: "dsh-nx-readOnly", role: "status", children: t("readOnly") }, undefined, false, undefined, this) : null, /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", { className: "dsh-nx-field", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", { className: "dsh-nx-fieldHead", children: [ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("label", { className: "dsh-nx-label", htmlFor: "netxops-api-token", children: t("apiToken") }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: "dsh-nx-badges", children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", { className: state.apiTokenConfigured ? "dsh-nx-badge" : "dsh-nx-badgeMuted", children: state.apiTokenConfigured ? t("apiTokenSet") : t("apiTokenUnset") }, undefined, false, undefined, this) }, undefined, false, undefined, this) ] }, undefined, true, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("input", { id: "netxops-api-token", className: "dsh-nx-input", type: "password", autoComplete: "off", value: state.apiToken.text, disabled: !state.apiTokenWritable, onChange: (event) => { props.edit("apiToken", event.target.value); } }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("p", { className: "dsh-nx-hint", children: t("apiTokenHint") }, undefined, false, undefined, this) ] }, undefined, true, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV(ValueField, { id: "netxops-api-url", label: t("apiUrl"), hint: t("apiUrlHint"), field: state.apiUrl, overriddenLabel: t("overridden"), resetLabel: t("reset"), invalidLabel: t("invalid"), disabled, onEdit: (text) => { props.edit("apiUrl", text); }, onReset: () => { props.resetField("apiUrl"); } }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV(ValueField, { id: "netxops-lang", label: t("lang"), hint: t("langHint"), field: state.lang, overriddenLabel: t("overridden"), resetLabel: t("reset"), invalidLabel: t("invalid"), disabled, onEdit: (text) => { props.edit("lang", text); }, onReset: () => { props.resetField("lang"); } }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV(ValueField, { id: "netxops-python", label: t("pythonCommand"), hint: t("pythonCommandHint"), field: state.pythonCommand, overriddenLabel: t("overridden"), resetLabel: t("reset"), invalidLabel: t("invalid"), disabled, onEdit: (text) => { props.edit("pythonCommand", text); }, onReset: () => { props.resetField("pythonCommand"); } }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", { className: "dsh-nx-footer", children: [ state.failed ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV("p", { className: "dsh-nx-failed", role: "status", children: t("saveFailed") }, undefined, false, undefined, this) : null, /* @__PURE__ */ jsx_dev_runtime.jsxDEV("button", { type: "button", className: "dsh-nx-btn dsh-nx-discard", disabled: !state.dirty || state.saving, onClick: props.discard, children: t("discard") }, undefined, false, undefined, this), /* @__PURE__ */ jsx_dev_runtime.jsxDEV("button", { type: "button", className: "dsh-nx-btn dsh-nx-save", disabled: blocked, onClick: props.save, children: t(state.saving ? "saving" : "save") }, undefined, false, undefined, this) ] }, undefined, true, undefined, this) ] }, undefined, true, undefined, this) : null ] }, undefined, true, undefined, this); } // src/client/card-form.ts var import_dsh_client_store = require("@deepseek-ai/dsh-client-store"); function textField(field) { return { field, format: (value) => typeof value === "string" ? value : "", parse: (text) => { const trimmed = text.trim(); return trimmed === "" ? { kind: "clear" } : { kind: "set", value: trimmed }; } }; } class CardForm { scope; specs; secretSpecs; staged = new Map; listeners = new Set; saving = false; failed = false; constructor(scope, specs, secrets = []) { this.scope = scope; this.specs = new Map(specs.map((spec) => [spec.field, spec])); this.secretSpecs = new Map(secrets.map((spec) => [spec.field, spec])); scope.subscribe(() => { this.publish(); }); } bind(project) { const store = import_dsh_client_store.createSnapshotStore(project()); this.listeners.add(() => { store.set(project()); }); return store; } shell() { const snapshot = this.scope.getSnapshot(); const plan = this.plan(); return { available: snapshot.status === "ready", writable: snapshot.writable, dirty: plan.length > 0, invalid: plan.some((item) => item.run === undefined), saving: this.saving, failed: this.failed }; } field(field) { const staged = this.staged.get(field); if (this.secretSpecs.has(field)) { return { text: staged?.text ?? "", overridden: false, invalid: false }; } const spec = this.spec(field); if (staged === undefined) { return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false }; } const write = staged.clear ? { kind: "clear" } : spec.parse(staged.text); return { text: staged.text, overridden: write?.kind === "set", invalid: write === undefined }; } actions() { return { edit: (field, text) => { this.stage(field, { text, clear: false }); }, resetField: (field) => { this.stage(field, { text: this.spec(field).format(this.baseValue(field)), clear: true }); }, save: () => { this.save(); }, discard: () => { if (this.staged.size === 0 && !this.failed) return; this.staged.clear(); this.failed = false; this.publish(); } }; } async save() { const plan = this.plan(); const writes = plan.flatMap((item) => item.run === undefined ? [] : [item.run]); if (plan.length === 0 || this.saving || writes.length !== plan.length) return; this.saving = true; this.failed = false; this.publish(); let landed = true; for (const write of writes) { landed = await write() && landed; } if (landed) this.staged.clear(); this.saving = false; this.failed = !landed; this.publish(); } plan() { const plan = []; for (const [field, staged] of this.staged) { const secret = this.secretSpecs.get(field); if (secret !== undefined) { const value = staged.text.trim(); if (value !== "") plan.push({ field, run: () => secret.write(value) }); continue; } const spec = this.spec(field); if (staged.clear) { if (this.stored(field)) plan.push({ field, run: () => this.clear(field) }); continue; } if (staged.text === spec.format(this.sectionValue(field))) continue; const write = spec.parse(staged.text); if (write === undefined) plan.push({ field, run: undefined }); else if (write.kind === "clear") plan.push({ field, run: () => this.clear(field) }); else plan.push({ field, run: () => this.store(field, write.value) }); } return plan; } async clear(field) { await this.scope.unset(field); return !this.stored(field); } async store(field, value) { await this.scope.set(field, value); return this.userLayer()?.[field] === value; } stage(field, edit) { this.staged.set(field, edit); this.failed = false; this.publish(); } spec(field) { const spec = this.specs.get(field); if (spec === undefined) throw new Error(`netxops card has no field ${field}`); return spec; } snapshotOf() { return this.scope.getSnapshot(); } sectionValue(field) { return this.snapshotOf().value?.[field]; } baseValue(field) { return this.snapshotOf().base?.[field]; } userLayer() { return this.snapshotOf().user; } stored(field) { const user = this.userLayer(); return user !== undefined && Object.hasOwn(user, field); } publish() { for (const listener of this.listeners) listener(); } } // src/client/controller.ts var NETXOPS_NS = "netxops"; var DEFAULT_TOKEN_REF = "NETX_API_TOKEN"; var API_TOKEN_FIELD = "apiToken"; class NetxopsCardController { scope; ctx; form; store; credential = { ref: "", configured: false, writable: true }; constructor(scope, ctx) { this.scope = scope; this.ctx = ctx; this.form = new CardForm(scope, [textField("apiUrl"), textField("lang"), textField("pythonCommand")], [{ field: API_TOKEN_FIELD, write: (text) => this.writeToken(text) }]); this.store = this.form.bind(() => this.projection()); scope.subscribe(() => { this.readCredential(); }); this.readCredential(); } projection() { return { ...this.form.shell(), apiUrl: this.form.field("apiUrl"), lang: this.form.field("lang"), pythonCommand: this.form.field("pythonCommand"), apiToken: this.form.field(API_TOKEN_FIELD), apiTokenConfigured: this.credential.configured, apiTokenWritable: this.credential.writable }; } async readCredential() { const ref = refOf(this.scope.getSnapshot()); if (ref !== this.credential.ref) { this.credential = { ref, configured: false, writable: true }; this.store.set(this.projection()); } const response = await this.ctx.remote.credentials.describe([ref]); if (!response.ok || ref !== refOf(this.scope.getSnapshot())) return; const view = response.value[ref]; const next = { ref, configured: view?.configured ?? false, writable: view?.writable ?? true }; if (next.configured === this.credential.configured && next.writable === this.credential.writable) return; this.credential = next; this.store.set(this.projection()); } refreshCredential(ref) { if (ref !== this.credential.ref) return; this.readCredential(); } inject() { return { hooks: { netxopsCard: this.store }, ...this.form.actions() }; } async writeToken(value) { await this.ctx.remote.credentials.set(refOf(this.scope.getSnapshot()), value); await this.readCredential(); return this.credential.configured; } } function refOf(snapshot) { const declared = snapshot.value?.tokenCredentialRef; return declared !== undefined && declared.length > 0 ? declared : DEFAULT_TOKEN_REF; } // src/client/locales.ts var en = { title: "Netx Ops", description: "UME API endpoint and bearer token for mcp__netx__* tools.", apiUrl: "API URL", apiUrlHint: "netx REST root, e.g. http://127.0.0.1:8890", lang: "Language", langHint: "Passed as NETX_LANG (zh / en).", pythonCommand: "Python command", pythonCommandHint: "Executable that can run `python -m netx_mcp`.", apiToken: "API token", apiTokenHint: "Stored as credential NETX_API_TOKEN (never written into settings). Leave blank to keep the current token.", apiTokenSet: "Configured", apiTokenUnset: "Not set", overridden: "Overridden", reset: "Reset", invalid: "Invalid value", expand: "Expand", collapse: "Collapse", unsaved: "Unsaved", readOnly: "This document is read-only.", saveFailed: "Save failed — drafts kept for correction.", discard: "Discard", save: "Save", saving: "Saving…" }; var zh = { title: "Netx Ops", description: "UME API 地址与 Bearer Token,供 mcp__netx__* 工具使用。", apiUrl: "API 地址", apiUrlHint: "netx REST 根地址,例如 http://127.0.0.1:8890", lang: "语言", langHint: "传给 MCP 的 NETX_LANG(zh / en)。", pythonCommand: "Python 命令", pythonCommandHint: "能执行 `python -m netx_mcp` 的解释器。", apiToken: "API Token", apiTokenHint: "写入凭据 NETX_API_TOKEN(不会进 settings)。留空表示保留已有 token。", apiTokenSet: "已配置", apiTokenUnset: "未设置", overridden: "已覆盖", reset: "重置", invalid: "无效值", expand: "展开", collapse: "收起", unsaved: "未保存", readOnly: "当前文档只读。", saveFailed: "保存失败,草稿已保留以便修改。", discard: "丢弃", save: "保存", saving: "保存中…" }; // src/client/index.ts var LOCALE_NS = "settings.netxops"; var inject = [ "slots", "locale", "remote", "remote.credentials", "settingsScope" ]; function apply(ctx) { ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), "netxops: locales"); const card = new NetxopsCardController(ctx.settingsScope.bind({ namespace: NETXOPS_NS }), ctx); ctx.effect(() => ctx.remote.$on("credentials/reference-updated", (ref) => { card.refreshCredential(ref); }), "netxops: credential invalidations"); ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({ name: "settings.plugin.item", key: NETXOPS_NS, locale: LOCALE_NS, inject: () => card.inject() }, NetxopsCard)); } return module.exports; } });