@luckypiggy/zhihu-ai-cli 0.2.1-test.4 → 0.2.1-test.5

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 CHANGED
@@ -27,19 +27,24 @@
27
27
 
28
28
  当前客户端只访问当前 Profile 的固定 `/api/zhihu/integrations/v1` 路径,拒绝任意 HTTP 重定向和非回环明文 HTTP;Profile 的 CA bundle 用于严格 TLS 校验。macOS 使用 `/usr/bin/security`,Linux 使用固定路径 `/usr/bin/secret-tool`,不提供明文文件 fallback。Refresh rotation 通过 profile 锁和进程内 single-flight 合并,并在 Keychain 写入失败时尝试撤销新会话和清理本机旧值。
29
29
 
30
- 本切片提供数字员工、资产、指标、告警、指定告警诊断、巡检和 A2A 四类只读委派的 `list`/`get`/`query`/`chat`/`diagnose`/`run` 能力,并在包内实现无密钥的外部 `zhihu-aiops` Codex Skill 及其受管安装/卸载,以及公网测试版 `zhihu install` 安装向导。自然语言任务默认通过 `zhihu a2a delegate` 进入 A2A;底层命令仍保留为显式结构化查询和受控诊断入口。CLI/Skill 不提交或猜测 target、source、root、turn、tool、user、tenant 或员工映射。canonical 与离线包的 Skill 使用当前构建提供的本地 `zhihu` runner,不读取本机认证材料,不提供写操作或 MCP;公网测试构建会在隔离 staging 中把 Skill runner 固定为指定 npx 版本。测试版支持客户 URL 和离线交付,正式 GA 与真实客户联调遵循各自发布门禁。安装向导严格验证 `/info` 的服务、协议、版本、能力顺序和终端登录契约,拒绝非回环明文 HTTP、重定向、userinfo、query/fragment、符号链接 CA;Profile 与 root current_profile 采用快照恢复,Skill 安装遵守 foreign/modified/force 语义。指定告警诊断只允许 `cpu`/`memory`/`load`/`io`/`disk`/`online`/`unknown` 确定性分类,巡检只允许固定模型/窗口、四个 Scope、资产/告警各 100 条和五类 VM IP 标签,证据不足时保守返回;两条链路均不会接受客户端 URL、PromQL、任意标签或写操作,不调用 LLM,均为 30 秒总超时且 401 最多刷新一次。CLI 只解析签名 JWT 的非敏感展示字段,不以本地解析结果作为授权决策。Chat conversation 映射当前是服务端单 worker 的有界 TTL 内存状态,同进程并发有锁保护;多 worker/多副本部署需要共享状态存储。
30
+ 本切片提供数字员工、资产、指标、告警、指定告警诊断、巡检和 A2A 四类只读委派的 `list`/`get`/`query`/`chat`/`diagnose`/`run` 能力,并在包内实现无密钥的外部 `zhihu-aiops` Codex Skill 及其受管安装/卸载,以及公网测试版 `zhihu install` 安装向导。自然语言任务默认通过 `zhihu a2a delegate` 进入 A2A;底层命令仍保留为显式结构化查询和受控诊断入口。CLI/Skill 不提交或猜测 target、source、root、turn、tool、user、tenant 或员工映射。canonical 与离线包的 Skill 使用当前构建提供的本地 `zhihu` runner,不读取本机认证材料,不提供写操作或 MCP。公网测试包在 staging 中仍把命令写成精确版本 npx;安装时会把该命令改写为本机持久化 runner,后续调用不再访问 npm registry。测试版支持客户 URL 和离线交付,正式 GA 与真实客户联调遵循各自发布门禁。安装向导严格验证 `/info` 的服务、协议、版本、能力顺序和终端登录契约,拒绝非回环明文 HTTP、重定向、userinfo、query/fragment、符号链接 CA;Profile 与 root current_profile 采用快照恢复,Skill 安装遵守 foreign/modified/force 语义。指定告警诊断只允许 `cpu`/`memory`/`load`/`io`/`disk`/`online`/`unknown` 确定性分类,巡检只允许固定模型/窗口、四个 Scope、资产/告警各 100 条和五类 VM IP 标签,证据不足时保守返回;两条链路均不会接受客户端 URL、PromQL、任意标签或写操作,不调用 LLM,均为 30 秒总超时且 401 最多刷新一次。CLI 只解析签名 JWT 的非敏感展示字段,不以本地解析结果作为授权决策。Chat conversation 映射当前是服务端单 worker 的有界 TTL 内存状态,同进程并发有锁保护;多 worker/多副本部署需要共享状态存储。
31
31
 
32
32
  ## 公网测试版安装
33
33
 
34
- 当前公网测试版命令为:
34
+ 本包身份是 `@luckypiggy/zhihu-ai-cli@0.2.1-test.5`,dist-tag 为 `test`。这是智护 Pilot 的公网测试包,不是 latest,也不是正式 GA。首次入口使用精确版本 npx;安装成功后使用本机持久化 runner。
35
+
36
+
37
+ 当前候选的第一次安装命令为:
35
38
 
36
39
  ```bash
37
- npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 install
40
+ npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install
38
41
  ```
39
42
 
40
- 这是智护 Pilot 的公网测试版(不是正式 GA);`npx` 只临时运行 CLI,安装向导会写入非敏感 Profile 并安装无密钥 Codex Skill,后续 Skill 调用继续使用上面的精确 npx runner。服务地址、权限和凭据仍由用户在安装向导中配置。客户 URL 和离线 `.tgz` 适用于受控网络或隔离环境。
43
+ 这是智护 Pilot 的公网测试包(不是 latest,也不是正式 GA)。上面的精确版本 npx 只负责第一次进入安装向导。安装成功后,Skill 只使用本机持久化 runner。之后即使 registry 不可达,只读调用仍通过这个本地 runner 工作。已经安装的 `0.2.1-test.4` 仍是 npx runner,要等包含本地 runner 的后续安装后才会切换。服务地址、权限和凭据仍由用户在安装向导中配置。客户 URL 和离线 `.tgz` 适用于受控网络或隔离环境。
41
44
 
42
- AI/Codex Skill 调用必须继续使用上面的精确 npx runner;一次 `npx ... install` 只运行临时包,不会把 `zhihu` 命令永久写入 PATH。若用户要手动直接输入 `zhihu`,必须先全局安装同一精确版本:`npm install --global --ignore-scripts @luckypiggy/zhihu-ai-cli@0.2.1-test.4`。离线包则应使用其自身的 `zhihu` 安装方式,不要为了绕过离线约束改用公网 runner。
45
+ 安装完成后,Codex 通过这个本地 Runner 工作。macOS 沙箱第一次执行某条精确只读命令时,可能显示一次权限确认。这次确认只允许同一条白名单命令按原样重试一次,不表示 CLI 读取或上传 Keychain、密码、Token 或验证码;这些材料仍不得进入 Agent 对话。用户拒绝或宿主不支持该确认时,Agent 应停止并说明当前沙箱无法确认登录状态。
46
+
47
+ 一次 `npx ... install` 不会把 `zhihu` 命令永久写入 PATH。若用户要手动直接输入 `zhihu`,必须先全局安装同一精确版本:`npm install --global --ignore-scripts @luckypiggy/zhihu-ai-cli@0.2.1-test.5`。离线包则应使用其自身的 `zhihu` 安装方式,不要为了绕过离线约束改用公网 runner。
43
48
 
44
49
  `install` 的固定顺序是:
45
50
 
@@ -61,9 +66,11 @@ AI/Codex Skill 调用必须继续使用上面的精确 npx runner;一次 `npx
61
66
  | `ZHIHU_CA_BUNDLE` | 客户 CA bundle 绝对路径 |
62
67
  | `ZHIHU_TIMEOUT` | 请求超时秒数(1~300) |
63
68
 
64
- 配置优先级固定为“CLI 参数 > 非空 env > 交互/default”。不支持也不建议 `ZHIHU_ACCESS_TOKEN`、`ZHIHU_USER` 或 `ZHIHU_PASSWORD`;密码和可选验证码只能通过隐藏/终端提示本次传入,令牌只由终端登录接口获得并由系统凭据库保存 Refresh Credential。安装失败或服务尚未配置时可再次运行相同的 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 install` 命令,向导会保留/恢复一致的本地 Profile。npm `install` 没有交互钩子,不会在依赖安装阶段启动向导、读取密码或打开浏览器。
69
+ 配置优先级固定为“CLI 参数 > 非空 env > 交互/default”。不支持也不建议 `ZHIHU_ACCESS_TOKEN`、`ZHIHU_USER` 或 `ZHIHU_PASSWORD`;密码和可选验证码只能通过隐藏/终端提示本次传入,令牌只由终端登录接口获得并由系统凭据库保存 Refresh Credential。安装失败或服务尚未配置时可再次运行相同的 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install` 命令,向导会保留/恢复一致的本地 Profile。npm `install` 没有交互钩子,不会在依赖安装阶段启动向导、读取密码或打开浏览器。
70
+
71
+ 安装完成后请新开一个 Codex 会话,让它重新发现 Skill;可以直接用自然语言尝试“列出当前活动告警”“查看资产 123 最近一小时 CPU 趋势”或“分析告警 456 的根因”。需要排查安装时运行本地持久化 runner 的 `doctor`,该命令只输出脱敏检查结果,不会访问 npm registry。尚未完成安装时,仍使用上面的精确版本 npx 进入安装向导。
65
72
 
66
- 安装完成后请新开一个 Codex 会话,让它重新发现 Skill;可以直接用自然语言尝试“列出当前活动告警”“查看资产 123 最近一小时 CPU 趋势”或“分析告警 456 的根因”。需要排查安装时运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 doctor`,该命令只输出脱敏检查结果。
73
+ 如果第一次安装在本地 runner 生成前失败,错误提示里的恢复命令就是当前这条精确版本命令,不要改成尚未生成的裸 zhihu,也不要改成浮动 tag。先查看状态:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json`。状态为 `held` 时表示可能仍有安装在进行,等待结束后重试。状态为 `stale` 时,使用返回的 fingerprint 显式恢复:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover --confirm recover-stale-runner-lock --fingerprint <fingerprint> --json`,然后再运行原来的安装命令。不要手工删除锁文件。本地 runner 已经安装成功后,诊断和 `doctor` 才改用安装结果里的本地绝对命令,不再指向 npm 上的版本。符号链接、损坏锁、年轻锁和错误 fingerprint 都会拒绝恢复。`doctor` 的 `local_runner_lock` 给出同样的下一步。
67
74
 
68
75
  开发和发布需要 Node.js 22 或更新版本:
69
76
 
@@ -5,13 +5,13 @@ description: 通过智护 Pilot CLI 将自然语言运维问题路由到智护 A
5
5
 
6
6
  # 智护 AI 运维
7
7
 
8
- 本 Skill 只把明确的自然语言问题交给当前构建的本地 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4` runner,并依据单一 JSON 结果给出分析。默认路径是一次 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate`,由 Pilot 服务端的 A2A 注册中心选择专业数字员工;Skill 只提交 capability_id 和用户原始问题,不提交或猜测 employee_id、target、root、turn、tool、user 或 tenant。离线包不会访问公网;公网测试包会在 staging 时将该 runner 固定为指定 npx 版本。先确认用户要看的租户、资产、告警或时间范围,再选择一个最小查询;多个查询之间保留用户给出的精确 ID。
8
+ 本 Skill 只把明确的自然语言问题交给安装时写入的本机持久化 runner,并依据单一 JSON 结果给出分析。默认路径先调用一次 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json`,再调用一次 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate`,由 Pilot 服务端的 A2A 注册中心选择专业数字员工;Skill 只提交 capability_id 和用户原始问题,不提交或猜测 employee_id、target、root、turn、tool、user 或 tenant。安装完成后只执行下面列出的本地命令,不再通过 npx 或 npm registry 重新解析 CLI。先确认用户要看的租户、资产、告警或时间范围,再选择一个最小查询;多个查询之间保留用户给出的精确 ID。
9
9
 
10
10
  ## 工作流程
11
11
 
12
12
  1. 确认目标和范围。缺少资产 ID、告警 ID、模型或时间窗时提问;不要用名称猜测唯一对象。只有用户明确指定可信员工 ID 并要求直接对话时,才使用 `employees chat`。
13
- 2. 只调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 ... --json`,每次调用都等待完整 JSON envelope。不要把诊断参数改写成 URL、查询表达式或任意参数。
14
- 3. 先查认证/能力,再按意图映射到一个只读 capability;认证失败时仅提示用户运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 auth login`,不要要求用户粘贴任何凭据。
13
+ 2. 只调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 ... --json`,每次调用都等待完整 JSON envelope。不要把诊断参数改写成 URL、查询表达式或任意参数。
14
+ 3. 默认 A2A 路径先调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json`,成功后再调用一次对应的 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate`。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth status --json` 和 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 capabilities list --json` 只在用户明确查询登录或能力、或排查错误时使用,不作为每次委派的前置步骤。认证失败时按下面的沙箱权限重试处理,不要要求用户粘贴任何凭据。
15
15
  4. 只依据 `ok`、稳定错误码和 `data` 字段回答。字段缺失、对象不唯一或结果过期时明确说明并停止推断。
16
16
  5. 对告警或巡检结论引用返回的证据、范围和时间窗;建议动作必须保持在只读排查范围内。
17
17
  6. 回复语言跟随当前用户语言;命令和稳定错误码保持原样,解释、确认和结论使用用户选择的中文或英文。
@@ -22,34 +22,56 @@ description: 通过智护 Pilot CLI 将自然语言运维问题路由到智护 A
22
22
 
23
23
  | 用户意图 | capability_id | 默认命令 |
24
24
  | --- | --- | --- |
25
- | 资产、指标、采集状态 | `resource_query` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability resource_query --message <原始问题> --json` |
26
- | 告警、故障、根因和恢复验证建议 | `incident_analysis` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability incident_analysis --message <原始问题> --json` |
27
- | 巡检、健康检查和状态摘要 | `inspection_summary` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability inspection_summary --message <原始问题> --json` |
28
- | 知识、案例、手册和 SOP | `knowledge_qa` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability knowledge_qa --message <原始问题> --json` |
25
+ | 资产、指标、采集状态 | `resource_query` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability resource_query --message <原始问题> --json` |
26
+ | 告警、故障、根因和恢复验证建议 | `incident_analysis` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability incident_analysis --message <原始问题> --json` |
27
+ | 巡检、健康检查和状态摘要 | `inspection_summary` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability inspection_summary --message <原始问题> --json` |
28
+ | 知识、案例、手册和 SOP | `knowledge_qa` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability knowledge_qa --message <原始问题> --json` |
29
29
 
30
- 先调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a capabilities --json` 检查 Pilot 是否声明 A2A;A2A 关闭、能力不可用、歧义或下游失败时报告稳定错误并停止,不静默绕过治理。`asset_onboarding`、`asset_discovery` 等写能力始终禁用。
30
+ 先调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json` 检查 Pilot 是否声明 A2A;A2A 关闭、能力不可用、歧义或下游失败时报告稳定错误并停止,不静默绕过治理。`asset_onboarding`、`asset_discovery` 等写能力始终禁用。
31
+
32
+ ## 沙箱权限重试
33
+
34
+ `AUTH_RELOGIN_REQUIRED` 第一次出现时,不要直接要求重新登录。只有下面六项同时成立,才请求宿主对这一条命令授权,并原样重试一次,且只重试一次:
35
+
36
+ 1. 失败命令是安装后写入的绝对本地 Runner。
37
+ 2. 命令属于本 Skill 明确列出的只读命令。
38
+ 3. 返回的稳定错误码是 `AUTH_RELOGIN_REQUIRED`。
39
+ 4. 宿主提供明确的权限申请机制。
40
+ 5. 用户尚未拒绝。
41
+ 6. 这条完全相同的命令在本次任务中还没有做过权限重试。
42
+
43
+ 原样重试必须保持可执行文件、参数、消息、标识、环境、工作目录和顺序不变,不循环。不得读取或展示 Keychain、Secret Service、Profile、Token、密码或配置。不得把范围扩大到其他命令、任意 shell、直接 API、网络探测或文件读取。宿主已经批准过完全相同的范围时可以复用,不得自行扩大范围。
44
+
45
+ 即使返回认证错误,也不得对下列命令使用权限重试:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth login`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth logout`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 uninstall`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover`、任何未列出的命令、`asset_onboarding`、`asset_discovery`、任何写能力、任意 shell、脚本、直接服务调用、任意 URL,以及 Agent 改过参数的命令。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json` 只属于既有显式锁恢复流程,不属于业务 A2A 权限重试。
46
+
47
+ 用户拒绝、宿主不支持明确权限机制,或命令不在白名单时,停止并说明 Agent 无法在当前沙箱确认登录状态。不得声称凭据已失效。没有权限机制时,可以建议用户在自己的终端核对登录状态,但不要在对话中索取密码、Token 或验证码。
48
+
49
+ 同一条命令第二次仍返回 `AUTH_RELOGIN_REQUIRED` 时,才提示用户在自己的终端重新登录,并使用安装结果给出的登录命令。其他错误码按各自规则处理,不得二次提权,也不得改走直接 API。权限申请失败、取消或超时不得编造查询结果。
50
+
51
+ macOS 上的 Codex 沙箱使用这一次性重试。第一条命令已经成功时,不做权限流程。Linux Secret Service、非沙箱终端或其他 Agent 没有明确权限机制时,按上面的停止规则处理。这不改变 CLI 的凭据存储、Token 有效期或服务端授权。回复语言跟随用户,稳定错误码保持原文。服务端仍负责选择数字员工;不要新增或猜测 employee、target、root、turn、tenant、user 或 tool。
31
52
 
32
53
  ## 可用查询
33
54
 
34
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 auth status --json`
35
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 capabilities list --json`
36
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a capabilities --json`
37
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability <resource_query|incident_analysis|inspection_summary|knowledge_qa> --message <用户原始问题> --json`
38
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a task get --id <opaque-task-id> --json`
39
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 employees list --json`
40
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 assets list --json` 或 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 assets get --id <id> --json`
41
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 metrics query --asset <id|ip> --metric <online|cpu|memory|load|disk|io|snmp> --window <5m..24h> --json`
42
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 alarms list --status <all|firing|resolved> --json` 或 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 alarms get --id <id> --json`
43
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 incidents diagnose --alarm-id <id> --json`
44
- - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 inspections run [--model <name|code|id>] [--window <5m..24h>] --json`
55
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth status --json`
56
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 capabilities list --json`
57
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json`
58
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability <resource_query|incident_analysis|inspection_summary|knowledge_qa> --message <用户原始问题> --json`
59
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a task get --id <opaque-task-id> --json`
60
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 employees list --json`
61
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 assets list --json` 或 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 assets get --id <id> --json`
62
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 metrics query --asset <id|ip> --metric <online|cpu|memory|load|disk|io|snmp> --window <5m..24h> --json`
63
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 alarms list --status <all|firing|resolved> --json` 或 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 alarms get --id <id> --json`
64
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 incidents diagnose --alarm-id <id> --json`
65
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 inspections run [--model <name|code|id>] [--window <5m..24h>] --json`
66
+ - `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json`,以及仅在状态为 stale 时使用的 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover --confirm recover-stale-runner-lock --fingerprint <fingerprint> --json`。不要手工删除锁文件。本地 runner 尚未生成时,重复安装错误里的精确版本命令,不要改用全局 zhihu 或浮动 tag。本地 runner 已经存在后,再使用安装结果里的本地命令。
45
67
 
46
- 筛选、分页、模型和时间窗必须使用 CLI 已声明的取值;不要自行拼接未列出的选项。底层资产、指标、告警、诊断和巡检命令只在用户明确要求原始结构化数据、排查 CLI 本身或 A2A 明确不可用且用户同意受控降级时使用;A2A 错误不得自动降级。员工对话只有在用户明确给出可信员工 ID 和问题时才可使用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 employees chat <employee-id> --message <text> --json`,不得把对话当作默认路由或执行变更的入口。
68
+ 筛选、分页、模型和时间窗必须使用 CLI 已声明的取值;不要自行拼接未列出的选项。底层资产、指标、告警、诊断和巡检命令只在用户明确要求原始结构化数据、排查 CLI 本身或 A2A 明确不可用且用户同意受控降级时使用;A2A 错误不得自动降级。员工对话只有在用户明确给出可信员工 ID 和问题时才可使用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 employees chat <employee-id> --message <text> --json`,不得把对话当作默认路由或执行变更的入口。
47
69
 
48
70
  ## 输出和安全边界
49
71
 
50
72
  - 只展示 CLI envelope 中必要的非敏感字段;不要复述认证材料、配置内容、环境变量或本机凭据。
51
- - 只读命令不等于服务端一定有数据。遇到 `AUTH_RELOGIN_REQUIRED`、`FORBIDDEN`、`CAPABILITY_NOT_AVAILABLE`、`A2A_DISABLED`、`A2A_CAPABILITY_NOT_FOUND`、`A2A_CAPABILITY_DISABLED`、`A2A_TASK_NOT_FOUND`、`A2A_FORBIDDEN`、`A2A_TIMEOUT`、`A2A_EXECUTION_INDETERMINATE`、`ASSET_NOT_FOUND`、`ALARM_NOT_FOUND`、`DATA_SOURCE_UNAVAILABLE`、`TIMEOUT` 或 `PROTOCOL_ERROR`,说明稳定错误码和下一步,不编造结果。
73
+ - 只读命令不等于服务端一定有数据。遇到 `AUTH_RELOGIN_REQUIRED` 时按沙箱权限重试处理,不要在第一次出现时要求重新登录。遇到 `FORBIDDEN`、`CAPABILITY_NOT_AVAILABLE`、`A2A_DISABLED`、`A2A_CAPABILITY_NOT_FOUND`、`A2A_CAPABILITY_DISABLED`、`A2A_TASK_NOT_FOUND`、`A2A_FORBIDDEN`、`A2A_TIMEOUT`、`A2A_EXECUTION_INDETERMINATE`、`ASSET_NOT_FOUND`、`ALARM_NOT_FOUND`、`DATA_SOURCE_UNAVAILABLE`、`TIMEOUT` 或 `PROTOCOL_ERROR`,说明稳定错误码和下一步,不编造结果。
52
74
  - 不直接访问智护服务、监控服务或容器;不执行额外命令,不访问本机配置/凭据存储,不联系外部地址。
53
- - 本 Skill 不提供 MCP、变更、部署或公网包发布能力;`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 install codex` 只安装本地 Skill 文件。
75
+ - 本 Skill 不提供 MCP、变更、部署或公网包发布能力;`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install codex` 只安装本地 Skill 文件。
54
76
 
55
77
  详细命令字段见 [references/commands.md](references/commands.md),错误处理见 [references/errors.md](references/errors.md),边界规则见 [references/safety.md](references/safety.md)。
@@ -1,16 +1,24 @@
1
1
  # CLI 命令参考
2
2
 
3
- 所有调用都必须以 `--json` 结束或等价地启用 JSON 模式;命令使用当前构建提供的本地 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4` runner。离线包不访问公网;公网测试包会在 staging 时替换为精确固定版本。只解析一个完整 envelope:成功看 `ok: true` 与 `data`,失败看 `ok: false`、`error.code`、`error.retryable`。
3
+ 所有调用都必须以 `--json` 结束或等价地启用 JSON 模式;命令使用安装时写入的本机持久化 runner。后续调用直接执行该本地程序,不再通过 npx 访问 npm registry。只解析一个完整 envelope:成功看 `ok: true` 与 `data`,失败看 `ok: false`、`error.code`、`error.retryable`。
4
+
5
+ ## 默认路径与沙箱权限重试
6
+
7
+ 默认 A2A 路径先调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json`,成功后再调用一次 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate`。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth status --json` 和 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 capabilities list --json` 只在用户明确查询登录或能力、或排查错误时使用,不作为每次委派的前置步骤。
8
+
9
+ `AUTH_RELOGIN_REQUIRED` 第一次出现时不要直接要求重新登录。只有绝对本地 Runner、本文件列出的只读命令、该稳定错误码、宿主明确权限机制、用户尚未拒绝,且这条命令尚未重试过,才对同一条命令原样重试,且只重试一次。原样重试保持可执行文件、参数、消息、标识、环境、工作目录和顺序不变。不得读取 Keychain、Secret Service、Profile、Token、密码或配置,不得扩大到任意 shell 或直接 API。
10
+
11
+ 不得对 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth login`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth logout`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 uninstall`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover`、未列出的命令、`asset_onboarding`、`asset_discovery` 或写能力使用权限重试。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json` 不属于业务 A2A 权限重试。用户拒绝或宿主不支持时,说明 Agent 无法在当前沙箱确认登录状态,不得声称凭据已失效。同一条命令第二次仍返回 `AUTH_RELOGIN_REQUIRED` 时,才提示用户在自己的终端重新登录。第一条命令已经成功时,不做权限流程。权限申请失败、取消或超时不得编造结果。
4
12
 
5
13
  ## 会话与能力
6
14
 
7
15
  | 目的 | 命令 |
8
16
  | --- | --- |
9
- | 查看当前登录状态 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 auth status --json` |
10
- | 查看能力范围 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 capabilities list --json` |
11
- | 查看 A2A 只读能力 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a capabilities --json` |
12
- | 查看可用数字员工 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 employees list --json` |
13
- | 发起只读员工问答 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 employees chat <employee-id> --message <text> --json` |
17
+ | 查看当前登录状态 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth status --json` |
18
+ | 查看能力范围 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 capabilities list --json` |
19
+ | 查看 A2A 只读能力 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json` |
20
+ | 查看可用数字员工 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 employees list --json` |
21
+ | 发起只读员工问答 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 employees chat <employee-id> --message <text> --json` |
14
22
 
15
23
  ## 自然语言默认路由
16
24
 
@@ -18,12 +26,12 @@
18
26
 
19
27
  | 用户意图 | capability_id | 命令 |
20
28
  | --- | --- | --- |
21
- | 资产、指标、采集状态 | `resource_query` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability resource_query --message <原始问题> --json` |
22
- | 告警、故障、根因和恢复验证建议 | `incident_analysis` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability incident_analysis --message <原始问题> --json` |
23
- | 巡检、健康检查和状态摘要 | `inspection_summary` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability inspection_summary --message <原始问题> --json` |
24
- | 知识、案例、手册和 SOP | `knowledge_qa` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a delegate --capability knowledge_qa --message <原始问题> --json` |
29
+ | 资产、指标、采集状态 | `resource_query` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability resource_query --message <原始问题> --json` |
30
+ | 告警、故障、根因和恢复验证建议 | `incident_analysis` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability incident_analysis --message <原始问题> --json` |
31
+ | 巡检、健康检查和状态摘要 | `inspection_summary` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability inspection_summary --message <原始问题> --json` |
32
+ | 知识、案例、手册和 SOP | `knowledge_qa` | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate --capability knowledge_qa --message <原始问题> --json` |
25
33
 
26
- 委派返回 opaque `task`、目标员工、状态、追踪字段和只读结论;需要复查时使用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 a2a task get --id <opaque-task-id> --json`。不添加 `--employee`、`--target-agent`、`--source-agent`、`--tool`、`--root-session`、`--tenant` 或 `--user`。
34
+ 委派返回 opaque `task`、目标员工、状态、追踪字段和只读结论;需要复查时使用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a task get --id <opaque-task-id> --json`。不添加 `--employee`、`--target-agent`、`--source-agent`、`--tool`、`--root-session`、`--tenant` 或 `--user`。
27
35
 
28
36
  普通 A2A 问答不要求用户知道员工 ID;只有用户明确要求直接与数字员工对话时,才确认可信员工 ID。`--message` 只描述查询,不描述变更。
29
37
 
@@ -31,17 +39,21 @@
31
39
 
32
40
  | 目的 | 命令 |
33
41
  | --- | --- |
34
- | 查资产 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 assets list [--keyword <text>] [--model <name|code|id>] --json` |
35
- | 取资产 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 assets get --id <id> --json` |
36
- | 查指标 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 metrics query --asset <id|ip> --metric <allowed-name> --window <5m..24h> --json` |
37
- | 列告警 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 alarms list --status <all|firing|resolved> --json` |
38
- | 取告警 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 alarms get --id <id> --json` |
42
+ | 查资产 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 assets list [--keyword <text>] [--model <name|code|id>] --json` |
43
+ | 取资产 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 assets get --id <id> --json` |
44
+ | 查指标 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 metrics query --asset <id|ip> --metric <allowed-name> --window <5m..24h> --json` |
45
+ | 列告警 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 alarms list --status <all|firing|resolved> --json` |
46
+ | 取告警 | `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 alarms get --id <id> --json` |
39
47
 
40
48
  资产和告警 ID 必须来自用户或此前 CLI 返回;IP、指标和窗口必须满足 CLI 的白名单与上限。底层查询命令是显式结构化数据入口,不是自然语言任务的默认路径。
41
49
 
50
+ ## 安装锁
51
+
52
+ 安装异常退出后不要手工删除锁文件。先运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json`。`held` 表示安装可能仍在进行,等待后重试。只有 `stale` 才使用 status 返回的 fingerprint 运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover --confirm recover-stale-runner-lock --fingerprint <fingerprint> --json`,成功后再重试安装。本地 runner 尚未生成时,重复安装错误里的精确版本命令,不要改用全局 zhihu 或浮动 tag。本地 runner 已经存在后,再使用本地命令。
53
+
42
54
  ## 诊断与巡检
43
55
 
44
- - 指定告警分析:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 incidents diagnose --alarm-id <id> --json`
45
- - 只读巡检摘要:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 inspections run [--model <name|code|id>] [--window <5m..24h>] --json`
56
+ - 指定告警分析:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 incidents diagnose --alarm-id <id> --json`
57
+ - 只读巡检摘要:`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 inspections run [--model <name|code|id>] [--window <5m..24h>] --json`
46
58
 
47
59
  诊断和巡检结果是保守的结构化摘要。证据不足时报告不足,不补全设备、租户、权限或因果关系。
@@ -4,11 +4,12 @@
4
4
 
5
5
  常见处理:
6
6
 
7
- - `AUTH_RELOGIN_REQUIRED`:提示用户运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4 auth login`,然后重试原查询。
7
+ - `AUTH_RELOGIN_REQUIRED`:不要把第一次出现直接解释成登录失效,也不要直接要求重新登录。只有绝对本地 Runner、Skill 只读白名单、该稳定错误码、宿主明确权限机制、用户尚未拒绝、且这条命令尚未重试过六项同时成立时,才对同一条命令原样重试,且只重试一次。原样重试保持可执行文件、参数、消息、标识、环境、工作目录和顺序不变。不得读取 Keychain、Secret Service、Profile、Token、密码或配置。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth login`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth logout`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 uninstall`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover`、白名单外命令、`asset_onboarding`、`asset_discovery`、写能力、任意 shell 和直接 API 都不得使用该机制。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json` 不属于业务 A2A 权限重试。用户拒绝或宿主不支持时,说明当前沙箱无法确认登录状态,不得声称凭据已失效;可以建议用户在自己的终端核对登录状态。同一条命令第二次仍返回 `AUTH_RELOGIN_REQUIRED` 时,才提示用户在自己的终端重新登录,并使用安装结果中的登录命令;不要索取密码、Token 或验证码。其他错误码不得二次提权。权限申请失败、取消或超时不得编造结果,也不得改走直接 API。第一条命令已经成功时,不做权限流程。
8
8
  - `FORBIDDEN`、`CAPABILITY_NOT_AVAILABLE`:说明当前账号没有所需范围,请用户联系管理员;不要尝试其他身份。
9
9
  - `EMPLOYEE_NOT_FOUND`、`ASSET_NOT_FOUND`、`ALARM_NOT_FOUND`:让用户确认精确 ID;不要从相似名称猜测。
10
10
  - `INCIDENT_ASSET_IDENTITY_UNAVAILABLE`、`ASSET_METRIC_IDENTITY_UNAVAILABLE`、`METRIC_DATA_NOT_FOUND`:报告目标无法安全关联或没有数据。
11
11
  - `DATA_SOURCE_UNAVAILABLE`、`TIMEOUT`、`NETWORK_ERROR`、`RATE_LIMITED`:保留原查询边界,按 `retryable` 决定是否建议稍后重试。
12
12
  - `INVALID_ARGUMENT`、`PROTOCOL_ERROR`、`VERSION_INCOMPATIBLE`:说明 CLI 参数或版本问题,先停止分析。
13
+ - `LOCAL_RUNNER_LOCK_TIMEOUT`:安装锁超时。运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json`。状态为 `held` 时不要恢复,等待安装结束后重试。状态为 `stale` 时,使用返回的 fingerprint 运行 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover --confirm recover-stale-runner-lock --fingerprint <fingerprint> --json`,然后再重试安装。不要手工删除锁文件。本地 runner 尚未生成时,重复安装错误里的精确版本命令,不要改用全局 zhihu 或浮动 tag。本地 runner 已经存在后,再使用本地命令。符号链接、损坏锁和错误 fingerprint 必须保持失败。
13
14
 
14
15
  认证、权限、数据源和协议错误都不能被改写成“正常”或“已修复”。
@@ -1,6 +1,6 @@
1
1
  # 安全边界
2
2
 
3
- - 唯一入口是当前构建提供的本地 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.4` runner 的 JSON 命令。离线包不会访问公网;公网测试包会在 staging 时固定 runner。不要从本机配置、环境变量、凭据服务或日志读取认证材料。
3
+ - 唯一入口是安装时写入的本机持久化 runner 的 JSON 命令,例如 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth status --json`。后续调用直接执行该本地程序,不再通过 npx 访问 npm registry。不要从本机配置、环境变量、凭据服务或日志读取认证材料。
4
4
  - 不要直接访问智护 API、监控后端、容器地址或任意网络地址;不要改写请求地址、查询表达式、标签、租户或权限。
5
5
  - 不执行额外系统命令,不操作主机、文件、进程、网络设备或部署;不要把 CLI 结果发送给外部服务。
6
6
  - 只读范围包括认证状态、能力、A2A 四类只读委派、数字员工、资产、指标、告警、指定告警诊断和巡检。任何创建、删除、更新、重启、发布或变更请求都应拒绝并说明当前能力限制;`asset_onboarding`、`asset_discovery` 等写 capability 不可调用。
@@ -8,4 +8,9 @@
8
8
  - 用户意图映射固定为 `resource_query`(资产/指标/采集)、`incident_analysis`(告警/故障/根因)、`inspection_summary`(巡检/健康摘要)和 `knowledge_qa`(知识/案例/SOP)。命中多个意图、缺少必要对象或包含“查询并处理”等写/歧义请求时先澄清。
9
9
  - 用户没有给出唯一目标时先询问:资产 ID、告警 ID、模型、指标、窗口和语言都不能凭空补全;只有用户明确要求直接员工对话时才询问员工 ID。
10
10
  - 结果只保留回答所需的字段。不要输出本机路径、内部地址、完整请求、会话材料或未脱敏正文。
11
- - 这是只读 Skill,不宣称 MCP、变更能力或公网包发布能力;离线构建使用本地 runner,公网测试构建使用固定版本 runner。
11
+ - 安装锁异常退出后,只通过 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json` 和 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover --confirm recover-stale-runner-lock --fingerprint <fingerprint> --json` 处理。不要手工删除锁文件,不要在 `held`、符号链接、损坏锁或 fingerprint 不一致时继续恢复。本地 runner 尚未生成时,重复安装错误里的精确版本命令,不要改用全局 zhihu 或浮动 tag。本地 runner 已经存在后,再使用本地命令。恢复成功后再重试原来的安装命令。
12
+ - 默认 A2A 路径先调用 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a capabilities --json`,成功后再调用一次 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 a2a delegate`。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth status --json` 和 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 capabilities list --json` 只在用户明确查询登录或能力、或排查错误时使用,不作为每次委派的前置步骤。
13
+ - `AUTH_RELOGIN_REQUIRED` 只有在绝对本地 Runner、只读白名单、该稳定错误码、明确权限机制、用户尚未拒绝且尚未重试过时,才允许原样重试,且只重试一次。原样重试保持可执行文件、参数、消息、标识、环境、工作目录和顺序不变。不得读取 Keychain、Secret Service、Profile、Token、密码或配置。
14
+ - 不得对 `npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth login`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 auth logout`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 install`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 uninstall`、`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock recover`、`asset_onboarding`、`asset_discovery`、写能力、任意 shell 或直接 API 使用权限重试。`npx --yes @luckypiggy/zhihu-ai-cli@0.2.1-test.5 runner lock status --json` 不属于业务 A2A 权限重试。
15
+ - 用户拒绝、宿主不支持或命令不在白名单时,停止并说明 Agent 无法在当前沙箱确认登录状态,不得声称凭据已失效。同一条命令第二次仍返回 `AUTH_RELOGIN_REQUIRED` 时,才提示用户在自己的终端重新登录。第一条命令已经成功时,不做权限流程。权限申请失败、取消或超时不得编造结果,也不得改走直接 API。
16
+ - 这是只读 Skill,不宣称 MCP、变更能力或公网包发布能力。安装后的命令指向本机持久化 runner,不要改回 npx、npm 或其他未列出的入口。
package/dist/cli.js CHANGED
@@ -3,7 +3,8 @@ import { isIP } from "node:net";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { CLI_VERSION, CLI_PACKAGE_NAME, MIN_NODE_MAJOR, } from "./contracts.js";
6
- import { CLI_DOCTOR_COMMAND, CLI_RUNNER } from "./release.js";
6
+ import { CLI_RUNNER } from "./release.js";
7
+ import { existingLocalRunnerCommand, inspectLocalRunner, inspectLocalRunnerLock, LocalRunnerError, recoverLocalRunnerLock, } from "./local-runner.js";
7
8
  import { ConfigError, DEFAULT_PROFILE, DEFAULT_PROFILE_NAME, checkProfile, configRoot, guardProfileConnectionChange, initializeProfile, ProfileConnectionGuardError, readConfigSnapshot, readProfileRaw, validateProfile, } from "./config.js";
8
9
  import { withProfileLock } from "./profile-lock.js";
9
10
  import { emitError, emitSuccess } from "./output.js";
@@ -277,9 +278,22 @@ export async function doctorData(environment = process.env, options = {}) {
277
278
  };
278
279
  const runtime = { ok: runtimeMajor >= MIN_NODE_MAJOR, node_version: process.versions.node, minimum_node_major: MIN_NODE_MAJOR };
279
280
  const codexCheck = { ok: codex.state === "managed", state: codex.state, ...(codex.target ? { target: codex.target } : {}) };
281
+ const runnerInspection = inspectLocalRunner(environment);
282
+ const lockInspection = inspectLocalRunnerLock(environment);
283
+ const localRunnerLock = {
284
+ ok: lockInspection.state === "missing",
285
+ state: lockInspection.state,
286
+ ...(lockInspection.pid !== undefined ? { pid: lockInspection.pid } : {}),
287
+ ...(lockInspection.age_ms !== undefined ? { age_ms: lockInspection.age_ms } : {}),
288
+ ...(lockInspection.fingerprint ? { fingerprint: lockInspection.fingerprint } : {}),
289
+ ...(lockInspection.detail ? { detail: lockInspection.detail } : {}),
290
+ };
280
291
  return {
281
292
  cli_version: CLI_VERSION,
282
293
  stage: "p1-readonly",
294
+ local_runner: runnerInspection.command,
295
+ local_runner_health: runnerInspection.health,
296
+ local_runner_lock: localRunnerLock,
283
297
  checks: {
284
298
  runtime,
285
299
  config: configCheckResult,
@@ -288,6 +302,7 @@ export async function doctorData(environment = process.env, options = {}) {
288
302
  authentication,
289
303
  capabilities: capabilityCheck,
290
304
  codex: codexCheck,
305
+ local_runner_lock: localRunnerLock,
291
306
  // Compatibility aliases retained for existing automation; the stable
292
307
  // Stage5B contract is the seven checks above.
293
308
  base_url: { ok: profileReady, state: profileReady ? "configured" : "not_configured" },
@@ -1063,7 +1078,11 @@ async function installCommand(flags, json) {
1063
1078
  process.removeListener("SIGTERM", onSignal);
1064
1079
  }
1065
1080
  }
1081
+ function displayedRunner() {
1082
+ return existingLocalRunnerCommand(process.env) ?? CLI_RUNNER;
1083
+ }
1066
1084
  export function installHumanSummary(data) {
1085
+ const runner = displayedRunner();
1067
1086
  const zh = data.language === "zh";
1068
1087
  const profile = typeof data.profile === "string" ? data.profile : "-";
1069
1088
  const baseUrl = typeof data.base_url === "string" ? data.base_url : "-";
@@ -1090,8 +1109,8 @@ export function installHumanSummary(data) {
1090
1109
  "- 用智护列出当前活动告警。",
1091
1110
  "- 用智护查看资产 123 最近一小时 CPU 趋势。",
1092
1111
  "- 用智护分析告警 456 的根因。",
1093
- `排查安装:${CLI_DOCTOR_COMMAND}`,
1094
- `Skill runner:${CLI_RUNNER}`,
1112
+ `排查安装:${runner} doctor`,
1113
+ `Skill runner:${runner}`,
1095
1114
  ].join("\n");
1096
1115
  }
1097
1116
  return [
@@ -1105,10 +1124,52 @@ export function installHumanSummary(data) {
1105
1124
  "- List the current firing alarms.",
1106
1125
  "- Show the last hour of CPU trend for asset 123.",
1107
1126
  "- Diagnose the root cause of alarm 456.",
1108
- `Troubleshoot installation: ${CLI_DOCTOR_COMMAND}`,
1109
- `Skill runner: ${CLI_RUNNER}`,
1127
+ `Troubleshoot installation: ${runner} doctor`,
1128
+ `Skill runner: ${runner}`,
1110
1129
  ].join("\n");
1111
1130
  }
1131
+ function runnerLockCommand(command, flags) {
1132
+ const [, second, third] = command;
1133
+ if (second !== "lock" || command.length !== 3 || (third !== "status" && third !== "recover")) {
1134
+ invalid("Use runner lock status or runner lock recover");
1135
+ }
1136
+ if (third === "status") {
1137
+ for (const key of flags.keys())
1138
+ invalid(`Unknown runner option: --${key}`);
1139
+ const inspection = inspectLocalRunnerLock(process.env);
1140
+ return {
1141
+ state: inspection.state,
1142
+ ...(inspection.pid !== undefined ? { pid: inspection.pid } : {}),
1143
+ ...(inspection.age_ms !== undefined ? { age_ms: inspection.age_ms } : {}),
1144
+ ...(inspection.fingerprint ? { fingerprint: inspection.fingerprint } : {}),
1145
+ ...(inspection.detail ? { detail: inspection.detail } : {}),
1146
+ };
1147
+ }
1148
+ for (const key of flags.keys()) {
1149
+ if (key !== "confirm" && key !== "fingerprint")
1150
+ invalid(`Unknown runner option: --${key}`);
1151
+ }
1152
+ const confirm = flags.get("confirm");
1153
+ const fingerprint = flags.get("fingerprint");
1154
+ if (typeof confirm !== "string" || typeof fingerprint !== "string") {
1155
+ invalid("runner lock recover requires --confirm and --fingerprint");
1156
+ }
1157
+ try {
1158
+ recoverLocalRunnerLock(process.env, { confirm, fingerprint });
1159
+ }
1160
+ catch (error) {
1161
+ if (error instanceof LocalRunnerError) {
1162
+ throw new CliFailure({
1163
+ code: error.code,
1164
+ message: error.message,
1165
+ retryable: error.code === "LOCAL_RUNNER_LOCK_TIMEOUT",
1166
+ exit_code: 2,
1167
+ });
1168
+ }
1169
+ throw error;
1170
+ }
1171
+ return { state: "recovered" };
1172
+ }
1112
1173
  function helpData() {
1113
1174
  return {
1114
1175
  commands: [
@@ -1135,6 +1196,8 @@ function helpData() {
1135
1196
  "install [--profile <name>] [--base-url <https-url>] [--timeout N] [--language zh-CN|en] [--ca-file <absolute-file>] [--codex-skill-dir <absolute-path>] [--skip-login] [--skip-codex] [--force]",
1136
1197
  "install codex [--profile <name>] [--codex-skill-dir <absolute-path>] [--force]",
1137
1198
  "uninstall codex [--profile <name>] [--codex-skill-dir <absolute-path>] [--force]",
1199
+ "runner lock status",
1200
+ "runner lock recover --confirm recover-stale-runner-lock --fingerprint <fingerprint>",
1138
1201
  ],
1139
1202
  json: "Append --json for the stable success/error envelope",
1140
1203
  note: "Authentication plus read-only employee, asset, metric, alarm, incident diagnosis, inspection queries, local installation, and the Codex Skill installer are available; login is terminal-only and passwords are never accepted through flags or environment variables; formal GA, write operations, and MCP are not available",
@@ -1163,6 +1226,8 @@ async function run(parsed) {
1163
1226
  return { data: showConfig(parsed.flags), exit_code: 0 };
1164
1227
  if (first === "config" && second === "init")
1165
1228
  return { data: await configInit(parsed.flags), exit_code: 0 };
1229
+ if (first === "runner")
1230
+ return { data: runnerLockCommand(parsed.command, parsed.flags), exit_code: 0 };
1166
1231
  if (first === "install") {
1167
1232
  if (!second && parsed.command.length === 1) {
1168
1233
  const data = await installCommand(parsed.flags, parsed.json);
@@ -3,7 +3,8 @@ import { createHash, randomBytes } from "node:crypto";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, isAbsolute, join, parse, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { CLI_PACKAGE_NAME, CLI_VERSION, } from "./contracts.js";
6
+ import { CLI_PACKAGE_NAME, CLI_RUNNER, CLI_VERSION, } from "./contracts.js";
7
+ import { LocalRunnerError, persistLocalCliRunner, retargetPackagedRunner, runnerCommandToken } from "./local-runner.js";
7
8
  import { ConfigError, readConfigSnapshot, validateProfile, } from "./config.js";
8
9
  export const CODEX_SKILL_NAME = "zhihu-aiops";
9
10
  export const CODEX_SKILL_VERSION = CLI_VERSION;
@@ -431,12 +432,58 @@ function inspectTarget(target) {
431
432
  localError("CODEX_SKILL_FOREIGN", "Codex Skill target is not a directory");
432
433
  return "directory";
433
434
  }
435
+ function installedSkillFiles(environment) {
436
+ let command;
437
+ try {
438
+ command = runnerCommandToken(persistLocalCliRunner(environment));
439
+ }
440
+ catch (error) {
441
+ if (error instanceof CodexSkillError)
442
+ throw error;
443
+ if (error instanceof LocalRunnerError && error.code === "LOCAL_RUNNER_SYMLINK") {
444
+ localError("CODEX_SKILL_SYMLINK", "Local CLI runner path is a symbolic link");
445
+ }
446
+ if (error instanceof LocalRunnerError && error.code === "LOCAL_RUNNER_UNSAFE_PATH") {
447
+ localError("CODEX_SKILL_PATH_INVALID", "Local CLI runner path cannot be embedded in a command");
448
+ }
449
+ if (error instanceof LocalRunnerError && error.code === "LOCAL_RUNNER_LOCK_TIMEOUT") {
450
+ localError("LOCAL_RUNNER_LOCK_TIMEOUT", error.message);
451
+ }
452
+ localError("CODEX_SKILL_IO_ERROR", "Local CLI runner could not be installed");
453
+ }
454
+ const files = sourceFiles();
455
+ let replaced = false;
456
+ for (const file of CODEX_SKILL_FILES) {
457
+ const current = files.get(file);
458
+ if (!current)
459
+ localError("CODEX_SKILL_IO_ERROR", "Codex Skill assets are incomplete");
460
+ const text = current.toString("utf8");
461
+ const packagedRunner = CLI_RUNNER;
462
+ const next = retargetPackagedRunner(text, packagedRunner, command);
463
+ if (next !== text)
464
+ replaced = true;
465
+ if (packagedRunner !== "zhihu" && next.includes(packagedRunner)) {
466
+ localError("CODEX_SKILL_IO_ERROR", "Installed Skill still references the packaged runner");
467
+ }
468
+ files.set(file, Buffer.from(next, "utf8"));
469
+ }
470
+ if (!replaced)
471
+ localError("CODEX_SKILL_IO_ERROR", "Codex Skill assets do not contain the packaged runner");
472
+ return files;
473
+ }
434
474
  export function installCodexSkill(options = {}) {
435
475
  const target = resolveCodexSkillTarget(options);
436
- const files = sourceFiles();
437
- const marker = markerFor(files);
438
476
  ensureParent(target);
439
477
  const state = inspectTarget(target);
478
+ const existingMarker = state === "missing" ? undefined : readMarker(target);
479
+ if (state !== "missing" && !existingMarker)
480
+ localError("CODEX_SKILL_FOREIGN", "Codex Skill target is not managed by this CLI");
481
+ const managedState = existingMarker ? regularManagedState(target, existingMarker) : undefined;
482
+ if (managedState === "modified" && !options.force) {
483
+ localError("CODEX_SKILL_MODIFIED", "Codex Skill files were modified; use --force to replace them");
484
+ }
485
+ const files = installedSkillFiles(options.environment ?? process.env);
486
+ const marker = markerFor(files);
440
487
  if (state === "missing") {
441
488
  const temporary = temporaryDirectory(dirname(target), CODEX_SKILL_NAME);
442
489
  try {
@@ -449,13 +496,8 @@ export function installCodexSkill(options = {}) {
449
496
  }
450
497
  return { action: "install", status: "installed", skill: CODEX_SKILL_NAME, version: CODEX_SKILL_VERSION, target };
451
498
  }
452
- const existingMarker = readMarker(target);
453
499
  if (!existingMarker)
454
500
  localError("CODEX_SKILL_FOREIGN", "Codex Skill target is not managed by this CLI");
455
- const managedState = regularManagedState(target, existingMarker);
456
- if (managedState === "modified" && !options.force) {
457
- localError("CODEX_SKILL_MODIFIED", "Codex Skill files were modified; use --force to replace them");
458
- }
459
501
  const sourceMatches = existingMarker.version === CODEX_SKILL_VERSION
460
502
  && CODEX_SKILL_FILES.every((file) => existingMarker.files[file] === marker.files[file]);
461
503
  if (managedState === "complete" && sourceMatches) {
@@ -20,9 +20,20 @@ function safeProfile(profile) {
20
20
  const MAC_SECURITY_EXECUTABLE = "/usr/bin/security";
21
21
  const MAC_EXPECT_EXECUTABLE = "/usr/bin/expect";
22
22
  const MAC_EXPECT_FAILURE = 70;
23
- const MAC_EXPECT_TIMEOUT = 124;
23
+ /** Reserved exit status of the fixed macOS Expect helper. Other children may also exit 124. */
24
+ export const MAC_EXPECT_TIMEOUT = 124;
25
+ /** Read, delete, availability, and Linux writes keep the original five-second bound. */
26
+ export const CREDENTIAL_COMMAND_TIMEOUT_MS = 5000;
27
+ /**
28
+ * Bounded Expect wait for the whole macOS Keychain handshake. Both prompts
29
+ * share this clock because exp_continue does not restart it.
30
+ */
31
+ export const MAC_KEYCHAIN_WRITE_EXPECT_TIMEOUT_SECONDS = 15;
32
+ /** Lets Expect exit and the child be reaped after its own timeout. */
33
+ export const MAC_KEYCHAIN_WRITE_CLEANUP_MARGIN_MS = 2000;
34
+ export const MAC_KEYCHAIN_WRITE_PROCESS_TIMEOUT_MS = MAC_KEYCHAIN_WRITE_EXPECT_TIMEOUT_SECONDS * 1000 + MAC_KEYCHAIN_WRITE_CLEANUP_MARGIN_MS;
24
35
  const MAC_EXPECT_SCRIPT_BODY = `
25
- set timeout 5
36
+ set timeout ${MAC_KEYCHAIN_WRITE_EXPECT_TIMEOUT_SECONDS}
26
37
  log_user 0
27
38
  if {[gets stdin secret] < 0} { exit ${MAC_EXPECT_FAILURE} }
28
39
  set prompt_count 0
@@ -32,13 +43,13 @@ expect {
32
43
  if {$prompt_count != 0} { exit ${MAC_EXPECT_FAILURE} }
33
44
  set prompt_count 1
34
45
  send -- "$secret\\r"
35
- exp_continue
46
+ exp_continue -continue_timer
36
47
  }
37
48
  "retype password for new item:" {
38
49
  if {$prompt_count != 1} { exit ${MAC_EXPECT_FAILURE} }
39
50
  set prompt_count 2
40
51
  send -- "$secret\\r"
41
- exp_continue
52
+ exp_continue -continue_timer
42
53
  }
43
54
  timeout { exit ${MAC_EXPECT_TIMEOUT} }
44
55
  eof {}
@@ -153,8 +164,8 @@ export const processCommandRunner = (command, args, options = {}) => new Promise
153
164
  const failUnavailable = (message) => {
154
165
  if (settled)
155
166
  return;
156
- terminateChild();
157
167
  settled = true;
168
+ terminateChild();
158
169
  clearMainTimer();
159
170
  reject(new CredentialStoreError("CREDENTIAL_STORE_UNAVAILABLE", message));
160
171
  };
@@ -189,7 +200,14 @@ export const processCommandRunner = (command, args, options = {}) => new Promise
189
200
  // that exited during the handshake cannot become an uncaught error.
190
201
  child.stdin.on("error", () => failUnavailable("System credential store is unavailable"));
191
202
  child.on("error", () => failUnavailable("System credential store is unavailable"));
192
- child.once("close", (code) => finish({ code: code ?? 1, stdout, stderr }));
203
+ child.once("close", (code) => {
204
+ const status = code ?? 1;
205
+ if (useMacSecurityExpect && status === MAC_EXPECT_TIMEOUT) {
206
+ failUnavailable("System credential store timed out");
207
+ return;
208
+ }
209
+ finish({ code: status, stdout, stderr });
210
+ });
193
211
  if (options.timeoutMs) {
194
212
  timer = setTimeout(() => {
195
213
  failUnavailable("System credential store timed out");
@@ -238,7 +256,7 @@ export class SecureCredentialStore {
238
256
  async available() {
239
257
  try {
240
258
  const command = this.command();
241
- const result = await this.runner(command.executable, command.checkArgs, { timeoutMs: 5000 });
259
+ const result = await this.runner(command.executable, command.checkArgs, { timeoutMs: CREDENTIAL_COMMAND_TIMEOUT_MS });
242
260
  if (result.code !== 0)
243
261
  throw new CredentialStoreError("CREDENTIAL_STORE_UNAVAILABLE", "System credential store is unavailable");
244
262
  return true;
@@ -252,7 +270,7 @@ export class SecureCredentialStore {
252
270
  async get() {
253
271
  try {
254
272
  const command = this.command();
255
- const result = await this.runner(command.executable, command.readArgs, { timeoutMs: 5000 });
273
+ const result = await this.runner(command.executable, command.readArgs, { timeoutMs: CREDENTIAL_COMMAND_TIMEOUT_MS });
256
274
  if (result.code !== 0) {
257
275
  const diagnostic = result.stderr.toLowerCase();
258
276
  if (missingResult(result) || diagnostic.includes("could not be found") || diagnostic.includes("no such secret") || diagnostic.includes("not found"))
@@ -282,7 +300,9 @@ export class SecureCredentialStore {
282
300
  // fixed macOS Expect helper, which answers both security prompts in
283
301
  // the child PTY without placing the secret in argv or the environment.
284
302
  input: `${credential}\n`,
285
- timeoutMs: 5000,
303
+ timeoutMs: this.platform === "darwin"
304
+ ? MAC_KEYCHAIN_WRITE_PROCESS_TIMEOUT_MS
305
+ : CREDENTIAL_COMMAND_TIMEOUT_MS,
286
306
  });
287
307
  if (result.code !== 0)
288
308
  throw new CredentialStoreError("CREDENTIAL_STORE_ERROR", "Unable to write system credential");
@@ -296,7 +316,7 @@ export class SecureCredentialStore {
296
316
  async delete() {
297
317
  try {
298
318
  const command = this.command();
299
- const result = await this.runner(command.executable, command.deleteArgs, { timeoutMs: 5000 });
319
+ const result = await this.runner(command.executable, command.deleteArgs, { timeoutMs: CREDENTIAL_COMMAND_TIMEOUT_MS });
300
320
  const diagnostic = result.stderr.toLowerCase();
301
321
  if (result.code !== 0 && !missingResult(result) && !diagnostic.includes("could not be found") && !diagnostic.includes("no such secret") && !diagnostic.includes("not found")) {
302
322
  throw new CredentialStoreError("CREDENTIAL_STORE_ERROR", "Unable to delete system credential");