@numa-tech/numa 1.14.15 → 1.14.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -4
- package/dist/application-onboarding/client.d.ts +2 -2
- package/dist/application-onboarding/client.js +4 -4
- package/dist/application-onboarding/client.js.map +1 -1
- package/dist/application-onboarding/commands.d.ts +1 -1
- package/dist/application-onboarding/commands.js +14 -5
- package/dist/application-onboarding/commands.js.map +1 -1
- package/dist/application-onboarding/schemas.d.ts +1 -4
- package/dist/application-onboarding/schemas.js +1 -1
- package/dist/application-onboarding/schemas.js.map +1 -1
- package/dist/application-onboarding/tui.js +1 -3
- package/dist/application-onboarding/tui.js.map +1 -1
- package/dist/command-catalog.js +2 -2
- package/dist/command-catalog.js.map +1 -1
- package/dist/gitops/client.d.ts +65 -0
- package/dist/gitops/client.js +76 -12
- package/dist/gitops/client.js.map +1 -1
- package/dist/gitops/schemas.d.ts +44 -0
- package/dist/gitops/schemas.js +12 -0
- package/dist/gitops/schemas.js.map +1 -1
- package/dist/platform-build-job-replans/client.d.ts +8 -0
- package/dist/platform-build-job-replans/commands.js +3 -1
- package/dist/platform-build-job-replans/commands.js.map +1 -1
- package/dist/platform-build-job-replans/schemas.d.ts +2 -0
- package/dist/platform-build-job-replans/schemas.js +1 -0
- package/dist/platform-build-job-replans/schemas.js.map +1 -1
- package/dist/platform-profile.d.ts +4 -0
- package/dist/platform-profile.js +3 -1
- package/dist/platform-profile.js.map +1 -1
- package/dist/web-console-page.d.ts +1 -1
- package/dist/web-console-page.js +5 -3
- package/dist/web-console-page.js.map +1 -1
- package/package.json +3 -3
- package/skills/numa-create-application/SKILL.md +5 -1
- package/skills/numa-create-application/agents/openai.yaml +1 -1
- package/skills/numa-create-application/evals/evals.json +25 -0
- package/skills/numa-create-application/references/checklist.md +2 -0
- package/skills/numa-create-application/references/numa-cli.md +6 -2
- package/skills/numa-create-application/references/promote-workflows.md +27 -1
- package/skills/numa-jenkins-deployment/SKILL.md +147 -0
- package/skills/numa-jenkins-deployment/agents/openai.yaml +4 -0
- package/skills/numa-jenkins-deployment/evals/evals.json +69 -0
- package/skills/numa-jenkins-deployment/references/cli-contract.md +120 -0
- package/skills/numa-jenkins-deployment/references/diagnostics.md +121 -0
- package/skills/numa-jenkins-deployment/references/release-policy.md +94 -0
- package/skills/numa-local-flux-deploy/SKILL.md +2 -1
- package/skills/numa-local-flux-deploy/agents/openai.yaml +1 -1
- package/skills/numa-local-flux-deploy/references/local-flux-release.md +4 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const CONSOLE_HTML = "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <title>Numa CLI \u63A7\u5236\u53F0</title>\n <link rel=\"stylesheet\" href=\"/assets/console.css\">\n</head>\n<body>\n <!--\n THESIS: A namespace-scoped operations ledger owns the release boundary and refuses a generic dashboard of disconnected cards.\n OWN-WORLD: Deep navy frame, mist work surface, paper records, cyan state signals, compact operational type, and flat control-plane materials.\n STORY: The operator sees their authorized boundary, understands resource and release state, then performs one capability-gated next action.\n FIRST VIEWPORT: Persistent rail and header frame a wide registry hero, six visible workspaces, namespace context, then a list-detail operating board with actions beside their record.\n FORM: Code-led control-room ledger, first choice; seed control-plane-ledger-v1.\n FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance\n -->\n <div id=\"app\" class=\"boot\"><div class=\"boot-mark\">N</div><p>\u6B63\u5728\u8FDE\u63A5\u672C\u5730\u63A7\u5236\u53F0\u2026</p></div>\n <script type=\"module\" src=\"/assets/console.js\"></script>\n</body>\n</html>";
|
|
2
2
|
export declare const CONSOLE_CSS = "\n:root{color-scheme:light;--ink:#0b1728;--navy:#07182b;--navy-2:#102943;--cyan:#0a9fa7;--cyan-soft:#d7f2f1;--mist:#eef4f8;--paper:#fbfdff;--line:#d7e2ea;--muted:#536b82;--amber:#e99a32;--red:#d94b58;--green:#147a5d;--shadow:0 18px 50px rgba(12,38,62,.1)}\n*{box-sizing:border-box}html,body,#app{min-height:100%}body{min-height:100vh;margin:0;background:var(--mist);color:var(--ink);font-family:\"Avenir Next\",\"Segoe UI\",system-ui,sans-serif}button,input,select,textarea{font:inherit}.mono,code,pre{font-family:\"SFMono-Regular\",Consolas,\"Liberation Mono\",monospace}.boot{min-height:100vh;display:grid;place-content:center;justify-items:center;gap:14px;color:var(--muted)}.boot-mark,.brand-mark{display:grid;place-items:center;background:var(--cyan);color:#00262a;font-weight:900}.boot-mark{width:50px;height:50px;border-radius:15px;box-shadow:0 0 0 8px rgba(14,183,191,.12)}\n.shell{min-height:100vh;display:grid;grid-template-columns:260px 1fr}.rail{position:sticky;top:0;height:100vh;padding:24px 18px;background:var(--navy);color:#d7e6f3;display:flex;flex-direction:column}.brand{display:flex;align-items:center;gap:12px;padding:8px 8px 26px}.brand-mark{width:38px;height:38px;border-radius:12px}.brand strong{display:block;letter-spacing:.04em}.brand small{display:block;margin-top:4px;color:#708aa3;font-size:10px;letter-spacing:.18em;text-transform:uppercase}.nav{display:grid;gap:7px}.nav button{border:0;background:transparent;color:#8da4b8;border-radius:11px;text-align:left;padding:11px 13px;cursor:pointer;display:flex;justify-content:space-between}.nav button:hover,.nav button.active{background:var(--navy-2);color:white}.nav button.active{box-shadow:inset 3px 0 var(--cyan)}.rail-foot{margin-top:auto;border:1px solid #203b55;border-radius:14px;padding:14px}.rail-foot .pulse{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--cyan);box-shadow:0 0 12px var(--cyan);margin-right:7px}.rail-foot p{margin:7px 0 0;color:#7791a8;font-size:11px;line-height:1.5}\n.main{min-width:0;min-height:100vh;display:flex;flex-direction:column}.topbar{height:76px;padding:0 34px;background:rgba(251,253,255,.94);border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:3;backdrop-filter:blur(12px)}.topbar h1{font-size:17px;margin:0}.eyebrow{font:700 10px/1.2 \"SFMono-Regular\",monospace;text-transform:uppercase;letter-spacing:.16em;color:var(--cyan)}.user-chip{display:flex;align-items:center;gap:10px}.avatar{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;background:var(--cyan-soft);color:#066b70;font-weight:800}.user-chip small{color:var(--muted)}.content{width:100%;max-width:1420px;flex:1;margin:0 auto;padding:30px 34px 56px}.hero{background:var(--navy);color:white;border-radius:22px;padding:28px 30px;display:grid;grid-template-columns:1fr auto;gap:24px;position:relative;overflow:hidden;box-shadow:var(--shadow)}.hero:after{content:\"\";position:absolute;right:-45px;top:-90px;width:320px;height:240px;border:34px solid rgba(14,183,191,.09);transform:rotate(-18deg);border-radius:48px}.hero h2{font-size:32px;line-height:1.08;margin:10px 0}.hero p{max-width:720px;color:#91aac0;margin:0;line-height:1.6}.session-track{align-self:center;display:flex;gap:0;position:relative;z-index:1}.track-node{min-width:112px;border:1px solid #2a4661;padding:13px 15px;background:#0b1e33}.track-node:first-child{border-radius:13px 0 0 13px}.track-node:last-child{border-radius:0 13px 13px 0}.track-node span{display:block;color:#7690a7;font-size:9px;text-transform:uppercase;letter-spacing:.12em}.track-node strong{display:block;margin-top:6px;font-size:13px}.track-node.live strong{color:#58e0df}\n.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:18px;margin-top:20px}.card{grid-column:span 4;background:var(--paper);border:1px solid var(--line);border-radius:17px;padding:20px;box-shadow:0 10px 30px rgba(25,54,79,.05)}.card.wide{grid-column:span 8}.card.full{grid-column:1/-1}.card h3{margin:0;font-size:15px}.card p{color:var(--muted);font-size:12px;line-height:1.6}.metric{font-size:28px;font-weight:750;margin-top:16px}.metric small{font-size:11px;color:var(--muted);font-weight:500}.tag{display:inline-flex;border-radius:999px;padding:5px 8px;font:700 9px/1 \"SFMono-Regular\",monospace;text-transform:uppercase;letter-spacing:.05em;background:var(--cyan-soft);color:#066b70}.tag.public{background:#edf1f5;color:#5d7082}.tag.role{background:#fff0d9;color:#9c5c09}.actions{display:flex;gap:8px;flex-wrap:wrap}.button{border:1px solid var(--line);border-radius:10px;background:white;color:var(--ink);padding:9px 12px;cursor:pointer;font-weight:700;font-size:12px}.button:hover{border-color:#9ab6c9;background:#f7fbfd}.button.primary{background:#0b949b;border-color:#0b949b;color:white}.button.danger{color:var(--red);border-color:#f0bec4}.button:disabled{opacity:.45;cursor:not-allowed}.field{display:grid;gap:6px}.field label{font-size:11px;color:var(--muted);font-weight:700}.field input,.field select,.field textarea{width:100%;border:1px solid var(--line);border-radius:10px;background:white;padding:10px 11px;color:var(--ink);outline:none}.field input:focus,.field textarea:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(14,183,191,.12)}.field textarea{min-height:110px;resize:vertical}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.form-grid .span-2{grid-column:1/-1}.command-toolbar{display:flex;gap:10px;margin-bottom:15px}.grow{flex:1}.mt-10{margin-top:10px}.mt-12{margin-top:12px}.mt-14{margin-top:14px}.command-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}.command{border:1px solid var(--line);border-radius:13px;padding:14px;background:white}.command-head{display:flex;gap:8px;align-items:center;justify-content:space-between}.command code{font-size:11px;color:#0b6f76}.command p{margin:9px 0}.command small{display:block;color:#8a9bab;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.output{background:#071522;color:#b8d1e2;border-radius:13px;padding:15px;min-height:170px;max-height:420px;overflow:auto;white-space:pre-wrap;font-size:11px;line-height:1.65}.notice{margin-top:14px;border-radius:11px;padding:11px 13px;font-size:12px}.notice.error{background:#fff0f2;color:#a52c3b;border:1px solid #f1c4ca}.notice.success{background:#e8f7f1;color:#176c54;border:1px solid #bce4d5}.login-gate{min-height:calc(100vh - 76px);display:grid;place-content:center;padding:30px}.login-card{width:min(520px,90vw);background:white;border:1px solid var(--line);border-radius:22px;padding:32px;box-shadow:var(--shadow)}.login-card h2{font-size:28px;margin:14px 0 9px}.login-card p{color:var(--muted);line-height:1.65}.hidden{display:none!important}\n.sync-board{display:grid;grid-template-columns:1fr 56px 1fr;align-items:stretch;margin-top:20px}.sync-stage{background:var(--paper);border:1px solid var(--line);border-radius:17px;padding:18px 20px;box-shadow:0 10px 30px rgba(25,54,79,.05)}.sync-stage strong{display:block;margin-top:8px;font-size:18px}.sync-stage p{margin:7px 0 0;color:var(--muted);font-size:11px}.sync-link{display:grid;place-items:center;color:var(--cyan);font-size:22px}.sync-link:before{content:\"\";height:2px;width:100%;background:linear-gradient(90deg,var(--cyan),#bad8dc)}.table-card{margin-top:18px;background:var(--paper);border:1px solid var(--line);border-radius:17px;box-shadow:0 10px 30px rgba(25,54,79,.05);overflow:hidden}.table-head{padding:18px 20px;display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--line)}.table-head h3{margin:0}.table-head p{margin:5px 0 0;color:var(--muted);font-size:11px}.account-table-wrap{overflow:auto}.account-table{width:100%;border-collapse:collapse;min-width:920px}.account-table th,.account-table td{text-align:left;padding:14px 16px;border-bottom:1px solid #e7eef3;vertical-align:middle}.account-table th{background:#f5f9fb;color:#536b82;font-size:10px;text-transform:uppercase;letter-spacing:.08em}.account-table tr:last-child td{border-bottom:0}.account-name strong{display:block;font-size:13px}.account-name small{display:block;margin-top:4px;color:var(--muted);font-size:10px}.quota-stack{min-width:210px;display:grid;gap:8px}.quota-line{display:grid;grid-template-columns:42px 1fr 38px;align-items:center;gap:7px;font-size:10px;color:var(--muted)}progress{width:100%;height:7px;border:0;border-radius:99px;overflow:hidden;background:#e7eef3}progress::-webkit-progress-bar{background:#e7eef3;border-radius:99px}progress::-webkit-progress-value{background:var(--cyan);border-radius:99px}progress::-moz-progress-bar{background:var(--cyan);border-radius:99px}.status-stack{display:flex;gap:6px;flex-wrap:wrap}.tag.success{background:#e8f7f1;color:var(--green)}.tag.failed{background:#fff0f2;color:var(--red)}.tag.waiting{background:#fff0d9;color:#8c5005}.tag.local{background:#e8f1fa;color:#32658f}.empty-state{padding:48px 20px;text-align:center;color:var(--muted)}.empty-state.account-request{margin:24px;border:2px solid #f0b5bd;border-radius:16px;background:#fff2f4;color:#8d2334;box-shadow:0 12px 30px rgba(167,44,64,.1)}.empty-state.account-request strong{display:block;margin-bottom:9px;font-size:16px}.empty-state.account-request span{display:block;font-size:13px;line-height:1.7}.table-meta{color:var(--muted);font-size:10px;line-height:1.55}.spin{animation:turn 1s linear infinite}.refresh-icon{display:inline-block;width:12px;height:12px;border:2px solid currentColor;border-left-color:transparent;border-radius:50%;vertical-align:-2px;position:relative}.refresh-icon:after{content:\"\";position:absolute;right:-3px;top:-2px;border-width:3px 0 3px 4px;border-style:solid;border-color:transparent transparent transparent currentColor;transform:rotate(-18deg)}@keyframes turn{to{transform:rotate(360deg)}}\n.registry-hero{background:linear-gradient(118deg,#06172a 0%,#0b2d42 67%,#0a4c55 100%)}.lifecycle{display:grid;grid-template-columns:repeat(5,70px);align-items:center;position:relative;z-index:1;margin-top:12px}.life-node{position:relative;text-align:center;color:#8ba7b8;font:700 8px/1.2 \"SFMono-Regular\",monospace;text-transform:uppercase;letter-spacing:.05em}.life-node:before{content:\"\";display:block;width:10px;height:10px;margin:0 auto 9px;border:2px solid #58e0df;border-radius:50%;background:#0a2638;box-shadow:0 0 0 4px rgba(88,224,223,.08)}.life-node:not(:last-child):after{content:\"\";position:absolute;height:1px;width:calc(100% - 22px);top:6px;left:calc(50% + 11px);background:linear-gradient(90deg,#58e0df,#31546a)}.registry-toolbar{display:grid;grid-template-columns:minmax(190px,1fr) auto minmax(190px,1fr) auto;gap:10px;align-items:end;margin-top:20px}.segment{display:flex;padding:3px;background:#dfe9ef;border-radius:11px}.segment button{border:0;background:transparent;padding:8px 13px;border-radius:8px;color:var(--muted);font-weight:750;font-size:11px;cursor:pointer}.segment button.active{background:white;color:#087d84;box-shadow:0 3px 12px rgba(7,24,43,.1)}.registry-board{display:grid;grid-template-columns:minmax(260px,32%) 1fr;min-height:560px;margin-top:14px;background:var(--paper);border:1px solid var(--line);border-radius:18px;overflow:hidden;box-shadow:var(--shadow)}.registry-list{border-right:1px solid var(--line);background:#f4f8fa}.registry-list-head{padding:15px 17px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center}.registry-items{padding:9px;display:grid;gap:6px;max-height:680px;overflow:auto}.registry-item{width:100%;border:1px solid transparent;background:transparent;border-radius:12px;padding:12px;text-align:left;cursor:pointer;display:grid;grid-template-columns:12px 1fr;gap:10px;color:var(--ink)}.registry-item:hover,.registry-item.active{background:white;border-color:var(--line);box-shadow:0 6px 20px rgba(18,49,71,.07)}.registry-dot{width:8px;height:8px;margin-top:4px;border-radius:50%;background:var(--amber);box-shadow:0 0 0 4px rgba(233,154,50,.1)}.registry-dot.online{background:var(--green);box-shadow:0 0 0 4px rgba(25,138,104,.1)}.registry-item strong,.registry-item small{display:block}.registry-item strong{font-size:12px}.registry-item small{margin-top:5px;color:var(--muted);font-size:10px}.registry-detail{min-width:0;padding:22px;display:flex;flex-direction:column}.registry-detail-head{display:flex;justify-content:space-between;align-items:start;gap:16px}.registry-detail h3{margin:5px 0 3px;font-size:20px}.registry-detail pre{flex:1;margin:16px 0;min-height:270px}.registry-actions{border-top:1px solid var(--line);padding-top:15px;display:grid;gap:12px}.install-row{display:grid;grid-template-columns:1fr 1fr auto;gap:9px}.registry-error{margin-top:14px}.button:focus-visible,.nav button:focus-visible,.registry-item:focus-visible,.segment button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid rgba(14,183,191,.3);outline-offset:2px}\n.registry-workspaces{display:flex;gap:2px;margin-top:22px;border-bottom:1px solid var(--line)}.registry-workspaces button{flex:1 1 auto;border:0;border-bottom:2px solid transparent;background:transparent;color:#536b82;padding:12px 10px;cursor:pointer;font-weight:720;font-size:12px}.registry-workspaces button:hover{color:var(--ink);background:#e4f0f3}.registry-workspaces button.active{color:#066f75;border-bottom-color:#0b949b}.registry-context{min-width:0;display:flex;flex-direction:column;justify-content:center;padding:7px 2px}.registry-context strong,.registry-context span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.registry-context span{margin-top:4px;color:var(--muted);font-size:11px}.registry-simple{margin-top:14px;min-height:430px;background:var(--paper);border:1px solid var(--line);border-radius:16px;padding:26px;box-shadow:0 12px 34px rgba(25,54,79,.08)}.registry-simple>h3{margin:0 0 8px;font-size:20px}.registry-simple>p{max-width:72ch;color:var(--muted);line-height:1.65}.governance-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin:26px 0;border-block:1px solid var(--line)}.governance-strip>div{min-width:0;padding:20px}.governance-strip>div+div{border-inline-start:1px solid var(--line)}.governance-strip strong,.governance-strip span{display:block;overflow-wrap:anywhere}.governance-strip strong{font-size:13px}.governance-strip span{margin-top:7px;color:var(--muted);font-size:11px}.registry-facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin:24px 0;background:var(--line)}.registry-facts>div{min-width:0;background:white;padding:18px}.registry-facts dt{color:var(--muted);font-size:11px}.registry-facts dd{margin:7px 0 0;overflow-wrap:anywhere;font-weight:700}.record-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:18px 0}.record-card{border:1px solid var(--line);border-radius:13px;background:white;padding:16px}.record-card h4{margin:0;font-size:14px}.record-card dl{display:grid;grid-template-columns:minmax(90px,.45fr) 1fr;gap:8px 12px;margin:14px 0 0;font-size:11px}.record-card dt{color:var(--muted)}.record-card dd{margin:0;overflow-wrap:anywhere;font-weight:650}.summary-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:16px 0}.summary-cell{border:1px solid var(--line);border-radius:12px;padding:14px;background:#f8fbfc}.summary-cell span,.summary-cell strong{display:block}.summary-cell span{color:var(--muted);font-size:10px}.summary-cell strong{margin-top:7px;overflow-wrap:anywhere;font-size:13px}.raw-disclosure{margin:14px 0}.raw-disclosure summary{cursor:pointer;color:#3e6078;font-size:11px;font-weight:700}.raw-disclosure .output{margin:9px 0 0;min-height:120px;max-height:280px}.permission-grid{display:flex;flex-wrap:wrap;gap:8px;margin:16px 0 22px}.permission-grid .tag{font-size:10px;padding:7px 9px}.wrap{overflow-wrap:anywhere;white-space:pre-wrap}.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}::selection{background:#0b949b;color:white}*{scrollbar-color:#8eb5c0 #e7eef3}input,textarea{caret-color:#0b949b}.output{font-variant-numeric:tabular-nums}\n@media(max-width:980px){.shell{grid-template-columns:78px 1fr}.rail{padding:18px 10px}.brand div:last-child,.nav button span:first-child,.rail-foot p,.rail-foot strong{display:none}.brand{justify-content:center}.nav button{justify-content:center}.card,.card.wide{grid-column:span 6}.hero{grid-template-columns:1fr}.session-track,.lifecycle{display:none}.registry-toolbar{grid-template-columns:1fr 1fr}.registry-board{grid-template-columns:1fr}.registry-list{border-right:0;border-bottom:1px solid var(--line)}.registry-items{max-height:260px}}\n@media(max-width:680px){.shell{display:block}.rail{height:auto;position:static;flex-direction:row;align-items:center;padding:10px 14px;overflow-x:auto}.brand{padding:0;margin-right:auto;flex:0 0 auto}.nav{display:flex}.nav button{padding:9px;flex:0 0 auto}.rail-foot{display:none}.topbar{padding:0 18px}.content{padding:20px 14px}.hero{padding:23px}.hero h2{font-size:25px}.card,.card.wide{grid-column:1/-1}.form-grid,.command-list,.registry-toolbar,.install-row{grid-template-columns:1fr}.form-grid .span-2{grid-column:auto}.user-chip div:not(.avatar){display:none}.sync-board{grid-template-columns:1fr}.sync-link{height:32px;transform:rotate(90deg)}.table-head{align-items:flex-start;flex-direction:column}.registry-detail{padding:17px}}\n@media(max-width:680px){.field input,.field select,.field textarea{font-size:16px}.governance-strip,.registry-facts,.record-grid,.summary-grid{grid-template-columns:1fr}.governance-strip>div+div{border-inline-start:0;border-block-start:1px solid var(--line)}.registry-simple{padding:19px}.registry-workspaces{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));border:1px solid var(--line);border-radius:13px;overflow:hidden}.registry-workspaces button{width:100%;border-bottom:1px solid var(--line);border-radius:0;padding:11px 8px}.registry-workspaces button.active{box-shadow:inset 3px 0 #0b949b;border-bottom-color:var(--line)}}\n@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important}}\n@media(prefers-reduced-motion:no-preference){.card,.hero{animation:arrive .35s ease both}@keyframes arrive{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}}\n";
|
|
3
|
-
export declare const CONSOLE_JS = "\nconst app=document.getElementById('app');let state=null;let csrf='';let active='overview';let output='\u9009\u62E9\u4E00\u6761\u547D\u4EE4\u6216\u8F93\u5165\u53C2\u6570\u540E\u8FD0\u884C\u3002';let chatgptLocal=null;let chatgptRemote=null;let chatgptLoading=false;let registryCapabilities=null;let registryNamespaces=[];let registryItems=[];let registryDetail=null;let registryAux=null;let registryMode='skill';let registryWorkspace='catalog';let registryNamespaceId=null;let registryName='';let registrySearch='';let registryLoading=false;let registryError='';let registryNotice='';const noAvailableAccountGuidance='\u5F53\u524D\u7528\u6237\u6CA1\u6709\u7ED1\u5B9A\u53EF\u7528\u7684 ChatGPT \u8D26\u53F7\u3002\u8BF7\u524D\u5F80 OA \u7CFB\u7EDF\u63D0\u4EA4 IT11 \u5DE5\u5355\uFF0C\u7533\u8BF7\u72EC\u4EAB\u6216\u5171\u4EAB\u7684 ChatGPT \u4E2A\u4EBA\u8D26\u53F7\u3002';\nconst esc=(v)=>String(v??'').replace(/[&<>\"']/g,(c)=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nasync function api(path,init={}){const headers={'Accept':'application/json',...(init.body?{'Content-Type':'application/json'}:{}),...(csrf?{'x-numa-console-csrf':csrf}:{})};const response=await fetch(path,{...init,headers:{...headers,...(init.headers||{})},credentials:'same-origin'});const body=await response.json().catch(()=>({error:{message:'\u54CD\u5E94\u4E0D\u662F JSON'}}));if(!response.ok)throw new Error(body.error?.message||body.stderr||body.stdout||('HTTP '+response.status));return body}\nasync function bootstrap(attempt=0){try{state=await api('/api/bootstrap');csrf=state.csrfToken;const callbackState=new URLSearchParams(location.search).get('login');const waitingForSuccess=callbackState==='complete'&&!state.loggedIn&&attempt<40;const waitingForFailure=callbackState==='failed'&&!state.loginError&&attempt<8;if(waitingForSuccess||waitingForFailure){app.innerHTML='<div class=\"boot\"><div class=\"boot-mark\">N</div><p>'+(callbackState==='complete'?'\u767B\u5F55\u6210\u529F\uFF0C\u6B63\u5728\u5EFA\u7ACB\u672C\u673A\u4F1A\u8BDD\u2026':'\u6B63\u5728\u8BFB\u53D6\u767B\u5F55\u5931\u8D25\u4FE1\u606F\u2026')+'</p></div>';setTimeout(()=>bootstrap(attempt+1),250);return}if(callbackState==='complete'&&!state.loggedIn&&!state.loginError)state.loginError='\u767B\u5F55\u51ED\u636E\u4EA4\u6362\u5C1A\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u767B\u5F55\u3002';if(callbackState==='failed'&&!state.loginError)state.loginError='Keycloak \u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u3002';if(callbackState)history.replaceState({},'',location.pathname);render()}catch(error){app.innerHTML='<div class=\"login-gate\"><div class=\"login-card\"><span class=\"tag role\">\u8FDE\u63A5\u5931\u8D25</span><h2>\u672C\u5730\u63A7\u5236\u53F0\u4E0D\u53EF\u7528</h2><p>'+esc(error.message)+'</p></div></div>'}}\nfunction navButton(id,label,mark){return '<button data-nav=\"'+id+'\" class=\"'+(active===id?'active':'')+'\"><span>'+label+'</span><span class=\"mono\">'+mark+'</span></button>'}\nfunction shell(content){const user=state.user||{};const label=user.username||user.email||'\u672A\u767B\u5F55';return '<div class=\"shell\"><aside class=\"rail\"><div class=\"brand\"><div class=\"brand-mark\">N</div><div><strong>Numa CLI</strong><small>Local control plane</small></div></div><nav class=\"nav\">'+navButton('overview','\u8FD0\u884C\u6982\u89C8','01')+navButton('registry','AI Registry','02')+navButton('chatgpt','ChatGPT \u8D26\u53F7','03')+navButton('config','\u57FA\u7840\u914D\u7F6E','04')+navButton('commands','\u547D\u4EE4\u80FD\u529B','05')+navButton('runner','\u6267\u884C\u5668','06')+'</nav><div class=\"rail-foot\"><strong><span class=\"pulse\"></span>\u4EC5\u76D1\u542C\u672C\u673A</strong><p>Token \u4E0D\u4F1A\u53D1\u9001\u7ED9\u7F51\u9875\u811A\u672C\uFF0C\u4E5F\u4E0D\u4F1A\u51FA\u73B0\u5728\u547D\u4EE4\u8F93\u51FA\u4E2D\u3002</p></div></aside><main class=\"main\"><header class=\"topbar\"><h1>'+sectionTitle()+'</h1><div class=\"user-chip\"><div class=\"avatar\">'+esc(label.slice(0,1).toUpperCase())+'</div><div><strong>'+esc(label)+'</strong><br><small>'+esc(state.loggedIn?'Keycloak \u4F1A\u8BDD\u6709\u6548':'\u9700\u8981\u767B\u5F55')+'</small></div></div></header>'+content+'</main></div>'}\nfunction sectionTitle(){return {overview:'\u8FD0\u884C\u6982\u89C8',registry:'AI Registry',chatgpt:'ChatGPT \u8D26\u53F7',config:'\u57FA\u7840\u914D\u7F6E',commands:'\u547D\u4EE4\u80FD\u529B',runner:'\u5B89\u5168\u6267\u884C\u5668'}[active]||'\u63A7\u5236\u53F0'}\nfunction loginGate(){const error=state?.loginError?'<div class=\"notice error\">'+esc(state.loginError)+'</div>':'';return '<div class=\"login-gate\"><div class=\"login-card\"><span class=\"tag role\">Keycloak PKCE</span><h2>\u767B\u5F55\u540E\u8FDB\u5165\u672C\u5730\u63A7\u5236\u53F0</h2><p>\u6D4F\u89C8\u5668\u53EA\u8D1F\u8D23\u5B8C\u6210\u8BA4\u8BC1\u548C\u64CD\u4F5C\u672C\u673A API\u3002\u8BBF\u95EE\u4EE4\u724C\u4FDD\u5B58\u5728 CLI \u7684\u79C1\u6709\u7F13\u5B58\u4E2D\uFF0C\u4E0D\u5199\u5165 localStorage\uFF0C\u4E5F\u4E0D\u8FD4\u56DE\u7ED9\u9875\u9762\u3002</p><div class=\"actions\"><button class=\"button primary\" data-action=\"login\">\u4F7F\u7528 Keycloak \u767B\u5F55</button></div><div id=\"notice\">'+error+'</div></div></div>'}\nfunction overview(){const u=state.user||{};const roles=[...(u.realmRoles||[]),...Object.values(u.clientRoles||{}).flat()];return '<div class=\"content\"><section class=\"hero\"><div><div class=\"eyebrow\">Authenticated workstation</div><h2>\u672C\u673A\u4F1A\u8BDD\u5DF2\u63A5\u5165\u7814\u53D1\u63A7\u5236\u9762</h2><p>\u4ECE\u540C\u4E00\u5904\u68C0\u67E5\u8EAB\u4EFD\u3001\u8C03\u6574 CLI \u914D\u7F6E\uFF0C\u5E76\u4EE5\u7ED3\u6784\u5316\u53C2\u6570\u8FD0\u884C\u53D7\u652F\u6301\u547D\u4EE4\u3002\u6240\u6709\u7F51\u7EDC\u8BF7\u6C42\u7EE7\u7EED\u7531 CLI \u6CE8\u5165 Bearer Token\u3002</p></div><div class=\"session-track\"><div class=\"track-node live\"><span>\u8EAB\u4EFD</span><strong>'+esc(u.username||'\u5DF2\u767B\u5F55')+'</strong></div><div class=\"track-node\"><span>API</span><strong>'+esc(state.config.apiBaseUrl?'\u5DF2\u914D\u7F6E':'\u672A\u914D\u7F6E')+'</strong></div><div class=\"track-node\"><span>\u89D2\u8272</span><strong>'+roles.length+'</strong></div></div></section><section class=\"grid\"><article class=\"card\"><div class=\"eyebrow\">Session</div><div class=\"metric\">'+esc(state.expiresAt?new Date(state.expiresAt*1000).toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'}):'\u2014')+' <small>\u8BBF\u95EE\u4EE4\u724C\u5230\u671F</small></div><p>\u9700\u8981\u65F6\u7531 CLI \u4F7F\u7528 refresh token \u81EA\u52A8\u7EED\u671F\u3002</p></article><article class=\"card\"><div class=\"eyebrow\">Commands</div><div class=\"metric\">'+state.commands.length+' <small>\u6761\u80FD\u529B\u5951\u7EA6</small></div><p>\u7F51\u9875\u6267\u884C\u5668\u53EA\u63A5\u53D7\u76EE\u5F55\u4E2D\u58F0\u660E\u7684\u547D\u4EE4\u3002</p></article><article class=\"card\"><div class=\"eyebrow\">Endpoint</div><div class=\"metric\">'+(state.config.apiBaseUrl?'READY':'SETUP')+'</div><p>'+esc(state.config.apiBaseUrl||'\u8BF7\u5728\u57FA\u7840\u914D\u7F6E\u4E2D\u586B\u5199\u5E73\u53F0 API \u5730\u5740')+'</p></article><article class=\"card wide\"><h3>\u5F53\u524D\u8EAB\u4EFD</h3><p>'+esc(u.displayName||u.username||'-')+' \u00B7 '+esc(u.email||'-')+'</p><div class=\"actions\">'+roles.slice(0,8).map((r)=>'<span class=\"tag\">'+esc(r)+'</span>').join('')+(roles.length>8?'<span class=\"tag public\">+'+(roles.length-8)+'</span>':'')+'</div></article><article class=\"card\"><h3>\u4F1A\u8BDD\u64CD\u4F5C</h3><p>\u5F3A\u5236\u5237\u65B0\u8EAB\u4EFD\u8D44\u6599\uFF0C\u6216\u6E05\u9664\u672C\u673A Keycloak \u7F13\u5B58\u3002</p><div class=\"actions\"><button class=\"button\" data-action=\"reload\">\u5237\u65B0\u72B6\u6001</button><button class=\"button danger\" data-action=\"logout\">\u9000\u51FA\u767B\u5F55</button></div></article></section></div>'}\nfunction instant(value){if(!value)return null;const number=typeof value==='number'?(value<1000000000000?value*1000:value):Date.parse(value);return Number.isFinite(number)?number:null}\nfunction timeText(value){const at=instant(value);return at?new Date(at).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}):'\u2014'}\nfunction windowName(window,fallback){const minutes=window?.windowMinutes;if(!minutes)return fallback;if(minutes%1440===0)return (minutes/1440)+'d';if(minutes%60===0)return (minutes/60)+'h';return minutes+'m'}\nfunction quotaWindowHasData(window){if(!window)return false;const minutes=window.windowMinutes;const reset=window.resetAt;const hasMinutes=typeof minutes==='number'&&Number.isFinite(minutes)&&minutes>0;const hasReset=(typeof reset==='number'&&Number.isFinite(reset)&&reset>0)||(typeof reset==='string'&&Number.isFinite(Date.parse(reset)));const hasUsage=typeof window.usedPercent==='number'&&Number.isFinite(window.usedPercent)&&window.usedPercent>0;return hasMinutes||hasReset||hasUsage}\nfunction quotaLine(window,fallback){if(!quotaWindowHasData(window)||typeof window.usedPercent!=='number')return '';const remain=Math.max(0,Math.min(100,100-window.usedPercent));return '<div class=\"quota-line\" title=\"\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4 '+esc(timeText(window.resetAt))+'\"><span>'+esc(windowName(window,fallback))+'</span><progress max=\"100\" value=\"'+remain.toFixed(1)+'\"></progress><strong>'+remain.toFixed(0)+'%</strong></div>'}\nfunction quotaCell(quota){if(!quota)return '<span class=\"table-meta\">\u6682\u65E0\u989D\u5EA6\u91C7\u6837</span>';const lines=quotaLine(quota.primary,'\u4E3B\u7A97\u53E3')+quotaLine(quota.secondary,'\u6B21\u7A97\u53E3');const detail=[quota.planType,quota.model,quota.activeLimit!=null?'limit '+quota.activeLimit:null].filter(Boolean).join(' \u00B7 ');return '<div class=\"quota-stack\">'+(lines||'<span class=\"table-meta\">\u989D\u5EA6\u7A97\u53E3\u6682\u65E0\u6570\u636E</span>')+'<div class=\"table-meta\">'+esc(detail||'\u5DF2\u91C7\u6837')+'<br>\u91C7\u6837 '+esc(timeText(quota.updatedAt))+'</div></div>'}\nfunction newerQuota(localQuota,remoteQuota){if(!localQuota)return remoteQuota;if(!remoteQuota)return localQuota;return (instant(remoteQuota.updatedAt)||0)>=(instant(localQuota.updatedAt)||0)?remoteQuota:localQuota}\nfunction mergedChatgptAccounts(){const local=chatgptLocal?.accounts||[];const remote=chatgptRemote?.accounts||[];const used=new Set();const rows=local.map((item)=>{const index=remote.findIndex((candidate,i)=>!used.has(i)&&item.accountId&&candidate.accountId===item.accountId);const match=index>=0?remote[index]:null;if(index>=0)used.add(index);return {key:item.id,local:item,remote:match,label:item.label,email:item.email,accountId:item.accountId||match?.accountId,enabled:item.enabled,selected:item.selected,quota:newerQuota(item.quota,match?.quota)}});remote.forEach((item,index)=>{if(!used.has(index))rows.push({key:'remote_'+item.id,local:null,remote:item,label:item.label,email:null,accountId:item.accountId,enabled:true,selected:item.isDefault,quota:item.quota})});return rows}\nfunction syncTag(row){if(chatgptRemote?.status==='LOADING')return '<span class=\"tag waiting\">\u6B63\u5728\u540C\u6B65</span>';if(chatgptRemote?.status==='FAILED')return '<span class=\"tag failed\">\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25</span>';if(chatgptRemote?.status==='NOT_CONFIGURED')return '<span class=\"tag waiting\">\u672A\u914D\u7F6E\u5E73\u53F0</span>';if(row.local&&row.remote)return '<span class=\"tag success\">\u5DF2\u540C\u6B65</span>';if(row.local)return '<span class=\"tag local\">\u4EC5\u672C\u5730</span>';return '<span class=\"tag success\">\u5E73\u53F0\u53EF\u7528</span>'}\nfunction remoteStage(){const remote=chatgptRemote;if(!remote||remote.status==='LOADING')return {tag:'<span class=\"tag waiting\"><span class=\"refresh-icon spin\" aria-hidden=\"true\"></span> \u8FDE\u63A5\u4E2D</span>',title:'\u6B63\u5728\u8FDE\u63A5\u5E73\u53F0',detail:'\u672C\u5730\u8D26\u53F7\u5DF2\u7ECF\u53EF\u89C1\uFF0C\u6B63\u5728\u8BFB\u53D6\u8FDC\u7A0B\u53EF\u7528\u8D26\u53F7\u3002'};if(remote.status==='SUCCESS')return {tag:'<span class=\"tag success\">\u540C\u6B65\u6210\u529F</span>',title:remote.accounts.length+' \u4E2A\u5E73\u53F0\u8D26\u53F7',detail:'\u5B8C\u6210\u4E8E '+timeText(remote.syncedAt)};if(remote.status==='FAILED')return {tag:'<span class=\"tag failed\">\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25</span>',title:'\u4FDD\u7559\u672C\u5730\u7ED3\u679C',detail:remote.error?.message||'\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u8D26\u53F7\u670D\u52A1'};return {tag:'<span class=\"tag waiting\">\u672A\u914D\u7F6E</span>',title:'\u672A\u914D\u7F6E\u5E73\u53F0\u5730\u5740',detail:'\u53EF\u5728\u57FA\u7840\u914D\u7F6E\u4E2D\u586B\u5199\u5E73\u53F0 API \u5730\u5740\u3002'}}\nfunction accountRows(rows){return rows.map((row)=>'<tr><td><div class=\"account-name\"><strong>'+esc(row.label)+'</strong><small>'+esc(row.email||row.accountId||row.key)+'</small></div></td><td><div class=\"status-stack\">'+(row.enabled?'<span class=\"tag success\">\u53EF\u7528</span>':'<span class=\"tag failed\">\u5DF2\u505C\u7528</span>')+(row.selected?'<span class=\"tag role\">\u5BA2\u6237\u7AEF\u5DF2\u9009</span>':'')+'</div></td><td>'+quotaCell(row.quota)+'</td><td><div class=\"table-meta\">Token '+esc(row.local?'\u672C\u5730\u53EF\u7528':'\u6309\u9700\u4ECE\u5E73\u53F0\u83B7\u53D6')+'<br>\u5230\u671F '+esc(timeText(row.local?.accessExpiresAt||row.remote?.accessExpiresAt))+'</div></td><td><div class=\"status-stack\">'+syncTag(row)+'</div></td></tr>').join('')}\nfunction chatgptPage(){const rows=mergedChatgptAccounts();const remote=remoteStage();const localReady=chatgptLocal?.status==='READY';const localCount=chatgptLocal?.available??0;const failure=chatgptRemote?.status==='FAILED'?'<div class=\"notice error\"><strong>\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\u3002</strong> '+esc(chatgptRemote.error?.message||'\u5E73\u53F0\u6682\u65F6\u4E0D\u53EF\u7528')+'\uFF1B\u672C\u5730\u8D26\u53F7\u6570\u636E\u4ECD\u53EF\u6B63\u5E38\u67E5\u770B\u3002</div>':'';return '<div class=\"content\"><section class=\"hero\"><div><div class=\"eyebrow\">Local first / Remote second</div><h2>ChatGPT \u8D26\u53F7\u8FD0\u884C\u89C6\u56FE</h2><p>\u5148\u8BFB\u53D6 codex-multi-auth \u672C\u5730\u8D26\u53F7\u4E0E\u989D\u5EA6\u7F13\u5B58\uFF0C\u518D\u8FDE\u63A5\u5E73\u53F0\u8865\u5145\u5F53\u524D\u7528\u6237\u53EF\u7528\u8D26\u53F7\u3002\u9875\u9762\u6C38\u4E0D\u63A5\u6536 access token \u6216 refresh token\u3002</p></div><div class=\"session-track\"><div class=\"track-node live\"><span>\u672C\u5730\u53EF\u7528</span><strong>'+localCount+'</strong></div><div class=\"track-node\"><span>\u6C47\u603B\u8D26\u53F7</span><strong>'+rows.length+'</strong></div><div class=\"track-node\"><span>\u8FDC\u7A0B</span><strong>'+esc(chatgptRemote?.status||'WAITING')+'</strong></div></div></section><section class=\"sync-board\"><article class=\"sync-stage\"><div class=\"eyebrow\">01 / Local pool</div><strong>'+(localReady?localCount+' \u4E2A\u672C\u5730\u53EF\u7528\u8D26\u53F7':'\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8D26\u53F7')+'</strong><p>'+(localReady?'\u8BFB\u53D6\u5B8C\u6210\u4E8E '+timeText(chatgptLocal.readAt):'\u4F18\u5148\u8BFB\u53D6 codex-multi-auth \u5B89\u5168\u6458\u8981\u3002')+'</p></article><div class=\"sync-link\" aria-label=\"\u7136\u540E\u8FDE\u63A5\u8FDC\u7A0B\"></div><article class=\"sync-stage\"><div class=\"eyebrow\">02 / Platform service</div>'+remote.tag+'<strong>'+esc(remote.title)+'</strong><p>'+esc(remote.detail)+'</p></article></section>'+failure+'<section class=\"table-card\"><header class=\"table-head\"><div><h3>\u53EF\u7528\u8D26\u53F7\u4E0E\u5269\u4F59\u989D\u5EA6</h3><p>\u989D\u5EA6\u4F18\u5148\u5C55\u793A\u672C\u5730\u6216\u670D\u52A1\u5668\u7AEF\u66F4\u65B0\u8F83\u65B0\u7684\u5B89\u5168\u5FEB\u7167\u3002</p></div><button class=\"button\" data-action=\"chatgpt-refresh\" '+(chatgptLoading?'disabled':'')+'><span class=\"refresh-icon '+(chatgptLoading?'spin':'')+'\" aria-hidden=\"true\"></span> '+(chatgptLoading?'\u540C\u6B65\u4E2D':'\u91CD\u65B0\u540C\u6B65')+'</button></header>'+(rows.length?'<div class=\"account-table-wrap\"><table class=\"account-table\"><thead><tr><th>\u8D26\u53F7</th><th>\u53EF\u7528\u72B6\u6001</th><th>\u5269\u4F59 limit</th><th>\u51ED\u636E\u65F6\u6548</th><th>\u540C\u6B65\u72B6\u6001</th></tr></thead><tbody>'+accountRows(rows)+'</tbody></table></div>':'<div class=\"empty-state'+(chatgptLoading?'':' account-request')+'\">'+(chatgptLoading?'\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8D26\u53F7\u2026':'<strong>\u9700\u8981\u7533\u8BF7 ChatGPT \u8D26\u53F7</strong><span>'+esc(noAvailableAccountGuidance)+'</span>')+'</div>')+'</section></div>'}\nfunction registryPageItems(payload){if(Array.isArray(payload))return payload.filter((item)=>item&&typeof item==='object');for(const key of ['pageItems','items','records','list','data']){const value=payload?.[key];if(Array.isArray(value))return value.filter((item)=>item&&typeof item==='object');if(value&&typeof value==='object'){const nested=registryPageItems(value);if(nested.length)return nested}}return []}\nfunction registryItemName(item,index){return String(item?.name??item?.skillName??item?.mcpName??item?.requestId??item?.eventType??item?.bindingId??((registryMode==='skill'?'skill-':'mcp-')+(index+1)))}\nfunction registryItemStatus(item){return String(item?.status??item?.releaseStatus??item?.state??item?.version??'registered')}\nfunction currentRegistryCapability(){const selected=registryNamespaces.find((namespace)=>Number(namespace.id)===Number(registryNamespaceId));return registryCapabilities?.namespaces?.find((candidate)=>candidate.namespaceKey===selected?.namespaceKey&&(!candidate.instanceCode||candidate.instanceCode===selected?.instanceCode))||null}\nfunction registryCan(action){const actions=currentRegistryCapability()?.actions||[];return actions.some((value)=>value===action||value.endsWith(':'+action)||value.endsWith('.'+action))}\nfunction registryWorkspaceTabs(){const tabs=[['catalog','\u76EE\u5F55'],['approvals','\u53D1\u5E03\u6536\u4EF6\u7BB1'],['instances','\u5B9E\u4F8B / \u51ED\u636E'],['namespace','Namespace / Profile'],['router','Router'],['audit','\u6743\u9650 / \u5BA1\u8BA1']];return '<div class=\"registry-workspaces\" role=\"tablist\" aria-label=\"AI Registry \u5DE5\u4F5C\u533A\">'+tabs.map(([id,label])=>'<button role=\"tab\" aria-selected=\"'+(registryWorkspace===id)+'\" data-registry-workspace=\"'+id+'\" class=\"'+(registryWorkspace===id?'active':'')+'\">'+label+'</button>').join('')+'</div>'}\nfunction registryNamespaceOptions(){return registryNamespaces.map((namespace)=>'<option value=\"'+namespace.id+'\" '+(Number(namespace.id)===Number(registryNamespaceId)?'selected':'')+'>'+esc(namespace.displayName)+' \u00B7 '+esc(namespace.namespaceKey)+' \u00B7 '+esc(namespace.instanceCode)+(namespace.isDefault?' \u00B7 default':'')+'</option>').join('')}\nfunction registryItemButtons(){return registryItems.map((item,index)=>{const name=registryItemName(item,index);const status=registryItemStatus(item);const online=/online|published|release/i.test(status);return '<button class=\"registry-item '+(name===registryName?'active':'')+'\" data-registry-name=\"'+esc(name)+'\"><span class=\"registry-dot '+(online?'online':'')+'\"></span><span><strong>'+esc(name)+'</strong><small>'+esc(status)+(item.scope?' \u00B7 '+esc(item.scope):'')+'</small></span></button>'}).join('')||'<div class=\"empty-state\">\u6CA1\u6709\u5339\u914D\u7684 '+(registryMode==='skill'?'Skill':'MCP Server')+'</div>'}\nfunction registryTargets(){const targets=registryMode==='skill'?['chatgpt','codex','claude','cursor','workbuddy','openclaw','agents']:['chatgpt','codex','claude','cursor','workbuddy','openclaw'];return targets.map((target)=>'<option value=\"'+target+'\">'+target+'</option>').join('')}\nfunction compactValue(value){if(value==null||value==='')return '\u2014';if(Array.isArray(value))return value.length?value.map((item)=>typeof item==='object'?(item.name??item.id??'record'):item).join(', '):'\u2014';if(typeof value==='object')return value.displayName??value.name??value.status??Object.keys(value).length+' fields';return String(value)}\nfunction summaryCells(entries){return '<div class=\"summary-grid\">'+entries.map(([label,value])=>'<div class=\"summary-cell\"><span>'+esc(label)+'</span><strong>'+esc(compactValue(value))+'</strong></div>').join('')+'</div>'}\nfunction rawDisclosure(value,label='\u67E5\u770B\u539F\u59CB\u54CD\u5E94'){return '<details class=\"raw-disclosure\"><summary>'+esc(label)+'</summary><pre class=\"output wrap\">'+esc(JSON.stringify(value??{},null,2))+'</pre></details>'}\nfunction registryDetailSummary(detail){const tags=detail?.tags??detail?.labels;const owner=detail?.ownerTeamCode??detail?.ownerTeam??detail?.teamCode;const entries=registryMode==='skill'?[['\u7CBE\u786E\u7248\u672C',detail?.version??detail?.latestVersion],['\u6CBB\u7406\u72B6\u6001',detail?.status??detail?.state],['\u53EF\u89C1\u6027',detail?.visibility??detail?.scope],['\u8D23\u4EFB\u56E2\u961F',owner],['\u6807\u7B7E',tags],['\u626B\u63CF',detail?.scanStatus??detail?.pipelineStatus]]:[['\u7CBE\u786E\u7248\u672C',detail?.version??detail?.latestVersion],['\u6CBB\u7406\u72B6\u6001',detail?.status??detail?.state],['\u534F\u8BAE',detail?.protocol??detail?.transport],['\u8D23\u4EFB\u56E2\u961F',owner],['\u5DE5\u5177\u6570\u91CF',Array.isArray(detail?.tools)?detail.tools.length:detail?.toolCount],['Endpoint \u5065\u5EB7',detail?.health??detail?.endpointStatus]];return summaryCells(entries)+rawDisclosure(detail)}\nfunction registryDetailPanel(){if(registryLoading)return '<div class=\"empty-state\" role=\"status\"><span class=\"refresh-icon spin\" aria-hidden=\"true\"></span> \u6B63\u5728\u8BFB\u53D6\u79C1\u6709 Registry\u2026</div>';if(!registryName)return '<div class=\"empty-state\">\u4ECE\u5217\u8868\u9009\u62E9\u4E00\u9879\u67E5\u770B\u7248\u672C\u3001\u6807\u7B7E\u548C\u6CBB\u7406\u72B6\u6001\u3002</div>';const canSubmit=registryCan('submit');const mutating='<button class=\"button\" data-registry-action=\"submit\" '+(canSubmit?'':'disabled')+'>\u63D0\u4EA4\u5BA1\u6838</button>';return '<div class=\"registry-detail-head\"><div><h3>'+esc(registryName)+'</h3><span class=\"tag '+(canSubmit?'role':'public')+'\">'+(canSubmit?'\u53EF\u63D0\u4EA4\u8349\u7A3F':'\u53EA\u8BFB')+'</span></div><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0\u8BE6\u60C5</button></div>'+registryDetailSummary(registryDetail)+'<div class=\"registry-actions\"><div class=\"install-row\"><label class=\"field\"><span>\u5B89\u88C5\u76EE\u6807</span><select id=\"registry-target\">'+registryTargets()+'</select></label><label class=\"field\"><span>\u5B89\u88C5\u8303\u56F4</span><select id=\"registry-scope\"><option value=\"project\">\u5F53\u524D\u9879\u76EE</option><option value=\"user\">\u5F53\u524D\u7528\u6237</option></select></label><button class=\"button primary\" data-registry-action=\"install\" '+(registryCan('install')||registryCan('use')?'':'disabled')+'>\u5B89\u88C5\u5E76\u9501\u5B9A</button></div><div class=\"actions\">'+mutating+'</div><small class=\"table-meta\">\u76EE\u5F55\u53EA\u5141\u8BB8\u5B89\u88C5\u4E0E\u63D0\u4EA4 release request\u3002\u6279\u51C6\u548C\u53D1\u5E03\u53EA\u5728\u53D1\u5E03\u6536\u4EF6\u7BB1\u51FA\u73B0\uFF0C\u6700\u7EC8\u6388\u6743\u548C\u4E24\u4EBA\u5236\u6821\u9A8C\u7531\u5E73\u53F0\u6267\u884C\u3002</small><div id=\"registry-notice\" aria-live=\"polite\">'+registryNotice+'</div></div>'}\nfunction instanceCards(){if(!registryItems.length)return '<div class=\"empty-state\">\u6CA1\u6709\u53EF\u89C1\u7684 Registry \u5B9E\u4F8B\u3002</div>';return '<div class=\"record-grid\">'+registryItems.map((item)=>{const credentials=item.credentials||{};return '<article class=\"record-card\"><div class=\"registry-detail-head\"><h4>'+esc(item.displayName||item.code||'\u5B9E\u4F8B')+'</h4><span class=\"tag '+(item.status==='ACTIVE'?'success':'waiting')+'\">'+esc(item.status||'UNKNOWN')+'</span></div><dl><dt>\u5B9E\u4F8B\u4EE3\u7801</dt><dd>'+esc(item.code||'\u2014')+'</dd><dt>\u8BA4\u8BC1\u65B9\u5F0F</dt><dd>'+esc(item.authenticationType||'\u2014')+'</dd><dt>READER</dt><dd>'+esc(credentials.readerConfigured??item.credentialConfigured?'\u5DF2\u9A8C\u8BC1':'\u672A\u5C31\u7EEA')+'</dd><dt>MAINTAINER</dt><dd>'+esc(credentials.maintainerConfigured??item.credentialConfigured?'\u5DF2\u9A8C\u8BC1':'\u672A\u5C31\u7EEA')+'</dd><dt>LEGACY_BOTH</dt><dd>'+esc(credentials.legacyBothConfigured?'\u8FC1\u79FB\u4E2D':'\u672A\u542F\u7528')+'</dd><dt>\u4E50\u89C2\u9501\u7248\u672C</dt><dd>'+esc(item.version??'\u2014')+'</dd></dl></article>'}).join('')+'</div>'}\nfunction registrySimplePanel(title,description){\nif(registryLoading)return '<section class=\"registry-simple\"><div class=\"empty-state\" role=\"status\">\u6B63\u5728\u8BFB\u53D6 '+esc(title)+'\u2026</div></section>';\nif(registryError)return '<section class=\"registry-simple\"><div class=\"notice error\" role=\"alert\"><strong>'+esc(title)+'\u52A0\u8F7D\u5931\u8D25\u3002</strong> '+esc(registryError)+' <button class=\"button\" data-action=\"registry-retry\">\u91CD\u8BD5</button></div></section>';\nif(registryWorkspace==='instances')return '<section class=\"registry-simple\"><h3>'+esc(title)+'</h3><p>'+esc(description)+'</p>'+instanceCards()+'<small class=\"table-meta\">\u6D4F\u89C8\u5668\u53EA\u63A5\u6536\u201C\u662F\u5426\u5DF2\u914D\u7F6E/\u9A8C\u8BC1\u201D\u7684\u5E03\u5C14\u72B6\u6001\uFF1BNacos \u5730\u5740\u4E0E credential \u6C38\u4E0D\u8FDB\u5165 capability\u3002</small></section>';\nif(registryWorkspace==='namespace'){\nconst cap=currentRegistryCapability()||{};const selected=registryNamespaces.find((item)=>Number(item.id)===Number(registryNamespaceId));\nreturn '<section class=\"registry-simple\"><h3>'+esc(title)+'</h3><p>'+esc(description)+'</p><dl class=\"registry-facts\"><div><dt>\u73AF\u5883</dt><dd>'+esc(cap.environment||selected?.environment||'\u672A\u58F0\u660E')+'</dd></div><div><dt>\u8BBF\u95EE\u7B56\u7565</dt><dd>'+esc(selected?.accessPolicy||'\u7531 capability \u51B3\u5B9A')+'</dd></div><div><dt>\u8D23\u4EFB\u56E2\u961F</dt><dd>'+esc(selected?.ownerTeamCode||'\u5171\u4EAB\u9ED8\u8BA4 namespace')+'</dd></div><div><dt>\u5F53\u524D\u7528\u6237\u9ED8\u8BA4\u503C</dt><dd>'+esc(state.config.aiRegistryDefaultNamespaceKey||'\u672A\u8BBE\u7F6E')+'</dd></div><div><dt>Profile \u9ED8\u8BA4\u503C</dt><dd>'+esc(state.config.aiRegistryDefaultNamespaceKey?'\u88AB\u7528\u6237\u504F\u597D\u8986\u76D6':'\u89E3\u6790\u65F6\u7531 profile \u63D0\u4F9B')+'</dd></div><div><dt>Namespace \u72B6\u6001</dt><dd>'+esc(registryAux?.status||selected?.status||'UNKNOWN')+'</dd></div></dl><form id=\"registry-default-form\" class=\"form-grid\"><label class=\"field\"><span>\u5C06\u6388\u6743 namespace \u8BBE\u4E3A\u7528\u6237\u9ED8\u8BA4\u503C</span><select name=\"namespaceKey\">'+registryNamespaces.map((item)=>'<option value=\"'+esc(item.namespaceKey)+'\" '+(item.namespaceKey===state.config.aiRegistryDefaultNamespaceKey?'selected':'')+'>'+esc(item.displayName)+' \u00B7 '+esc(item.namespaceKey)+'</option>').join('')+'</select></label><div class=\"actions\"><button class=\"button primary\" type=\"submit\">\u4FDD\u5B58\u9ED8\u8BA4\u503C</button><button class=\"button\" type=\"button\" data-registry-action=\"namespace-check\">\u91CD\u65B0\u68C0\u67E5</button></div></form><small class=\"table-meta\">\u89E3\u6790\u987A\u5E8F\u4FDD\u6301\u4E3A\u547D\u4EE4\u53C2\u6570 \u2192 \u9879\u76EE manifest \u2192 \u7528\u6237\u504F\u597D \u2192 profile \u9ED8\u8BA4\u503C\uFF1B\u65E0\u6548\u9ED8\u8BA4\u503C\u4F1A\u660E\u786E\u5931\u8D25\u3002</small><div id=\"registry-notice\" aria-live=\"polite\">'+registryNotice+'</div></section>';\n}\nif(registryWorkspace==='router'){\nconst router=registryDetail||currentRegistryCapability()?.router||{};const canConnect=registryCan('router.connect')||registryCan('connect');\nreturn '<section class=\"registry-simple\"><div class=\"registry-detail-head\"><div><h3>'+esc(title)+'</h3><span class=\"tag '+(router.status==='READY'?'success':'waiting')+'\">'+esc(router.status||'UNAVAILABLE')+'</span></div><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0\u5065\u5EB7</button></div><p>'+esc(description)+'</p>'+summaryCells([['\u7ED1\u5B9A',router.bindingId],['\u534F\u8BAE',router.protocol],['\u7B56\u7565 revision',router.policyRevision],['\u5065\u5EB7',registryAux?.status??router.health],['Endpoint',router.endpoint],['OAuth resource',router.oauthResource]])+rawDisclosure({router:router,health:registryAux})+'<div class=\"actions\"><button class=\"button\" data-load=\"'+esc(JSON.stringify(['registry','router','connect']))+'\" '+(canConnect?'':'disabled')+'>\u8FDE\u63A5\u9002\u914D\u5668</button></div></section>';\n}\nif(registryWorkspace==='audit'){const cap=currentRegistryCapability()||{};const detail=registryDetail?'<div class=\"registry-detail-head\"><h3>'+esc(registryName)+'</h3><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0</button></div>'+summaryCells([['\u4E8B\u4EF6',registryDetail.eventType??registryDetail.action],['Request ID',registryDetail.requestId],['\u64CD\u4F5C\u8005',registryDetail.actor??registryDetail.subject],['\u7ED3\u679C',registryDetail.result??registryDetail.status],['\u53D1\u751F\u65F6\u95F4',registryDetail.createdAt??registryDetail.occurredAt],['\u7B56\u7565 revision',registryDetail.policyRevision]])+rawDisclosure(registryDetail):'<div class=\"empty-state\">\u9009\u62E9\u5BA1\u8BA1\u4E8B\u4EF6\u67E5\u770B\u5173\u8054 requestId \u4E0E\u7ED3\u679C\u3002</div>';return '<section class=\"registry-simple\"><h3>'+esc(title)+'</h3><p>'+esc(description)+'</p><div class=\"permission-grid\">'+(cap.actions||[]).map((action)=>'<span class=\"tag role\">'+esc(action)+'</span>').join('')+(!(cap.actions||[]).length?'<span class=\"tag public\">\u53EA\u8BFB</span>':'')+'</div>'+summaryCells([['Policy revision',registryCapabilities?.policyRevision],['\u73AF\u5883',cap.environment],['Router',cap.router?.status],['\u53EF\u7528\u52A8\u4F5C',(cap.actions||[]).length]])+'<section class=\"registry-board\"><aside class=\"registry-list\"><div class=\"registry-list-head\"><strong>\u8FFD\u52A0\u5F0F\u5BA1\u8BA1</strong><span class=\"tag public\">'+registryItems.length+'</span></div><div class=\"registry-items\">'+registryItemButtons()+'</div></aside><article class=\"registry-detail\">'+detail+'</article></section></section>'}\nconst requestState=String(registryDetail?.state??registryDetail?.status??'').toUpperCase();const canReview=requestState==='SUBMITTED'&®istryCan('review');const canPublish=requestState==='APPROVED'&®istryCan('publish');const approvalActions=registryWorkspace==='approvals'&®istryDetail?'<div class=\"registry-actions\"><div class=\"actions\"><button class=\"button\" data-registry-action=\"approval-approve\" '+(canReview?'':'disabled')+'>\u6279\u51C6</button><button class=\"button danger\" data-registry-action=\"approval-reject\" '+(canReview?'':'disabled')+'>\u62D2\u7EDD</button><button class=\"button primary\" data-registry-action=\"approval-publish\" '+(canPublish?'':'disabled')+'>\u53D1\u5E03\u5DF2\u6279\u51C6\u7533\u8BF7</button></div><small class=\"table-meta\">\u63D0\u4EA4\u4EBA\u4E0D\u80FD\u5BA1\u6838\u6216\u53D1\u5E03\u81EA\u5DF1\u7684\u7533\u8BF7\uFF1B\u72B6\u6001\u548C capability \u5FC5\u987B\u540C\u65F6\u5141\u8BB8\uFF0C\u670D\u52A1\u7AEF\u4F1A\u518D\u6B21\u6821\u9A8C\u3002</small><div id=\"registry-notice\" aria-live=\"polite\">'+registryNotice+'</div></div>':'';\nconst detail=registryDetail?'<div class=\"registry-detail-head\"><h3>'+esc(registryName)+'</h3><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0</button></div>'+summaryCells([['\u8D44\u6E90\u7C7B\u578B',registryDetail.resourceType],['\u8D44\u6E90',registryDetail.resourceName??registryDetail.name],['\u7CBE\u786E\u7248\u672C',registryDetail.resourceVersion],['\u72B6\u6001',registryDetail.state??registryDetail.status],['\u63D0\u4EA4\u4EBA',registryDetail.submitter??registryDetail.createdBy],['\u4E50\u89C2\u9501\u7248\u672C',registryDetail.version]])+rawDisclosure(registryDetail)+approvalActions:'<div class=\"empty-state\">\u9009\u62E9 release request \u67E5\u770B\u72B6\u6001\u548C\u53EF\u6267\u884C\u64CD\u4F5C\u3002</div>';\nreturn '<section class=\"registry-board\"><aside class=\"registry-list\"><div class=\"registry-list-head\"><strong>'+esc(title)+'</strong><span class=\"tag public\">'+registryItems.length+'</span></div><div class=\"registry-items\">'+registryItemButtons()+'</div></aside><article class=\"registry-detail\">'+detail+'</article></section>';\n}\nfunction registryContent(){if(registryWorkspace==='catalog')return '<section class=\"registry-board\"><aside class=\"registry-list\"><div class=\"registry-list-head\"><strong>'+(registryMode==='skill'?'Skills':'MCP Servers')+'</strong><span class=\"tag public\">'+registryItems.length+'</span></div><div class=\"registry-items\">'+registryItemButtons()+'</div></aside><article class=\"registry-detail\">'+registryDetailPanel()+'</article></section>';const labels={approvals:['\u53D1\u5E03\u6536\u4EF6\u7BB1','\u5BA1\u6838\u8005\u4E0E\u53D1\u5E03\u8005\u5728\u8FD9\u91CC\u5904\u7406\u4E24\u4EBA\u5236 release request\u3002'],instances:['\u5B9E\u4F8B\u4E0E\u53CC\u51ED\u636E','\u5E73\u53F0\u53EA\u663E\u793A\u51ED\u636E\u662F\u5426\u5C31\u7EEA\uFF0C\u4E0D\u628A secret \u8FD4\u56DE\u6D4F\u89C8\u5668\u3002'],namespace:['Namespace \u4E0E Profile \u9ED8\u8BA4\u503C','\u5F53\u524D\u73AF\u5883\u3001\u6388\u6743\u52A8\u4F5C\u548C namespace \u89E3\u6790\u6765\u6E90\u4FDD\u6301\u53EF\u89C1\u3002'],router:['\u4F01\u4E1A Router','\u53EA\u6709 READY \u7684 Streamable HTTP Endpoint \u624D\u4F1A\u8FDB\u5165 capability\u3002'],audit:['\u6743\u9650\u4E0E\u8FFD\u52A0\u5F0F\u5BA1\u8BA1','\u6309 requestId \u8FFD\u8E2A\u62D2\u7EDD\u3001\u5BA1\u6279\u3001\u53D1\u5E03\u548C\u5BF9\u8D26\u4E8B\u4EF6\u3002']};return registrySimplePanel(...labels[registryWorkspace])}\nfunction registryPage(){const selected=registryNamespaces.find((namespace)=>Number(namespace.id)===Number(registryNamespaceId));const cap=currentRegistryCapability();const error=registryError&®istryWorkspace==='catalog'?'<div class=\"notice error registry-error\" role=\"alert\"><strong>\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25\u3002</strong> '+esc(registryError)+' <button class=\"button\" data-action=\"registry-retry\">\u91CD\u8BD5</button></div>':'';return '<div class=\"content\"><section class=\"hero registry-hero\"><div><h2>\u56E2\u961F\u80FD\u529B\u7684\u53D1\u5E03\u4E0E\u8FD0\u884C\u8FB9\u754C</h2><p>\u76EE\u5F55\u3001\u5BA1\u6279\u548C Router \u5171\u4EAB\u4E00\u4E2A namespace \u9009\u62E9\uFF1BNuma \u53EA\u63A5\u6536 capability \u548C\u6CBB\u7406\u5143\u6570\u636E\uFF0C\u4E0D\u63A5\u89E6 Nacos \u51ED\u636E\u3002</p></div><div><small class=\"table-meta\">\u751F\u547D\u5468\u671F\u5951\u7EA6 \u00B7 rejected / failed / offline \u4E3A\u5206\u652F\u72B6\u6001</small><div class=\"lifecycle\" aria-label=\"\u751F\u547D\u5468\u671F\u56FE\u4F8B\uFF0C\u4E0D\u8868\u793A\u5F53\u524D\u72B6\u6001\"><div class=\"life-node\">draft</div><div class=\"life-node\">submitted</div><div class=\"life-node\">approved</div><div class=\"life-node\">publishing</div><div class=\"life-node\">published</div></div></div></section>'+registryWorkspaceTabs()+'<section class=\"registry-toolbar\"><label class=\"field\"><span>Namespace</span><select id=\"registry-namespace\">'+registryNamespaceOptions()+'</select></label>'+(registryWorkspace==='catalog'?'<div class=\"segment\" role=\"group\" aria-label=\"\u8D44\u6E90\u7C7B\u578B\"><button data-registry-mode=\"skill\" class=\"'+(registryMode==='skill'?'active':'')+'\">Skills</button><button data-registry-mode=\"mcp\" class=\"'+(registryMode==='mcp'?'active':'')+'\">MCP</button></div><label class=\"field\"><span>\u8FC7\u6EE4</span><input id=\"registry-search\" maxlength=\"80\" value=\"'+esc(registrySearch)+'\" placeholder=\"\u540D\u79F0\u6216\u63CF\u8FF0\"></label>':'<div class=\"registry-context\"><strong>'+esc(selected?.displayName||'\u672A\u9009\u62E9')+'</strong><span>'+esc(cap?.environment||'unknown')+' \u00B7 '+esc(cap?.router?.status||'NO ROUTER')+'</span></div>')+'<button class=\"button\" data-action=\"registry-refresh\" '+(registryLoading?'disabled':'')+'><span class=\"refresh-icon '+(registryLoading?'spin':'')+'\" aria-hidden=\"true\"></span> '+(registryLoading?'\u8BFB\u53D6\u4E2D\u2026':'\u5237\u65B0')+'</button></section>'+error+registryContent()+'<div class=\"sr-only\" aria-live=\"polite\">'+esc(registryLoading?'\u6B63\u5728\u52A0\u8F7D':registryError||registryNotice||'\u52A0\u8F7D\u5B8C\u6210')+'</div></div>'}\nasync function loadRegistry(resetNamespaces=false){if(registryLoading)return;registryLoading=true;registryError='';render();try{if(resetNamespaces||!registryNamespaces.length||!registryCapabilities){[registryCapabilities,registryNamespaces]=await Promise.all([api('/api/registry/capabilities'),api('/api/registry/namespaces')]);if(!Array.isArray(registryNamespaces)||!Array.isArray(registryCapabilities?.namespaces))throw new Error('capability \u6216 namespace \u54CD\u5E94\u683C\u5F0F\u65E0\u6548');const visible=registryNamespaces.filter((namespace)=>registryCapabilities.namespaces.some((candidate)=>candidate.namespaceKey===namespace.namespaceKey&&(!candidate.instanceCode||candidate.instanceCode===namespace.instanceCode)));registryNamespaces=visible;if(!registryNamespaceId||!registryNamespaces.some((namespace)=>Number(namespace.id)===Number(registryNamespaceId))){const preferred=state.config.aiRegistryDefaultNamespaceKey;const preferredNamespace=preferred?registryNamespaces.find((namespace)=>namespace.namespaceKey===preferred):null;if(preferred&&!preferredNamespace)throw new Error('\u7528\u6237\u9ED8\u8BA4 namespace '+preferred+' \u4E0D\u5728 capability \u6388\u6743\u5217\u8868\u4E2D\uFF1B\u8BF7\u663E\u5F0F\u4FEE\u6539\u9ED8\u8BA4\u503C\u3002');registryNamespaceId=preferredNamespace?.id??registryNamespaces[0]?.id??null}}if(!registryNamespaceId){registryItems=[];registryDetail=null;throw new Error('\u5F53\u524D\u7528\u6237\u6CA1\u6709\u53EF\u89C1\u7684 Registry namespace\u3002\u8BF7\u8054\u7CFB\u8D23\u4EFB\u56E2\u961F\u6216\u7BA1\u7406\u5458\u6388\u6743\u3002')}registryAux=null;if(registryWorkspace==='catalog'){const endpoint=registryMode==='skill'?'/api/registry/skills':'/api/registry/mcp';const payload=await api(endpoint+'?namespaceId='+encodeURIComponent(registryNamespaceId)+(registrySearch?'&search='+encodeURIComponent(registrySearch):''));registryItems=registryPageItems(payload);if(registryName&&!registryItems.some((item,index)=>registryItemName(item,index)===registryName)){registryName='';registryDetail=null}}else if(registryWorkspace==='approvals'||registryWorkspace==='audit'){const endpoint=registryWorkspace==='approvals'?'/api/registry/approvals':'/api/registry/audit';const payload=await api(endpoint+'?namespaceId='+encodeURIComponent(registryNamespaceId));registryItems=registryPageItems(payload);registryDetail=null;registryName=''}else if(registryWorkspace==='instances'){registryItems=registryPageItems(await api('/api/registry/instances'));registryDetail=null;registryName=''}else if(registryWorkspace==='namespace'){registryAux=await api('/api/registry/namespace-health?namespaceId='+encodeURIComponent(registryNamespaceId));registryItems=[];registryDetail=null;registryName=''}else if(registryWorkspace==='router'){[registryDetail,registryAux]=await Promise.all([api('/api/registry/router?namespaceId='+encodeURIComponent(registryNamespaceId)),api('/api/registry/router-health?namespaceId='+encodeURIComponent(registryNamespaceId))]);registryItems=[];registryName=''}else{registryItems=[];registryDetail=null;registryName=''}}catch(error){registryError=error.message;registryItems=[];registryDetail=null;registryAux=null}finally{registryLoading=false;render()}}\nasync function selectRegistryItem(name){registryName=name;if(registryWorkspace!=='catalog'){registryDetail=registryItems.find((item,index)=>registryItemName(item,index)===name)||null;render();return}registryLoading=true;registryError='';render();try{const endpoint=registryMode==='skill'?'/api/registry/skill':'/api/registry/mcp-detail';registryDetail=await api(endpoint+'?namespaceId='+encodeURIComponent(registryNamespaceId)+'&name='+encodeURIComponent(name))}catch(error){registryError=error.message;registryDetail=null}finally{registryLoading=false;render()}}\nasync function runRegistryCommand(argv,successMessage){const selectedName=registryName;registryNotice='<div class=\"notice success\">\u6B63\u5728\u6267\u884C\u2026</div>';render();try{const result=await api('/api/commands/run',{method:'POST',body:JSON.stringify({argv,confirm:false})});if(result.exitCode!==0)throw new Error(result.stderr||result.stdout||'\u547D\u4EE4\u6267\u884C\u5931\u8D25');await loadRegistry();if(selectedName&®istryItems.some((item,index)=>registryItemName(item,index)===selectedName))await selectRegistryItem(selectedName);registryNotice='<div class=\"notice success\">'+esc(successMessage)+'</div>';render()}catch(error){registryNotice='<div class=\"notice error\">'+esc(error.message)+'</div>';render()}}\nasync function registryAction(action){if(!registryNamespaceId)return;const selected=registryNamespaces.find((namespace)=>Number(namespace.id)===Number(registryNamespaceId));if(!selected)throw new Error('\u672A\u9009\u62E9 namespace');if(action==='namespace-check'){registryNotice='<div class=\"notice success\">\u6B63\u5728\u68C0\u67E5 namespace\u2026</div>';render();try{registryAux=await api('/api/registry/namespace-health?namespaceId='+encodeURIComponent(registryNamespaceId));registryNotice='<div class=\"notice success\">\u68C0\u67E5\u5B8C\u6210\uFF1A'+esc(registryAux?.status||'\u5DF2\u54CD\u5E94')+'</div>'}catch(error){registryNotice='<div class=\"notice error\">'+esc(error.message)+'</div>'}render();return}if(!registryName)return;const targetArgs=['--namespace',String(selected.namespaceKey),'--instance',String(selected.instanceCode)];if(action==='install'){const agent=document.getElementById('registry-target').value;let scope=document.getElementById('registry-scope').value;if(registryMode==='mcp'&&(agent==='codex'||agent==='openclaw'))scope='user';const argv=[registryMode==='skill'?'skills':'mcp','install',registryName,...targetArgs,'--agent',agent,'--scope',scope];if(registryMode==='skill')argv.push('--label','latest');await runRegistryCommand(argv,'\u5DF2\u5B8C\u6210\u672C\u673A\u5B89\u88C5');return}if(action==='submit'){const version=prompt('\u8F93\u5165\u8981\u63D0\u4EA4\u5BA1\u6838\u7684\u7CBE\u786E\u7248\u672C');if(!version)return;const reason=prompt('\u8F93\u5165\u53D1\u5E03\u8BF4\u660E');if(!reason)return;await runRegistryCommand(['registry','approvals','submit',registryMode,registryName,...targetArgs,'--resource-version',version,'--reason',reason],'\u5DF2\u63D0\u4EA4\u53D1\u5E03\u7533\u8BF7');return}if(action.startsWith('approval-')){const operation=action.slice('approval-'.length);if(!confirm('\u786E\u8BA4\u5BF9\u7533\u8BF7 '+registryName+' \u6267\u884C '+operation+'\uFF1F'))return;const optimisticVersion=registryDetail?.version;const versionArgs=Number.isInteger(optimisticVersion)?['--version',String(optimisticVersion)]:[];if(operation==='publish'){await runRegistryCommand(['registry','approvals','publish',registryName,...versionArgs],'\u53D1\u5E03\u5DF2\u5F00\u59CB');return}const reason=prompt(operation==='approve'?'\u8F93\u5165\u6279\u51C6\u539F\u56E0':'\u8F93\u5165\u62D2\u7EDD\u539F\u56E0');if(!reason)return;await runRegistryCommand(['registry','approvals','review',registryName,'--decision',operation==='approve'?'APPROVED':'REJECTED','--reason',reason,...versionArgs],operation==='approve'?'\u5DF2\u6279\u51C6':'\u5DF2\u62D2\u7EDD');return}}\nfunction configPage(){const c=state.config;return '<div class=\"content\"><section class=\"grid\"><article class=\"card full\"><h3>\u57FA\u7840\u914D\u7F6E</h3><p>\u4E0E <code>numa config --set</code> \u4F7F\u7528\u540C\u4E00\u4EFD\u914D\u7F6E\u548C\u6821\u9A8C\u903B\u8F91\u3002</p><form id=\"config-form\" class=\"form-grid\"><div class=\"field\"><label>\u754C\u9762\u8BED\u8A00</label><select name=\"locale\"><option '+(c.locale==='zh-CN'?'selected':'')+'>zh-CN</option><option '+(c.locale==='en-US'?'selected':'')+'>en-US</option></select></div><div class=\"field\"><label>\u9ED8\u8BA4\u8F93\u51FA</label><select name=\"output\"><option '+(c.output==='text'?'selected':'')+'>text</option><option '+(c.output==='json'?'selected':'')+'>json</option></select></div><div class=\"field span-2\"><label>\u5E73\u53F0 API \u5730\u5740</label><input name=\"apiBaseUrl\" value=\"'+esc(c.apiBaseUrl)+'\" placeholder=\"https://platform.example/api\"></div><div class=\"field\"><label>AI Registry \u7528\u6237\u9ED8\u8BA4 namespace</label><input name=\"aiRegistryDefaultNamespaceKey\" maxlength=\"160\" value=\"'+esc(c.aiRegistryDefaultNamespaceKey||'')+'\" placeholder=\"\u7531\u9879\u76EE\u6216 profile \u63D0\u4F9B\u65F6\u53EF\u7559\u7A7A\"></div><div class=\"field\"><label>npm registry</label><input name=\"registryUrl\" value=\"'+esc(c.registryUrl)+'\"></div><label class=\"field span-2\"><span>\u66F4\u65B0\u68C0\u67E5</span><select name=\"checkUpdates\"><option value=\"true\" '+(c.checkUpdates?'selected':'')+'>\u542F\u7528</option><option value=\"false\" '+(!c.checkUpdates?'selected':'')+'>\u5173\u95ED</option></select></label><div class=\"actions span-2\"><button class=\"button primary\" type=\"submit\">\u4FDD\u5B58\u914D\u7F6E</button></div></form><div id=\"notice\" aria-live=\"polite\"></div></article></section></div>'}\nfunction commandsPage(){return '<div class=\"content\"><section class=\"card full\"><div class=\"command-toolbar\"><div class=\"field grow\"><label>\u8FC7\u6EE4\u547D\u4EE4</label><input id=\"command-search\" placeholder=\"\u4F8B\u5982 codex\u3001media\u3001auth\"></div></div><div id=\"command-list\" class=\"command-list\">'+commandCards('')+'</div></section></div>'}\nfunction commandCards(query){const q=query.toLowerCase();return state.commands.filter((c)=>(c.command+' '+c.summary).toLowerCase().includes(q)).map((c)=>'<article class=\"command\"><div class=\"command-head\"><code>'+esc(c.command)+'</code><span class=\"tag '+esc(c.access)+'\">'+esc(c.access)+'</span></div><p>'+esc(c.summary)+'</p><small>'+esc(c.examples[0]||'')+'</small><div class=\"actions mt-10\"><button class=\"button\" data-load=\"'+esc(JSON.stringify(commandPrefix(c.command)))+'\">\u8F7D\u5165\u6267\u884C\u5668</button></div></article>').join('')||'<p>\u6CA1\u6709\u5339\u914D\u547D\u4EE4\u3002</p>'}\nfunction commandPrefix(command){return command.split(/\\s+/).filter((part)=>!part.startsWith('[')&&!part.startsWith('<')&&!part.startsWith('--'))}\nfunction runnerPage(){return '<div class=\"content\"><section class=\"grid\"><article class=\"card\"><div class=\"eyebrow\">Structured argv</div><h3>\u5B89\u5168\u6267\u884C\u5668</h3><p>\u4F7F\u7528 JSON \u6570\u7EC4\u4F20\u5165\u53C2\u6570\uFF0C\u4E0D\u7ECF\u8FC7 shell\u3002\u957F\u9A7B\u6216\u4EA4\u4E92\u547D\u4EE4\u9700\u8981\u5728\u7EC8\u7AEF\u8FD0\u884C\u3002</p><form id=\"runner-form\"><div class=\"field\"><label>\u547D\u4EE4\u53C2\u6570</label><textarea id=\"argv\" class=\"mono\">[\"doctor\"]</textarea></div><label class=\"field mt-12\"><span>\u5371\u9669\u64CD\u4F5C\u786E\u8BA4</span><select id=\"confirm\"><option value=\"false\">\u672A\u786E\u8BA4</option><option value=\"true\">\u786E\u8BA4\u6267\u884C\u5220\u9664/\u91CD\u7F6E</option></select></label><div class=\"actions mt-14\"><button class=\"button primary\" type=\"submit\">\u8FD0\u884C\u547D\u4EE4</button></div></form></article><article class=\"card wide\"><div class=\"eyebrow\">Result stream</div><h3>\u7ED3\u6784\u5316\u8F93\u51FA</h3><pre id=\"output\" class=\"output\">'+esc(output)+'</pre></article></section></div>'}\nasync function loadChatgpt(){if(chatgptLoading)return;chatgptLoading=true;chatgptLocal=null;chatgptRemote={status:'LOADING',accounts:[]};render();try{chatgptLocal=await api('/api/chatgpt/local')}catch(error){chatgptLocal={status:'FAILED',accounts:[],available:0,total:0,error:{message:error.message}}}render();try{chatgptRemote=await api('/api/chatgpt/remote')}catch(error){chatgptRemote={status:'FAILED',accounts:[],syncedAt:Date.now(),error:{message:error.message}}}finally{chatgptLoading=false;render()}}\nfunction render(){if(!state.loggedIn){app.innerHTML=loginGate();bind();return}const page=active==='overview'?overview():active==='registry'?registryPage():active==='chatgpt'?chatgptPage():active==='config'?configPage():active==='commands'?commandsPage():runnerPage();app.innerHTML=shell(page);bind()}\nfunction notice(message,type='success'){const target=document.getElementById('notice');if(target)target.innerHTML='<div class=\"notice '+type+'\">'+esc(message)+'</div>'}\nfunction bindCommandLoaders(){document.querySelectorAll('[data-load]').forEach((b)=>b.addEventListener('click',()=>{const argv=JSON.parse(b.dataset.load);active='runner';render();document.getElementById('argv').value=JSON.stringify(argv)}))}\nfunction bind(){\ndocument.querySelectorAll('[data-nav]').forEach((b)=>b.addEventListener('click',()=>{active=b.dataset.nav;render();if(active==='chatgpt'&&!chatgptLocal&&!chatgptLoading)void loadChatgpt();if(active==='registry'&&!registryNamespaces.length&&!registryLoading)void loadRegistry(true)}));\ndocument.querySelector('[data-action=\"chatgpt-refresh\"]')?.addEventListener('click',()=>void loadChatgpt());\ndocument.querySelectorAll('[data-action=\"registry-refresh\"],[data-action=\"registry-retry\"]')?.forEach((button)=>button.addEventListener('click',()=>registryWorkspace==='catalog'&®istryName?void selectRegistryItem(registryName):void loadRegistry()));\ndocument.querySelectorAll('[data-registry-workspace]').forEach((button)=>button.addEventListener('click',()=>{registryWorkspace=button.dataset.registryWorkspace;registryName='';registryDetail=null;registryAux=null;registryError='';registryNotice='';void loadRegistry()}));\ndocument.querySelectorAll('[data-registry-mode]').forEach((button)=>button.addEventListener('click',()=>{registryMode=button.dataset.registryMode;registryName='';registryDetail=null;void loadRegistry()}));\ndocument.querySelectorAll('[data-registry-name]').forEach((button)=>button.addEventListener('click',()=>void selectRegistryItem(button.dataset.registryName)));\ndocument.querySelectorAll('[data-registry-action]').forEach((button)=>button.addEventListener('click',()=>void registryAction(button.dataset.registryAction)));\ndocument.getElementById('registry-namespace')?.addEventListener('change',(event)=>{registryNamespaceId=Number(event.target.value);registryName='';registryDetail=null;registryAux=null;void loadRegistry()});\ndocument.getElementById('registry-search')?.addEventListener('change',(event)=>{registrySearch=event.target.value.trim();registryName='';registryDetail=null;void loadRegistry()});\ndocument.getElementById('registry-search')?.addEventListener('keydown',(event)=>{if(event.key==='Enter'){event.preventDefault();registrySearch=event.target.value.trim();registryName='';registryDetail=null;void loadRegistry()}});\ndocument.getElementById('registry-default-form')?.addEventListener('submit',async(event)=>{event.preventDefault();const namespaceKey=String(new FormData(event.currentTarget).get('namespaceKey')||'');if(!registryNamespaces.some((item)=>item.namespaceKey===namespaceKey)){registryNotice='<div class=\"notice error\">\u53EA\u80FD\u9009\u62E9 capability \u4E2D\u6388\u6743\u7684 namespace\u3002</div>';render();return}registryNotice='<div class=\"notice success\">\u6B63\u5728\u4FDD\u5B58\u9ED8\u8BA4\u503C\u2026</div>';render();try{const data={...state.config,aiRegistryDefaultNamespaceKey:namespaceKey};const result=await api('/api/config',{method:'PUT',body:JSON.stringify(data)});state.config=result.config;registryNotice='<div class=\"notice success\">\u7528\u6237\u9ED8\u8BA4 namespace \u5DF2\u4FDD\u5B58\u3002</div>'}catch(error){registryNotice='<div class=\"notice error\">'+esc(error.message)+'</div>'}render()});\ndocument.querySelector('[data-action=\"login\"]')?.addEventListener('click',async()=>{try{notice('\u6B63\u5728\u542F\u52A8\u767B\u5F55\u2026');const result=await api('/api/auth/login',{method:'POST',body:'{}'});location.href=result.authorizationUrl}catch(e){notice(e.message,'error')}});\ndocument.querySelector('[data-action=\"reload\"]')?.addEventListener('click',()=>bootstrap());\ndocument.querySelector('[data-action=\"logout\"]')?.addEventListener('click',async()=>{if(!confirm('\u6E05\u9664\u672C\u673A Keycloak \u767B\u5F55\u7F13\u5B58\uFF1F'))return;await api('/api/auth/logout',{method:'POST',body:'{}'});location.reload()});\ndocument.getElementById('config-form')?.addEventListener('submit',async(e)=>{e.preventDefault();const submit=e.currentTarget.querySelector('button[type=\"submit\"]');if(submit)submit.disabled=true;const data=Object.fromEntries(new FormData(e.currentTarget));data.checkUpdates=data.checkUpdates==='true';try{const result=await api('/api/config',{method:'PUT',body:JSON.stringify(data)});state.config=result.config;notice('\u914D\u7F6E\u5DF2\u4FDD\u5B58')}catch(err){notice(err.message,'error')}finally{if(submit)submit.disabled=false}});\ndocument.getElementById('command-search')?.addEventListener('input',(e)=>{document.getElementById('command-list').innerHTML=commandCards(e.target.value);bindCommandLoaders()});\nbindCommandLoaders();\ndocument.getElementById('runner-form')?.addEventListener('submit',async(e)=>{e.preventDefault();try{const argv=JSON.parse(document.getElementById('argv').value);const confirmed=document.getElementById('confirm')?.value==='true';output='\u8FD0\u884C\u4E2D\u2026';render();const result=await api('/api/commands/run',{method:'POST',body:JSON.stringify({argv,confirm:confirmed})});output=result.stdout||result.stderr||JSON.stringify(result,null,2);render()}catch(err){output='\u9519\u8BEF: '+err.message;render()}})\n}\nbootstrap();\n";
|
|
3
|
+
export declare const CONSOLE_JS = "\nconst app=document.getElementById('app');let state=null;let csrf='';let active='overview';let output='\u9009\u62E9\u4E00\u6761\u547D\u4EE4\u6216\u8F93\u5165\u53C2\u6570\u540E\u8FD0\u884C\u3002';let chatgptLocal=null;let chatgptRemote=null;let chatgptLoading=false;let registryCapabilities=null;let registryNamespaces=[];let registryItems=[];let registryDetail=null;let registryAux=null;let registryMode='skill';let registryWorkspace='catalog';let registryNamespaceId=null;let registryName='';let registrySearch='';let registryLoading=false;let registryError='';let registryNotice='';const noAvailableAccountGuidance='\u5F53\u524D\u7528\u6237\u6CA1\u6709\u7ED1\u5B9A\u53EF\u7528\u7684 ChatGPT \u8D26\u53F7\u3002\u8BF7\u524D\u5F80 OA \u7CFB\u7EDF\u63D0\u4EA4 IT11 \u5DE5\u5355\uFF0C\u7533\u8BF7\u72EC\u4EAB\u6216\u5171\u4EAB\u7684 ChatGPT \u4E2A\u4EBA\u8D26\u53F7\u3002';\nconst esc=(v)=>String(v??'').replace(/[&<>\"']/g,(c)=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nasync function api(path,init={}){const headers={'Accept':'application/json',...(init.body?{'Content-Type':'application/json'}:{}),...(csrf?{'x-numa-console-csrf':csrf}:{})};const response=await fetch(path,{...init,headers:{...headers,...(init.headers||{})},credentials:'same-origin'});const body=await response.json().catch(()=>({error:{message:'\u54CD\u5E94\u4E0D\u662F JSON'}}));if(!response.ok)throw new Error(body.error?.message||body.stderr||body.stdout||('HTTP '+response.status));return body}\nasync function bootstrap(attempt=0){try{state=await api('/api/bootstrap');csrf=state.csrfToken;const callbackState=new URLSearchParams(location.search).get('login');const waitingForSuccess=callbackState==='complete'&&!state.loggedIn&&attempt<40;const waitingForFailure=callbackState==='failed'&&!state.loginError&&attempt<8;if(waitingForSuccess||waitingForFailure){app.innerHTML='<div class=\"boot\"><div class=\"boot-mark\">N</div><p>'+(callbackState==='complete'?'\u767B\u5F55\u6210\u529F\uFF0C\u6B63\u5728\u5EFA\u7ACB\u672C\u673A\u4F1A\u8BDD\u2026':'\u6B63\u5728\u8BFB\u53D6\u767B\u5F55\u5931\u8D25\u4FE1\u606F\u2026')+'</p></div>';setTimeout(()=>bootstrap(attempt+1),250);return}if(callbackState==='complete'&&!state.loggedIn&&!state.loginError)state.loginError='\u767B\u5F55\u51ED\u636E\u4EA4\u6362\u5C1A\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u767B\u5F55\u3002';if(callbackState==='failed'&&!state.loginError)state.loginError='Keycloak \u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u3002';if(callbackState)history.replaceState({},'',location.pathname);render()}catch(error){app.innerHTML='<div class=\"login-gate\"><div class=\"login-card\"><span class=\"tag role\">\u8FDE\u63A5\u5931\u8D25</span><h2>\u672C\u5730\u63A7\u5236\u53F0\u4E0D\u53EF\u7528</h2><p>'+esc(error.message)+'</p></div></div>'}}\nfunction navButton(id,label,mark){return '<button data-nav=\"'+id+'\" class=\"'+(active===id?'active':'')+'\"><span>'+label+'</span><span class=\"mono\">'+mark+'</span></button>'}\nfunction shell(content){const user=state.user||{};const label=user.username||user.email||'\u672A\u767B\u5F55';return '<div class=\"shell\"><aside class=\"rail\"><div class=\"brand\"><div class=\"brand-mark\">N</div><div><strong>Numa CLI</strong><small>Local control plane</small></div></div><nav class=\"nav\">'+navButton('overview','\u8FD0\u884C\u6982\u89C8','01')+navButton('registry','AI Registry','02')+navButton('chatgpt','ChatGPT \u8D26\u53F7','03')+navButton('config','\u57FA\u7840\u914D\u7F6E','04')+navButton('commands','\u547D\u4EE4\u80FD\u529B','05')+navButton('runner','\u6267\u884C\u5668','06')+'</nav><div class=\"rail-foot\"><strong><span class=\"pulse\"></span>\u4EC5\u76D1\u542C\u672C\u673A</strong><p>Token \u4E0D\u4F1A\u53D1\u9001\u7ED9\u7F51\u9875\u811A\u672C\uFF0C\u4E5F\u4E0D\u4F1A\u51FA\u73B0\u5728\u547D\u4EE4\u8F93\u51FA\u4E2D\u3002</p></div></aside><main class=\"main\"><header class=\"topbar\"><h1>'+sectionTitle()+'</h1><div class=\"user-chip\"><div class=\"avatar\">'+esc(label.slice(0,1).toUpperCase())+'</div><div><strong>'+esc(label)+'</strong><br><small>'+esc(state.loggedIn?'Keycloak \u4F1A\u8BDD\u6709\u6548':'\u9700\u8981\u767B\u5F55')+'</small></div></div></header>'+content+'</main></div>'}\nfunction sectionTitle(){return {overview:'\u8FD0\u884C\u6982\u89C8',registry:'AI Registry',chatgpt:'ChatGPT \u8D26\u53F7',config:'\u57FA\u7840\u914D\u7F6E',commands:'\u547D\u4EE4\u80FD\u529B',runner:'\u5B89\u5168\u6267\u884C\u5668'}[active]||'\u63A7\u5236\u53F0'}\nfunction loginGate(){const error=state?.loginError?'<div class=\"notice error\">'+esc(state.loginError)+'</div>':'';return '<div class=\"login-gate\"><div class=\"login-card\"><span class=\"tag role\">Keycloak PKCE</span><h2>\u767B\u5F55\u540E\u8FDB\u5165\u672C\u5730\u63A7\u5236\u53F0</h2><p>\u6D4F\u89C8\u5668\u53EA\u8D1F\u8D23\u5B8C\u6210\u8BA4\u8BC1\u548C\u64CD\u4F5C\u672C\u673A API\u3002\u8BBF\u95EE\u4EE4\u724C\u4FDD\u5B58\u5728 CLI \u7684\u79C1\u6709\u7F13\u5B58\u4E2D\uFF0C\u4E0D\u5199\u5165 localStorage\uFF0C\u4E5F\u4E0D\u8FD4\u56DE\u7ED9\u9875\u9762\u3002</p><div class=\"actions\"><button class=\"button primary\" data-action=\"login\">\u4F7F\u7528 Keycloak \u767B\u5F55</button></div><div id=\"notice\">'+error+'</div></div></div>'}\nfunction overview(){const u=state.user||{};const roles=[...(u.realmRoles||[]),...Object.values(u.clientRoles||{}).flat()];return '<div class=\"content\"><section class=\"hero\"><div><div class=\"eyebrow\">Authenticated workstation</div><h2>\u672C\u673A\u4F1A\u8BDD\u5DF2\u63A5\u5165\u7814\u53D1\u63A7\u5236\u9762</h2><p>\u4ECE\u540C\u4E00\u5904\u68C0\u67E5\u8EAB\u4EFD\u3001\u8C03\u6574 CLI \u914D\u7F6E\uFF0C\u5E76\u4EE5\u7ED3\u6784\u5316\u53C2\u6570\u8FD0\u884C\u53D7\u652F\u6301\u547D\u4EE4\u3002\u6240\u6709\u7F51\u7EDC\u8BF7\u6C42\u7EE7\u7EED\u7531 CLI \u6CE8\u5165 Bearer Token\u3002</p></div><div class=\"session-track\"><div class=\"track-node live\"><span>\u8EAB\u4EFD</span><strong>'+esc(u.username||'\u5DF2\u767B\u5F55')+'</strong></div><div class=\"track-node\"><span>API</span><strong>'+esc(state.config.apiBaseUrl?'\u5DF2\u914D\u7F6E':'\u672A\u914D\u7F6E')+'</strong></div><div class=\"track-node\"><span>\u89D2\u8272</span><strong>'+roles.length+'</strong></div></div></section><section class=\"grid\"><article class=\"card\"><div class=\"eyebrow\">Session</div><div class=\"metric\">'+esc(state.expiresAt?new Date(state.expiresAt*1000).toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'}):'\u2014')+' <small>\u8BBF\u95EE\u4EE4\u724C\u5230\u671F</small></div><p>\u9700\u8981\u65F6\u7531 CLI \u4F7F\u7528 refresh token \u81EA\u52A8\u7EED\u671F\u3002</p></article><article class=\"card\"><div class=\"eyebrow\">Commands</div><div class=\"metric\">'+state.commands.length+' <small>\u6761\u80FD\u529B\u5951\u7EA6</small></div><p>\u7F51\u9875\u6267\u884C\u5668\u53EA\u63A5\u53D7\u76EE\u5F55\u4E2D\u58F0\u660E\u7684\u547D\u4EE4\u3002</p></article><article class=\"card\"><div class=\"eyebrow\">Endpoint</div><div class=\"metric\">'+(state.config.apiBaseUrl?'READY':'SETUP')+'</div><p>'+esc(state.config.apiBaseUrl||'\u8BF7\u5728\u57FA\u7840\u914D\u7F6E\u4E2D\u586B\u5199\u5E73\u53F0 API \u5730\u5740')+'</p></article><article class=\"card wide\"><h3>\u5F53\u524D\u8EAB\u4EFD</h3><p>'+esc(u.displayName||u.username||'-')+' \u00B7 '+esc(u.email||'-')+'</p><div class=\"actions\">'+roles.slice(0,8).map((r)=>'<span class=\"tag\">'+esc(r)+'</span>').join('')+(roles.length>8?'<span class=\"tag public\">+'+(roles.length-8)+'</span>':'')+'</div></article><article class=\"card\"><h3>\u4F1A\u8BDD\u64CD\u4F5C</h3><p>\u5F3A\u5236\u5237\u65B0\u8EAB\u4EFD\u8D44\u6599\uFF0C\u6216\u6E05\u9664\u672C\u673A Keycloak \u7F13\u5B58\u3002</p><div class=\"actions\"><button class=\"button\" data-action=\"reload\">\u5237\u65B0\u72B6\u6001</button><button class=\"button danger\" data-action=\"logout\">\u9000\u51FA\u767B\u5F55</button></div></article></section></div>'}\nfunction instant(value){if(!value)return null;const number=typeof value==='number'?(value<1000000000000?value*1000:value):Date.parse(value);return Number.isFinite(number)?number:null}\nfunction timeText(value){const at=instant(value);return at?new Date(at).toLocaleString('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}):'\u2014'}\nfunction windowName(window,fallback){const minutes=window?.windowMinutes;if(!minutes)return fallback;if(minutes%1440===0)return (minutes/1440)+'d';if(minutes%60===0)return (minutes/60)+'h';return minutes+'m'}\nfunction quotaWindowHasData(window){if(!window)return false;const minutes=window.windowMinutes;const reset=window.resetAt;const hasMinutes=typeof minutes==='number'&&Number.isFinite(minutes)&&minutes>0;const hasReset=(typeof reset==='number'&&Number.isFinite(reset)&&reset>0)||(typeof reset==='string'&&Number.isFinite(Date.parse(reset)));const hasUsage=typeof window.usedPercent==='number'&&Number.isFinite(window.usedPercent)&&window.usedPercent>0;return hasMinutes||hasReset||hasUsage}\nfunction quotaLine(window,fallback){if(!quotaWindowHasData(window)||typeof window.usedPercent!=='number')return '';const remain=Math.max(0,Math.min(100,100-window.usedPercent));return '<div class=\"quota-line\" title=\"\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4 '+esc(timeText(window.resetAt))+'\"><span>'+esc(windowName(window,fallback))+'</span><progress max=\"100\" value=\"'+remain.toFixed(1)+'\"></progress><strong>'+remain.toFixed(0)+'%</strong></div>'}\nfunction quotaCell(quota){if(!quota)return '<span class=\"table-meta\">\u6682\u65E0\u989D\u5EA6\u91C7\u6837</span>';const lines=quotaLine(quota.primary,'\u4E3B\u7A97\u53E3')+quotaLine(quota.secondary,'\u6B21\u7A97\u53E3');const detail=[quota.planType,quota.model,quota.activeLimit!=null?'limit '+quota.activeLimit:null].filter(Boolean).join(' \u00B7 ');return '<div class=\"quota-stack\">'+(lines||'<span class=\"table-meta\">\u989D\u5EA6\u7A97\u53E3\u6682\u65E0\u6570\u636E</span>')+'<div class=\"table-meta\">'+esc(detail||'\u5DF2\u91C7\u6837')+'<br>\u91C7\u6837 '+esc(timeText(quota.updatedAt))+'</div></div>'}\nfunction newerQuota(localQuota,remoteQuota){if(!localQuota)return remoteQuota;if(!remoteQuota)return localQuota;return (instant(remoteQuota.updatedAt)||0)>=(instant(localQuota.updatedAt)||0)?remoteQuota:localQuota}\nfunction remoteAccountExpired(item){if(!item)return false;if(String(item.subscriptionStatus||'').toUpperCase()==='EXPIRED'||item.credentialStatus==='EXPIRED')return true;return Number.isFinite(item.subscriptionExpiresAt)&&item.subscriptionExpiresAt<=Date.now()}\nfunction remoteAccountEnabled(item){return !remoteAccountExpired(item)&&(!item?.credentialStatus||item.credentialStatus==='AVAILABLE')}\nfunction mergedChatgptAccounts(){const local=chatgptLocal?.accounts||[];const remote=chatgptRemote?.accounts||[];const used=new Set();const rows=local.map((item)=>{const index=remote.findIndex((candidate,i)=>!used.has(i)&&item.accountId&&candidate.accountId===item.accountId);const match=index>=0?remote[index]:null;if(index>=0)used.add(index);return {key:item.id,local:item,remote:match,label:item.label,email:item.email,accountId:item.accountId||match?.accountId,enabled:item.enabled&&(!match||remoteAccountEnabled(match)),selected:item.selected,quota:newerQuota(item.quota,match?.quota)}});remote.forEach((item,index)=>{if(!used.has(index))rows.push({key:'remote_'+item.id,local:null,remote:item,label:item.label,email:null,accountId:item.accountId,enabled:remoteAccountEnabled(item),selected:item.isDefault,quota:item.quota})});return rows}\nfunction syncTag(row){if(chatgptRemote?.status==='LOADING')return '<span class=\"tag waiting\">\u6B63\u5728\u540C\u6B65</span>';if(chatgptRemote?.status==='FAILED')return '<span class=\"tag failed\">\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25</span>';if(chatgptRemote?.status==='NOT_CONFIGURED')return '<span class=\"tag waiting\">\u672A\u914D\u7F6E\u5E73\u53F0</span>';if(remoteAccountExpired(row.remote))return '<span class=\"tag failed\">\u5E73\u53F0\u5DF2\u8FC7\u671F</span>';if(row.local&&row.remote)return '<span class=\"tag success\">\u5DF2\u540C\u6B65</span>';if(row.local)return '<span class=\"tag local\">\u4EC5\u672C\u5730</span>';return '<span class=\"tag success\">\u5E73\u53F0\u53EF\u7528</span>'}\nfunction remoteStage(){const remote=chatgptRemote;if(!remote||remote.status==='LOADING')return {tag:'<span class=\"tag waiting\"><span class=\"refresh-icon spin\" aria-hidden=\"true\"></span> \u8FDE\u63A5\u4E2D</span>',title:'\u6B63\u5728\u8FDE\u63A5\u5E73\u53F0',detail:'\u672C\u5730\u8D26\u53F7\u5DF2\u7ECF\u53EF\u89C1\uFF0C\u6B63\u5728\u8BFB\u53D6\u8FDC\u7A0B\u53EF\u7528\u8D26\u53F7\u3002'};if(remote.status==='SUCCESS')return {tag:'<span class=\"tag success\">\u540C\u6B65\u6210\u529F</span>',title:remote.accounts.length+' \u4E2A\u5E73\u53F0\u8D26\u53F7',detail:'\u5B8C\u6210\u4E8E '+timeText(remote.syncedAt)};if(remote.status==='FAILED')return {tag:'<span class=\"tag failed\">\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25</span>',title:'\u4FDD\u7559\u672C\u5730\u7ED3\u679C',detail:remote.error?.message||'\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u8D26\u53F7\u670D\u52A1'};return {tag:'<span class=\"tag waiting\">\u672A\u914D\u7F6E</span>',title:'\u672A\u914D\u7F6E\u5E73\u53F0\u5730\u5740',detail:'\u53EF\u5728\u57FA\u7840\u914D\u7F6E\u4E2D\u586B\u5199\u5E73\u53F0 API \u5730\u5740\u3002'}}\nfunction accountRows(rows){return rows.map((row)=>'<tr><td><div class=\"account-name\"><strong>'+esc(row.label)+'</strong><small>'+esc(row.email||row.accountId||row.key)+'</small></div></td><td><div class=\"status-stack\">'+(row.enabled?'<span class=\"tag success\">\u53EF\u7528</span>':remoteAccountExpired(row.remote)?'<span class=\"tag failed\">\u5DF2\u8FC7\u671F</span>':'<span class=\"tag failed\">\u5DF2\u505C\u7528</span>')+(row.selected?'<span class=\"tag role\">\u5BA2\u6237\u7AEF\u5DF2\u9009</span>':'')+'</div></td><td>'+quotaCell(row.quota)+'</td><td><div class=\"table-meta\">Token '+esc(row.local?'\u672C\u5730\u53EF\u7528':'\u6309\u9700\u4ECE\u5E73\u53F0\u83B7\u53D6')+'<br>\u5230\u671F '+esc(timeText(row.local?.accessExpiresAt||row.remote?.accessExpiresAt))+(row.remote?.subscriptionExpiresAt?'<br>\u8BA2\u9605 '+esc(timeText(row.remote.subscriptionExpiresAt)):'')+'</div></td><td><div class=\"status-stack\">'+syncTag(row)+'</div></td></tr>').join('')}\nfunction chatgptPage(){const rows=mergedChatgptAccounts();const remote=remoteStage();const localReady=chatgptLocal?.status==='READY';const localCount=chatgptLocal?.available??0;const failure=chatgptRemote?.status==='FAILED'?'<div class=\"notice error\"><strong>\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\u3002</strong> '+esc(chatgptRemote.error?.message||'\u5E73\u53F0\u6682\u65F6\u4E0D\u53EF\u7528')+'\uFF1B\u672C\u5730\u8D26\u53F7\u6570\u636E\u4ECD\u53EF\u6B63\u5E38\u67E5\u770B\u3002</div>':'';return '<div class=\"content\"><section class=\"hero\"><div><div class=\"eyebrow\">Local first / Remote second</div><h2>ChatGPT \u8D26\u53F7\u8FD0\u884C\u89C6\u56FE</h2><p>\u5148\u8BFB\u53D6 codex-multi-auth \u672C\u5730\u8D26\u53F7\u4E0E\u989D\u5EA6\u7F13\u5B58\uFF0C\u518D\u8FDE\u63A5\u5E73\u53F0\u8865\u5145\u5F53\u524D\u7528\u6237\u53EF\u7528\u8D26\u53F7\u3002\u9875\u9762\u6C38\u4E0D\u63A5\u6536 access token \u6216 refresh token\u3002</p></div><div class=\"session-track\"><div class=\"track-node live\"><span>\u672C\u5730\u53EF\u7528</span><strong>'+localCount+'</strong></div><div class=\"track-node\"><span>\u6C47\u603B\u8D26\u53F7</span><strong>'+rows.length+'</strong></div><div class=\"track-node\"><span>\u8FDC\u7A0B</span><strong>'+esc(chatgptRemote?.status||'WAITING')+'</strong></div></div></section><section class=\"sync-board\"><article class=\"sync-stage\"><div class=\"eyebrow\">01 / Local pool</div><strong>'+(localReady?localCount+' \u4E2A\u672C\u5730\u53EF\u7528\u8D26\u53F7':'\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8D26\u53F7')+'</strong><p>'+(localReady?'\u8BFB\u53D6\u5B8C\u6210\u4E8E '+timeText(chatgptLocal.readAt):'\u4F18\u5148\u8BFB\u53D6 codex-multi-auth \u5B89\u5168\u6458\u8981\u3002')+'</p></article><div class=\"sync-link\" aria-label=\"\u7136\u540E\u8FDE\u63A5\u8FDC\u7A0B\"></div><article class=\"sync-stage\"><div class=\"eyebrow\">02 / Platform service</div>'+remote.tag+'<strong>'+esc(remote.title)+'</strong><p>'+esc(remote.detail)+'</p></article></section>'+failure+'<section class=\"table-card\"><header class=\"table-head\"><div><h3>\u53EF\u7528\u8D26\u53F7\u4E0E\u5269\u4F59\u989D\u5EA6</h3><p>\u989D\u5EA6\u4F18\u5148\u5C55\u793A\u672C\u5730\u6216\u670D\u52A1\u5668\u7AEF\u66F4\u65B0\u8F83\u65B0\u7684\u5B89\u5168\u5FEB\u7167\u3002</p></div><button class=\"button\" data-action=\"chatgpt-refresh\" '+(chatgptLoading?'disabled':'')+'><span class=\"refresh-icon '+(chatgptLoading?'spin':'')+'\" aria-hidden=\"true\"></span> '+(chatgptLoading?'\u540C\u6B65\u4E2D':'\u91CD\u65B0\u540C\u6B65')+'</button></header>'+(rows.length?'<div class=\"account-table-wrap\"><table class=\"account-table\"><thead><tr><th>\u8D26\u53F7</th><th>\u53EF\u7528\u72B6\u6001</th><th>\u5269\u4F59 limit</th><th>\u51ED\u636E\u65F6\u6548</th><th>\u540C\u6B65\u72B6\u6001</th></tr></thead><tbody>'+accountRows(rows)+'</tbody></table></div>':'<div class=\"empty-state'+(chatgptLoading?'':' account-request')+'\">'+(chatgptLoading?'\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8D26\u53F7\u2026':'<strong>\u9700\u8981\u7533\u8BF7 ChatGPT \u8D26\u53F7</strong><span>'+esc(noAvailableAccountGuidance)+'</span>')+'</div>')+'</section></div>'}\nfunction registryPageItems(payload){if(Array.isArray(payload))return payload.filter((item)=>item&&typeof item==='object');for(const key of ['pageItems','items','records','list','data']){const value=payload?.[key];if(Array.isArray(value))return value.filter((item)=>item&&typeof item==='object');if(value&&typeof value==='object'){const nested=registryPageItems(value);if(nested.length)return nested}}return []}\nfunction registryItemName(item,index){return String(item?.name??item?.skillName??item?.mcpName??item?.requestId??item?.eventType??item?.bindingId??((registryMode==='skill'?'skill-':'mcp-')+(index+1)))}\nfunction registryItemStatus(item){return String(item?.status??item?.releaseStatus??item?.state??item?.version??'registered')}\nfunction currentRegistryCapability(){const selected=registryNamespaces.find((namespace)=>Number(namespace.id)===Number(registryNamespaceId));return registryCapabilities?.namespaces?.find((candidate)=>candidate.namespaceKey===selected?.namespaceKey&&(!candidate.instanceCode||candidate.instanceCode===selected?.instanceCode))||null}\nfunction registryCan(action){const actions=currentRegistryCapability()?.actions||[];return actions.some((value)=>value===action||value.endsWith(':'+action)||value.endsWith('.'+action))}\nfunction registryWorkspaceTabs(){const tabs=[['catalog','\u76EE\u5F55'],['approvals','\u53D1\u5E03\u6536\u4EF6\u7BB1'],['instances','\u5B9E\u4F8B / \u51ED\u636E'],['namespace','Namespace / Profile'],['router','Router'],['audit','\u6743\u9650 / \u5BA1\u8BA1']];return '<div class=\"registry-workspaces\" role=\"tablist\" aria-label=\"AI Registry \u5DE5\u4F5C\u533A\">'+tabs.map(([id,label])=>'<button role=\"tab\" aria-selected=\"'+(registryWorkspace===id)+'\" data-registry-workspace=\"'+id+'\" class=\"'+(registryWorkspace===id?'active':'')+'\">'+label+'</button>').join('')+'</div>'}\nfunction registryNamespaceOptions(){return registryNamespaces.map((namespace)=>'<option value=\"'+namespace.id+'\" '+(Number(namespace.id)===Number(registryNamespaceId)?'selected':'')+'>'+esc(namespace.displayName)+' \u00B7 '+esc(namespace.namespaceKey)+' \u00B7 '+esc(namespace.instanceCode)+(namespace.isDefault?' \u00B7 default':'')+'</option>').join('')}\nfunction registryItemButtons(){return registryItems.map((item,index)=>{const name=registryItemName(item,index);const status=registryItemStatus(item);const online=/online|published|release/i.test(status);return '<button class=\"registry-item '+(name===registryName?'active':'')+'\" data-registry-name=\"'+esc(name)+'\"><span class=\"registry-dot '+(online?'online':'')+'\"></span><span><strong>'+esc(name)+'</strong><small>'+esc(status)+(item.scope?' \u00B7 '+esc(item.scope):'')+'</small></span></button>'}).join('')||'<div class=\"empty-state\">\u6CA1\u6709\u5339\u914D\u7684 '+(registryMode==='skill'?'Skill':'MCP Server')+'</div>'}\nfunction registryTargets(){const targets=registryMode==='skill'?['chatgpt','codex','claude','cursor','workbuddy','openclaw','agents']:['chatgpt','codex','claude','cursor','workbuddy','openclaw'];return targets.map((target)=>'<option value=\"'+target+'\">'+target+'</option>').join('')}\nfunction compactValue(value){if(value==null||value==='')return '\u2014';if(Array.isArray(value))return value.length?value.map((item)=>typeof item==='object'?(item.name??item.id??'record'):item).join(', '):'\u2014';if(typeof value==='object')return value.displayName??value.name??value.status??Object.keys(value).length+' fields';return String(value)}\nfunction summaryCells(entries){return '<div class=\"summary-grid\">'+entries.map(([label,value])=>'<div class=\"summary-cell\"><span>'+esc(label)+'</span><strong>'+esc(compactValue(value))+'</strong></div>').join('')+'</div>'}\nfunction rawDisclosure(value,label='\u67E5\u770B\u539F\u59CB\u54CD\u5E94'){return '<details class=\"raw-disclosure\"><summary>'+esc(label)+'</summary><pre class=\"output wrap\">'+esc(JSON.stringify(value??{},null,2))+'</pre></details>'}\nfunction registryDetailSummary(detail){const tags=detail?.tags??detail?.labels;const owner=detail?.ownerTeamCode??detail?.ownerTeam??detail?.teamCode;const entries=registryMode==='skill'?[['\u7CBE\u786E\u7248\u672C',detail?.version??detail?.latestVersion],['\u6CBB\u7406\u72B6\u6001',detail?.status??detail?.state],['\u53EF\u89C1\u6027',detail?.visibility??detail?.scope],['\u8D23\u4EFB\u56E2\u961F',owner],['\u6807\u7B7E',tags],['\u626B\u63CF',detail?.scanStatus??detail?.pipelineStatus]]:[['\u7CBE\u786E\u7248\u672C',detail?.version??detail?.latestVersion],['\u6CBB\u7406\u72B6\u6001',detail?.status??detail?.state],['\u534F\u8BAE',detail?.protocol??detail?.transport],['\u8D23\u4EFB\u56E2\u961F',owner],['\u5DE5\u5177\u6570\u91CF',Array.isArray(detail?.tools)?detail.tools.length:detail?.toolCount],['Endpoint \u5065\u5EB7',detail?.health??detail?.endpointStatus]];return summaryCells(entries)+rawDisclosure(detail)}\nfunction registryDetailPanel(){if(registryLoading)return '<div class=\"empty-state\" role=\"status\"><span class=\"refresh-icon spin\" aria-hidden=\"true\"></span> \u6B63\u5728\u8BFB\u53D6\u79C1\u6709 Registry\u2026</div>';if(!registryName)return '<div class=\"empty-state\">\u4ECE\u5217\u8868\u9009\u62E9\u4E00\u9879\u67E5\u770B\u7248\u672C\u3001\u6807\u7B7E\u548C\u6CBB\u7406\u72B6\u6001\u3002</div>';const canSubmit=registryCan('submit');const mutating='<button class=\"button\" data-registry-action=\"submit\" '+(canSubmit?'':'disabled')+'>\u63D0\u4EA4\u5BA1\u6838</button>';return '<div class=\"registry-detail-head\"><div><h3>'+esc(registryName)+'</h3><span class=\"tag '+(canSubmit?'role':'public')+'\">'+(canSubmit?'\u53EF\u63D0\u4EA4\u8349\u7A3F':'\u53EA\u8BFB')+'</span></div><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0\u8BE6\u60C5</button></div>'+registryDetailSummary(registryDetail)+'<div class=\"registry-actions\"><div class=\"install-row\"><label class=\"field\"><span>\u5B89\u88C5\u76EE\u6807</span><select id=\"registry-target\">'+registryTargets()+'</select></label><label class=\"field\"><span>\u5B89\u88C5\u8303\u56F4</span><select id=\"registry-scope\"><option value=\"project\">\u5F53\u524D\u9879\u76EE</option><option value=\"user\">\u5F53\u524D\u7528\u6237</option></select></label><button class=\"button primary\" data-registry-action=\"install\" '+(registryCan('install')||registryCan('use')?'':'disabled')+'>\u5B89\u88C5\u5E76\u9501\u5B9A</button></div><div class=\"actions\">'+mutating+'</div><small class=\"table-meta\">\u76EE\u5F55\u53EA\u5141\u8BB8\u5B89\u88C5\u4E0E\u63D0\u4EA4 release request\u3002\u6279\u51C6\u548C\u53D1\u5E03\u53EA\u5728\u53D1\u5E03\u6536\u4EF6\u7BB1\u51FA\u73B0\uFF0C\u6700\u7EC8\u6388\u6743\u548C\u4E24\u4EBA\u5236\u6821\u9A8C\u7531\u5E73\u53F0\u6267\u884C\u3002</small><div id=\"registry-notice\" aria-live=\"polite\">'+registryNotice+'</div></div>'}\nfunction instanceCards(){if(!registryItems.length)return '<div class=\"empty-state\">\u6CA1\u6709\u53EF\u89C1\u7684 Registry \u5B9E\u4F8B\u3002</div>';return '<div class=\"record-grid\">'+registryItems.map((item)=>{const credentials=item.credentials||{};return '<article class=\"record-card\"><div class=\"registry-detail-head\"><h4>'+esc(item.displayName||item.code||'\u5B9E\u4F8B')+'</h4><span class=\"tag '+(item.status==='ACTIVE'?'success':'waiting')+'\">'+esc(item.status||'UNKNOWN')+'</span></div><dl><dt>\u5B9E\u4F8B\u4EE3\u7801</dt><dd>'+esc(item.code||'\u2014')+'</dd><dt>\u8BA4\u8BC1\u65B9\u5F0F</dt><dd>'+esc(item.authenticationType||'\u2014')+'</dd><dt>READER</dt><dd>'+esc(credentials.readerConfigured??item.credentialConfigured?'\u5DF2\u9A8C\u8BC1':'\u672A\u5C31\u7EEA')+'</dd><dt>MAINTAINER</dt><dd>'+esc(credentials.maintainerConfigured??item.credentialConfigured?'\u5DF2\u9A8C\u8BC1':'\u672A\u5C31\u7EEA')+'</dd><dt>LEGACY_BOTH</dt><dd>'+esc(credentials.legacyBothConfigured?'\u8FC1\u79FB\u4E2D':'\u672A\u542F\u7528')+'</dd><dt>\u4E50\u89C2\u9501\u7248\u672C</dt><dd>'+esc(item.version??'\u2014')+'</dd></dl></article>'}).join('')+'</div>'}\nfunction registrySimplePanel(title,description){\nif(registryLoading)return '<section class=\"registry-simple\"><div class=\"empty-state\" role=\"status\">\u6B63\u5728\u8BFB\u53D6 '+esc(title)+'\u2026</div></section>';\nif(registryError)return '<section class=\"registry-simple\"><div class=\"notice error\" role=\"alert\"><strong>'+esc(title)+'\u52A0\u8F7D\u5931\u8D25\u3002</strong> '+esc(registryError)+' <button class=\"button\" data-action=\"registry-retry\">\u91CD\u8BD5</button></div></section>';\nif(registryWorkspace==='instances')return '<section class=\"registry-simple\"><h3>'+esc(title)+'</h3><p>'+esc(description)+'</p>'+instanceCards()+'<small class=\"table-meta\">\u6D4F\u89C8\u5668\u53EA\u63A5\u6536\u201C\u662F\u5426\u5DF2\u914D\u7F6E/\u9A8C\u8BC1\u201D\u7684\u5E03\u5C14\u72B6\u6001\uFF1BNacos \u5730\u5740\u4E0E credential \u6C38\u4E0D\u8FDB\u5165 capability\u3002</small></section>';\nif(registryWorkspace==='namespace'){\nconst cap=currentRegistryCapability()||{};const selected=registryNamespaces.find((item)=>Number(item.id)===Number(registryNamespaceId));\nreturn '<section class=\"registry-simple\"><h3>'+esc(title)+'</h3><p>'+esc(description)+'</p><dl class=\"registry-facts\"><div><dt>\u73AF\u5883</dt><dd>'+esc(cap.environment||selected?.environment||'\u672A\u58F0\u660E')+'</dd></div><div><dt>\u8BBF\u95EE\u7B56\u7565</dt><dd>'+esc(selected?.accessPolicy||'\u7531 capability \u51B3\u5B9A')+'</dd></div><div><dt>\u8D23\u4EFB\u56E2\u961F</dt><dd>'+esc(selected?.ownerTeamCode||'\u5171\u4EAB\u9ED8\u8BA4 namespace')+'</dd></div><div><dt>\u5F53\u524D\u7528\u6237\u9ED8\u8BA4\u503C</dt><dd>'+esc(state.config.aiRegistryDefaultNamespaceKey||'\u672A\u8BBE\u7F6E')+'</dd></div><div><dt>Profile \u9ED8\u8BA4\u503C</dt><dd>'+esc(state.config.aiRegistryDefaultNamespaceKey?'\u88AB\u7528\u6237\u504F\u597D\u8986\u76D6':'\u89E3\u6790\u65F6\u7531 profile \u63D0\u4F9B')+'</dd></div><div><dt>Namespace \u72B6\u6001</dt><dd>'+esc(registryAux?.status||selected?.status||'UNKNOWN')+'</dd></div></dl><form id=\"registry-default-form\" class=\"form-grid\"><label class=\"field\"><span>\u5C06\u6388\u6743 namespace \u8BBE\u4E3A\u7528\u6237\u9ED8\u8BA4\u503C</span><select name=\"namespaceKey\">'+registryNamespaces.map((item)=>'<option value=\"'+esc(item.namespaceKey)+'\" '+(item.namespaceKey===state.config.aiRegistryDefaultNamespaceKey?'selected':'')+'>'+esc(item.displayName)+' \u00B7 '+esc(item.namespaceKey)+'</option>').join('')+'</select></label><div class=\"actions\"><button class=\"button primary\" type=\"submit\">\u4FDD\u5B58\u9ED8\u8BA4\u503C</button><button class=\"button\" type=\"button\" data-registry-action=\"namespace-check\">\u91CD\u65B0\u68C0\u67E5</button></div></form><small class=\"table-meta\">\u89E3\u6790\u987A\u5E8F\u4FDD\u6301\u4E3A\u547D\u4EE4\u53C2\u6570 \u2192 \u9879\u76EE manifest \u2192 \u7528\u6237\u504F\u597D \u2192 profile \u9ED8\u8BA4\u503C\uFF1B\u65E0\u6548\u9ED8\u8BA4\u503C\u4F1A\u660E\u786E\u5931\u8D25\u3002</small><div id=\"registry-notice\" aria-live=\"polite\">'+registryNotice+'</div></section>';\n}\nif(registryWorkspace==='router'){\nconst router=registryDetail||currentRegistryCapability()?.router||{};const canConnect=registryCan('router.connect')||registryCan('connect');\nreturn '<section class=\"registry-simple\"><div class=\"registry-detail-head\"><div><h3>'+esc(title)+'</h3><span class=\"tag '+(router.status==='READY'?'success':'waiting')+'\">'+esc(router.status||'UNAVAILABLE')+'</span></div><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0\u5065\u5EB7</button></div><p>'+esc(description)+'</p>'+summaryCells([['\u7ED1\u5B9A',router.bindingId],['\u534F\u8BAE',router.protocol],['\u7B56\u7565 revision',router.policyRevision],['\u5065\u5EB7',registryAux?.status??router.health],['Endpoint',router.endpoint],['OAuth resource',router.oauthResource]])+rawDisclosure({router:router,health:registryAux})+'<div class=\"actions\"><button class=\"button\" data-load=\"'+esc(JSON.stringify(['registry','router','connect']))+'\" '+(canConnect?'':'disabled')+'>\u8FDE\u63A5\u9002\u914D\u5668</button></div></section>';\n}\nif(registryWorkspace==='audit'){const cap=currentRegistryCapability()||{};const detail=registryDetail?'<div class=\"registry-detail-head\"><h3>'+esc(registryName)+'</h3><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0</button></div>'+summaryCells([['\u4E8B\u4EF6',registryDetail.eventType??registryDetail.action],['Request ID',registryDetail.requestId],['\u64CD\u4F5C\u8005',registryDetail.actor??registryDetail.subject],['\u7ED3\u679C',registryDetail.result??registryDetail.status],['\u53D1\u751F\u65F6\u95F4',registryDetail.createdAt??registryDetail.occurredAt],['\u7B56\u7565 revision',registryDetail.policyRevision]])+rawDisclosure(registryDetail):'<div class=\"empty-state\">\u9009\u62E9\u5BA1\u8BA1\u4E8B\u4EF6\u67E5\u770B\u5173\u8054 requestId \u4E0E\u7ED3\u679C\u3002</div>';return '<section class=\"registry-simple\"><h3>'+esc(title)+'</h3><p>'+esc(description)+'</p><div class=\"permission-grid\">'+(cap.actions||[]).map((action)=>'<span class=\"tag role\">'+esc(action)+'</span>').join('')+(!(cap.actions||[]).length?'<span class=\"tag public\">\u53EA\u8BFB</span>':'')+'</div>'+summaryCells([['Policy revision',registryCapabilities?.policyRevision],['\u73AF\u5883',cap.environment],['Router',cap.router?.status],['\u53EF\u7528\u52A8\u4F5C',(cap.actions||[]).length]])+'<section class=\"registry-board\"><aside class=\"registry-list\"><div class=\"registry-list-head\"><strong>\u8FFD\u52A0\u5F0F\u5BA1\u8BA1</strong><span class=\"tag public\">'+registryItems.length+'</span></div><div class=\"registry-items\">'+registryItemButtons()+'</div></aside><article class=\"registry-detail\">'+detail+'</article></section></section>'}\nconst requestState=String(registryDetail?.state??registryDetail?.status??'').toUpperCase();const canReview=requestState==='SUBMITTED'&®istryCan('review');const canPublish=requestState==='APPROVED'&®istryCan('publish');const approvalActions=registryWorkspace==='approvals'&®istryDetail?'<div class=\"registry-actions\"><div class=\"actions\"><button class=\"button\" data-registry-action=\"approval-approve\" '+(canReview?'':'disabled')+'>\u6279\u51C6</button><button class=\"button danger\" data-registry-action=\"approval-reject\" '+(canReview?'':'disabled')+'>\u62D2\u7EDD</button><button class=\"button primary\" data-registry-action=\"approval-publish\" '+(canPublish?'':'disabled')+'>\u53D1\u5E03\u5DF2\u6279\u51C6\u7533\u8BF7</button></div><small class=\"table-meta\">\u63D0\u4EA4\u4EBA\u4E0D\u80FD\u5BA1\u6838\u6216\u53D1\u5E03\u81EA\u5DF1\u7684\u7533\u8BF7\uFF1B\u72B6\u6001\u548C capability \u5FC5\u987B\u540C\u65F6\u5141\u8BB8\uFF0C\u670D\u52A1\u7AEF\u4F1A\u518D\u6B21\u6821\u9A8C\u3002</small><div id=\"registry-notice\" aria-live=\"polite\">'+registryNotice+'</div></div>':'';\nconst detail=registryDetail?'<div class=\"registry-detail-head\"><h3>'+esc(registryName)+'</h3><button class=\"button\" data-action=\"registry-refresh\"><span class=\"refresh-icon\" aria-hidden=\"true\"></span> \u5237\u65B0</button></div>'+summaryCells([['\u8D44\u6E90\u7C7B\u578B',registryDetail.resourceType],['\u8D44\u6E90',registryDetail.resourceName??registryDetail.name],['\u7CBE\u786E\u7248\u672C',registryDetail.resourceVersion],['\u72B6\u6001',registryDetail.state??registryDetail.status],['\u63D0\u4EA4\u4EBA',registryDetail.submitter??registryDetail.createdBy],['\u4E50\u89C2\u9501\u7248\u672C',registryDetail.version]])+rawDisclosure(registryDetail)+approvalActions:'<div class=\"empty-state\">\u9009\u62E9 release request \u67E5\u770B\u72B6\u6001\u548C\u53EF\u6267\u884C\u64CD\u4F5C\u3002</div>';\nreturn '<section class=\"registry-board\"><aside class=\"registry-list\"><div class=\"registry-list-head\"><strong>'+esc(title)+'</strong><span class=\"tag public\">'+registryItems.length+'</span></div><div class=\"registry-items\">'+registryItemButtons()+'</div></aside><article class=\"registry-detail\">'+detail+'</article></section>';\n}\nfunction registryContent(){if(registryWorkspace==='catalog')return '<section class=\"registry-board\"><aside class=\"registry-list\"><div class=\"registry-list-head\"><strong>'+(registryMode==='skill'?'Skills':'MCP Servers')+'</strong><span class=\"tag public\">'+registryItems.length+'</span></div><div class=\"registry-items\">'+registryItemButtons()+'</div></aside><article class=\"registry-detail\">'+registryDetailPanel()+'</article></section>';const labels={approvals:['\u53D1\u5E03\u6536\u4EF6\u7BB1','\u5BA1\u6838\u8005\u4E0E\u53D1\u5E03\u8005\u5728\u8FD9\u91CC\u5904\u7406\u4E24\u4EBA\u5236 release request\u3002'],instances:['\u5B9E\u4F8B\u4E0E\u53CC\u51ED\u636E','\u5E73\u53F0\u53EA\u663E\u793A\u51ED\u636E\u662F\u5426\u5C31\u7EEA\uFF0C\u4E0D\u628A secret \u8FD4\u56DE\u6D4F\u89C8\u5668\u3002'],namespace:['Namespace \u4E0E Profile \u9ED8\u8BA4\u503C','\u5F53\u524D\u73AF\u5883\u3001\u6388\u6743\u52A8\u4F5C\u548C namespace \u89E3\u6790\u6765\u6E90\u4FDD\u6301\u53EF\u89C1\u3002'],router:['\u4F01\u4E1A Router','\u53EA\u6709 READY \u7684 Streamable HTTP Endpoint \u624D\u4F1A\u8FDB\u5165 capability\u3002'],audit:['\u6743\u9650\u4E0E\u8FFD\u52A0\u5F0F\u5BA1\u8BA1','\u6309 requestId \u8FFD\u8E2A\u62D2\u7EDD\u3001\u5BA1\u6279\u3001\u53D1\u5E03\u548C\u5BF9\u8D26\u4E8B\u4EF6\u3002']};return registrySimplePanel(...labels[registryWorkspace])}\nfunction registryPage(){const selected=registryNamespaces.find((namespace)=>Number(namespace.id)===Number(registryNamespaceId));const cap=currentRegistryCapability();const error=registryError&®istryWorkspace==='catalog'?'<div class=\"notice error registry-error\" role=\"alert\"><strong>\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25\u3002</strong> '+esc(registryError)+' <button class=\"button\" data-action=\"registry-retry\">\u91CD\u8BD5</button></div>':'';return '<div class=\"content\"><section class=\"hero registry-hero\"><div><h2>\u56E2\u961F\u80FD\u529B\u7684\u53D1\u5E03\u4E0E\u8FD0\u884C\u8FB9\u754C</h2><p>\u76EE\u5F55\u3001\u5BA1\u6279\u548C Router \u5171\u4EAB\u4E00\u4E2A namespace \u9009\u62E9\uFF1BNuma \u53EA\u63A5\u6536 capability \u548C\u6CBB\u7406\u5143\u6570\u636E\uFF0C\u4E0D\u63A5\u89E6 Nacos \u51ED\u636E\u3002</p></div><div><small class=\"table-meta\">\u751F\u547D\u5468\u671F\u5951\u7EA6 \u00B7 rejected / failed / offline \u4E3A\u5206\u652F\u72B6\u6001</small><div class=\"lifecycle\" aria-label=\"\u751F\u547D\u5468\u671F\u56FE\u4F8B\uFF0C\u4E0D\u8868\u793A\u5F53\u524D\u72B6\u6001\"><div class=\"life-node\">draft</div><div class=\"life-node\">submitted</div><div class=\"life-node\">approved</div><div class=\"life-node\">publishing</div><div class=\"life-node\">published</div></div></div></section>'+registryWorkspaceTabs()+'<section class=\"registry-toolbar\"><label class=\"field\"><span>Namespace</span><select id=\"registry-namespace\">'+registryNamespaceOptions()+'</select></label>'+(registryWorkspace==='catalog'?'<div class=\"segment\" role=\"group\" aria-label=\"\u8D44\u6E90\u7C7B\u578B\"><button data-registry-mode=\"skill\" class=\"'+(registryMode==='skill'?'active':'')+'\">Skills</button><button data-registry-mode=\"mcp\" class=\"'+(registryMode==='mcp'?'active':'')+'\">MCP</button></div><label class=\"field\"><span>\u8FC7\u6EE4</span><input id=\"registry-search\" maxlength=\"80\" value=\"'+esc(registrySearch)+'\" placeholder=\"\u540D\u79F0\u6216\u63CF\u8FF0\"></label>':'<div class=\"registry-context\"><strong>'+esc(selected?.displayName||'\u672A\u9009\u62E9')+'</strong><span>'+esc(cap?.environment||'unknown')+' \u00B7 '+esc(cap?.router?.status||'NO ROUTER')+'</span></div>')+'<button class=\"button\" data-action=\"registry-refresh\" '+(registryLoading?'disabled':'')+'><span class=\"refresh-icon '+(registryLoading?'spin':'')+'\" aria-hidden=\"true\"></span> '+(registryLoading?'\u8BFB\u53D6\u4E2D\u2026':'\u5237\u65B0')+'</button></section>'+error+registryContent()+'<div class=\"sr-only\" aria-live=\"polite\">'+esc(registryLoading?'\u6B63\u5728\u52A0\u8F7D':registryError||registryNotice||'\u52A0\u8F7D\u5B8C\u6210')+'</div></div>'}\nasync function loadRegistry(resetNamespaces=false){if(registryLoading)return;registryLoading=true;registryError='';render();try{if(resetNamespaces||!registryNamespaces.length||!registryCapabilities){[registryCapabilities,registryNamespaces]=await Promise.all([api('/api/registry/capabilities'),api('/api/registry/namespaces')]);if(!Array.isArray(registryNamespaces)||!Array.isArray(registryCapabilities?.namespaces))throw new Error('capability \u6216 namespace \u54CD\u5E94\u683C\u5F0F\u65E0\u6548');const visible=registryNamespaces.filter((namespace)=>registryCapabilities.namespaces.some((candidate)=>candidate.namespaceKey===namespace.namespaceKey&&(!candidate.instanceCode||candidate.instanceCode===namespace.instanceCode)));registryNamespaces=visible;if(!registryNamespaceId||!registryNamespaces.some((namespace)=>Number(namespace.id)===Number(registryNamespaceId))){const preferred=state.config.aiRegistryDefaultNamespaceKey;const preferredNamespace=preferred?registryNamespaces.find((namespace)=>namespace.namespaceKey===preferred):null;if(preferred&&!preferredNamespace)throw new Error('\u7528\u6237\u9ED8\u8BA4 namespace '+preferred+' \u4E0D\u5728 capability \u6388\u6743\u5217\u8868\u4E2D\uFF1B\u8BF7\u663E\u5F0F\u4FEE\u6539\u9ED8\u8BA4\u503C\u3002');registryNamespaceId=preferredNamespace?.id??registryNamespaces[0]?.id??null}}if(!registryNamespaceId){registryItems=[];registryDetail=null;throw new Error('\u5F53\u524D\u7528\u6237\u6CA1\u6709\u53EF\u89C1\u7684 Registry namespace\u3002\u8BF7\u8054\u7CFB\u8D23\u4EFB\u56E2\u961F\u6216\u7BA1\u7406\u5458\u6388\u6743\u3002')}registryAux=null;if(registryWorkspace==='catalog'){const endpoint=registryMode==='skill'?'/api/registry/skills':'/api/registry/mcp';const payload=await api(endpoint+'?namespaceId='+encodeURIComponent(registryNamespaceId)+(registrySearch?'&search='+encodeURIComponent(registrySearch):''));registryItems=registryPageItems(payload);if(registryName&&!registryItems.some((item,index)=>registryItemName(item,index)===registryName)){registryName='';registryDetail=null}}else if(registryWorkspace==='approvals'||registryWorkspace==='audit'){const endpoint=registryWorkspace==='approvals'?'/api/registry/approvals':'/api/registry/audit';const payload=await api(endpoint+'?namespaceId='+encodeURIComponent(registryNamespaceId));registryItems=registryPageItems(payload);registryDetail=null;registryName=''}else if(registryWorkspace==='instances'){registryItems=registryPageItems(await api('/api/registry/instances'));registryDetail=null;registryName=''}else if(registryWorkspace==='namespace'){registryAux=await api('/api/registry/namespace-health?namespaceId='+encodeURIComponent(registryNamespaceId));registryItems=[];registryDetail=null;registryName=''}else if(registryWorkspace==='router'){[registryDetail,registryAux]=await Promise.all([api('/api/registry/router?namespaceId='+encodeURIComponent(registryNamespaceId)),api('/api/registry/router-health?namespaceId='+encodeURIComponent(registryNamespaceId))]);registryItems=[];registryName=''}else{registryItems=[];registryDetail=null;registryName=''}}catch(error){registryError=error.message;registryItems=[];registryDetail=null;registryAux=null}finally{registryLoading=false;render()}}\nasync function selectRegistryItem(name){registryName=name;if(registryWorkspace!=='catalog'){registryDetail=registryItems.find((item,index)=>registryItemName(item,index)===name)||null;render();return}registryLoading=true;registryError='';render();try{const endpoint=registryMode==='skill'?'/api/registry/skill':'/api/registry/mcp-detail';registryDetail=await api(endpoint+'?namespaceId='+encodeURIComponent(registryNamespaceId)+'&name='+encodeURIComponent(name))}catch(error){registryError=error.message;registryDetail=null}finally{registryLoading=false;render()}}\nasync function runRegistryCommand(argv,successMessage){const selectedName=registryName;registryNotice='<div class=\"notice success\">\u6B63\u5728\u6267\u884C\u2026</div>';render();try{const result=await api('/api/commands/run',{method:'POST',body:JSON.stringify({argv,confirm:false})});if(result.exitCode!==0)throw new Error(result.stderr||result.stdout||'\u547D\u4EE4\u6267\u884C\u5931\u8D25');await loadRegistry();if(selectedName&®istryItems.some((item,index)=>registryItemName(item,index)===selectedName))await selectRegistryItem(selectedName);registryNotice='<div class=\"notice success\">'+esc(successMessage)+'</div>';render()}catch(error){registryNotice='<div class=\"notice error\">'+esc(error.message)+'</div>';render()}}\nasync function registryAction(action){if(!registryNamespaceId)return;const selected=registryNamespaces.find((namespace)=>Number(namespace.id)===Number(registryNamespaceId));if(!selected)throw new Error('\u672A\u9009\u62E9 namespace');if(action==='namespace-check'){registryNotice='<div class=\"notice success\">\u6B63\u5728\u68C0\u67E5 namespace\u2026</div>';render();try{registryAux=await api('/api/registry/namespace-health?namespaceId='+encodeURIComponent(registryNamespaceId));registryNotice='<div class=\"notice success\">\u68C0\u67E5\u5B8C\u6210\uFF1A'+esc(registryAux?.status||'\u5DF2\u54CD\u5E94')+'</div>'}catch(error){registryNotice='<div class=\"notice error\">'+esc(error.message)+'</div>'}render();return}if(!registryName)return;const targetArgs=['--namespace',String(selected.namespaceKey),'--instance',String(selected.instanceCode)];if(action==='install'){const agent=document.getElementById('registry-target').value;let scope=document.getElementById('registry-scope').value;if(registryMode==='mcp'&&(agent==='codex'||agent==='openclaw'))scope='user';const argv=[registryMode==='skill'?'skills':'mcp','install',registryName,...targetArgs,'--agent',agent,'--scope',scope];if(registryMode==='skill')argv.push('--label','latest');await runRegistryCommand(argv,'\u5DF2\u5B8C\u6210\u672C\u673A\u5B89\u88C5');return}if(action==='submit'){const version=prompt('\u8F93\u5165\u8981\u63D0\u4EA4\u5BA1\u6838\u7684\u7CBE\u786E\u7248\u672C');if(!version)return;const reason=prompt('\u8F93\u5165\u53D1\u5E03\u8BF4\u660E');if(!reason)return;await runRegistryCommand(['registry','approvals','submit',registryMode,registryName,...targetArgs,'--resource-version',version,'--reason',reason],'\u5DF2\u63D0\u4EA4\u53D1\u5E03\u7533\u8BF7');return}if(action.startsWith('approval-')){const operation=action.slice('approval-'.length);if(!confirm('\u786E\u8BA4\u5BF9\u7533\u8BF7 '+registryName+' \u6267\u884C '+operation+'\uFF1F'))return;const optimisticVersion=registryDetail?.version;const versionArgs=Number.isInteger(optimisticVersion)?['--version',String(optimisticVersion)]:[];if(operation==='publish'){await runRegistryCommand(['registry','approvals','publish',registryName,...versionArgs],'\u53D1\u5E03\u5DF2\u5F00\u59CB');return}const reason=prompt(operation==='approve'?'\u8F93\u5165\u6279\u51C6\u539F\u56E0':'\u8F93\u5165\u62D2\u7EDD\u539F\u56E0');if(!reason)return;await runRegistryCommand(['registry','approvals','review',registryName,'--decision',operation==='approve'?'APPROVED':'REJECTED','--reason',reason,...versionArgs],operation==='approve'?'\u5DF2\u6279\u51C6':'\u5DF2\u62D2\u7EDD');return}}\nfunction configPage(){const c=state.config;return '<div class=\"content\"><section class=\"grid\"><article class=\"card full\"><h3>\u57FA\u7840\u914D\u7F6E</h3><p>\u4E0E <code>numa config --set</code> \u4F7F\u7528\u540C\u4E00\u4EFD\u914D\u7F6E\u548C\u6821\u9A8C\u903B\u8F91\u3002</p><form id=\"config-form\" class=\"form-grid\"><div class=\"field\"><label>\u754C\u9762\u8BED\u8A00</label><select name=\"locale\"><option '+(c.locale==='zh-CN'?'selected':'')+'>zh-CN</option><option '+(c.locale==='en-US'?'selected':'')+'>en-US</option></select></div><div class=\"field\"><label>\u9ED8\u8BA4\u8F93\u51FA</label><select name=\"output\"><option '+(c.output==='text'?'selected':'')+'>text</option><option '+(c.output==='json'?'selected':'')+'>json</option></select></div><div class=\"field span-2\"><label>\u5E73\u53F0 API \u5730\u5740</label><input name=\"apiBaseUrl\" value=\"'+esc(c.apiBaseUrl)+'\" placeholder=\"https://platform.example/api\"></div><div class=\"field\"><label>AI Registry \u7528\u6237\u9ED8\u8BA4 namespace</label><input name=\"aiRegistryDefaultNamespaceKey\" maxlength=\"160\" value=\"'+esc(c.aiRegistryDefaultNamespaceKey||'')+'\" placeholder=\"\u7531\u9879\u76EE\u6216 profile \u63D0\u4F9B\u65F6\u53EF\u7559\u7A7A\"></div><div class=\"field\"><label>npm registry</label><input name=\"registryUrl\" value=\"'+esc(c.registryUrl)+'\"></div><label class=\"field span-2\"><span>\u66F4\u65B0\u68C0\u67E5</span><select name=\"checkUpdates\"><option value=\"true\" '+(c.checkUpdates?'selected':'')+'>\u542F\u7528</option><option value=\"false\" '+(!c.checkUpdates?'selected':'')+'>\u5173\u95ED</option></select></label><div class=\"actions span-2\"><button class=\"button primary\" type=\"submit\">\u4FDD\u5B58\u914D\u7F6E</button></div></form><div id=\"notice\" aria-live=\"polite\"></div></article></section></div>'}\nfunction commandsPage(){return '<div class=\"content\"><section class=\"card full\"><div class=\"command-toolbar\"><div class=\"field grow\"><label>\u8FC7\u6EE4\u547D\u4EE4</label><input id=\"command-search\" placeholder=\"\u4F8B\u5982 codex\u3001media\u3001auth\"></div></div><div id=\"command-list\" class=\"command-list\">'+commandCards('')+'</div></section></div>'}\nfunction commandCards(query){const q=query.toLowerCase();return state.commands.filter((c)=>(c.command+' '+c.summary).toLowerCase().includes(q)).map((c)=>'<article class=\"command\"><div class=\"command-head\"><code>'+esc(c.command)+'</code><span class=\"tag '+esc(c.access)+'\">'+esc(c.access)+'</span></div><p>'+esc(c.summary)+'</p><small>'+esc(c.examples[0]||'')+'</small><div class=\"actions mt-10\"><button class=\"button\" data-load=\"'+esc(JSON.stringify(commandPrefix(c.command)))+'\">\u8F7D\u5165\u6267\u884C\u5668</button></div></article>').join('')||'<p>\u6CA1\u6709\u5339\u914D\u547D\u4EE4\u3002</p>'}\nfunction commandPrefix(command){return command.split(/\\s+/).filter((part)=>!part.startsWith('[')&&!part.startsWith('<')&&!part.startsWith('--'))}\nfunction runnerPage(){return '<div class=\"content\"><section class=\"grid\"><article class=\"card\"><div class=\"eyebrow\">Structured argv</div><h3>\u5B89\u5168\u6267\u884C\u5668</h3><p>\u4F7F\u7528 JSON \u6570\u7EC4\u4F20\u5165\u53C2\u6570\uFF0C\u4E0D\u7ECF\u8FC7 shell\u3002\u957F\u9A7B\u6216\u4EA4\u4E92\u547D\u4EE4\u9700\u8981\u5728\u7EC8\u7AEF\u8FD0\u884C\u3002</p><form id=\"runner-form\"><div class=\"field\"><label>\u547D\u4EE4\u53C2\u6570</label><textarea id=\"argv\" class=\"mono\">[\"doctor\"]</textarea></div><label class=\"field mt-12\"><span>\u5371\u9669\u64CD\u4F5C\u786E\u8BA4</span><select id=\"confirm\"><option value=\"false\">\u672A\u786E\u8BA4</option><option value=\"true\">\u786E\u8BA4\u6267\u884C\u5220\u9664/\u91CD\u7F6E</option></select></label><div class=\"actions mt-14\"><button class=\"button primary\" type=\"submit\">\u8FD0\u884C\u547D\u4EE4</button></div></form></article><article class=\"card wide\"><div class=\"eyebrow\">Result stream</div><h3>\u7ED3\u6784\u5316\u8F93\u51FA</h3><pre id=\"output\" class=\"output\">'+esc(output)+'</pre></article></section></div>'}\nasync function loadChatgpt(){if(chatgptLoading)return;chatgptLoading=true;chatgptLocal=null;chatgptRemote={status:'LOADING',accounts:[]};render();try{chatgptLocal=await api('/api/chatgpt/local')}catch(error){chatgptLocal={status:'FAILED',accounts:[],available:0,total:0,error:{message:error.message}}}render();try{chatgptRemote=await api('/api/chatgpt/remote')}catch(error){chatgptRemote={status:'FAILED',accounts:[],syncedAt:Date.now(),error:{message:error.message}}}finally{chatgptLoading=false;render()}}\nfunction render(){if(!state.loggedIn){app.innerHTML=loginGate();bind();return}const page=active==='overview'?overview():active==='registry'?registryPage():active==='chatgpt'?chatgptPage():active==='config'?configPage():active==='commands'?commandsPage():runnerPage();app.innerHTML=shell(page);bind()}\nfunction notice(message,type='success'){const target=document.getElementById('notice');if(target)target.innerHTML='<div class=\"notice '+type+'\">'+esc(message)+'</div>'}\nfunction bindCommandLoaders(){document.querySelectorAll('[data-load]').forEach((b)=>b.addEventListener('click',()=>{const argv=JSON.parse(b.dataset.load);active='runner';render();document.getElementById('argv').value=JSON.stringify(argv)}))}\nfunction bind(){\ndocument.querySelectorAll('[data-nav]').forEach((b)=>b.addEventListener('click',()=>{active=b.dataset.nav;render();if(active==='chatgpt'&&!chatgptLocal&&!chatgptLoading)void loadChatgpt();if(active==='registry'&&!registryNamespaces.length&&!registryLoading)void loadRegistry(true)}));\ndocument.querySelector('[data-action=\"chatgpt-refresh\"]')?.addEventListener('click',()=>void loadChatgpt());\ndocument.querySelectorAll('[data-action=\"registry-refresh\"],[data-action=\"registry-retry\"]')?.forEach((button)=>button.addEventListener('click',()=>registryWorkspace==='catalog'&®istryName?void selectRegistryItem(registryName):void loadRegistry()));\ndocument.querySelectorAll('[data-registry-workspace]').forEach((button)=>button.addEventListener('click',()=>{registryWorkspace=button.dataset.registryWorkspace;registryName='';registryDetail=null;registryAux=null;registryError='';registryNotice='';void loadRegistry()}));\ndocument.querySelectorAll('[data-registry-mode]').forEach((button)=>button.addEventListener('click',()=>{registryMode=button.dataset.registryMode;registryName='';registryDetail=null;void loadRegistry()}));\ndocument.querySelectorAll('[data-registry-name]').forEach((button)=>button.addEventListener('click',()=>void selectRegistryItem(button.dataset.registryName)));\ndocument.querySelectorAll('[data-registry-action]').forEach((button)=>button.addEventListener('click',()=>void registryAction(button.dataset.registryAction)));\ndocument.getElementById('registry-namespace')?.addEventListener('change',(event)=>{registryNamespaceId=Number(event.target.value);registryName='';registryDetail=null;registryAux=null;void loadRegistry()});\ndocument.getElementById('registry-search')?.addEventListener('change',(event)=>{registrySearch=event.target.value.trim();registryName='';registryDetail=null;void loadRegistry()});\ndocument.getElementById('registry-search')?.addEventListener('keydown',(event)=>{if(event.key==='Enter'){event.preventDefault();registrySearch=event.target.value.trim();registryName='';registryDetail=null;void loadRegistry()}});\ndocument.getElementById('registry-default-form')?.addEventListener('submit',async(event)=>{event.preventDefault();const namespaceKey=String(new FormData(event.currentTarget).get('namespaceKey')||'');if(!registryNamespaces.some((item)=>item.namespaceKey===namespaceKey)){registryNotice='<div class=\"notice error\">\u53EA\u80FD\u9009\u62E9 capability \u4E2D\u6388\u6743\u7684 namespace\u3002</div>';render();return}registryNotice='<div class=\"notice success\">\u6B63\u5728\u4FDD\u5B58\u9ED8\u8BA4\u503C\u2026</div>';render();try{const data={...state.config,aiRegistryDefaultNamespaceKey:namespaceKey};const result=await api('/api/config',{method:'PUT',body:JSON.stringify(data)});state.config=result.config;registryNotice='<div class=\"notice success\">\u7528\u6237\u9ED8\u8BA4 namespace \u5DF2\u4FDD\u5B58\u3002</div>'}catch(error){registryNotice='<div class=\"notice error\">'+esc(error.message)+'</div>'}render()});\ndocument.querySelector('[data-action=\"login\"]')?.addEventListener('click',async()=>{try{notice('\u6B63\u5728\u542F\u52A8\u767B\u5F55\u2026');const result=await api('/api/auth/login',{method:'POST',body:'{}'});location.href=result.authorizationUrl}catch(e){notice(e.message,'error')}});\ndocument.querySelector('[data-action=\"reload\"]')?.addEventListener('click',()=>bootstrap());\ndocument.querySelector('[data-action=\"logout\"]')?.addEventListener('click',async()=>{if(!confirm('\u6E05\u9664\u672C\u673A Keycloak \u767B\u5F55\u7F13\u5B58\uFF1F'))return;await api('/api/auth/logout',{method:'POST',body:'{}'});location.reload()});\ndocument.getElementById('config-form')?.addEventListener('submit',async(e)=>{e.preventDefault();const submit=e.currentTarget.querySelector('button[type=\"submit\"]');if(submit)submit.disabled=true;const data=Object.fromEntries(new FormData(e.currentTarget));data.checkUpdates=data.checkUpdates==='true';try{const result=await api('/api/config',{method:'PUT',body:JSON.stringify(data)});state.config=result.config;notice('\u914D\u7F6E\u5DF2\u4FDD\u5B58')}catch(err){notice(err.message,'error')}finally{if(submit)submit.disabled=false}});\ndocument.getElementById('command-search')?.addEventListener('input',(e)=>{document.getElementById('command-list').innerHTML=commandCards(e.target.value);bindCommandLoaders()});\nbindCommandLoaders();\ndocument.getElementById('runner-form')?.addEventListener('submit',async(e)=>{e.preventDefault();try{const argv=JSON.parse(document.getElementById('argv').value);const confirmed=document.getElementById('confirm')?.value==='true';output='\u8FD0\u884C\u4E2D\u2026';render();const result=await api('/api/commands/run',{method:'POST',body:JSON.stringify({argv,confirm:confirmed})});output=result.stdout||result.stderr||JSON.stringify(result,null,2);render()}catch(err){output='\u9519\u8BEF: '+err.message;render()}})\n}\nbootstrap();\n";
|
package/dist/web-console-page.js
CHANGED
|
@@ -51,10 +51,12 @@ function quotaWindowHasData(window){if(!window)return false;const minutes=window
|
|
|
51
51
|
function quotaLine(window,fallback){if(!quotaWindowHasData(window)||typeof window.usedPercent!=='number')return '';const remain=Math.max(0,Math.min(100,100-window.usedPercent));return '<div class="quota-line" title="额度重置时间 '+esc(timeText(window.resetAt))+'"><span>'+esc(windowName(window,fallback))+'</span><progress max="100" value="'+remain.toFixed(1)+'"></progress><strong>'+remain.toFixed(0)+'%</strong></div>'}
|
|
52
52
|
function quotaCell(quota){if(!quota)return '<span class="table-meta">暂无额度采样</span>';const lines=quotaLine(quota.primary,'主窗口')+quotaLine(quota.secondary,'次窗口');const detail=[quota.planType,quota.model,quota.activeLimit!=null?'limit '+quota.activeLimit:null].filter(Boolean).join(' · ');return '<div class="quota-stack">'+(lines||'<span class="table-meta">额度窗口暂无数据</span>')+'<div class="table-meta">'+esc(detail||'已采样')+'<br>采样 '+esc(timeText(quota.updatedAt))+'</div></div>'}
|
|
53
53
|
function newerQuota(localQuota,remoteQuota){if(!localQuota)return remoteQuota;if(!remoteQuota)return localQuota;return (instant(remoteQuota.updatedAt)||0)>=(instant(localQuota.updatedAt)||0)?remoteQuota:localQuota}
|
|
54
|
-
function
|
|
55
|
-
function
|
|
54
|
+
function remoteAccountExpired(item){if(!item)return false;if(String(item.subscriptionStatus||'').toUpperCase()==='EXPIRED'||item.credentialStatus==='EXPIRED')return true;return Number.isFinite(item.subscriptionExpiresAt)&&item.subscriptionExpiresAt<=Date.now()}
|
|
55
|
+
function remoteAccountEnabled(item){return !remoteAccountExpired(item)&&(!item?.credentialStatus||item.credentialStatus==='AVAILABLE')}
|
|
56
|
+
function mergedChatgptAccounts(){const local=chatgptLocal?.accounts||[];const remote=chatgptRemote?.accounts||[];const used=new Set();const rows=local.map((item)=>{const index=remote.findIndex((candidate,i)=>!used.has(i)&&item.accountId&&candidate.accountId===item.accountId);const match=index>=0?remote[index]:null;if(index>=0)used.add(index);return {key:item.id,local:item,remote:match,label:item.label,email:item.email,accountId:item.accountId||match?.accountId,enabled:item.enabled&&(!match||remoteAccountEnabled(match)),selected:item.selected,quota:newerQuota(item.quota,match?.quota)}});remote.forEach((item,index)=>{if(!used.has(index))rows.push({key:'remote_'+item.id,local:null,remote:item,label:item.label,email:null,accountId:item.accountId,enabled:remoteAccountEnabled(item),selected:item.isDefault,quota:item.quota})});return rows}
|
|
57
|
+
function syncTag(row){if(chatgptRemote?.status==='LOADING')return '<span class="tag waiting">正在同步</span>';if(chatgptRemote?.status==='FAILED')return '<span class="tag failed">远程同步失败</span>';if(chatgptRemote?.status==='NOT_CONFIGURED')return '<span class="tag waiting">未配置平台</span>';if(remoteAccountExpired(row.remote))return '<span class="tag failed">平台已过期</span>';if(row.local&&row.remote)return '<span class="tag success">已同步</span>';if(row.local)return '<span class="tag local">仅本地</span>';return '<span class="tag success">平台可用</span>'}
|
|
56
58
|
function remoteStage(){const remote=chatgptRemote;if(!remote||remote.status==='LOADING')return {tag:'<span class="tag waiting"><span class="refresh-icon spin" aria-hidden="true"></span> 连接中</span>',title:'正在连接平台',detail:'本地账号已经可见,正在读取远程可用账号。'};if(remote.status==='SUCCESS')return {tag:'<span class="tag success">同步成功</span>',title:remote.accounts.length+' 个平台账号',detail:'完成于 '+timeText(remote.syncedAt)};if(remote.status==='FAILED')return {tag:'<span class="tag failed">远程同步失败</span>',title:'保留本地结果',detail:remote.error?.message||'无法连接远程账号服务'};return {tag:'<span class="tag waiting">未配置</span>',title:'未配置平台地址',detail:'可在基础配置中填写平台 API 地址。'}}
|
|
57
|
-
function accountRows(rows){return rows.map((row)=>'<tr><td><div class="account-name"><strong>'+esc(row.label)+'</strong><small>'+esc(row.email||row.accountId||row.key)+'</small></div></td><td><div class="status-stack">'+(row.enabled?'<span class="tag success">可用</span>':'<span class="tag failed">已停用</span>')+(row.selected?'<span class="tag role">客户端已选</span>':'')+'</div></td><td>'+quotaCell(row.quota)+'</td><td><div class="table-meta">Token '+esc(row.local?'本地可用':'按需从平台获取')+'<br>到期 '+esc(timeText(row.local?.accessExpiresAt||row.remote?.accessExpiresAt))+'</div></td><td><div class="status-stack">'+syncTag(row)+'</div></td></tr>').join('')}
|
|
59
|
+
function accountRows(rows){return rows.map((row)=>'<tr><td><div class="account-name"><strong>'+esc(row.label)+'</strong><small>'+esc(row.email||row.accountId||row.key)+'</small></div></td><td><div class="status-stack">'+(row.enabled?'<span class="tag success">可用</span>':remoteAccountExpired(row.remote)?'<span class="tag failed">已过期</span>':'<span class="tag failed">已停用</span>')+(row.selected?'<span class="tag role">客户端已选</span>':'')+'</div></td><td>'+quotaCell(row.quota)+'</td><td><div class="table-meta">Token '+esc(row.local?'本地可用':'按需从平台获取')+'<br>到期 '+esc(timeText(row.local?.accessExpiresAt||row.remote?.accessExpiresAt))+(row.remote?.subscriptionExpiresAt?'<br>订阅 '+esc(timeText(row.remote.subscriptionExpiresAt)):'')+'</div></td><td><div class="status-stack">'+syncTag(row)+'</div></td></tr>').join('')}
|
|
58
60
|
function chatgptPage(){const rows=mergedChatgptAccounts();const remote=remoteStage();const localReady=chatgptLocal?.status==='READY';const localCount=chatgptLocal?.available??0;const failure=chatgptRemote?.status==='FAILED'?'<div class="notice error"><strong>远程同步失败。</strong> '+esc(chatgptRemote.error?.message||'平台暂时不可用')+';本地账号数据仍可正常查看。</div>':'';return '<div class="content"><section class="hero"><div><div class="eyebrow">Local first / Remote second</div><h2>ChatGPT 账号运行视图</h2><p>先读取 codex-multi-auth 本地账号与额度缓存,再连接平台补充当前用户可用账号。页面永不接收 access token 或 refresh token。</p></div><div class="session-track"><div class="track-node live"><span>本地可用</span><strong>'+localCount+'</strong></div><div class="track-node"><span>汇总账号</span><strong>'+rows.length+'</strong></div><div class="track-node"><span>远程</span><strong>'+esc(chatgptRemote?.status||'WAITING')+'</strong></div></div></section><section class="sync-board"><article class="sync-stage"><div class="eyebrow">01 / Local pool</div><strong>'+(localReady?localCount+' 个本地可用账号':'正在读取本地账号')+'</strong><p>'+(localReady?'读取完成于 '+timeText(chatgptLocal.readAt):'优先读取 codex-multi-auth 安全摘要。')+'</p></article><div class="sync-link" aria-label="然后连接远程"></div><article class="sync-stage"><div class="eyebrow">02 / Platform service</div>'+remote.tag+'<strong>'+esc(remote.title)+'</strong><p>'+esc(remote.detail)+'</p></article></section>'+failure+'<section class="table-card"><header class="table-head"><div><h3>可用账号与剩余额度</h3><p>额度优先展示本地或服务器端更新较新的安全快照。</p></div><button class="button" data-action="chatgpt-refresh" '+(chatgptLoading?'disabled':'')+'><span class="refresh-icon '+(chatgptLoading?'spin':'')+'" aria-hidden="true"></span> '+(chatgptLoading?'同步中':'重新同步')+'</button></header>'+(rows.length?'<div class="account-table-wrap"><table class="account-table"><thead><tr><th>账号</th><th>可用状态</th><th>剩余 limit</th><th>凭据时效</th><th>同步状态</th></tr></thead><tbody>'+accountRows(rows)+'</tbody></table></div>':'<div class="empty-state'+(chatgptLoading?'':' account-request')+'">'+(chatgptLoading?'正在读取本地账号…':'<strong>需要申请 ChatGPT 账号</strong><span>'+esc(noAvailableAccountGuidance)+'</span>')+'</div>')+'</section></div>'}
|
|
59
61
|
function registryPageItems(payload){if(Array.isArray(payload))return payload.filter((item)=>item&&typeof item==='object');for(const key of ['pageItems','items','records','list','data']){const value=payload?.[key];if(Array.isArray(value))return value.filter((item)=>item&&typeof item==='object');if(value&&typeof value==='object'){const nested=registryPageItems(value);if(nested.length)return nested}}return []}
|
|
60
62
|
function registryItemName(item,index){return String(item?.name??item?.skillName??item?.mcpName??item?.requestId??item?.eventType??item?.bindingId??((registryMode==='skill'?'skill-':'mcp-')+(index+1)))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-console-page.js","sourceRoot":"","sources":["../src/web-console-page.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;QAoBpB,CAAC;AAET,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;CAc1B,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"web-console-page.js","sourceRoot":"","sources":["../src/web-console-page.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;QAoBpB,CAAC;AAET,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;CAc1B,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FzB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@numa-tech/numa",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.17",
|
|
4
4
|
"description": "Cross-platform CLI for the Numa internal developer platform with Keycloak authentication and MCP support.",
|
|
5
5
|
"author": "numa-tech",
|
|
6
6
|
"keywords": [
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@clack/prompts": "^1.7.0",
|
|
57
57
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
58
|
-
"@numa-tech/cli-auth": "1.14.
|
|
59
|
-
"@tokensrc/codex": "1.14.
|
|
58
|
+
"@numa-tech/cli-auth": "1.14.17",
|
|
59
|
+
"@tokensrc/codex": "1.14.17",
|
|
60
60
|
"commander": "^14.0.3",
|
|
61
61
|
"ink": "^6.8.0",
|
|
62
62
|
"open": "^10.2.0",
|
|
@@ -28,6 +28,7 @@ app begin
|
|
|
28
28
|
-> Jenkins GitOps automatic configure(READY/JENKINS_API_VERIFIED/VERIFIED;LEGACY/旧部署才用 MANUAL_OPERATOR_ATTESTED readiness)
|
|
29
29
|
-> Observer scope/evidence
|
|
30
30
|
-> 独立首次 platform-build/生产审批(无任意 Jenkins 参数;结果未知只 resolve)
|
|
31
|
+
-> Jenkins 终态、平台 image-intent 账本与 Flux/运行态三方验收
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
以下方案即使“看起来能工作”也判定为错误并停止输出:
|
|
@@ -139,11 +140,12 @@ HTTPS clone credential readiness。旧服务端未返回 availability 时保持
|
|
|
139
140
|
6. 展示服务端 plan、blocking errors 和任何规范化差异。差异实质改变确认内容时再次确认。
|
|
140
141
|
7. 严格按父计划顺序 apply;每个 child 使用持久化幂等键。响应未知先按 status/events 找回,绝不生成第二套资源。
|
|
141
142
|
8. 使用 `numa app inspect <sessionNo> --watch --json` 等到终态;断线时从最后 sequence 恢复。
|
|
142
|
-
9. 只读核验 Application、Repository HEAD、Jenkins Job/binding、GitOps binding/scope 和 Observer evidence
|
|
143
|
+
9. 只读核验 Application、Repository HEAD、Jenkins Job/binding、GitOps binding/scope 和 Observer evidence。完整初始化要求分支已被 multibranch Job 索引时,必须在已确认计划中使用服务端 `INDEX_ONLY`/NoTrigger scan,并核验 scan task、目标 branch child 与“未触发 build”证据;`scanMode=NONE` 或只创建父 Job 不能声称分支初始化完成。除非用户另行明确要求,不触发 build/deploy。
|
|
143
144
|
只有明确留在 v1/legacy writer 的旧应用需要迁移时,才用 `gitops service legacy-image-migration plan`;请求只能包含 application/target/profile/service stable references,当前没有 service binding 时不得猜测 `--service-id`。新应用 v2 禁止调用 legacy migration:GitOps structural apply 完成后,显式执行 `numa app writer-bootstrap ensure <same-session> --idempotency-key <stable-key> --yes --json`,服务端才在同一 session/task 内执行 `gitops-platform-writer-bootstrap/v2`,且只读写服务自身的 base release、exact overlay values 与 overlay kustomization 三个文件。CLI 不接受 repository/path/branch/YAML/binding/target/Secret,也不创建新 Application、session、binding 或 target;用原 session 的 `app inspect --watch --after-sequence` 读取 `GITOPS_V2_WRITER_BOOTSTRAP` 事件。只有 exact contract/identity/revision/digest 匹配且状态成功才能进入 Observer/first-build;初始 POST 结果未知时只用原 key 精确重放,已持久 child 为 `UNKNOWN` 时先 inspect,再显式 `numa app retry <same-session> --json` 恢复原 same-run/key。仍为 `UNKNOWN` 或出现 drift 时继续 fail closed。输出不展示 path/diff/pointer/file content。
|
|
144
145
|
若 blocker 是 `GITOPS_RAW_SECRET_FORBIDDEN`,不得读取/回显 Secret value、把值改成普通 base64、直接删除资源或绕过 plan。优先迁移到平台已登记的 ExternalSecret/SealedSecret provider。目标集群没有 provider 时,只能提出经单独变更单审批的两阶段桥:先在 clean、受控的 Flux 仓库中为现有 Flux-managed Secret 加 `kustomize.toolkit.fluxcd.io/prune: disabled`,reconcile 并由 Observer/live evidence 验证 annotation;再以另一变更单移除 raw manifest/reference,reconcile 并验证 Secret 仍存在,最后由 secret owner 轮换所有曾进入 Git 历史的值。本地 `mci-flux` dirty 时停止,应用创建的笼统确认不能替代任一步审批。
|
|
145
146
|
新应用必须使用独立 `jenkins platform-build-rollout` plan/apply/configure;旧 `jenkins gitops-rollout` 保持 v1/manual readiness,不得用来承载 v2。automatic configure 前,新入口仓库必须已通过 Project Publication CAS 发布只含 exact `@Library('mciShareLib@<approvedSharedLibraryRevision>') _` 与 `platformBuildEntry()` 的 Jenkinsfile,以及 strict `.mci/platform-build.yaml`。该清单只允许平台 build profile 白名单字段;生产快照执行 server-owned wrapper,不加载应用 Jenkinsfile。缺少任一证据时停止并回到 Publication plan,不能手工 Git push、改 Jenkins global library、复用旧 `buildEntry()` 或改用 `mcexShareLib`。configure 只提交 `expectedVersion`,不接受 env/XML/Groovy/token/credential ID/value/operator attestation;只有服务端回传 credential 配置布尔值、observed shared-library revision、verified source revision、Jenkinsfile/build-profile/configuration digest 和 `READY/JENKINS_API_VERIFIED/VERIFIED` 才通过。verified source revision 是源码快照,不要求等于 approved shared-library revision;固定 annotation 才必须引用 approved revision。v2 没有 manual readiness;`MANUAL_OPERATOR_ATTESTED readiness` 仅属于 v1/LEGACY。rollout 达到 READY 前不触发首次 build。
|
|
146
147
|
10. 用户明确要求 promote/deploy 时,完成入驻后转入 `numa-jenkins-deployment` 的生产预检。v2 首次构建由原 onboarding session 的 first-build action 内部路由到 platform-build;独立恢复使用 `numa pipeline platform-build resolve/status/log`。不得回退旧 `pipeline build`,不得传任意 build parameters;应用创建确认不能替代生产审批。
|
|
148
|
+
11. v2 first-build/Jenkins 在“更新环境镜像”阶段终态失败或 image intent 长时间 `UNKNOWN` 时,先按同一 request 读取 status/log,再把平台请求、SCM 远端和 Flux/Observer 当作三个独立事实核验。远端目标 HEAD 已含本次稳定 request marker 且目标文件的 image repository/tag/digest 与原 intent 完全一致,只能说明原 CAS 可能已成功;它不授权重放 build、生成新 key、手工再写 Flux 或把平台记录改成成功。运行时已健康但平台账本未收敛时,明确报告“运行态已部署、平台账本未决”,等待服务端支持的原请求 reconcile/recover;运行时健康不能冒充 onboarding/发布闭环完成。
|
|
147
149
|
|
|
148
150
|
### 6. 失败与恢复
|
|
149
151
|
|
|
@@ -158,6 +160,7 @@ HTTPS clone credential readiness。旧服务端未返回 availability 时保持
|
|
|
158
160
|
- `ENSURE_WRITER_BOOTSTRAP` 是 onboarding v3 的独立 capability action,对应 `app writer-bootstrap ensure`;请求只能含原 session 与稳定幂等键。初始 POST 结果未知时先 inspect,再只用原 key 精确重放;已持久 child 为 `UNKNOWN` 时才显式调用通用 `app retry <same-session>` 恢复原 run/key。普通 status/inspect 与 first-build 未知结果恢复保持纯读,不重放 first-build POST。仍为 `UNKNOWN`、三文件集合/identity/revision/digest drift 都阻断 Observer 和 first-build;不得改调 legacy migration、新建 session/身份或直读/改 SCM/Secret 来恢复。
|
|
159
161
|
- legacy image migration plan 没有 Idempotency-Key/resolve-by-key,结果不明时必须 fail closed,不自动重放。Jenkins rollout plan create 也没有 resolve-by-key;apply 只有当 list 显示 exact application/instance/plan mode 且较写前 baseline version 增长才报告恢复;configure 响应未知时不重放,只 GET 同 rollout,并要求 exact identity/intent、version 增长、credential booleans=true、observed shared-library revision、verified source revision、Jenkinsfile/configuration digest 完整和 `READY/JENKINS_API_VERIFIED/VERIFIED`;verified source revision 不与 approved library revision 混同。`STALE/DRIFTED/UNAVAILABLE` 或证据缺失均 result unknown。旧 readiness 只有 GET 同 rollout 显示 exact reference/mode/status/identity 且 version 增长才报告恢复。预存 READY/LEGACY 不是本次写入证据。
|
|
160
162
|
- v2 `platform-build trigger` 的 POST 只发送一次;未知响应只按原 idempotency key 调 `pipeline platform-build resolve`。stop 也只发送一次,未知后只 GET 同 requestId。任何参数、APPROVE_ID、Docker/Git/proxy 环境变量都不得进入 snapshot;productionReason 只留平台审计。Jenkins 不持有 Flux Git 凭据,镜像更新由平台 server-owned CAS 完成。
|
|
163
|
+
- `platform-build` 的 GitOps writer 结果未知时,恢复必须匹配同一 application、binding/target、request/build、source revision、image repository/tag/digest、expected old head、稳定 request marker、远端 target HEAD/revision 和目标文件内容。只有受支持的服务端 reconcile/recover 能据此把账本从 `UNKNOWN` 推进到已验证终态;当前 command catalog 没有该能力时保持未决,不直改数据库、不伪造 `SUCCESS`、不通过本地 Flux fallback 重写同一版本。
|
|
161
164
|
|
|
162
165
|
所有展示给用户的命令必须可直接复制:不要带 diff 的字面 `+`/`-` 前缀,不要把 shell continuation
|
|
163
166
|
写成伪格式,也不要在命令中展开凭据、绝对本地路径或文件内容。
|
|
@@ -171,6 +174,7 @@ HTTPS clone credential readiness。旧服务端未返回 availability 时保持
|
|
|
171
174
|
- 本地源码发布场景中,远端 tree/commit evidence 与锁定 manifest/HEAD 一致;
|
|
172
175
|
- 流水线绑定存在,tier/branch/buildability 符合确认;
|
|
173
176
|
- 要求 GitOps 时,service binding、scope 和运行态 evidence 状态明确;未达 ACTIVE 时返回具体 recovery action;
|
|
177
|
+
- 完整 promote/deploy 还要求 Jenkins/build 终态、平台 image-intent 账本和 Flux/Observer exact revision 三方一致;仅 Flux/Deployment 健康时只报告运行态成功,不报告平台自动化闭环成功;
|
|
174
178
|
- 未意外触发构建或部署;
|
|
175
179
|
- session 终态明确,idempotency key 和安全的资源 ID 已记录;
|
|
176
180
|
- 数据库/集群待办明确标为已执行或 `DEFERRED`,没有把推断值冒充平台事实。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Numa 应用创建"
|
|
3
3
|
short_description: "安全规划源码、应用入驻、Jenkins v1/v2 与 GitOps promote 闭环"
|
|
4
|
-
default_prompt: "使用 $numa-create-application 分析当前项目,先自述运行时能力,再协作确认并通过 Numa CLI 完成 Repository、源码发布、应用、Jenkins
|
|
4
|
+
default_prompt: "使用 $numa-create-application 分析当前项目,先自述运行时能力,再协作确认并通过 Numa CLI 完成 Repository、源码发布、应用、Jenkins multibranch INDEX_ONLY 扫描、GitOps 与 Observer 计划。新应用使用 v2 writer bootstrap、独立 platform-build-rollout automatic configure 和 platform-build;默认不触发构建或部署。若运行态已部署但 image-intent 账本未决,分开报告并只恢复原请求。"
|
|
@@ -130,6 +130,31 @@
|
|
|
130
130
|
"拒绝手工写 Flux SCM、枚举/读取/修改 Secret、调用 legacy-image-migration;UNKNOWN 或 drift 继续 fail closed。",
|
|
131
131
|
"旧 v1 流程不受这一 onboarding v3 内部恢复语义影响。"
|
|
132
132
|
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": 10,
|
|
136
|
+
"prompt": "新应用的 platform-build 在 Update Environment Image 阶段报告 GitOpsUnknownResult;原 request 对应的远端 Flux HEAD 已含稳定 marker,image tag/digest 精确匹配,Flux 和 Deployment 已健康,但平台 intent 仍 UNKNOWN。能否重新 first-build 或用本地 Flux 再推一次?应用初始化是否算完成?",
|
|
137
|
+
"expected_output": "区分运行态成功和平台账本未决,不重放 first-build/Flux 写入;只恢复同一 durable request,并要求 Jenkins/build、platform ledger、Flux/runtime 三方一致后才宣告完整初始化与部署闭环。",
|
|
138
|
+
"files": [],
|
|
139
|
+
"assertions": [
|
|
140
|
+
"不重新 first-build、不换 key、不创建第二个 image/commit、不手工再写 Flux、不直改数据库或伪造 SUCCESS。",
|
|
141
|
+
"要求精确匹配 application、binding/target、request/build、source revision、image repository/tag/digest、expected old head、marker 和 remote HEAD/file content。",
|
|
142
|
+
"说明 Flux/Deployment healthy 只证明 runtime,报告 RUNTIME_DEPLOYED / PLATFORM_LEDGER_UNRESOLVED。",
|
|
143
|
+
"只有服务端对同一 durable request 的受支持 reconcile/recover 能推进账本;当前 command catalog 没有时停止。",
|
|
144
|
+
"明确完整初始化/发布闭环还未完成,直到 Jenkins/build、platform ledger、Flux/runtime 三方一致。"
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": 11,
|
|
149
|
+
"prompt": "新建了 multibranch 父 Job,但 onboarding spec 使用 scanMode=NONE,Jenkins 中没有任何 branch child。用户要求完成应用初始化且不能触发构建。现在能否算完成?",
|
|
150
|
+
"expected_output": "不能;完整初始化需经服务端 INDEX_ONLY/NoTrigger scan,验证 scan task、目标 branch child 和无意外 build,然后才能通过该门禁。",
|
|
151
|
+
"files": [],
|
|
152
|
+
"assertions": [
|
|
153
|
+
"说明只创建父 Job 或 scanMode=NONE 不等于 branch 已初始化。",
|
|
154
|
+
"要求使用受治理的 INDEX_ONLY/NoTrigger scan,不直接在 Jenkins UI 绕过平台。",
|
|
155
|
+
"核验 scan task 终态、目标 branch child/Jenkinsfile 和没有触发 build 的证据。",
|
|
156
|
+
"保持首次 build 为独立授权,scan 不能替代生产审批。"
|
|
157
|
+
]
|
|
133
158
|
}
|
|
134
159
|
]
|
|
135
160
|
}
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
- Repository、Publication、Jenkins、GitOps、Observer 各 child plan/run 和 last event sequence;
|
|
95
95
|
- standalone publication plan create 的原 clientRequestId、一次 POST、有界 GET resolve、`STANDALONE`/repository identity 与 retryable 404 待恢复证据;
|
|
96
96
|
- v2 Job provenance、platform-build 与独立 `platform-build-rollout` plan/apply/configure(旧 v1 `gitops-rollout` 才列 migration/manual readiness)的 unknown-result baseline/readback 证据;writer bootstrap 记录独立 action key、原 session 中 `GITOPS_V2_WRITER_BOOTSTRAP` 的 exact contract/status/identity/revision/digest。初始 POST UNKNOWN 必须先 inspect 并只重放原 key;显式通用 `app retry` 仅恢复已持久 child 的原 session/run/key;记录 retry 前后 sequence 与服务端 recovery action。platform-build trigger/stop 都只POST一次并用原key/request GET恢复;configure 只接受同 rollout version 增长、source/Jenkinsfile digest 完整且 exact `READY/JENKINS_API_VERIFIED/VERIFIED` 的 GET evidence;旧 READY/LEGACY 不得记为本次 recovered;
|
|
97
|
+
- multibranch 初始化是否要求 `INDEX_ONLY` scan、scan task/branch child evidence 和 no-trigger 证明;`scanMode=NONE` 或只有父 Job 时不得标记分支初始化完成;
|
|
98
|
+
- platform-build writer 未决时记录 application、binding/target、request/build、source revision、image repository/tag/digest、expected old head、request marker、远端 target HEAD/revision/file content 与 Flux observed revision;分别给出 Jenkins/build、platform ledger、Flux/runtime 状态,禁止把 runtime healthy 单独写成自动化闭环成功;
|
|
97
99
|
- 预期外部副作用;
|
|
98
100
|
- 生产审批需求;
|
|
99
101
|
- blocking warnings、恢复动作和回滚边界。
|