@numa-tech/numa 1.12.6 → 1.12.8
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 +141 -43
- package/dist/ai-registry/bridge.d.ts +8 -0
- package/dist/ai-registry/bridge.js +63 -0
- package/dist/ai-registry/bridge.js.map +1 -0
- package/dist/ai-registry/client.d.ts +29 -3
- package/dist/ai-registry/client.js +112 -8
- package/dist/ai-registry/client.js.map +1 -1
- package/dist/ai-registry/commands.js +367 -112
- package/dist/ai-registry/commands.js.map +1 -1
- package/dist/ai-registry/errors.d.ts +2 -1
- package/dist/ai-registry/errors.js +12 -1
- package/dist/ai-registry/errors.js.map +1 -1
- package/dist/ai-registry/mcp-installer.d.ts +1 -0
- package/dist/ai-registry/mcp-installer.js +4 -0
- package/dist/ai-registry/mcp-installer.js.map +1 -1
- package/dist/ai-registry/namespace.d.ts +17 -0
- package/dist/ai-registry/namespace.js +53 -0
- package/dist/ai-registry/namespace.js.map +1 -0
- package/dist/ai-registry/schemas.d.ts +127 -2
- package/dist/ai-registry/schemas.js +42 -2
- package/dist/ai-registry/schemas.js.map +1 -1
- package/dist/ai-registry/sync.d.ts +23 -0
- package/dist/ai-registry/sync.js +276 -0
- package/dist/ai-registry/sync.js.map +1 -0
- package/dist/ai-registry/tui.d.ts +2 -0
- package/dist/ai-registry/tui.js +140 -0
- package/dist/ai-registry/tui.js.map +1 -0
- package/dist/ai-registry/workspace.d.ts +191 -0
- package/dist/ai-registry/workspace.js +167 -0
- package/dist/ai-registry/workspace.js.map +1 -0
- package/dist/app-config.d.ts +3 -2
- package/dist/app-config.js +5 -3
- package/dist/app-config.js.map +1 -1
- package/dist/chatgpt-desktop.d.ts +7 -0
- package/dist/chatgpt-desktop.js +95 -0
- package/dist/chatgpt-desktop.js.map +1 -1
- package/dist/cli.js +25 -3
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +1 -0
- package/dist/command-catalog.js +97 -10
- package/dist/command-catalog.js.map +1 -1
- package/dist/config-page.js +2 -0
- package/dist/config-page.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/pipeline/client.d.ts +10 -6
- package/dist/pipeline/client.js +7 -4
- package/dist/pipeline/client.js.map +1 -1
- package/dist/pipeline/commands.d.ts +5 -1
- package/dist/pipeline/commands.js +89 -18
- package/dist/pipeline/commands.js.map +1 -1
- package/dist/pipeline/schemas.d.ts +7 -7
- package/dist/pipeline/schemas.js +2 -2
- package/dist/pipeline/schemas.js.map +1 -1
- package/dist/platform-profile.d.ts +8 -2
- package/dist/platform-profile.js +5 -2
- package/dist/platform-profile.js.map +1 -1
- package/dist/web-console-page.d.ts +3 -3
- package/dist/web-console-page.js +75 -20
- package/dist/web-console-page.js.map +1 -1
- package/dist/web-console.d.ts +1 -1
- package/dist/web-console.js +46 -0
- package/dist/web-console.js.map +1 -1
- package/package.json +8 -5
- package/skills/numa-ai-registry/SKILL.md +25 -19
- package/skills/numa-ai-registry/evals/evals.json +10 -10
- package/skills/numa-ai-registry/references/command-contract.md +41 -6
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import process from "node:process";
|
|
3
|
-
import { cancel, confirm, isCancel, select, text } from "@clack/prompts";
|
|
4
3
|
import { loadAppConfig } from "../app-config.js";
|
|
5
4
|
import { loadConfig as loadPlatformConfig } from "../config.js";
|
|
6
5
|
import { AiRegistryClient } from "./client.js";
|
|
6
|
+
import { runMcpBridge } from "./bridge.js";
|
|
7
7
|
import { AiRegistryError, sanitizeRegistryOutput } from "./errors.js";
|
|
8
|
-
import {
|
|
8
|
+
import { installMcpClientDefinition, mcpDefinitionFromNacos } from "./mcp-installer.js";
|
|
9
|
+
import { resolveRegistryNamespace } from "./namespace.js";
|
|
9
10
|
import { installSkillArchive } from "./skill-installer.js";
|
|
11
|
+
import { assertRouterHealth, lockRegistryWorkspace, portableInstallPath, resolveVersion, routerMcpDefinition, syncRegistryWorkspace } from "./sync.js";
|
|
12
|
+
import { runRegistryTui } from "./tui.js";
|
|
13
|
+
import { mergeMcpEntry, mergeSkillEntry, readRegistryLock, readRegistryManifest, registryWorkspaceAt, upsertLockResource, writeRegistryLock, writeRegistryManifest, canonicalSha256 } from "./workspace.js";
|
|
10
14
|
// A selected profile must route this module independently from the platform API.
|
|
11
15
|
const loadConfig = (..._args) => loadPlatformConfig("aiRegistry");
|
|
12
16
|
function addExamples(command, examples) {
|
|
@@ -43,7 +47,10 @@ async function resolveInstance(client, value) {
|
|
|
43
47
|
return matches[0];
|
|
44
48
|
}
|
|
45
49
|
async function resolveNamespace(client, options) {
|
|
46
|
-
return client
|
|
50
|
+
return (await resolveContext(client, options)).namespace;
|
|
51
|
+
}
|
|
52
|
+
async function resolveContext(client, options, requiredAction) {
|
|
53
|
+
return resolveRegistryNamespace(client, { ...options, requiredAction });
|
|
47
54
|
}
|
|
48
55
|
async function readObject(path) {
|
|
49
56
|
try {
|
|
@@ -70,18 +77,86 @@ export function registerAiRegistryCommands(program) {
|
|
|
70
77
|
.command("registry")
|
|
71
78
|
.alias("ai-registry")
|
|
72
79
|
.description("通过平台 Keycloak 授权安全管理私有 Nacos Skill 与 MCP Registry");
|
|
80
|
+
addExamples(registry.command("capabilities").description("列出当前用户可见 namespace、动作、Router 与客户端适配器")
|
|
81
|
+
.action(async (_options, command) => {
|
|
82
|
+
const result = await new AiRegistryClient(loadConfig("aiRegistry")).getCapabilities();
|
|
83
|
+
await output(command, { ok: true, capabilities: result }, result.namespaces.map((item) => `${item.namespaceKey}\t${item.environment}\t${item.router?.status ?? "NO_ROUTER"}\t${item.actions.join(",")}`).join("\n") || "没有可见 namespace");
|
|
84
|
+
}), ["numa registry capabilities --json"]);
|
|
85
|
+
addExamples(registry.command("init").description("创建可提交到版本库的 .numa/registry.json")
|
|
86
|
+
.requiredOption("-n, --namespace <key>", "namespaceKey")
|
|
87
|
+
.option("-i, --instance <id-or-code>", "显式绑定实例")
|
|
88
|
+
.option("--workspace <path>", "项目根目录", process.cwd())
|
|
89
|
+
.action(async (options, command) => {
|
|
90
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
91
|
+
const context = await resolveContext(client, options);
|
|
92
|
+
const files = registryWorkspaceAt(options.workspace);
|
|
93
|
+
const existing = await readRegistryManifest(options.workspace, false);
|
|
94
|
+
if (existing)
|
|
95
|
+
throw new AiRegistryError(`Registry manifest already exists at ${existing.files.manifestPath}.`, "ai_registry_manifest_exists");
|
|
96
|
+
await writeRegistryManifest(files, {
|
|
97
|
+
schemaVersion: 1,
|
|
98
|
+
namespaceKey: context.namespace.namespaceKey,
|
|
99
|
+
instanceCode: context.namespace.instanceCode,
|
|
100
|
+
skills: [],
|
|
101
|
+
mcpServers: []
|
|
102
|
+
});
|
|
103
|
+
await output(command, { ok: true, manifestPath: files.manifestPath, namespace: context.namespace }, `已创建 ${files.manifestPath}`);
|
|
104
|
+
}), ["numa registry init --namespace team-ai"]);
|
|
105
|
+
addExamples(registry.command("lock").description("解析 manifest 的标签并写入精确版本与 SHA-256")
|
|
106
|
+
.option("-n, --namespace <key>", "覆盖项目 namespace")
|
|
107
|
+
.option("-i, --instance <id-or-code>", "显式绑定实例")
|
|
108
|
+
.option("--workspace <path>", "项目目录", process.cwd())
|
|
109
|
+
.action(async (options, command) => {
|
|
110
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
111
|
+
const context = await resolveContext(client, options);
|
|
112
|
+
const result = await lockRegistryWorkspace(client, context, options.workspace);
|
|
113
|
+
await output(command, { ok: true, namespace: context.namespace, lock: result.lock, lockPath: result.files.lockPath }, `已锁定 ${result.lock.resources.length} 个资源: ${result.files.lockPath}`);
|
|
114
|
+
}), ["numa registry lock --json"]);
|
|
115
|
+
addExamples(registry.command("sync").description("严格按 lock 文件安装;不会解析标签或隐式升级")
|
|
116
|
+
.option("-n, --namespace <key>", "覆盖项目 namespace")
|
|
117
|
+
.option("-i, --instance <id-or-code>", "显式绑定实例")
|
|
118
|
+
.option("--workspace <path>", "项目目录", process.cwd())
|
|
119
|
+
.option("--force", "重新安装并保留适配器备份")
|
|
120
|
+
.action(async (options, command) => {
|
|
121
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
122
|
+
const context = await resolveContext(client, options);
|
|
123
|
+
const result = await syncRegistryWorkspace(client, context, options.workspace, { force: options.force });
|
|
124
|
+
await output(command, { ok: true, namespace: context.namespace, lock: result.lock, lockPath: result.files.lockPath }, `同步完成: ${result.lock.resources.filter((item) => item.install.status === "INSTALLED").length}/${result.lock.resources.length}`);
|
|
125
|
+
}), ["numa registry sync --json"]);
|
|
126
|
+
addExamples(registry.command("upgrade [names...]").description("显式重新解析跟踪标签、更新 lock 并同步")
|
|
127
|
+
.option("-n, --namespace <key>", "覆盖项目 namespace")
|
|
128
|
+
.option("-i, --instance <id-or-code>", "显式绑定实例")
|
|
129
|
+
.option("--workspace <path>", "项目目录", process.cwd())
|
|
130
|
+
.option("--no-sync", "只更新 lock,不安装")
|
|
131
|
+
.option("--force", "重新安装并保留适配器备份")
|
|
132
|
+
.action(async (names, options, command) => {
|
|
133
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
134
|
+
const context = await resolveContext(client, options);
|
|
135
|
+
const project = await readRegistryManifest(options.workspace, true);
|
|
136
|
+
if (!project)
|
|
137
|
+
throw new AiRegistryError("Registry manifest is required.", "ai_registry_manifest_not_found");
|
|
138
|
+
const previous = await readRegistryLock(project.files, false);
|
|
139
|
+
const knownNames = new Set([...project.manifest.skills, ...project.manifest.mcpServers].map((item) => item.name));
|
|
140
|
+
const selected = new Set(names.length ? names : [...knownNames]);
|
|
141
|
+
for (const name of selected)
|
|
142
|
+
if (!knownNames.has(name))
|
|
143
|
+
throw new AiRegistryError(`Manifest has no resource named ${name}.`, "ai_registry_input_invalid");
|
|
144
|
+
const result = await lockRegistryWorkspace(client, context, options.workspace, { previous, upgradeNames: selected });
|
|
145
|
+
const synced = options.sync ? await syncRegistryWorkspace(client, context, options.workspace, { force: options.force }) : result;
|
|
146
|
+
await output(command, { ok: true, namespace: context.namespace, upgraded: [...selected].sort(), lock: synced.lock, lockPath: synced.files.lockPath }, `已升级 ${selected.size} 个资源${options.sync ? "并同步安装" : ""}`);
|
|
147
|
+
}), ["numa registry upgrade", "numa registry upgrade release-helper --no-sync --json"]);
|
|
73
148
|
addExamples(registry.command("tui").description("交互式浏览、安装和治理 Skill/MCP").action(runTui), ["numa registry tui"]);
|
|
74
149
|
const instances = registry.command("instances").alias("services").description("管理数据库中的 Nacos 集群实例与 OIDC 配置");
|
|
75
150
|
addExamples(instances.command("list").description("列出 Nacos 实例(不返回密文或明文 secret)").action(async (_options, command) => {
|
|
76
151
|
const result = await new AiRegistryClient(loadConfig("aiRegistry")).listInstances();
|
|
77
152
|
await output(command, { ok: true, instances: result }, result.map((item) => `${item.id}\t${item.code}\t${item.status}\t${item.credentialConfigured ? "credential-ready" : "credential-missing"}\t${item.displayName}`).join("\n") || "没有配置 Nacos 实例");
|
|
78
153
|
}), ["numa registry instances list --json"]);
|
|
79
|
-
addExamples(instances.command("create <json>").description("创建实例;
|
|
154
|
+
addExamples(instances.command("create <json>").description("创建实例;READER/MAINTAINER 凭据仅写入平台密文表")
|
|
80
155
|
.action(async (file, _options, command) => {
|
|
81
156
|
const result = await new AiRegistryClient(loadConfig("aiRegistry")).createInstance(await readObject(file));
|
|
82
157
|
await output(command, { ok: true, instance: result }, `Nacos 实例已创建: ${result.id} ${result.code}`);
|
|
83
158
|
}), ["numa registry instances create ./nacos-instance.json --json"]);
|
|
84
|
-
addExamples(instances.command("update <id-or-code> <json>").description("更新实例;JSON 必须包含当前 version
|
|
159
|
+
addExamples(instances.command("update <id-or-code> <json>").description("更新实例;JSON 必须包含当前 version,省略某套凭据表示保留")
|
|
85
160
|
.action(async (value, file, _options, command) => {
|
|
86
161
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
87
162
|
const instance = await resolveInstance(client, value);
|
|
@@ -143,7 +218,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
143
218
|
}), ["numa registry namespaces delete 7 --yes --json"]);
|
|
144
219
|
const skill = registry.command("skill").description("查看、上传、治理和安装 Nacos Skills");
|
|
145
220
|
addExamples(skill.command("list")
|
|
146
|
-
.
|
|
221
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
147
222
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
148
223
|
.option("-q, --search <text>", "搜索关键词")
|
|
149
224
|
.option("--page <number>", "页码", "1")
|
|
@@ -160,7 +235,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
160
235
|
await output(command, { ok: true, namespace: targetNamespace, skills: result }, items.map((item, index) => itemName(item, `skill-${index + 1}`)).join("\n") || "没有找到 Skill");
|
|
161
236
|
}), ["numa registry skill list --namespace team-ai --search deploy --json"]);
|
|
162
237
|
addExamples(skill.command("inspect <name>")
|
|
163
|
-
.
|
|
238
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
164
239
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
165
240
|
.action(async (name, options, command) => {
|
|
166
241
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
@@ -169,7 +244,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
169
244
|
await output(command, { ok: true, namespace: targetNamespace, skill: result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
|
|
170
245
|
}), ["numa registry skill inspect release-helper --namespace team-ai --json"]);
|
|
171
246
|
addExamples(skill.command("install <name>")
|
|
172
|
-
.
|
|
247
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
173
248
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
174
249
|
.requiredOption("-a, --agent <agent>", "chatgpt|codex|claude|cursor|workbuddy|openclaw|agents|custom")
|
|
175
250
|
.option("--scope <scope>", "project|user", "project")
|
|
@@ -180,19 +255,36 @@ export function registerAiRegistryCommands(program) {
|
|
|
180
255
|
.option("--force", "备份并替换已有 Skill")
|
|
181
256
|
.action(async (name, options, command) => {
|
|
182
257
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
183
|
-
const
|
|
184
|
-
const
|
|
258
|
+
const context = await resolveContext(client, options);
|
|
259
|
+
const detail = await client.getSkill(context.namespace.id, name);
|
|
260
|
+
const trackedLabel = options.version ? undefined : options.label ?? "latest";
|
|
261
|
+
const version = resolveVersion(detail, options.version, trackedLabel, name);
|
|
262
|
+
const download = await client.downloadSkill(context.namespace.id, name, { version });
|
|
185
263
|
const result = await installSkillArchive(download.bytes, {
|
|
186
264
|
agent: skillAgent(options.agent), scope: installScope(options.scope), skillName: name,
|
|
187
265
|
workspace: options.workspace, customRoot: options.customRoot, force: options.force
|
|
188
266
|
});
|
|
189
|
-
|
|
267
|
+
const lock = await recordSkillInstallation(context, options.workspace, {
|
|
268
|
+
name, version: options.version, label: trackedLabel, agent: skillAgent(options.agent),
|
|
269
|
+
scope: installScope(options.scope), customRoot: options.customRoot
|
|
270
|
+
}, version, download.sha256, result.installedPath);
|
|
271
|
+
await output(command, { ok: true, namespace: context.namespace, resolvedVersion: version, download: { sha256: download.sha256, fileName: download.fileName }, installation: result, lock }, `已安装 ${name}@${version}: ${result.installedPath}${result.backupPath ? `\n原版本备份: ${result.backupPath}` : ""}`);
|
|
190
272
|
}), [
|
|
191
273
|
"numa registry skill install release-helper --namespace team-ai --agent claude",
|
|
192
274
|
"numa registry skill install release-helper --namespace team-ai --instance cluster-b --agent cursor --scope user --label latest --json"
|
|
193
275
|
]);
|
|
276
|
+
addExamples(skill.command("draft <name>")
|
|
277
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
278
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
279
|
+
.option("--from-version <semver>", "从在线版本创建草稿")
|
|
280
|
+
.action(async (name, options, command) => {
|
|
281
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
282
|
+
const targetNamespace = await resolveNamespace(client, options);
|
|
283
|
+
const result = await client.createSkillDraft(targetNamespace.id, name, { baseVersion: options.fromVersion });
|
|
284
|
+
await output(command, { ok: true, namespace: targetNamespace, result }, `Skill ${name} 草稿已创建`);
|
|
285
|
+
}), ["numa registry skill draft release-helper --from-version 1.0.0 --json"]);
|
|
194
286
|
addExamples(skill.command("upload <zip>")
|
|
195
|
-
.
|
|
287
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
196
288
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
197
289
|
.option("--overwrite", "覆盖现有 draft")
|
|
198
290
|
.option("--target-version <semver>", "目标版本")
|
|
@@ -205,10 +297,12 @@ export function registerAiRegistryCommands(program) {
|
|
|
205
297
|
}), ["numa registry skill upload ./release-helper.zip --namespace team-ai --target-version 1.0.0 --json"]);
|
|
206
298
|
for (const action of ["submit", "publish", "online", "offline"]) {
|
|
207
299
|
addExamples(skill.command(`${action} <name>`)
|
|
208
|
-
.
|
|
300
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
209
301
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
210
302
|
.option("--version <semver>", "版本(publish 必填)")
|
|
211
303
|
.action(async (name, options, command) => {
|
|
304
|
+
if (action === "publish" && !options.version)
|
|
305
|
+
throw new AiRegistryError("publish requires --version.", "ai_registry_input_invalid");
|
|
212
306
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
213
307
|
const targetNamespace = await resolveNamespace(client, options);
|
|
214
308
|
const result = await client.skillAction(targetNamespace.id, name, action, { version: options.version });
|
|
@@ -216,7 +310,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
216
310
|
}), [`numa registry skill ${action} release-helper --namespace team-ai${action === "publish" ? " --version 1.0.0" : ""}`]);
|
|
217
311
|
}
|
|
218
312
|
addExamples(skill.command("scope <name> <scope>")
|
|
219
|
-
.
|
|
313
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
220
314
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
221
315
|
.action(async (name, scope, options, command) => {
|
|
222
316
|
const normalized = scope.toUpperCase();
|
|
@@ -228,7 +322,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
228
322
|
await output(command, { ok: true, namespace: targetNamespace, result }, `Skill ${name} scope 已更新为 ${normalized}`);
|
|
229
323
|
}), ["numa registry skill scope release-helper PRIVATE --namespace team-ai"]);
|
|
230
324
|
addExamples(skill.command("tags <name> <tags...>")
|
|
231
|
-
.
|
|
325
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
232
326
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
233
327
|
.action(async (name, tags, options, command) => {
|
|
234
328
|
const normalized = [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
|
|
@@ -240,8 +334,21 @@ export function registerAiRegistryCommands(program) {
|
|
|
240
334
|
const result = await client.skillAction(targetNamespace.id, name, "biz-tags", { bizTags: normalized });
|
|
241
335
|
await output(command, { ok: true, namespace: targetNamespace, result }, `Skill ${name} tags 已更新`);
|
|
242
336
|
}), ["numa registry skill tags release-helper deploy java --namespace team-ai --json"]);
|
|
337
|
+
addExamples(skill.command("labels <name> <labels...>")
|
|
338
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
339
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
340
|
+
.requiredOption("--version <semver>", "绑定标签的精确版本")
|
|
341
|
+
.action(async (name, labels, options, command) => {
|
|
342
|
+
const normalized = [...new Set(labels.map((label) => label.trim()).filter(Boolean))];
|
|
343
|
+
if (!normalized.length || normalized.length > 20)
|
|
344
|
+
throw new AiRegistryError("labels must contain 1-20 values.", "ai_registry_input_invalid");
|
|
345
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
346
|
+
const targetNamespace = await resolveNamespace(client, options);
|
|
347
|
+
const result = await client.skillAction(targetNamespace.id, name, "labels", { version: options.version, labels: normalized });
|
|
348
|
+
await output(command, { ok: true, namespace: targetNamespace, result }, `Skill ${name}@${options.version} labels 已更新`);
|
|
349
|
+
}), ["numa registry skill labels release-helper latest stable --version 1.2.0 --json"]);
|
|
243
350
|
addExamples(skill.command("delete <name>")
|
|
244
|
-
.
|
|
351
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
245
352
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
246
353
|
.option("--yes", "确认不可逆的远端删除")
|
|
247
354
|
.action(async (name, options, command) => {
|
|
@@ -254,7 +361,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
254
361
|
}), ["numa registry skill delete obsolete-skill --namespace team-ai --yes --json"]);
|
|
255
362
|
const mcp = registry.command("mcp").description("查看、维护和安装 Nacos MCP Servers");
|
|
256
363
|
addExamples(mcp.command("list")
|
|
257
|
-
.
|
|
364
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
258
365
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
259
366
|
.option("-q, --search <text>", "搜索关键词")
|
|
260
367
|
.option("--page <number>", "页码", "1")
|
|
@@ -270,7 +377,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
270
377
|
await output(command, { ok: true, namespace: targetNamespace, mcpServers: result }, pageItems(result).map((item, index) => itemName(item, `mcp-${index + 1}`)).join("\n") || "没有找到 MCP Server");
|
|
271
378
|
}), ["numa registry mcp list --namespace team-ai --json"]);
|
|
272
379
|
addExamples(mcp.command("inspect <name>")
|
|
273
|
-
.
|
|
380
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
274
381
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
275
382
|
.option("--version <semver>", "精确版本")
|
|
276
383
|
.action(async (name, options, command) => {
|
|
@@ -280,29 +387,88 @@ export function registerAiRegistryCommands(program) {
|
|
|
280
387
|
await output(command, { ok: true, namespace: targetNamespace, mcpServer: result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
|
|
281
388
|
}), ["numa registry mcp inspect internal-docs --namespace team-ai --json"]);
|
|
282
389
|
addExamples(mcp.command("install <name>")
|
|
283
|
-
.
|
|
390
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
284
391
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
285
392
|
.requiredOption("-a, --agent <agent>", "chatgpt|codex|claude|cursor|workbuddy|openclaw|custom")
|
|
286
393
|
.option("--scope <scope>", "project|user", "project")
|
|
287
394
|
.option("--version <semver>", "精确版本")
|
|
395
|
+
.option("--label <label>", "首次安装时解析的标签", "latest")
|
|
396
|
+
.option("--transport <transport>", "auto|remote|bridge", "auto")
|
|
288
397
|
.option("--workspace <path>", "项目根目录")
|
|
289
398
|
.option("--custom-config <path>", "自定义 mcp.json 路径")
|
|
290
399
|
.option("--force", "备份并替换 JSON 配置中的同名项")
|
|
291
400
|
.action(async (name, options, command) => {
|
|
292
401
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
|
|
402
|
+
const context = await resolveContext(client, options);
|
|
403
|
+
const trackedLabel = options.version ? undefined : options.label ?? "latest";
|
|
404
|
+
const detail = await client.getMcpServer(context.namespace.id, name, options.version ? { version: options.version } : { label: trackedLabel });
|
|
405
|
+
const version = resolveVersion(detail, options.version, trackedLabel, name);
|
|
406
|
+
const upstream = mcpDefinitionFromNacos(detail);
|
|
407
|
+
const entry = {
|
|
408
|
+
name, version: options.version, label: trackedLabel, agent: mcpAgent(options.agent),
|
|
409
|
+
scope: installScope(options.scope), transport: mcpTransport(options.transport), customConfig: options.customConfig
|
|
410
|
+
};
|
|
411
|
+
if (!context.capability.router?.endpoint || context.capability.router.status !== "READY") {
|
|
412
|
+
throw new AiRegistryError(`Namespace ${context.namespace.namespaceKey} has no ready enterprise Router.`, "ai_registry_router_unavailable");
|
|
413
|
+
}
|
|
414
|
+
assertRouterHealth(await client.checkRouter(context.namespace.id));
|
|
415
|
+
const result = await installMcpClientDefinition(name, routerMcpDefinition(context, entry), {
|
|
416
|
+
agent: entry.agent, scope: entry.scope, workspace: options.workspace,
|
|
297
417
|
customConfig: options.customConfig, force: options.force
|
|
298
418
|
});
|
|
419
|
+
const lock = await recordMcpInstallation(context, options.workspace, entry, version, canonicalSha256({ version, definition: upstream }), result.configPath ?? result.endpoint ?? `managed:${result.agent}`);
|
|
299
420
|
const textResult = result.manualSteps
|
|
300
421
|
? `已生成 ChatGPT MCP 安装指引: ${result.endpoint}\n${result.manualSteps.map((step, index) => `${index + 1}. ${step}`).join("\n")}`
|
|
301
422
|
: `MCP ${name} 已安装${result.configPath ? `: ${result.configPath}` : "(由客户端 CLI 管理)"}${result.backupPath ? `\n原配置备份: ${result.backupPath}` : ""}`;
|
|
302
|
-
await output(command, { ok: true, namespace:
|
|
423
|
+
await output(command, { ok: true, namespace: context.namespace, resolvedVersion: version, installation: result, lock }, textResult);
|
|
303
424
|
}), ["numa registry mcp install internal-docs --namespace team-ai --agent cursor"]);
|
|
425
|
+
addExamples(mcp.command("bridge").description("把 namespace 企业 Router 桥接为本地 stdio MCP")
|
|
426
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
427
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
428
|
+
.option("--workspace <path>", "项目目录")
|
|
429
|
+
.action(async (options) => {
|
|
430
|
+
await runMcpBridge(new AiRegistryClient(loadConfig("aiRegistry")), options);
|
|
431
|
+
}), ["numa registry mcp bridge --namespace team-ai"]);
|
|
432
|
+
for (const action of ["online", "offline"]) {
|
|
433
|
+
addExamples(mcp.command(`${action} <name>`)
|
|
434
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
435
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
436
|
+
.option("--version <semver>", "精确版本")
|
|
437
|
+
.action(async (name, options, command) => {
|
|
438
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
439
|
+
const targetNamespace = await resolveNamespace(client, options);
|
|
440
|
+
const result = await client.mcpAction(targetNamespace.id, name, action, { version: options.version });
|
|
441
|
+
await output(command, { ok: true, namespace: targetNamespace, result }, `MCP ${name}: ${action} 完成`);
|
|
442
|
+
}), [`numa registry mcp ${action} internal-docs --version 1.0.0 --json`]);
|
|
443
|
+
}
|
|
444
|
+
addExamples(mcp.command("tags <name> <tags...>")
|
|
445
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
446
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
447
|
+
.action(async (name, tags, options, command) => {
|
|
448
|
+
const normalized = [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
|
|
449
|
+
if (!normalized.length || normalized.length > 20)
|
|
450
|
+
throw new AiRegistryError("tags must contain 1-20 values.", "ai_registry_input_invalid");
|
|
451
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
452
|
+
const targetNamespace = await resolveNamespace(client, options);
|
|
453
|
+
const result = await client.mcpAction(targetNamespace.id, name, "biz-tags", { bizTags: normalized });
|
|
454
|
+
await output(command, { ok: true, namespace: targetNamespace, result }, `MCP ${name} tags 已更新`);
|
|
455
|
+
}), ["numa registry mcp tags internal-docs knowledge search --json"]);
|
|
456
|
+
const tools = mcp.command("tool").description("启用或禁用 MCP 工具");
|
|
457
|
+
for (const action of ["enable", "disable"]) {
|
|
458
|
+
addExamples(tools.command(`${action} <mcp-name> <tool-name>`)
|
|
459
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
460
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
461
|
+
.option("--version <number>", "乐观锁版本")
|
|
462
|
+
.action(async (mcpName, toolName, options, command) => {
|
|
463
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
464
|
+
const targetNamespace = await resolveNamespace(client, options);
|
|
465
|
+
const version = options.version == null ? undefined : nonnegativeInteger(options.version, "version");
|
|
466
|
+
const result = await client.setMcpToolEnabled(targetNamespace.id, mcpName, toolName, action === "enable", version);
|
|
467
|
+
await output(command, { ok: true, namespace: targetNamespace, result }, `MCP tool ${mcpName}/${toolName}: ${action} 完成`);
|
|
468
|
+
}), [`numa registry mcp tool ${action} internal-docs search --version 3 --json`]);
|
|
469
|
+
}
|
|
304
470
|
addExamples(mcp.command("create <json>")
|
|
305
|
-
.
|
|
471
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
306
472
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
307
473
|
.action(async (file, options, command) => {
|
|
308
474
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
@@ -311,7 +477,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
311
477
|
await output(command, { ok: true, namespace: targetNamespace, result }, "MCP create 完成");
|
|
312
478
|
}), ["numa registry mcp create ./mcp.json --namespace team-ai --json"]);
|
|
313
479
|
addExamples(mcp.command("update <name> <json>")
|
|
314
|
-
.
|
|
480
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
315
481
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
316
482
|
.action(async (name, file, options, command) => {
|
|
317
483
|
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
@@ -320,7 +486,7 @@ export function registerAiRegistryCommands(program) {
|
|
|
320
486
|
await output(command, { ok: true, namespace: targetNamespace, result }, "MCP update 完成");
|
|
321
487
|
}), ["numa registry mcp update internal-docs ./mcp.json --namespace team-ai --json"]);
|
|
322
488
|
addExamples(mcp.command("delete <name>")
|
|
323
|
-
.
|
|
489
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
324
490
|
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
325
491
|
.option("--version <semver>", "精确版本")
|
|
326
492
|
.option("--yes", "确认不可逆的远端删除")
|
|
@@ -332,93 +498,106 @@ export function registerAiRegistryCommands(program) {
|
|
|
332
498
|
const result = await client.deleteMcpServer(targetNamespace.id, name, options.version);
|
|
333
499
|
await output(command, { ok: true, namespace: targetNamespace, result }, `MCP ${name} 已从远端删除`);
|
|
334
500
|
}), ["numa registry mcp delete obsolete --namespace team-ai --yes --json"]);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
501
|
+
const approvals = registry.command("approvals").description("查看、审核和发布两人制 release request");
|
|
502
|
+
addExamples(approvals.command("submit <type> <name>")
|
|
503
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
504
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
505
|
+
.requiredOption("--resource-version <version>", "提交审核的精确资源版本")
|
|
506
|
+
.requiredOption("--reason <reason>", "发布说明")
|
|
507
|
+
.option("--scanner-exemption <reason>", "仅非生产 Reviewer 可审批的扫描豁免原因")
|
|
508
|
+
.action(async (type, name, options, command) => {
|
|
509
|
+
const resourceType = type.toUpperCase();
|
|
510
|
+
if (resourceType !== "SKILL" && resourceType !== "MCP") {
|
|
511
|
+
throw new AiRegistryError("type must be skill or mcp.", "ai_registry_input_invalid");
|
|
512
|
+
}
|
|
513
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
514
|
+
const context = await resolveContext(client, options);
|
|
515
|
+
const result = await client.createReleaseRequest({
|
|
516
|
+
namespaceId: context.namespace.id,
|
|
517
|
+
resourceType,
|
|
518
|
+
resourceName: name,
|
|
519
|
+
resourceVersion: options.resourceVersion,
|
|
520
|
+
reason: options.reason,
|
|
521
|
+
...(options.scannerExemption ? { scannerExemptionReason: options.scannerExemption } : {})
|
|
522
|
+
});
|
|
523
|
+
await output(command, { ok: true, namespace: context.namespace, result }, `${resourceType} ${name}@${options.resourceVersion}: submitted`);
|
|
524
|
+
}), ["numa registry approvals submit skill release-helper --resource-version 1.2.0 --reason 'ready for production' --json"]);
|
|
525
|
+
addExamples(approvals.command("list")
|
|
526
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
527
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
528
|
+
.option("--state <state>", "生命周期状态")
|
|
529
|
+
.option("--limit <number>", "每页 1-100", "20")
|
|
530
|
+
.action(async (options, command) => {
|
|
531
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
532
|
+
const context = await resolveContext(client, options);
|
|
533
|
+
const result = await client.listReleaseRequests({
|
|
534
|
+
namespaceId: context.namespace.id,
|
|
535
|
+
state: options.state,
|
|
536
|
+
limit: positiveInteger(options.limit, "limit", 20, 100)
|
|
537
|
+
});
|
|
538
|
+
await output(command, { ok: true, namespace: context.namespace, releaseRequests: result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
|
|
539
|
+
}), ["numa registry approvals list --state SUBMITTED --json"]);
|
|
540
|
+
addExamples(approvals.command("review <request-id>")
|
|
541
|
+
.requiredOption("--decision <decision>", "APPROVED|REJECTED")
|
|
542
|
+
.requiredOption("--reason <reason>", "审核原因")
|
|
543
|
+
.option("--version <number>", "乐观锁版本")
|
|
544
|
+
.action(async (requestId, options, command) => {
|
|
545
|
+
const decision = options.decision.toUpperCase();
|
|
546
|
+
if (decision !== "APPROVED" && decision !== "REJECTED")
|
|
547
|
+
throw new AiRegistryError("decision must be APPROVED or REJECTED.", "ai_registry_input_invalid");
|
|
548
|
+
const result = await new AiRegistryClient(loadConfig("aiRegistry")).reviewReleaseRequest(requestId, {
|
|
549
|
+
decision,
|
|
550
|
+
reason: options.reason,
|
|
551
|
+
...(options.version == null ? {} : { version: nonnegativeInteger(options.version, "version") })
|
|
552
|
+
});
|
|
553
|
+
await output(command, { ok: true, requestId, result }, `Release request ${requestId}: ${decision}`);
|
|
554
|
+
}), ["numa registry approvals review rr_123 --decision APPROVED --reason 'scan passed' --version 2 --json"]);
|
|
555
|
+
addExamples(approvals.command("publish <request-id>")
|
|
556
|
+
.option("--version <number>", "乐观锁版本")
|
|
557
|
+
.action(async (requestId, options, command) => {
|
|
558
|
+
const result = await new AiRegistryClient(loadConfig("aiRegistry")).publishReleaseRequest(requestId, {
|
|
559
|
+
...(options.version == null ? {} : { version: nonnegativeInteger(options.version, "version") })
|
|
560
|
+
});
|
|
561
|
+
await output(command, { ok: true, requestId, result }, `Release request ${requestId}: publish started`);
|
|
562
|
+
}), ["numa registry approvals publish rr_123 --version 3 --json"]);
|
|
563
|
+
const router = registry.command("router").description("查看 namespace Router 绑定、连接和健康");
|
|
564
|
+
for (const action of ["inspect", "health", "reconcile"]) {
|
|
565
|
+
addExamples(router.command(action)
|
|
566
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
567
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
568
|
+
.action(async (options, command) => {
|
|
569
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
570
|
+
const context = await resolveContext(client, options);
|
|
571
|
+
const result = action === "inspect" ? await client.getRouter(context.namespace.id)
|
|
572
|
+
: action === "health" ? await client.checkRouter(context.namespace.id)
|
|
573
|
+
: await client.reconcileNamespace(context.namespace.id);
|
|
574
|
+
await output(command, { ok: true, namespace: context.namespace, result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
|
|
575
|
+
}), [`numa registry router ${action} --namespace team-ai --json`]);
|
|
396
576
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
577
|
+
addExamples(router.command("connect")
|
|
578
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
579
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
580
|
+
.option("--adapter <adapter>", "客户端适配器")
|
|
581
|
+
.action(async (options, command) => {
|
|
582
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
583
|
+
const context = await resolveContext(client, options);
|
|
584
|
+
const result = await client.connectRouter(context.namespace.id, { adapter: options.adapter });
|
|
585
|
+
await output(command, { ok: true, namespace: context.namespace, result }, `Router connection prepared for ${context.namespace.namespaceKey}`);
|
|
586
|
+
}), ["numa registry router connect --namespace team-ai --adapter codex --json"]);
|
|
587
|
+
addExamples(registry.command("audit").description("查询追加式 Registry 审计事件")
|
|
588
|
+
.option("-n, --namespace <key>", "namespaceKey")
|
|
589
|
+
.option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
|
|
590
|
+
.option("--cursor <cursor>", "分页游标")
|
|
591
|
+
.option("--limit <number>", "每页 1-100", "20")
|
|
592
|
+
.action(async (options, command) => {
|
|
593
|
+
const client = new AiRegistryClient(loadConfig("aiRegistry"));
|
|
594
|
+
const context = await resolveContext(client, options);
|
|
595
|
+
const result = await client.listAuditEvents({ namespaceId: context.namespace.id, cursor: options.cursor, limit: positiveInteger(options.limit, "limit", 20, 100) });
|
|
596
|
+
await output(command, { ok: true, namespace: context.namespace, events: result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
|
|
597
|
+
}), ["numa registry audit --namespace team-ai --json"]);
|
|
402
598
|
}
|
|
403
|
-
async function
|
|
404
|
-
|
|
405
|
-
const items = pageItems(page);
|
|
406
|
-
if (!items.length)
|
|
407
|
-
return void process.stdout.write("没有可用 MCP Server。\n");
|
|
408
|
-
const name = await select({ message: "选择 MCP Server", options: items.map((item, index) => ({ value: itemName(item, `mcp-${index + 1}`), label: itemName(item, `mcp-${index + 1}`), hint: String(item.description ?? item.protocol ?? "") })) });
|
|
409
|
-
if (isCancel(name))
|
|
410
|
-
return void cancel("已取消");
|
|
411
|
-
const action = await select({ message: `对 ${name} 执行`, options: [{ value: "inspect", label: "查看详情" }, { value: "install", label: "安装到客户端" }] });
|
|
412
|
-
if (isCancel(action))
|
|
413
|
-
return void cancel("已取消");
|
|
414
|
-
const detail = await client.getMcpServer(namespaceId, name);
|
|
415
|
-
if (action === "inspect")
|
|
416
|
-
return void process.stdout.write(`${JSON.stringify(sanitizeRegistryOutput(detail), null, 2)}\n`);
|
|
417
|
-
const agent = await select({ message: "安装目标", options: ["chatgpt", "codex", "claude", "cursor", "workbuddy", "openclaw"].map((value) => ({ value, label: value })) });
|
|
418
|
-
if (isCancel(agent))
|
|
419
|
-
return void cancel("已取消");
|
|
420
|
-
const result = await installMcpDefinition(detail, { agent: mcpAgent(agent), scope: agent === "codex" || agent === "openclaw" ? "user" : "project" });
|
|
421
|
-
process.stdout.write(`已安装 MCP: ${result.configPath ?? "客户端 CLI 配置"}\n`);
|
|
599
|
+
async function runTui() {
|
|
600
|
+
await runRegistryTui(new AiRegistryClient(loadConfig("aiRegistry")));
|
|
422
601
|
}
|
|
423
602
|
function skillAgent(value) {
|
|
424
603
|
if (["chatgpt", "codex", "claude", "cursor", "workbuddy", "openclaw", "agents", "custom"].includes(value))
|
|
@@ -435,4 +614,80 @@ function installScope(value) {
|
|
|
435
614
|
return value;
|
|
436
615
|
throw new AiRegistryError("scope must be project or user.", "ai_registry_install_target_invalid");
|
|
437
616
|
}
|
|
617
|
+
function mcpTransport(value) {
|
|
618
|
+
if (value === "auto" || value === "remote" || value === "bridge")
|
|
619
|
+
return value;
|
|
620
|
+
throw new AiRegistryError("transport must be auto, remote, or bridge.", "ai_registry_install_target_invalid");
|
|
621
|
+
}
|
|
622
|
+
function nonnegativeInteger(value, label) {
|
|
623
|
+
const parsed = Number(value);
|
|
624
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > Number.MAX_SAFE_INTEGER) {
|
|
625
|
+
throw new AiRegistryError(`${label} must be a non-negative integer.`, "ai_registry_input_invalid");
|
|
626
|
+
}
|
|
627
|
+
return parsed;
|
|
628
|
+
}
|
|
629
|
+
async function recordSkillInstallation(context, workspace, entry, version, sha256, installedPath) {
|
|
630
|
+
const project = await registryProjectForWrite(context, workspace);
|
|
631
|
+
const manifest = mergeSkillEntry(project.manifest, entry);
|
|
632
|
+
await writeRegistryManifest(project.files, manifest);
|
|
633
|
+
const lock = upsertLockResource(project.lock, {
|
|
634
|
+
type: "SKILL",
|
|
635
|
+
name: entry.name,
|
|
636
|
+
version,
|
|
637
|
+
...(entry.label ? { trackedLabel: entry.label } : {}),
|
|
638
|
+
sha256,
|
|
639
|
+
source: { registry: "NACOS", namespaceKey: context.namespace.namespaceKey, instanceCode: context.namespace.instanceCode },
|
|
640
|
+
install: {
|
|
641
|
+
agent: entry.agent,
|
|
642
|
+
scope: entry.scope,
|
|
643
|
+
status: "INSTALLED",
|
|
644
|
+
path: portableInstallPath(installedPath, workspace ?? process.cwd(), entry.agent)
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
await writeRegistryLock(project.files, lock);
|
|
648
|
+
return { manifestPath: project.files.manifestPath, lockPath: project.files.lockPath };
|
|
649
|
+
}
|
|
650
|
+
async function recordMcpInstallation(context, workspace, entry, version, sha256, installedPath) {
|
|
651
|
+
const project = await registryProjectForWrite(context, workspace);
|
|
652
|
+
const manifest = mergeMcpEntry(project.manifest, entry);
|
|
653
|
+
await writeRegistryManifest(project.files, manifest);
|
|
654
|
+
const lock = upsertLockResource(project.lock, {
|
|
655
|
+
type: "MCP",
|
|
656
|
+
name: entry.name,
|
|
657
|
+
version,
|
|
658
|
+
...(entry.label ? { trackedLabel: entry.label } : {}),
|
|
659
|
+
sha256,
|
|
660
|
+
source: { registry: "NACOS", namespaceKey: context.namespace.namespaceKey, instanceCode: context.namespace.instanceCode },
|
|
661
|
+
install: {
|
|
662
|
+
agent: entry.agent,
|
|
663
|
+
scope: entry.scope,
|
|
664
|
+
status: "INSTALLED",
|
|
665
|
+
path: portableInstallPath(installedPath, workspace ?? process.cwd(), entry.agent)
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
await writeRegistryLock(project.files, lock);
|
|
669
|
+
return { manifestPath: project.files.manifestPath, lockPath: project.files.lockPath };
|
|
670
|
+
}
|
|
671
|
+
async function registryProjectForWrite(context, workspace) {
|
|
672
|
+
const existing = await readRegistryManifest(workspace, false);
|
|
673
|
+
const files = existing?.files ?? registryWorkspaceAt(workspace);
|
|
674
|
+
const manifest = existing?.manifest ?? {
|
|
675
|
+
schemaVersion: 1,
|
|
676
|
+
namespaceKey: context.namespace.namespaceKey,
|
|
677
|
+
instanceCode: context.namespace.instanceCode,
|
|
678
|
+
skills: [],
|
|
679
|
+
mcpServers: []
|
|
680
|
+
};
|
|
681
|
+
if (manifest.namespaceKey !== context.namespace.namespaceKey || (manifest.instanceCode && manifest.instanceCode !== context.namespace.instanceCode)) {
|
|
682
|
+
throw new AiRegistryError("Existing registry manifest points to a different namespace binding.", "ai_registry_manifest_namespace_mismatch");
|
|
683
|
+
}
|
|
684
|
+
const lock = await readRegistryLock(files, false) ?? {
|
|
685
|
+
schemaVersion: 1,
|
|
686
|
+
namespaceKey: context.namespace.namespaceKey,
|
|
687
|
+
instanceCode: context.namespace.instanceCode,
|
|
688
|
+
policyRevision: context.capabilities.policyRevision,
|
|
689
|
+
resources: []
|
|
690
|
+
};
|
|
691
|
+
return { files, manifest, lock };
|
|
692
|
+
}
|
|
438
693
|
//# sourceMappingURL=commands.js.map
|