@numa-tech/numa 1.14.52 → 1.16.0

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.
Files changed (78) hide show
  1. package/README.md +161 -24
  2. package/dist/ai-registry/commands.d.ts +8 -1
  3. package/dist/ai-registry/commands.js +70 -15
  4. package/dist/ai-registry/commands.js.map +1 -1
  5. package/dist/ai-registry/mcp-installer.d.ts +1 -1
  6. package/dist/ai-registry/mcp-installer.js +26 -0
  7. package/dist/ai-registry/mcp-installer.js.map +1 -1
  8. package/dist/ai-registry/sync.js +3 -2
  9. package/dist/ai-registry/sync.js.map +1 -1
  10. package/dist/cli.js +42 -14
  11. package/dist/cli.js.map +1 -1
  12. package/dist/command-catalog.js +28 -3
  13. package/dist/command-catalog.js.map +1 -1
  14. package/dist/interactive/entry.d.ts +2 -0
  15. package/dist/interactive/entry.js +91 -0
  16. package/dist/interactive/entry.js.map +1 -0
  17. package/dist/interactive/mcp.d.ts +4 -0
  18. package/dist/interactive/mcp.js +88 -0
  19. package/dist/interactive/mcp.js.map +1 -0
  20. package/dist/interactive/prompts.d.ts +26 -0
  21. package/dist/interactive/prompts.js +79 -0
  22. package/dist/interactive/prompts.js.map +1 -0
  23. package/dist/interactive/registry.d.ts +28 -0
  24. package/dist/interactive/registry.js +147 -0
  25. package/dist/interactive/registry.js.map +1 -0
  26. package/dist/maintenance/modules.js +1 -1
  27. package/dist/maintenance/modules.js.map +1 -1
  28. package/dist/mcp/catalog.d.ts +35 -0
  29. package/dist/mcp/catalog.js +203 -0
  30. package/dist/mcp/catalog.js.map +1 -0
  31. package/dist/mcp/commands.d.ts +10 -0
  32. package/dist/mcp/commands.js +96 -0
  33. package/dist/mcp/commands.js.map +1 -0
  34. package/dist/mcp/environment.d.ts +5 -0
  35. package/dist/mcp/environment.js +35 -0
  36. package/dist/mcp/environment.js.map +1 -0
  37. package/dist/mcp/inspect.d.ts +35 -0
  38. package/dist/mcp/inspect.js +17 -0
  39. package/dist/mcp/inspect.js.map +1 -0
  40. package/dist/mcp/install.d.ts +55 -0
  41. package/dist/mcp/install.js +151 -0
  42. package/dist/mcp/install.js.map +1 -0
  43. package/dist/mcp/modules.d.ts +4 -0
  44. package/dist/mcp/modules.js +11 -0
  45. package/dist/mcp/modules.js.map +1 -0
  46. package/dist/oss/commands.d.ts +3 -0
  47. package/dist/oss/commands.js +74 -7
  48. package/dist/oss/commands.js.map +1 -1
  49. package/dist/oss/search.d.ts +70 -0
  50. package/dist/oss/search.js +172 -0
  51. package/dist/oss/search.js.map +1 -0
  52. package/dist/oss/tools.d.ts +8 -0
  53. package/dist/oss/tools.js +69 -0
  54. package/dist/oss/tools.js.map +1 -0
  55. package/dist/oss/transfers.d.ts +17 -2
  56. package/dist/oss/transfers.js +29 -7
  57. package/dist/oss/transfers.js.map +1 -1
  58. package/dist/server.d.ts +4 -1
  59. package/dist/server.js +192 -169
  60. package/dist/server.js.map +1 -1
  61. package/dist/wecom/tools.js +15 -4
  62. package/dist/wecom/tools.js.map +1 -1
  63. package/package.json +4 -4
  64. package/skills/numa-cli/SKILL.md +4 -2
  65. package/skills/numa-cli/agents/openai.yaml +1 -1
  66. package/skills/numa-cli/evals/evals.json +31 -1
  67. package/skills/numa-cli/numa-skill.json +1 -1
  68. package/skills/numa-cli/references/cli-mcp.md +121 -0
  69. package/skills/numa-oss-access/SKILL.md +7 -3
  70. package/skills/numa-oss-access/agents/openai.yaml +1 -1
  71. package/skills/numa-oss-access/evals/evals.json +18 -0
  72. package/skills/numa-oss-access/numa-skill.json +2 -2
  73. package/skills/numa-oss-access/references/cli-bootstrap.md +2 -2
  74. package/skills/numa-oss-access/references/search-and-mcp.md +63 -0
  75. package/skills/numa-wecom/SKILL.md +7 -3
  76. package/skills/numa-wecom/numa-skill.json +2 -2
  77. package/skills/numa-wecom/references/cli-bootstrap.md +2 -2
  78. package/skills/numa-wecom/references/mcp.md +12 -0
package/README.md CHANGED
@@ -253,7 +253,7 @@ numa mail archive search kenneth.davis@mail.microconnect.com \
253
253
  | `numa media asset upload` | 需登录 | 流式 multipart 上传资产 |
254
254
  | `numa media library search/inspect/pull/publish` | 需登录 | 查询、锁定、下载和发布素材 |
255
255
  | `numa oss grants/session` | 需登录 | 查看当前身份的 OSS Policy 授权并申请短期 STS(不输出密钥) |
256
- | `numa oss ls/stat/get/put/rm` | 需登录 | 使用内存中的 STS 通过 Aliyun OSS SDK 读写受限 Bucket/Prefix |
256
+ | `numa oss ls/search/stat/get/put/rm` | 需登录 | 使用内存中的 STS 通过 Aliyun OSS SDK 读写受限 Bucket/Prefix |
257
257
  | `numa codex list` | 需登录 | 列出服务端账号元数据,不下载 refresh token |
258
258
  | `numa codex login --assign-to <username>` | 需角色 | 通过一次隔离的官方 Codex 登录,为指定用户补录或替换失效凭据 |
259
259
  | `numa codex admin accounts renew <account> --expires-at <ISO-8601>` | 需角色 | 续订已过期账号;新到期时间必须晚于当前时间和原到期时间 |
@@ -505,11 +505,11 @@ capability 发布且健康的企业 Router,不安装 Nacos 中的下游 endpoi
505
505
  ### 客户端安装适配
506
506
 
507
507
  `numa-cli` 是基础运行与 Skill 发现入口;业务 Skills 通过 `numa-skill.json` 声明同来源、
508
- 精确版本依赖。`skills install --plan` 只读展示完整安装计划;交互终端询问是否立即安装,
509
- JSON/非交互必须在用户同意后提供 `--with-dependencies`。依赖冲突/本地修改不自动覆盖,
508
+ 精确版本依赖。`skills install --plan` 只读展示完整安装计划;安装向导询问是否立即安装,
509
+ 完整参数调用必须在用户同意后提供 `--with-dependencies`。依赖冲突/本地修改不自动覆盖,
510
510
  自动更新不会静默增加依赖。此元数据由 Numa CLI 解析,不代表所有 Agent 原生支持依赖加载。
511
511
 
512
- 用户级 Skills 现在支持自愿开启 `latest` 追踪。首次符合条件的交互安装会询问:仅当前
512
+ 用户级 Skills 现在支持自愿开启 `latest` 追踪。首次符合条件且未固定版本的交互安装会询问:仅当前
513
513
  Skill、全部受管用户级 Skills、或不自动升级且不再询问。脚本可明确指定
514
514
  `--updates current|all|never`;JSON/非交互调用不会询问,也不会默认同意。
515
515
 
@@ -1065,6 +1065,66 @@ numa oss rm 123 projects/old.png --yes --json
1065
1065
  `oss grants` 文本输出显示 Grant ID、Policy、Bucket/Prefix、主体与配置状态;未就绪的
1066
1066
  Grant 不计入就绪数。JSON 字段保持兼容,`oss session` 增加安全的 Actions / Effect 元数据。
1067
1067
 
1068
+ ### OSS 目录与组合检索
1069
+
1070
+ `oss ls` 列出一页对象;`--delimiter /` 将子目录汇总到 `directories`,不递归展开。
1071
+ 使用返回的 `nextMarker` 配合 `--marker` 续页,单页最多 1000 项(对象与目录合计)。
1072
+ 不传 `--fields` 时,`ls --json` 保留原 `listing.objects` 结构,增加 `directories` 与对象存储类型。
1073
+
1074
+ ```bash
1075
+ # Grant ID 和路径按 oss grants 的实际授权选择
1076
+ numa oss ls 123 projects/ --delimiter / --fields key,kind,sizeBytes --format table
1077
+ numa oss ls 123 projects/ --marker projects/last-key --json
1078
+ numa oss search 123 projects/ --directories-only --contains finance
1079
+ numa oss search 123 projects/ --contains report --suffix .pdf --ignore-case \
1080
+ --modified-after 2026-09-01 --modified-before 2026-10-01 \
1081
+ --min-size 1024 --max-size 10485760 --sort modified --order desc \
1082
+ --scan-limit 10000 --limit 50 --fields key,sizeBytes,lastModified --json
1083
+ numa oss search 123 projects/ --meta team=finance --meta status=approved \
1084
+ --content-type application/pdf --fields key,contentType,metadata --format jsonl
1085
+ numa oss search 123 projects/ --suffix .pdf --format keys
1086
+ ```
1087
+
1088
+ `search` 默认递归扫描指定前缀;`--delimiter /` 限制到当前层级,
1089
+ `--directories-only` 只返回这一层的子目录,支持名称筛选与按 key 排序。
1090
+ `--contains`/`--suffix` 匹配完整对象 key,`--ignore-case` 仅影响这两项。
1091
+ `--meta KEY=VALUE` 匹配自定义 `x-oss-meta-*`,可重复,所有条件必须同时满足。
1092
+ 元数据键不区分大小写,值和 Content-Type 精确匹配;仅指定值不是全文或模糊查询。
1093
+ 大小按字节、上下界均包含;日期下界包含、上界不包含。日期格式 `YYYY-MM-DD`
1094
+ 表示 UTC 零点;时间戳需显式提供 `Z` 或时区偏移。
1095
+
1096
+ **创建时间与最后修改时间不同。** OSS 对象列表返回 `lastModified`,不提供可保留的
1097
+ 原始创建时间。只有业务已在自定义元数据中记录创建时间时,才能指定字段检索:
1098
+
1099
+ ```bash
1100
+ numa oss search 123 projects/ --created-time-key created-at \
1101
+ --created-after 2026-01-01 --created-before 2027-01-01 \
1102
+ --sort created --fields key,createdAt --json
1103
+ ```
1104
+
1105
+ 该字段必须符合上述日期格式。缺失或格式无效的创建时间不会通过创建日期筛选,
1106
+ 排序时缺失值放末尾。CLI 不会用 `lastModified` 替代,也不会回填或修改对象元数据。
1107
+
1108
+ `--sort` 支持 `key`、`size`、`modified`、`created`,`--order` 为 `asc`/`desc`。
1109
+ `--fields` 可选 `key,kind,sizeBytes,lastModified,createdAt,contentType,etag,type,storageClass,metadata`。
1110
+ `--format` 支持表格 `table`、带执行摘要的 `json`、逐行对象 `jsonl`、仅 key 的 `keys`。
1111
+ 后两者将扫描摘要写到 stderr,方便 stdout 管道处理。`--json` 不能与非 JSON 的
1112
+ `--format` 同时使用。`ls` 不读取对象头;需要 `metadata`/`contentType` 时使用 `search` 或 `stat --json`。
1113
+ `stat` 从 SDK 的响应头读取标准属性,同时返回自定义 `metadata`;敏感字段继续脱敏。
1114
+
1115
+ **检索是有界扫描,不是服务端全文索引。** `--scan-limit` 默认 1000、最高 100000,
1116
+ `--limit` 默认输出 100、最高 1000。排序只覆盖本次扫描集合;达到扫描上限时返回
1117
+ `complete:false`、`truncated:true` 和 `nextMarker`。`resultsLimited:true` 表示匹配数
1118
+ 超过输出上限;`nextMarker` 继续扫描尚未扫描的范围,不会补回已经扫描但因输出上限省略的匹配项。
1119
+ 不同扫描批次分别排序,不能拼接成全量全局排序结果。需要精确全量排序时,先确认
1120
+ 扫描完成且未截断;更大规模需要另外建设索引。扫描期间对象可能变化,不是时间点快照。
1121
+
1122
+ 普通筛选使用 ListObjects 返回的属性;自定义元数据、Content-Type、创建时间以及
1123
+ 输出 `metadata`/`contentType` 会对候选对象额外执行 HEAD,需要 Grant 的 `oss:GetObject`
1124
+ 权限,并产生额外请求。`headRequests` 显示请求数量。遇到鉴权失败、对象消失、会话过期
1125
+ 或上游错误时整次命令失败,不会把跳过的对象伪装成完整结果。未指定元数据相关选项
1126
+ 时不会逐个 HEAD,更不会下载文件内容。
1127
+
1068
1128
  ### 管理员初始化默认 OSS Role
1069
1129
 
1070
1130
  需要新版 DevOps 后端(V171)及账号已完成系统和共享业务身份初始化。执行前用
@@ -1470,36 +1530,113 @@ numa --json request /api/projects
1470
1530
  numa --json request /api/admin --require-role platform-admin
1471
1531
  ```
1472
1532
 
1473
- ## MCP 兼容
1533
+ ## MCP 服务与 Agent 安装
1474
1534
 
1475
- ```json
1476
- {
1477
- "mcpServers": {
1478
- "numa": {
1479
- "command": "numa",
1480
- "args": ["serve"]
1481
- }
1482
- }
1483
- }
1535
+ ### 模块检索与交互安装(下一版本)
1536
+
1537
+ 在交互终端运行 `numa` 会进入安装向导;`numa mcp`、`numa skills` 分别进入 MCP 与 Skills 入口。
1538
+ `numa mcp install-cli`、`numa mcp install`、`numa skills install` 缺少目标时也会引导选择。
1539
+ 可输入业务关键词选择模块,查看工具和相关 Skills,选择 Agent、范围与身份,核对完整计划后安装。
1540
+ 企业资源向导只展示当前身份可安装的来源;资源搜索匹配当前页,支持翻页与输入精确名称。
1541
+ Skills 只选择已发布版本,计划包含依赖、来源和安装路径。
1542
+
1543
+ ```sh
1544
+ numa
1545
+ numa modules --search 文件 --json
1546
+ numa mcp modules 通知
1547
+ numa mcp install-cli --interactive
1548
+ numa skills install --interactive
1484
1549
  ```
1485
1550
 
1486
- MCP 工具:
1551
+ 参数齐全时使用默认值并直接执行;显式 `--interactive` 可补选其他选项,已给定参数会保留。
1552
+ `--no-interactive` 禁止提示。JSON 或非 TTY 不进入向导,缺少必要参数会报错;Agent/CI 应先用
1553
+ `--plan --json` 核对,执行时给齐参数,Skills 依赖通过 `--with-dependencies` 明确安装。
1554
+ 在有默认 Agent 的 profile 下,非交互 Skills 安装仍可沿用该默认配置。
1555
+
1556
+ `--modules` 决定业务模块;`--tools` 和 `--exclude-tools` 进一步控制实际暴露的工具。
1557
+ 模块检索不启用任何工具。`--server-name` 和 `--server-description` 设置 MCP 自描述,CLI 自动附加
1558
+ 实际模块和工具范围;命令目录也随筛选收窄。向导支持按工具标注选择只读集合。
1487
1559
 
1488
- - `numa_command_catalog`
1489
- - `numa_auth_status`
1490
- - `numa_auth_start_login`
1491
- - `numa_auth_complete_login`
1492
- - `numa_auth_whoami`
1493
- - `numa_auth_logout`
1494
- - `numa_backend_request`
1560
+ ```sh
1561
+ numa mcp tools --modules oss --tools numa_oss_search,numa_oss_stat --json
1562
+ numa mcp install-cli --agent workbuddy --modules oss \
1563
+ --tools numa_oss_search,numa_oss_stat --server-name numa-files \
1564
+ --server-description '查询授权 OSS 文件' --plan --json
1565
+ ```
1566
+
1567
+ MCP 安装支持重复的 `--env NAME=VALUE`,合并到本地服务的启动环境;值可包含 `=` 或为空。
1568
+ 重复变量名会报错。预览隐藏敏感字段,实际值只写入所选客户端配置(Tunnel 写入私有 launcher)。
1569
+ 向导中的环境变量值采用隐藏输入。用户自定义变量不写进全局 shell 配置。
1570
+
1571
+ ```sh
1572
+ numa mcp install-cli --agent workbuddy --modules auth,oss \
1573
+ --env LOG_LEVEL=debug --env 'EXAMPLE_URL=https://example.com?a=b' --plan --json
1574
+ # 确认计划后去掉 --plan 执行;Registry 本地桥接也支持
1575
+ numa mcp install internal-docs --namespace TEAM --agent workbuddy \
1576
+ --transport bridge --env LOG_LEVEL=debug --plan --json
1577
+ ```
1578
+
1579
+ 远程 HTTP endpoint 没有本地进程环境,不接受 `--env`。CLI 服务的身份必须通过 `--auth-mode`、
1580
+ `--client-id`、`--client-secret-file` 设置,不能用自定义环境变量覆盖这四个身份字段。
1581
+ Registry 的自定义环境保存在目标客户端,不进入可共享的 registry manifest/lock;在新机器重建配置时应重新提供。
1582
+
1583
+
1584
+ CLI 1.15.0 支持将 OSS 与企微能力安装为 Agent 的 stdio MCP 服务:
1585
+
1586
+ ```sh
1587
+ numa login
1588
+ numa mcp tools --modules auth,oss,wecom --json
1589
+ numa mcp install-cli --agent workbuddy --scope user --plan --json
1590
+ numa mcp install-cli --agent workbuddy --scope user --json
1591
+ # 其他目标按需选择
1592
+ numa mcp install-cli --agent claude --scope user --plan --json
1593
+ numa mcp install-cli --agent claude-desktop --scope user --plan --json
1594
+ numa mcp install-cli --agent codex --scope user --plan --json
1595
+ ```
1596
+
1597
+ `install-cli` 管理 CLI 自身的工具;现有 `mcp install NAME` / `bridge` 管理企业 Registry 资源。
1598
+ 支持 WorkBuddy、Claude Code、Claude Desktop(macOS/Windows)、Codex、Cursor、OpenClaw、custom 和 ChatGPT。
1599
+ 默认 user scope;Claude/WorkBuddy/Cursor 可显式选择 project。JSON 安装会合并配置、保留备份并拒绝默认覆盖同名服务。
1600
+ 配置固定运行当前 CLI 的精确 npm 版本,升级 CLI 后需要重新更新 Agent 配置才能使用新版 MCP。
1601
+
1602
+ 默认安装 `auth,oss,wecom` 模块,可用 `--modules` 调整;`notion` 与 `platform` 按需加入。
1603
+ `numa serve` 是 stdio 入口,不传模块时保留全部工具以兼容现有配置;stdout 仅用于 MCP 协议。
1604
+ `numa mcp tools` 通过真实 tools/list 列举 schema,不需要调用业务 API。
1605
+
1606
+ | 模块 | 工具 |
1607
+ | --- | --- |
1608
+ | 默认公共工具(也可筛选) | `numa_command_catalog`、`numa_module_catalog` |
1609
+ | auth | `numa_auth_status/start_login/complete_login/whoami/logout` |
1610
+ | oss | `numa_oss_grants/list/search/stat/download/upload/delete` |
1611
+ | wecom | `numa_wecom_capabilities/recipients/recipient_by_email/send/status`、4 个 mappings 工具 |
1612
+ | notion | 现有 Notion Grant、检索及内容工具 |
1613
+ | platform | `numa_backend_request`,服务端执行最终授权 |
1614
+
1615
+ 默认 `--auth-mode browser` 复用同一系统用户的登录缓存,明确清除 MCP 进程继承的机器身份设置。
1616
+ 机器模式指定 `--auth-mode client_credentials --client-id ID --client-secret-file /ABS/SECRET_FILE`,只存文件路径,
1617
+ 不读取或复制 Secret。Issuer/API 继续使用 platform profile 与既有配置优先级,安装器不会写入本地开发地址。
1618
+ 发送消息、上传、下载和删除工具必须基于用户明确授权传 `confirmed:true`;下载/上传的路径属于 MCP 服务机器。
1619
+
1620
+ ChatGPT 可以通过 [官方 Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)
1621
+ 连接本地 stdio。先安装官方 `tunnel-client` 并在 OpenAI Platform 取得当前工作区可用的 Tunnel ID:
1622
+
1623
+ ```sh
1624
+ numa mcp install-cli --agent chatgpt --tunnel-id CONFIRMED_TUNNEL_ID --plan --json
1625
+ numa mcp install-cli --agent chatgpt --tunnel-id CONFIRMED_TUNNEL_ID --json
1626
+ tunnel-client doctor --profile numa-cli --explain
1627
+ tunnel-client run --profile numa-cli
1628
+ ```
1495
1629
 
1496
- `numa_backend_request` 可传 `required_roles` 做 CLI 侧预检查;服务端仍须执行最终授权。
1630
+ 自动初始化支持 macOS/Linux,不创建 API key,不自动运行后台服务。保持 Tunnel 运行后,在 ChatGPT Plugins
1631
+ 创建开发者应用,选择对应 Tunnel 并完成授权与工具审核。`--endpoint` 可为已有 HTTPS MCP 生成连接指引;
1632
+ 不提供 ID 或 endpoint 时返回 `manual_required`,不能视为已安装。完整路径、恢复流程与身份说明见
1633
+ [CLI MCP Skill 指引](skills/numa-cli/references/cli-mcp.md)。
1497
1634
 
1498
1635
  ### 企业微信消息与用户映射
1499
1636
 
1500
1637
  `numa wecom` 通过 DevOps 平台向企业成员发送 text/markdown 消息,CLI 和 MCP 均不接触企微应用密钥。支持明确企微 userid,或通过 Keycloak subject 的邮箱自动/人工映射选择收件人。`numa wecom mappings sync` 默认预览,`--apply --yes` 才应用唯一邮箱映射;冲突保留审核结果,人工映射不会被自动覆盖。
1501
1638
 
1502
- 发送使用 `--text-file`(`-` 表示标准输入)、稳定 `--request-id` 和 `--yes`。结果未知时用 `numa wecom status --request-id ...` 查询,不能换幂等键盲目重发。`numa serve` 提供 8 个相应 MCP 工具,发送工具要求用户明确授权及 `confirmed:true`。完整命令、API 契约与恢复说明见 [企业微信消息指南](docs/wecom-messaging.md)。
1639
+ 发送使用 `--text-file`(`-` 表示标准输入)、稳定 `--request-id` 和 `--yes`。结果未知时用 `numa wecom status --request-id ...` 查询,不能换幂等键盲目重发。`numa serve` 提供 9 个相应 MCP 工具(包括精确邮箱查询和发送),发送工具要求用户明确授权及 `confirmed:true`。完整命令、API 契约与恢复说明见 [企业微信消息指南](docs/wecom-messaging.md)。
1503
1640
 
1504
1641
  ## 云账号与 OSS 初始化
1505
1642
 
@@ -1,2 +1,9 @@
1
+ import { type InstallUi } from "../interactive/prompts.js";
1
2
  import type { Command } from "commander";
2
- export declare function registerAiRegistryCommands(program: Command): void;
3
+ import { AiRegistryClient } from "./client.js";
4
+ export declare function registerAiRegistryCommands(program: Command, dependencies?: {
5
+ ui?: InstallUi;
6
+ tty?: boolean;
7
+ clientFactory?: () => AiRegistryClient;
8
+ initializeProfile?: () => Promise<void>;
9
+ }): void;
@@ -1,3 +1,6 @@
1
+ import { appendEnvironment, collectEnvironment, parseEnvironment, safeMcpOutput } from "../mcp/environment.js";
2
+ import { completeRegistryInstall } from "../interactive/registry.js";
3
+ import { terminalUi, useInteractive, requireInput, confirmInstall, chooseEnvironment } from "../interactive/prompts.js";
1
4
  import { lstat, readFile } from "node:fs/promises";
2
5
  import { resolve } from "node:path";
3
6
  import process from "node:process";
@@ -81,7 +84,7 @@ function itemName(item, fallback) {
81
84
  const value = item.name ?? item.skillName ?? item.mcpName;
82
85
  return typeof value === "string" ? value : fallback;
83
86
  }
84
- export function registerAiRegistryCommands(program) {
87
+ export function registerAiRegistryCommands(program, dependencies = {}) {
85
88
  const registry = program
86
89
  .command("registry")
87
90
  .alias("ai-registry")
@@ -265,7 +268,7 @@ export function registerAiRegistryCommands(program) {
265
268
  const result = await client.getSkill(targetNamespace.id, name);
266
269
  await output(command, { ok: true, namespace: targetNamespace, skill: result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
267
270
  }), ["numa skills inspect release-helper --namespace team-ai --json"]);
268
- addExamples(skills.command("install <name>")
271
+ addExamples(skills.command("install [name]")
269
272
  .option("-n, --namespace <key>", "namespaceKey")
270
273
  .option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
271
274
  .option("-a, --agent <agent>", "覆盖 profile 默认客户端:chatgpt|codex|claude|cursor|workbuddy|openclaw|agents|custom")
@@ -278,18 +281,31 @@ export function registerAiRegistryCommands(program) {
278
281
  .option("--updates <choice>", "current 仅当前 Skill 自动升级;all 全部受管用户级 Skills;never 不自动升级且不再询问")
279
282
  .option("--plan", "仅解析并显示安装及依赖计划,不修改本地安装")
280
283
  .option("--with-dependencies", "明确同意安装计划中的依赖;不覆盖已有冲突依赖")
284
+ .option("--interactive", "交互选择来源、Skill、版本、Agent 和安装范围")
285
+ .option("--no-interactive", "不询问;依赖安装须显式 --with-dependencies")
281
286
  .action(async (name, options, command) => {
287
+ const json = jsonEnabled(command, (await loadAppConfig()).output), ui = dependencies.ui ?? terminalUi;
288
+ const interactive = useInteractive(options, !name || !options.agent || options.agent === "custom" && !options.customRoot, json, dependencies.tty);
289
+ if (!interactive)
290
+ requireInput(name, "缺少 Skill 名称");
291
+ await dependencies.initializeProfile?.();
292
+ const client = dependencies.clientFactory?.() ?? new AiRegistryClient(loadConfig("aiRegistry"));
293
+ if (interactive) {
294
+ const selected = await completeRegistryInstall("skill", name, { ...options, scope: command.getOptionValueSource("scope") === "default" ? undefined : options.scope }, client, ui);
295
+ name = selected.name;
296
+ options = { ...options, ...selected.options, scope: selected.options.scope ?? options.scope };
297
+ }
298
+ requireInput(name, "缺少 Skill 名称");
282
299
  const targets = await resolveSkillInstallTargets(options);
283
300
  if (targets.scope === "project" && options.updates === "current")
284
301
  throw new Error("Project Skills remain locked; use registry upgrade.");
285
- const client = new AiRegistryClient(loadConfig("aiRegistry"));
286
302
  const context = await resolveContext(client, options, "install");
287
303
  const detail = await client.getSkill(context.namespace.id, name);
288
304
  const trackedLabel = options.version ? undefined : options.label ?? "latest";
289
305
  const version = resolveVersion(detail, options.version, trackedLabel, name);
290
306
  const download = await client.downloadSkill(context.namespace.id, name, { version });
291
307
  const graph = await resolveSkillDependencies(client, context.namespace.id, { name, version, download, dependencies: [] });
292
- const dependencies = graph.filter(node => node.name !== name);
308
+ const dependencyNodes = graph.filter(node => node.name !== name);
293
309
  const source = { namespace: context.namespace.namespaceKey, instance: context.namespace.instanceCode,
294
310
  namespaceId: context.namespace.id, instanceId: context.namespace.instanceId };
295
311
  const plan = { source, scope: targets.scope, agents: targets.agents, packages: graph.map(node => ({
@@ -304,22 +320,26 @@ export function registerAiRegistryCommands(program) {
304
320
  const target = { agent, scope: targets.scope, workspace: options.workspace, customRoot: options.customRoot, skillName: name };
305
321
  const rootPath = resolve(skillTargetRoot(target), name);
306
322
  await assertConsumersCompatible(name, version, rootPath);
307
- if (dependencies.length) {
323
+ if (dependencyNodes.length) {
308
324
  const existing = await lstat(rootPath).catch((e) => { if (e.code === "ENOENT")
309
325
  return undefined; throw e; });
310
326
  if (existing && !options.force)
311
327
  throw new AiRegistryError("Root Skill already exists; inspect local changes and explicitly approve --force before installing dependencies.", "ai_registry_install_exists");
312
328
  }
313
- for (const dep of dependencies)
329
+ for (const dep of dependencyNodes)
314
330
  await reusableDependency(dep, target, source);
315
331
  }
316
- if (dependencies.length)
317
- await confirmSkillDependencies(plan, { confirmed: options.withDependencies, json: jsonEnabled(command, (await loadAppConfig()).output) });
332
+ if (interactive) {
333
+ await confirmInstall(ui, { ...plan, overwrite: Boolean(options.force) });
334
+ options.withDependencies = true;
335
+ }
336
+ if (dependencyNodes.length)
337
+ await confirmSkillDependencies(plan, { confirmed: options.withDependencies, json: true });
318
338
  const choice = await chooseSkillUpdates({ choice: options.updates,
319
339
  eligible: targets.scope === "user" && !options.version && (!options.label || options.label === "latest"),
320
- json: jsonEnabled(command, (await loadAppConfig()).output) });
340
+ json: !interactive || json });
321
341
  const identity = await maintenanceIdentity(loadConfig("aiRegistry"));
322
- if (dependencies.length && !identity)
342
+ if (dependencyNodes.length && !identity)
323
343
  throw new AiRegistryError("Dependency provenance needs a verified current identity; login and retry.", "skills_dependency_identity_required");
324
344
  if (choice && !identity)
325
345
  throw new Error("Cannot associate update settings with the current identity; run numa login and retry.");
@@ -328,7 +348,7 @@ export function registerAiRegistryCommands(program) {
328
348
  const dependencyInstallations = [];
329
349
  try {
330
350
  const target = { agent, scope: targets.scope, workspace: options.workspace, customRoot: options.customRoot, skillName: name };
331
- for (const dep of dependencies) {
351
+ for (const dep of dependencyNodes) {
332
352
  const depPath = resolve(skillTargetRoot(target), dep.name);
333
353
  if (await reusableDependency(dep, target, source)) {
334
354
  dependencyInstallations.push({ name: dep.name, version: dep.version, status: "REUSED", path: depPath });
@@ -507,19 +527,44 @@ export function registerAiRegistryCommands(program) {
507
527
  const result = await client.getMcpServer(targetNamespace.id, name, options.version);
508
528
  await output(command, { ok: true, namespace: targetNamespace, mcpServer: result }, JSON.stringify(sanitizeRegistryOutput(result), null, 2));
509
529
  }), ["numa mcp inspect internal-docs --namespace team-ai --json"]);
510
- addExamples(mcp.command("install <name>")
530
+ addExamples(mcp.command("install [name]")
511
531
  .option("-n, --namespace <key>", "namespaceKey")
512
532
  .option("-i, --instance <id-or-code>", "同名 namespace 的实例消歧")
513
- .requiredOption("-a, --agent <agent>", "chatgpt|codex|claude|cursor|workbuddy|openclaw|custom")
533
+ .option("-a, --agent <agent>", "chatgpt|codex|claude|cursor|workbuddy|openclaw|custom")
514
534
  .option("--scope <scope>", "project|user", "project")
515
535
  .option("--version <semver>", "精确版本")
516
536
  .option("--label <label>", "首次安装时解析的标签", "latest")
517
537
  .option("--transport <transport>", "auto|remote|bridge", "auto")
518
538
  .option("--workspace <path>", "项目根目录")
519
539
  .option("--custom-config <path>", "自定义 mcp.json 路径")
540
+ .option("--env <NAME=VALUE>", "追加本地 bridge 的启动环境变量,可重复;远程 HTTP 不支持", collectEnvironment)
520
541
  .option("--force", "备份并替换 JSON 配置中的同名项")
542
+ .option("--plan", "只核对 Router 和目标,输出计划,不写配置")
543
+ .option("--interactive", "交互选择企业 MCP、来源、Agent 和范围")
544
+ .option("--no-interactive", "禁止交互,缺少必要参数时报错")
521
545
  .action(async (name, options, command) => {
522
- const client = new AiRegistryClient(loadConfig("aiRegistry"));
546
+ parseEnvironment(options.env);
547
+ const json = jsonEnabled(command, (await loadAppConfig()).output), ui = dependencies.ui ?? terminalUi;
548
+ const interactive = useInteractive(options, !name || !options.agent || options.agent === "custom" && !options.customConfig, json, dependencies.tty);
549
+ if (!interactive) {
550
+ requireInput(name, "缺少 MCP 名称");
551
+ requireInput(options.agent, "缺少 --agent");
552
+ }
553
+ await dependencies.initializeProfile?.();
554
+ const client = dependencies.clientFactory?.() ?? new AiRegistryClient(loadConfig("aiRegistry"));
555
+ if (interactive) {
556
+ const selected = await completeRegistryInstall("mcp", name, { ...options, scope: command.getOptionValueSource("scope") === "default" ? undefined : options.scope }, client, ui);
557
+ name = selected.name;
558
+ options = { ...options, ...selected.options, scope: selected.options.scope ?? options.scope };
559
+ if (command.getOptionValueSource("transport") === "default") {
560
+ options.transport = options.agent === "chatgpt" ? "remote" : await ui.choose("MCP 连接方式", [
561
+ { value: "remote", label: "远程 HTTP", hint: "由 Agent 连接企业 Router" },
562
+ { value: "bridge", label: "本地 stdio 桥接", hint: "复用 Numa 登录,可追加环境变量" }
563
+ ], options.env?.length ? "bridge" : "remote");
564
+ }
565
+ }
566
+ requireInput(name, "缺少 MCP 名称");
567
+ requireInput(options.agent, "缺少 --agent");
523
568
  const context = await resolveContext(client, options);
524
569
  const trackedLabel = options.version ? undefined : options.label ?? "latest";
525
570
  const detail = await client.getMcpServer(context.namespace.id, name, options.version ? { version: options.version } : { label: trackedLabel });
@@ -533,7 +578,17 @@ export function registerAiRegistryCommands(program) {
533
578
  throw new AiRegistryError(`Namespace ${context.namespace.namespaceKey} has no ready enterprise Router.`, "ai_registry_router_unavailable");
534
579
  }
535
580
  assertRouterHealth(await client.checkRouter(context.namespace.id));
536
- const result = await installMcpClientDefinition(name, routerMcpDefinition(context, entry), {
581
+ if (interactive && entry.transport === "bridge")
582
+ options.env = await chooseEnvironment(ui, options.env);
583
+ const definition = appendEnvironment(routerMcpDefinition(context, entry), options.env);
584
+ const plan = { name, version, namespace: context.namespace.namespaceKey, instance: context.namespace.instanceCode, agent: entry.agent, scope: entry.scope, workspace: options.workspace, customConfig: options.customConfig, overwrite: Boolean(options.force), definition };
585
+ if (options.plan) {
586
+ await output(command, safeMcpOutput({ ok: true, plan }), JSON.stringify(safeMcpOutput(plan), null, 2));
587
+ return;
588
+ }
589
+ if (interactive)
590
+ await confirmInstall(ui, plan);
591
+ const result = await installMcpClientDefinition(name, definition, {
537
592
  agent: entry.agent, scope: entry.scope, workspace: options.workspace,
538
593
  customConfig: options.customConfig, force: options.force
539
594
  });