@yijiancaotang/cli 1.1.0 → 1.2.1

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
@@ -1,6 +1,8 @@
1
1
  # 一间草堂命令行
2
2
 
3
- 一间草堂 CLI 1.1.0 待发布,随产品 1.1.0 统一交付;实际发布后可更新。npm 包名为 `@yijiancaotang/cli`,命令为 `caotang`。
3
+ 通用接手的默认JSON供Agent读取完整有界投影;`--human`只供人便读,省略详细规矩/记录,不作为Agent已取得完整上下文的证明。
4
+
5
+ 一间草堂 CLI,版本独立于网页发布。npm 包名为 `@yijiancaotang/cli`,命令为 `caotang`。
4
6
 
5
7
  ## 接入
6
8
 
@@ -18,7 +20,7 @@ caotang --agent "Codex" doctor
18
20
  caotang --agent "Codex" projects
19
21
  ```
20
22
 
21
- 同一电脑的不同 AI 分别在网页批准一次;再次接入只复用自己的连接。已知平台安装的技能会固定带上本次名字,不把一把钥匙发给多个 AI。`--platform` 只选一个已知平台安装技能;名单外 AI 不装技能,按 setup 返回的通用命令说明使用。确认接好后提示:“开个新对话,说‘开工了’。”
23
+ 同一电脑的不同 AI 分别在网页批准一次;再次接入只复用自己的连接。已知平台安装的技能会固定带上本次名字,不把一把钥匙发给多个 AI。`--platform`只选一个已知平台;名单外Skill宿主可用互斥的`--skills-dir`显式父目录,HOME外共享/远程位置先导出干净ZIP再按宿主实际入口导入。无Skill但能执行CLI仍可用完整业务。当前分支的新说明/问法是候选,安装、正文取得与行为验证分开,不强制所有宿主新对话或重启。
22
24
 
23
25
  同名复用会返回 `reused_existing: true`、`connection_name`、`connection_name_source` 和 `next_action`;首次新批准为 `false`。给用户的一句是“这个名字已经接过了,沿用原来的连接,不用再批准。”,同时必须转述“如果你不是它,请用你自己的名字重新运行 setup --agent <你的名字>,不要继续用这条连接。”旧版本没有可靠名称记录时只报本机规范化名字并说明来源;只有实际设备授权记录提供的时间才返回 `connected_at`,不从文件时间猜。新批准的说明另存于当前环境、当前名字、当前连接的非密记录,不改变原凭据格式;本机名称不代表网页后来改过的标签。
24
26
 
@@ -26,13 +28,13 @@ caotang --agent "Codex" projects
26
28
 
27
29
  ### 更新到这版说明
28
30
 
29
- MCP 接入随服务器部署生效。命令行接入在 CLI 1.1.0 发布后,让原来的 AI 重跑:
31
+ MCP 接入随服务器部署生效。命令行接入让原来的 AI 运行:
30
32
 
31
33
  ```sh
32
34
  npx @yijiancaotang/cli@latest setup --agent "原来的名字"
33
35
  ```
34
36
 
35
- 保留原来选择的环境和必要目录参数;同名有效连接不再批准。已核验的标准 CLI 1.0.0 和本名字六技能可在原位置更新为 1.1.0 的七项技能,保留旧程序包;个人改动、未知版本或不安全文件会拒绝覆盖,不自动删除或强制修复。旧自定义目录若不再提供原参数,不能靠扫描整台电脑找回。
37
+ 保留原来选择的环境和必要目录参数;同名有效连接不再批准。已核验的标准 CLI 1.0.0(六项技能)或 1.1.0(七项技能)可在原位置安全升级,保留旧程序包;个人改动、未知版本或不安全文件会拒绝覆盖,不自动删除或强制修复。旧自定义目录若不再提供原参数,不能靠扫描整台电脑找回。
36
38
 
37
39
  本机连接按环境和 AI 名字分开保存。现有旧文件和验证接口没有可靠平台名,旧共用钥匙归“命令行”,仅显式 `setup/auth login --agent 命令行` 时懒迁移;原文件不改写、不删除,其他 AI 不读取它、各自接入。网页吊销一条连接或当前名字运行 logout 不影响其他 AI。
38
40
 
@@ -53,7 +55,7 @@ npm run test:agent-cli
53
55
 
54
56
  ### 按需加载
55
57
 
56
- 每个新对话第一件工作前(改报价、写邮件、写代码都算),即使未提草堂也主动短读一次;闲聊和纯问答不读。只选 `caotang-context`,已有可信短读就复用,不为开工加载第二技能。接入同意/拒绝门优先,不靠用户说“开工了”触发。第一条命令直接短读,不先运行 `command -v`、`env`、`auth status` 或 `ToolSearch`;出错后才排查。沿用接入时用户已选环境,正式版默认生产,不重新确认环境、不自行切换;明确使用其他环境时保留本次已选参数。
58
+ 新工作对话/恢复按有效授权与本次任务范围纯读接手;闲聊和纯问答不读。先取得同源`caotang guide`公共短合同,当前会话已有有效正文就复用,不把清单描述当已加载。只选`caotang-context`,已有可信短读就复用,不重复读取或加载第二个开工技能。接入同意/拒绝门优先,项目请求先定向查询,整体查看才用`context --handoff`;只有明确要求记录开始/开工时间才用`context --start`。公共准备与同意完成后第一条私人读取按本次任务定向执行,不先运行`command -v`、`env`、`auth status`或`ToolSearch`;出错后才排查。沿用用户已选环境,不重新确认环境、不自行切换;新协议不支持不回退start。
57
59
 
58
60
  若本人明确限定只用 caotang 命令,就不使用 MCP;不改配置,误调用其他入口即停。
59
61
 
@@ -78,12 +80,22 @@ npm run test:agent-cli
78
80
 
79
81
  本机安装不等于真实 AI 的业务验收,也不证明输入/输出 Token 消耗。AGENTS.md、CLAUDE.md 等常驻用户配置不写;如需另行修改,须取得本人明确范围许可。
80
82
 
83
+ ## 自然任务与读取范围
84
+
85
+ “看看XXX项目/继续这个项目”是本次该项目的必要读取意图;已知ID先定向读取项目、相关进展和待办,未知ID只作必要定位,不先拉全局摘要,不泛问是否读取。“看看草堂/整体安排”才在有效同意和权限范围读工作摘要;账号或范围有歧义才核对。只有本人明确要求记录开始/开工时间才选start(CLI `context --start` / MCP `get_start_context`),普通查看、恢复或开始处理任务不记录开工。公共guide是Agent准备,不要求人说“开工了”或背命令/触发词。
86
+
81
87
  ## 安装与诊断
82
88
 
83
89
  | 命令 | 行为 |
84
90
  | --- | --- |
85
- | setup --agent 名字 [--platform 平台] [--no-open] | 独立接入该 AI;platform 只给一个已知平台安装技能;无名字提示下一步,不申请连接。 |
86
- | doctor --agent 名字 [--platform 平台] | 只读检查该 AI 的技能、独立连接和实时有效性,不安装、不启动 AI 客户端。 |
91
+ | setup --agent 名字 [--platform 平台 或 --skills-dir 绝对父目录] [--no-open] | 独立接入该 AI;platform 只给一个已知平台安装技能;无名字提示下一步,不申请连接。 |
92
+ | doctor --agent 名字 [--platform 平台 或 --skills-dir 绝对父目录] | 只读检查该 AI 的技能、独立连接和实时有效性,不安装、不启动 AI 客户端。 |
93
+ | guide | 无需身份的离线公共操作说明;没有Skill的Agent也先取得同源规则。 |
94
+ | context --handoff [--project UUID] | 有权且已有任务/同意时读取完整有界工作包;不记录开工。 |
95
+ | skills export --output 新ZIP路径 | 离线导出官方七项技能和说明,不含私人数据、拒绝覆盖。 |
96
+ | project bind UUID [--include-subdirs] | 先预览目录/实际服务/账号/目标;本人确认后才加--confirm DIGEST --local-workspace保存本机关联。 |
97
+ | project binding --local-workspace | 仅在本人已确认独立本机范围时读取有效定位;本轮显式项目优先。 |
98
+ | project unbind --local-workspace [--confirm DIGEST] | 先预览,再按确认解除精确目录关联;不删除或归档草堂项目。 |
87
99
  | version / --version | 离线返回包/Node版本及 `release_status=released`,不读取凭据、不构造网络客户端 |
88
100
  | update | 只返回“运行 `npx @yijiancaotang/cli@latest setup --agent <你的名字>` 更新到最新版。”;不查 registry、不下载、不自动升级。 |
89
101
 
@@ -93,7 +105,7 @@ npm run test:agent-cli
93
105
  本机锁只面向同一主机/PID命名空间的本地常规文件系统,不支持共享网络盘。固定单文件追加申请/释放记录,按先后顺序仅首个未释放的活跃或未知持有者占有;只跳过已确认死亡者,不删除、改名、截断或调用外部程序。没有16次崩溃或64KiB准入耗尽;文件数量固定,但日志字节和读取工作量随使用增长,不声称磁盘体积恒定。保留旧协议标记,活跃、未知或不安全旧标记继续拒绝;不能与旧改名/压缩协议进程并发混跑。未知旧目录/损坏记录不自动修复。文件系统的保护规则可能阻止本地安装,请按实际诊断核对。
94
106
 
95
107
  支持 Claude Code、Codex、OpenClaw;`kimi-code`、`kimi-work`、`workbuddy` 仅支持macOS本机安装。平台检测只检查程序元数据和已核实的本名字技能,不运行客户端;`kimi-code`可检测PATH中的 `kimi` 或桌面bundle,`kimi-work`与`workbuddy`不支持PATH命令检测,不能把 `kimi` 当作工作模式。每次只选一个 `--platform`,非macOS拒绝这三项;安装只面向当前名字对应的平台。已核实安装过的平台算检测到,不靠宿主 App 猜。未选新平台的技能根异常不得阻断旧平台。
96
- 六项 canonical 技能(开工续接、沉淀知识、项目进展、查找、完成收工、首次接入)与manifest继续作为共用来源。各平台沿用相同的按需加载和安全确认边界,不改 MCP 配置,不提供卸载命令。
108
+ 七项canonical技能(接手续接、沉淀知识、项目进展、查找、完成收工、首次接入、日程待办)与manifest继续作为共用来源。各平台沿用相同的按需加载和安全确认边界,不改 MCP 配置,不提供卸载命令。
97
109
 
98
110
  | 平台 | 用户级技能目标 |
99
111
  | --- | --- |
@@ -119,27 +131,27 @@ doctor的JSON位于`data`:`ready`、`issues[]`、`next_actions[]`、`command`
119
131
 
120
132
  ### 接入对话
121
133
 
122
- 首次按当前 AI 是否第一次安装技能判断;新授权不等于首次,同一个 AI 已有连接但首次安装技能时仍可能是首次。`data.onboarding.newly_connected_platforms` 仅列本次首次接好的入口,不包括已装重复 setup、恢复或更新。核对当前 AI 的入口,不能把其他 AI 的首次状态套过来。认证和 ready 原字段保留,开场字段不是接入成功证明。
134
+ ### 候选guidance:人的经验、Agent准备与内容同意分开
123
135
 
124
- `data.account_state` 只含 `empty|has_content|unknown`,不带任何内容,不为判断空账号先读工作台;`data.onboarding.action` 为 `introduce|ask_once|none`。技能转述 `data.user_message` 的 CLI 固定人话,不贴字段名或账号 ID,包括 `auth.status`、`read_only`、`ready` 等,不让模型编造状态;用户问细节时才给必要说明。缺字段、矛盾或入口不明时不猜、不自动读写。
136
+ 本分支新增`data.guidance.schema_version=1`,只含公共元数据与同源说明索引,不请求私人内容、不造教程标记。`user_experience`保持unknown/not_recorded,人的首次使用只据本人明确意图;新安装、新token、空号或连接数都不能推导。`agent_preparation`提供合同版本/SHA及`caotang guide`,正文取得证据unknown,不能由CLI自报模型ready。
125
137
 
126
- CLI 给用户看的话以统一接入提示为准,只含固定文案、已验证的 URL、确认码,以及沿用连接名称和有据时间;技能和接入页用同样的说法,不各写一套。不主动介绍功能或堆卖点;用户问“能做什么”时才说明。AI 可按语境调整,但不能变长,不能贴字段。introduce 保留为动作标记,不要求功能介绍;下表只说明行为边界,短句按统一文案输出。
138
+ 认证和ready原字段保留。`account_state`的empty、has_content、unknown,以及`onboarding.action`的introduce、ask_once、none和`onboarding.newly_connected_platforms`继续原枚举/含义;新授权不是人的首次标记,旧introduce不重定义为教程完成。旧`user_message`/`onboarding_message`固定文案保留兼容,但新Skill同一问题只说一次,不各问一遍。`next_conversation`/`skill_activation`是旧接续资料,不据此强制新对话或开工。
139
+
140
+ | 当前场景 | 新Skill处理 |
141
+ | --- | --- |
142
+ | 本人明确首次使用/主动问用法 | 先简短介绍六类用途及适用例子,目录取同源guide;具体执行邀请按有效能力过滤。 |
143
+ | 老用户换Agent | 新Agent仍先取得说明,核自己的连接/权限/内容同意;已有明确任务直接做,不重播教程。 |
144
+ | 日常查看/恢复 | 复用有效说明;项目任务定向查,整体查看才读摘要,普通查看不记时间。 |
145
+ | 重复setup/升级 | 只报沿用及安装状态,不自动读工作包、写开工或再发批准码。 |
127
146
 
128
- | 场景 | 状态 / 动作 | 对话行为 |
129
- | --- | --- | --- |
130
- | 首次且空 | empty / introduce | 有写权限才用短句问是否保存;同意整理后只用当前对话拟私有条目,具体条目明确同意才写,没有素材不凑数。 |
131
- | 首次有内容 | has_content / ask_once | 问一次是否读工作台,说明内容会发给当前 AI 服务,同意后才读。 |
132
- | 首次未知 | unknown / ask_once | 不说账号空;同样问一次并说明内容会发给当前 AI 服务,同意后才读。 |
133
- | 已装重复 setup | 任意 / none | 说明沿用的名字、有据时间及“如果你不是它……”的下一步,不介绍、不问、不读;环境覆盖单独说明。 |
134
- | 日常新工作对话 | 不在接入对话 | 维持一次短读与按模式简报。 |
147
+ 未经同意不读,接入同意门优先;不同意、沉默或含糊不读,本对话不再问,不换搜索、详情、MCP或会话绕过。已有明确且可核同范围有效授权沿用。短读范围有限,只含规矩、有效待确认和近期记录;未查全量项目、知识和历史,不能推断全账号为空。只看可信account_state或account_empty,缺权、null和空数组不是空,不主动建议清理。
135
148
 
136
- 接入对话优先于 context 的自动短读;workday不承担开工。不同意、沉默或含糊不读,本对话不再问,不换搜索/详情/MCP绕过。本人之后主动明确要求读取时,只按新授权范围操作,不能当写入同意。短读只含规矩、有效待确认和有限近期记录,不含全量项目、知识、历史;只看可信 `account_state` 或 `account_empty` 判断空,不由空数组、缺权或部分结果推断全账号空,不主动建议清理。
149
+ read_only只能读,不能承诺写或发起写类待确认;只读不提保存,也不问“要存几条吗”。RW也不等于细分能力全开:guidance账号开关和有效细权未知就保unknown,不邀请未核写入。本人要求时可留对话草稿;改权限要在网页重新批准一条连接,账号开关到Agent 管理核对读写权限,由本人处理。只读不能靠反复auth login升级。有效连接运行auth login会复用、不新签,不能把auth login说成重新授权或升级权限的办法。
137
150
 
138
- `read_only` 只能读,不能承诺写或发写类申请;只读不提保存,也不问“要存几条吗”,按统一文案的只读分支说明。本人要求时可留对话内草稿。只读需要改权限时说“改权限要在网页重新批准一条连接。”账号开关仍须到 Agent 管理核对读写权限,由本人处理,不自动更换连接;只读不能靠反复 `auth login` 升级。不能把 `auth login` 说成重新授权或升级权限的办法。不换工具、代签或自行扩大权限。
151
+ 连接结果按已核名称和来源解释,本机名字不代表网页改名;缺时间不编。已复用且是自己连接时,用户问怎么批准只报沿用,不再发批准链接或码;“如果你不是它”时停止借用。用户摘要不贴字段名或账号ID,用户问细节时才给必要说明。确认码过期、拒绝/取消准确说明;authorization_delivery_unknown请本人在Agent 管理核对,确需本机查询才用caotang auth status。auth logout只断开当前名字的连接,不影响其他 AI,先说明影响并获本人同意。
139
152
 
140
- 已复用本机同名连接,用户问“怎么批准”时,说明沿用的名字并转述“如果你不是它……”的下一步;不发批准链接或码,不要求为原名字再次 login。已有有效连接再次运行 `auth login` 时只会复用、不新签。若不是该名字所指的 AI,停止借用,由它以自己的名字接入并由本人批准。
153
+ 候选文案仅供本人预览;功能指引只在本人明确首次或主动问用法时给,不主动推销或每次长播。只转述已验证URL、确认码、名称和有据时间,不伪造示例码。安装、可发现、正文实际获得、行为验证分别取证;不由安装成功推定全部技能生效,不按品牌承诺新对话一定加载,不让AI自动重启。现有六安装入口是回归样本,不是通用范围白名单。
141
154
 
142
- Codex、Claude Code 已证实新对话能读取技能;不由安装成功推定全部技能已生效或真实读写全部通过。OpenClaw、Kimi Code、Kimi.app 工作模式、WorkBuddy 保守提示“如果看不到技能,开新对话或重启应用”,由本人操作,不能要求 AI 自动重启。
143
155
 
144
156
  ## 授权与输出
145
157
 
@@ -173,7 +185,7 @@ login 只走浏览器确认码,不接收密码。stderr 按行输出安全JSON
173
185
 
174
186
  - `caotang --agent "Codex" context --start` 做一次短读,不与旧 `context --since` 筛选混用。始终沿用当前账号/环境。除规矩外,每个段落受自己原有的读取权限控制;最近 **三个有记录的北京时间日期**而非最近三个日历日,每日最多 3 条、每条摘录最多 120 个字符;等待本人确认的申请最多 3 条。只在确切可见的本人项目进展中取最新的独立 `【下一步】` 标记。隐藏、不可用和 0 分开解释,不扩大读取。
175
187
  - 本人的有效令牌(包括只读)可 `caotang --agent "Codex" rules` / MCP `get_my_rules`,开工也能读到;**没有 Agent 写规矩的命令、工具或待确认申请**。增删改仅在网页“我的规矩”,最多 20 条,每条 200 个 Unicode 字符;不要填写任何秘密。规矩是偏好,不是授权。
176
- - CLI 只按环境、已核账号及北京时间日期决定简报长短,不记业务内容、对话或授权证据。缺少可信状态/访问失败时保守用简短模式;MCP `get_start_context` 一律简短,并提示“要详细就说一声”。用户说这次别问就不再提;长期不汇报/不询问可由本人写进规矩。敏感信息先去除,不自动重新授权/安装启动钩子。
188
+ - CLI 只按环境、已核账号及北京时间日期决定简报长短,不记业务内容、对话或授权证据。缺少可信状态/访问失败时保守用简短模式;仅要求记录开始时间才用MCP `get_start_context`,一律简短,并提示“要详细就说一声”。用户说这次别问就不再提;长期不汇报/不询问可由本人写进规矩。敏感信息先去除,不自动重新授权/安装启动钩子。
177
189
  - `caotang-context` 独占开工/续接短读,短读不足且任务确需知识时才检索复用结果/开花卡,引用真实环境出处。`caotang-workday` 只在完成重要工作或收工时整理候选,让本人挑要记录的项目进展/知识/认知,明确同意才写。独立进展正文 `【下一步】具体动作` 仅保存本人同意的下一步;未确认写入保留去敏建议,不自动重试。
178
190
  - `caotang-first-connect` 按上方接入对话规则分流,不看是否新授权、不预读内容判空。MCP 无可靠首接标记,仅本人明确说刚接入时问一次并说明内容去向;不查回收站、本机文件或其它会话。七项技能安装/核验不代表真实模型已完成验收。
179
191
 
package/dist/cli.js CHANGED
@@ -9,11 +9,14 @@ import { CredentialStore } from "./credential-store.js";
9
9
  import { executeCommand } from "./commands.js";
10
10
  import { startReportMode } from "./habit-day.js";
11
11
  import { KNOWLEDGE_GROWTH_HELP } from "./generated/knowledge-growth.js";
12
+ import { getAgentOperations } from "./generated/agent-operations.js";
13
+ import { exportSkillsBundle } from "./skills-export.js";
14
+ import { executeProjectBindingCLI, isProjectBindingCommand, projectBindingLocalScopeOutcome } from "./project-binding-cli.js";
12
15
  import { resolveConfig } from "./config.js";
13
16
  import { CliError } from "./errors.js";
14
17
  import { ApiClient } from "./http.js";
15
18
  import { explicitUnknownWrite, redact, renderError, renderResult } from "./output.js";
16
- import { localDoctor, localSetup, localVersion, manualUpdateInstructions, selectedPlatforms } from "./local-commands.js";
19
+ import { localDoctor, localSetup, localVersion, manualUpdateInstructions, selectedPlatforms, selectedSkillsDirectory } from "./local-commands.js";
17
20
  import { commandHelp } from "./command-help.js";
18
21
  import { scheduleCommandUsages } from "./schedule-help.js";
19
22
  import { calendarCommandUsages } from "./calendar-help.js";
@@ -24,7 +27,7 @@ const help = {
24
27
  usage: "caotang --agent <你的名字> <command> [--env production|test] [--human]",
25
28
  write_routing: WRITE_ROUTING_HELP,
26
29
  advance_example: { command: "caotang advance CARD_ID --input -", input: { internalization_status: "applied", source_type: "project", source_id: "PROJECT_ID", evidence_summary: "经本人确认,这张卡实际用于该项目的登录回归检查。" } },
27
- commands: ["setup [--platform claude-code,codex,openclaw,kimi-code,kimi-work,workbuddy] [--no-open]", "doctor [--platform claude-code,codex,openclaw,kimi-code,kimi-work,workbuddy]", "version", "update", "auth login [--device-name name] [--client-name name] [--no-open]", "auth status", "auth logout", "context [--since cursor]", "context --start", "rules", "activity --start-date YYYY-MM-DD --end-date YYYY-MM-DD [--limit N --offset N]", "projects", "project <id>", "project create --input file|-", "project update <id> --input file|-", "progress list <id>", "checkins", "progress add <id> --input file|- [--idempotency-key key]", "cognition set <id> --input file|-", "stage set <id> --input file|-", "knowledge", "knowledge show <id>", "search [query]", "knowledge add --input file|-", "knowledge update <id> --input file|-", "source add <id> --input file|-", "link <card> <project> [--core]", "unlink <card> <project>", "advance <card> --input file|-", "upload <pdf> --card <id> [--source-title title]", "pending", "notifications [--filter all|unread|read|agent]", "drafts", "request delete --input file|-", "request archive --input file|-", "request visibility --input file|-"],
30
+ commands: ["setup [--platform claude-code,codex,openclaw,kimi-code,kimi-work,workbuddy] [--no-open]", "setup --skills-dir /absolute/parent [--no-open]", "doctor [--platform claude-code,codex,openclaw,kimi-code,kimi-work,workbuddy]", "doctor --skills-dir /absolute/parent", "skills export --output file.zip", "version", "update", "guide", "auth login [--device-name name] [--client-name name] [--no-open]", "auth status", "auth logout", "context [--since cursor]", "context --handoff", "context --start", "rules", "activity --start-date YYYY-MM-DD --end-date YYYY-MM-DD [--limit N --offset N]", "projects", "project <id>", "project create --input file|-", "project update <id> --input file|-", "progress list <id>", "checkins", "progress add <id> --input file|- [--idempotency-key key]", "cognition set <id> --input file|-", "stage set <id> --input file|-", "knowledge", "knowledge show <id>", "search [query]", "knowledge add --input file|-", "knowledge update <id> --input file|-", "source add <id> --input file|-", "link <card> <project> [--core]", "unlink <card> <project>", "advance <card> --input file|-", "upload <pdf> --card <id> [--source-title title]", "pending", "notifications [--filter all|unread|read|agent]", "drafts", "request delete --input file|-", "request archive --input file|-", "request visibility --input file|-"],
28
31
  platform_scope: "kimi-code、kimi-work(Kimi.app工作模式)和workbuddy仅支持macOS本机安装;目录发现不代表真实AI端到端通过。Kimi Code和Kimi工作模式是独立入口,不互相代替。",
29
32
  options: ["--limit", "--offset", "--q", "--project", "--topic", "--status", "--publication-status", "--start-date YYYY-MM-DD", "--end-date YYYY-MM-DD", "--date-field created_at|updated_at", "--since", "notifications: --filter", "upload: --source-title / --summary"],
30
33
  command_help: "每个命令用 --help 查看自己的字段和固定 JSON 示例;写入 JSON 支持 --input -。knowledge 不带参数是知识列表;记录一件事完成了,用 progress add。source add 的 source_title 选填;progress add 的 visibility 省略时默认 private。",
@@ -41,10 +44,12 @@ const help = {
41
44
  const usageError = () => new CliError("invalid_arguments", "命令或参数无效。", "运行 --help,写入数据使用 --input file 或 --input -。");
42
45
  help.commands.push(...scheduleCommandUsages);
43
46
  help.commands.push(...calendarCommandUsages);
47
+ help.commands.push("project bind <id> [--include-subdirs] [--confirm DIGEST --local-workspace]", "project binding --local-workspace", "project unbind --local-workspace [--confirm DIGEST]");
44
48
  const parserOptions = {
45
- agent: { type: "string" }, env: { type: "string" }, human: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" }, platform: { type: "string" },
49
+ agent: { type: "string" }, env: { type: "string" }, human: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" }, platform: { type: "string" }, "skills-dir": { type: "string" }, output: { type: "string" },
50
+ confirm: { type: "string" }, "local-workspace": { type: "boolean" }, "include-subdirs": { type: "boolean" },
46
51
  input: { type: "string" }, limit: { type: "string" }, offset: { type: "string" }, q: { type: "string" }, project: { type: "string" }, topic: { type: "string" }, status: { type: "string" }, "publication-status": { type: "string" }, since: { type: "string" }, filter: { type: "string" },
47
- start: { type: "boolean" }, "start-date": { type: "string" }, "end-date": { type: "string" }, "date-field": { type: "string" }, "core-only": { type: "boolean" },
52
+ start: { type: "boolean" }, handoff: { type: "boolean" }, "start-date": { type: "string" }, "end-date": { type: "string" }, "date-field": { type: "string" }, "core-only": { type: "boolean" },
48
53
  "idempotency-key": { type: "string" }, card: { type: "string" }, "source-title": { type: "string" }, summary: { type: "string" }, core: { type: "boolean" },
49
54
  "device-name": { type: "string" }, "client-name": { type: "string" }, "no-open": { type: "boolean" },
50
55
  view: { type: "string" }, "group-by": { type: "string" }, revision: { type: "string" },
@@ -177,14 +182,35 @@ export async function runCLI(args, options = {}) {
177
182
  throw usageError();
178
183
  positionals = ["version"];
179
184
  }
180
- if (!["setup", "doctor", "version", "update", "auth", "context", "rules", "activity", "projects", "project", "progress", "checkins", "cognition", "stage", "knowledge", "search", "source", "link", "unlink", "advance", "upload", "pending", "notifications", "drafts", "request", "schedule", "calendar", "timeline", "daily-summary"].includes(positionals[0] ?? ""))
185
+ if (!["setup", "doctor", "version", "update", "guide", "skills", "auth", "context", "rules", "activity", "projects", "project", "progress", "checkins", "cognition", "stage", "knowledge", "search", "source", "link", "unlink", "advance", "upload", "pending", "notifications", "drafts", "request", "schedule", "calendar", "timeline", "daily-summary"].includes(positionals[0] ?? ""))
181
186
  throw usageError();
182
187
  validateCommandArguments(positionals, flags);
188
+ if (positionals[0] === "guide") {
189
+ if (parsed.values.env !== undefined && !["production", "test"].includes(parsed.values.env)) {
190
+ throw new CliError("invalid_environment", "环境只能是 production 或 test。", "默认使用生产环境;测试时指定 --env test。");
191
+ }
192
+ if (parsed.values.agent !== undefined)
193
+ agentIdentity(parsed.values.agent);
194
+ stdout(renderResult({ status: 200, body: getAgentOperations() }, human, []));
195
+ return 0;
196
+ }
197
+ if (positionals[0] === "skills") {
198
+ if (positionals.length !== 2 || positionals[1] !== "export" || typeof flags.output !== "string" || !flags.output.trim()
199
+ || Object.keys(flags).some(key => key !== "output"))
200
+ throw usageError();
201
+ if (parsed.values.env !== undefined && !["production", "test"].includes(parsed.values.env))
202
+ throw new CliError("invalid_environment", "环境只能是 production 或 test。", "公共导出无需连接任何环境。");
203
+ if (parsed.values.agent !== undefined)
204
+ agentIdentity(parsed.values.agent);
205
+ stdout(renderResult(await exportSkillsBundle({ outputPath: flags.output, packageRoot: options.packageRoot }), human, []));
206
+ return 0;
207
+ }
183
208
  const localCommand = ["setup", "doctor", "version", "update"].includes(positionals[0]);
184
209
  if (localCommand) {
185
- const allowed = positionals[0] === "setup" ? ["platform", "device-name", "client-name", "no-open"] : positionals[0] === "doctor" ? ["platform"] : [];
210
+ const allowed = positionals[0] === "setup" ? ["platform", "skills-dir", "device-name", "client-name", "no-open"] : positionals[0] === "doctor" ? ["platform", "skills-dir"] : [];
186
211
  if (positionals.length !== 1 || Object.keys(flags).some(name => !allowed.includes(name)))
187
212
  throw usageError();
213
+ selectedSkillsDirectory(flags);
188
214
  selectedPlatforms(flags.platform, options.operatingSystem);
189
215
  }
190
216
  else if (flags.platform !== undefined)
@@ -204,6 +230,13 @@ export async function runCLI(args, options = {}) {
204
230
  return 0;
205
231
  }
206
232
  const agent = agentIdentity(parsed.values.agent);
233
+ if (isProjectBindingCommand(positionals)) {
234
+ const missingScope = projectBindingLocalScopeOutcome(positionals, flags);
235
+ if (missingScope) {
236
+ stdout(renderResult(missingScope.result, human, []));
237
+ return missingScope.exitCode;
238
+ }
239
+ }
207
240
  const env = options.env ?? process.env, homeDir = options.homeDir ?? homedir();
208
241
  let config, configurationFailure;
209
242
  try {
@@ -238,8 +271,14 @@ export async function runCLI(args, options = {}) {
238
271
  else {
239
272
  const credential = await resolveCredential(context);
240
273
  secrets.push(credential.token);
241
- result = await executeCommand(positionals, flags, { api, token: credential.token, readInput: options.readInput ?? ((file) => readInput(file, options.signal)),
242
- startMode: owner => startReportMode(config.homeDir, config.environment, owner, options.now?.() ?? Date.now()) });
274
+ if (isProjectBindingCommand(positionals)) {
275
+ const binding = await executeProjectBindingCLI(positionals, flags, { auth: context, token: credential.token, homeDir: config.homeDir, cwd: options.cwd ?? process.cwd(), localScopeConfirmed: flags["local-workspace"] === true });
276
+ result = binding.result;
277
+ exitCode = binding.exitCode;
278
+ }
279
+ else
280
+ result = await executeCommand(positionals, flags, { api, token: credential.token, readInput: options.readInput ?? ((file) => readInput(file, options.signal)),
281
+ startMode: owner => startReportMode(config.homeDir, config.environment, owner, options.now?.() ?? Date.now()) });
243
282
  // All validated business writes take --input except link, unlink and upload.
244
283
  if ((flags.input !== undefined || ["link", "unlink", "upload"].includes(positionals[0])) && explicitUnknownWrite(result))
245
284
  exitCode = 6;
@@ -30,6 +30,9 @@ const read = (command, args, description, options = [], notes) => ({
30
30
  example: { command: `caotang ${command}${args.replace(/<[^>]+>/g, id)}` }, ...(notes ? { notes } : {}),
31
31
  });
32
32
  const help = {
33
+ "project bind": { ...read("project bind", " <id>", "仅预览指定项目的本机目录关联;不写开工或平台业务。", ["--include-subdirs", "--confirm", "--local-workspace"], "先核对目录、实际服务、已核账号及真实项目;预览不创建本机绑定。仅本人明确同意后才以--confirm DIGEST和--local-workspace记录独立本机范围;参数和摘要不自证同意。确认重核范围,不读取目录正文,不上传路径。必要目标详情检查并非0内容轻量API。"), usage: "caotang project bind <id> [--include-subdirs] [--confirm DIGEST --local-workspace]" },
34
+ "project binding": { ...read("project binding", "", "查看当前本机目录的有效项目关联。", ["--local-workspace"], "本机范围未由本人明确确认时只报缺口;不同账号/实际服务不读取旧关联。目标不可读、目录变化或状态未知不显示旧项目ID/标题,也不自动修复。"), usage: "caotang project binding --local-workspace" },
35
+ "project unbind": { ...read("project unbind", "", "先预览,再按本人明确确认解除当前精确目录的本机关联。", ["--confirm", "--local-workspace"], "不删除、归档或修改平台项目;目标项目已不可读也可安全解除当前本机映射,不回显旧ID/标题。只继承父目录时需回绑定根目录确认,不能从子目录解除父绑定。确认摘要仅防范围变化,不替代本人同意。"), usage: "caotang project unbind --local-workspace [--confirm DIGEST]" },
33
36
  "project create": write("project create", "", "创建项目;标题和简介必填。", {
34
37
  title, summary: projectSummary, stage: "选填,最多 30 字符。", stages: "选填,最多 8 个阶段,每项最多 30 字符。",
35
38
  cognition_summary: "选填,最多 1000 字符。", visibility: privateOnly, status: "选填:draft、active、paused、archived、hidden;默认 active。",
@@ -67,7 +70,8 @@ const help = {
67
70
  search: { ...read("search", "", "按名称或关键词统一搜索项目、进展、知识、待办标题和当天总结正文;省略查询词按日期浏览。", knowledge.filter(option => option !== "--q"), "--project、--topic、--status、--publication-status 只筛知识分支;定向项目进展用 progress list。待办含 pending(未完成)、completed(已完成)、cancelled(已取消),归 read_workbench;搜索须至少开启 read_projects、read_knowledge、read_workbench 之一。AI 知识分支默认 published,显式 --publication-status all 可含可读草稿;关闭 read_drafts 仍不读草稿。--core-only 不含待办和当天总结,待办不继承所属项目的核心标记。待办日期用 created_at / updated_at。当天总结归 read_projects,返回 date 和 confirmed_at;按业务日期筛选、按 confirmed_at 排序,不提供创建/更新时间,不伪造 created_at/updated_at,date_field 不改变总结日期口径。"), usage: "caotang search [query]", example: { command: 'caotang search "项目名称"' } },
68
71
  "progress list": read("progress list", " <id>", "读取指定项目的进展。", [...page, ...dates]),
69
72
  checkins: read("checkins", "", "列出手写进展。", [...page, ...dates]),
70
- context: read("context", "", "读取工作台上下文;--start 读取新工作对话的短上下文。", ["--start", "--limit", "--since"], "--start 不与 --limit 或 --since 混用;短读不覆盖账号全部内容,不据此判断账号为空。"),
73
+ context: read("context", "", "读取工作台上下文;--handoff 纯读接手,--start 读取新工作对话的短上下文并记录开工。", ["--handoff", "--project", "--start", "--limit", "--since"], "--handoff 不与 --start、--limit、--since 混用;获准整体摘要时可用 --project UUID 指定焦点,它不会缩小整体摘要范围。只查看某项目请先用 project ID / progress list ID 定向读取,不先拉全局简报。不支持新协议时不回退开工;--start 不与 --limit 或 --since 混用。短读不覆盖账号全部内容,不据此判断账号为空。"),
74
+ guide: read("guide", "", "离线读取公共 Agent 短操作合同,不读取身份、凭据或私人工作内容。"),
71
75
  rules: read("rules", "", "读取本人给 AI 的规矩。"),
72
76
  activity: { ...read("activity", "", "按北京时间起止日期读取每日活动统计。", ["--start-date", "--end-date", ...page]), usage: "caotang activity --start-date YYYY-MM-DD --end-date YYYY-MM-DD", example: { command: "caotang activity --start-date 2026-11-01 --end-date 2026-11-07" } },
73
77
  pending: read("pending", "", "读取待确认申请;默认有效待确认,--status all 查历史。", [...page, "--status"]),
@@ -76,8 +80,9 @@ const help = {
76
80
  link: read("link", " <card> <project>", "将知识关联到项目,需本人确认;--core 只是兼容关联参数,不设置核心标记。", ["--core"], "写入不自动重试,待审或结果未知时先在网页核对。"),
77
81
  unlink: read("unlink", " <card> <project>", "解除知识与项目关联,需本人确认;不会删除知识或项目。", [], "写入不自动重试,待审或结果未知时先在网页核对。"),
78
82
  upload: { ...read("upload", " <pdf> --card <id>", "上传本地普通 PDF 并关联知识卡,不接受链接或标准输入。", ["--card", "--source-title", "--summary"], "仅 1-10485760 字节的普通 PDF;不接受符号链接。不自动重试上传或申请替代凭证。"), fields: { card: "必填,知识卡 ID。", "source-title": "选填,最多 300 字符。", summary: "选填,最多 1200 字符。" }, example: { command: `caotang upload ./reference.pdf --card ${id}` } },
79
- setup: read("setup", "", "安装选定 AI 入口并连接草堂,不改 shell 或 PATH 配置。", ["--platform", "--device-name", "--client-name", "--no-open"], "--platform 可选 claude-code、codex、openclaw、kimi-code、kimi-work、workbuddy;可逗号分隔。"),
80
- doctor: read("doctor", "", "检查本机安装与连接状态,不输出凭据。", ["--platform"]),
83
+ setup: read("setup", "", "安装选定 AI 入口并连接草堂,不改 shell 或 PATH 配置。", ["--platform", "--skills-dir", "--device-name", "--client-name", "--no-open"], "--skills-dir 为七项技能子目录的绝对父目录,与 --platform 互斥;当前受管写入保留 HOME 围栏。--platform 可选 claude-code、codex、openclaw、kimi-code、kimi-work、workbuddy;可逗号分隔。"),
84
+ doctor: read("doctor", "", "检查本机安装与连接状态,不输出凭据。", ["--platform", "--skills-dir"], "显式技能目录与 --platform 互斥;多个已记录目录不自动选择或扫描。"),
85
+ "skills export": { ...read("skills export", "", "离线导出官方七项公共技能;不读取个人安装目录或身份,不上传。", ["--output"], "--output 必填新ZIP文件路径;父目录须已存在且安全,已有目标不覆盖。不等于安装、授权或宿主已加载。"), usage: "caotang skills export --output file.zip", example: { command: "caotang skills export --output ./caotang-skills.zip" } },
81
86
  version: read("version", "", "查看当前 CLI 候选版本。"),
82
87
  update: read("update", "", "当前为未公开发布候选,不能自动更新。"),
83
88
  "auth login": read("auth login", "", "已有有效凭据时验证并复用,不发起新授权;不是强制重新连接。", ["--device-name", "--client-name", "--no-open"], "没有凭据时发起授权,在指定环境的网页输入确认码并批准后回来。文件凭据明确 401 失效才清理后新授权;环境令牌覆盖不会回退,需先处理覆盖。若要切换账号,先明确同意共享影响后 auth logout,再在同一 --env 下 auth login 并由目标账号批准。不要读取凭据文件。"),
@@ -97,7 +102,7 @@ export function commandHelp(positionals) {
97
102
  const pair = `${first} ${second}`;
98
103
  if (Object.hasOwn(help, pair))
99
104
  return help[pair];
100
- if (["auth", "request", "progress", "source", "cognition", "stage", "schedule", "daily-summary"].includes(first))
105
+ if (["skills", "auth", "request", "progress", "source", "cognition", "stage", "schedule", "daily-summary"].includes(first))
101
106
  return undefined;
102
107
  if (first === "knowledge" && second !== undefined)
103
108
  return undefined;
package/dist/commands.js CHANGED
@@ -10,6 +10,7 @@ import { expectDailyOK, validateDailyDates } from "./query-daily.js";
10
10
  import { createText, createHTTPURL, validateCreateSources, isCreatePath, expectCreateOK, expectKnowledgeTextOK } from "./create-bundle.js";
11
11
  import { withKnowledgeGrowth } from "./knowledge-growth.js";
12
12
  import { readHabit } from "./habit-read.js";
13
+ import { readHandoff } from "./handoff-read.js";
13
14
  import { citationOrigin, webCitations } from "./web-citations.js";
14
15
  import { KNOWLEDGE_GROWTH_STEPS, knowledgeGrowthMetadata } from "./generated/knowledge-growth.js";
15
16
  import { inputDetails, invalidInput, validateInput, validateCommandArguments } from "./input-validation.js";
@@ -227,6 +228,10 @@ async function dispatch(p, flags, ctx) {
227
228
  allowedFlags(flags, []);
228
229
  return readHabit(ctx, false);
229
230
  }
231
+ if (p[0] === "context" && p.length === 1 && flags.handoff === true) {
232
+ allowedFlags(flags, ["handoff", "project"]);
233
+ return readHandoff(ctx, flags.project);
234
+ }
230
235
  if (p[0] === "context" && p.length === 1 && flags.start === true) {
231
236
  allowedFlags(flags, ["start"]);
232
237
  return readHabit(ctx, true);
package/dist/doctor.js CHANGED
@@ -15,6 +15,7 @@ const localMessages = {
15
15
  command_outdated: ["已核对为旧版受管命令行,需要更新。", "沿用原来的名字运行 npx @yijiancaotang/cli@latest setup --agent <你的名字> 更新,不需要重新批准连接。"],
16
16
  command_not_on_path: ["当前找不到可直接使用的一间草堂命令。", "请先核对安装是否完整;有已核验的完整路径时使用它,不修改 shell 或 PATH 配置。"],
17
17
  command_version_unknown: ["无法确认本地命令行版本。", "请核对本地安装版本,不要自动升级或执行未知程序。"],
18
+ skill_target_ambiguous: ["这个名字记录了多个技能目录,未自动选择。", "请保留本次明确的--skills-dir或--platform参数,不批量更新不同工作区。"],
18
19
  no_platform_detected: ["尚未识别到支持的 AI 客户端程序。", "请由本人确认 Claude Code、Codex、OpenClaw,或 macOS 上 Kimi Code、Kimi 工作模式、WorkBuddy 的程序安装位置后重新检查。"],
19
20
  skills_missing: ["已识别平台的技能包不完整。", "请按当前安装包的技能清单核对所选平台,再由本人决定是否运行 setup。"],
20
21
  skills_modified: ["已识别平台的技能内容与本包不一致。", "请保留现有技能,由本人核对差异;不要自动覆盖或删除。"],
@@ -151,7 +152,7 @@ export async function doctor(ctx) {
151
152
  }
152
153
  }
153
154
  }
154
- const ids = PLATFORM_IDS;
155
+ const ids = [...PLATFORM_IDS, ...(Array.isArray(local?.platforms) && local.platforms.some(platform => platform?.id === "generic") ? ["generic"] : [])];
155
156
  if (local && (!Array.isArray(local.platforms) || local.platforms.some((platform) => !platform || !ids.includes(platform.id)) || new Set(local.platforms.map((platform) => platform?.id)).size !== local.platforms.length))
156
157
  addLocal("local_inspection_failed");
157
158
  if (local?.skill_platforms !== undefined && (!Array.isArray(local.skill_platforms) || local.skill_platforms.some(id => !ids.includes(id)) || new Set(local.skill_platforms).size !== local.skill_platforms.length))
@@ -0,0 +1,12 @@
1
+ const operations = {
2
+ "schema_version": 1,
3
+ "version": "1.0.1",
4
+ "format": "text/markdown",
5
+ "content": "# 一间草堂 Agent 公共短操作合同\n\n这是公共操作说明,合同版本独立于 CLI 软件版本;不含账号、连接身份、本人规矩或私人工作内容。获得本文不代表连接已批准、当前模型已理解或真实流程已验证。详细命令参数按当前入口的帮助取得,完整人用手册见同一服务的 `/resources/usage`。\n\n## 六类用途\n\n- 推进项目:查看有权项目、已确认进展和留下的下一步,围绕本人本次目标继续工作。\n- 记进展:先整理实际完成、未完成和下一步,确认内容及目标后私有保存。\n- 存知识:把本人选中的经验、方法或来源拟成知识卡,确认后默认私有保存。\n- 排待办:拟定具体事项和明确日期/时间;带日期的同一待办可在日历查看,不另建重复日程。\n- 找记录:按任务查询项目、进展或知识,需要时再读详情/下一页;说明实际范围和权限缺口。\n- 开工/收工:获准后读取有限工作事实;收工先拟稿,由本人选中要留下的进展、知识和待办,再确认保存。\n\nCLI默认JSON是完整的有界机器投影;`--human`是给人看的紧凑摘要,不包含全部规矩和记录。Agent正常读取保持默认JSON,不能拿人用摘要冒称已读完规矩或完整工作事实。\n\n## 公共帮助、接入命名与用户呈现\n\n- 仅看公共帮助/说明时不运行doctor、auth status、setup或login,不做身份预检或读取私有内容。诊断只在真实错误已经发生且任务确有必要时使用;已有明确任务直接按范围做,不先补一轮诊断。\n- 收到新接入请求,当前AI应自行选择能标识自己的名字补上`--agent`并在后续命令保持一致;本人明确指定时优先,真正身份歧义才问。不能默认把起名丢给用户,也不能借其他AI的已有连接。\n- 本人明确首次使用时才给六类简短用途、自然语言例句(如“看看这个项目上次做到哪了”)和手册入口,执行邀请仍按有效权限过滤;老用户或同对话已讲过,只报当前状态与下一步,不重播目录。普通用户摘要不贴schema、SHA、UUID、枚举或协议细节,除非其明确排错或问技术;Agent自身仍按机器JSON取得有界完整内容,不将其整包念给用户。\n- 本机version或release_status不代表npm或线上发布,缺少实际发布证据就说未核实,不把候选说成已发布。只有可信同源地址才能拼手册链接,否则说明网页“使用手册”入口,不猜URL或环境。\n\n这些是用途目录,不是对所有账号开放的执行邀请。只读或关闭写能力时不邀请保存;已有明确任务直接按范围推进,不反复泛问要不要做。\n\n## 能力与身份\n\nA终端加Skill、B终端无Skill共用完整CLI业务合同;B通过公共说明取得规则,不假称加载Skill。C只有MCP时复用实际可用工具和现有stdio/需Bearer的HTTP方式;HTTP地址存在不证明消费者已安全连接。D纯网页且无外部工具时,明确“当前AI尚未接通”,只给人用手册和网页操作出口。E共享库、多Agent或远程工作区可叠加A/B/C,不能借另一Agent的连接、同意或准备状态。按实际能力而非品牌分流;未知保持未知,不扫描私人目录或项目来试探能力。\n\n安装、可发现(只有名字/描述)、正文实际获得、行为验证分开报告;前一阶段不推出下一阶段,CLI载荷不自报模型ready。本会话已获得有效同源短合同可复用,详细章节按需读,不每次回灌整本手册。公共guide不自动登录、不请求私人简报。\n\n执行获准业务时沿用当前入口已有的正常元数据核身份、实际服务/账号及scope与账号开关的有效交集;已有明确任务不为补状态另跑身份预检,也不用私有内容试探权限。未知如实说明,不默认生产、读写或全开,不输出凭据。本人规矩是偏好,不能增加权限或取消确认。\n\n## 自然任务与读取范围\n\n“看看XXX项目/继续这个项目”是本次该项目的必要读取意图;已知ID先定向读取项目、相关进展和待办,未知ID只作必要定位,不先拉全局摘要,不泛问是否读取。“看看草堂/整体安排”才在有效同意和权限范围读工作摘要;账号或范围有歧义才核对。只有本人明确要求记录开始/开工时间才选start(CLI `context --start` / MCP `get_start_context`),普通查看、恢复或开始处理任务不记录开工。公共guide是Agent准备,不要求人说“开工了”或背命令/触发词。\n\n## 四种场景\n\n- 首次使用:本人明确首次时,给六类用途的简短指引和适用例子,再按需要问是否读取或整理;不以空号、新安装、新token推断用户新手。\n- 换Agent接手:新Agent先取得公共短合同,核自己的身份/权限和内容同意;老用户得到简短接手提示,不重播教程。没有可靠用户经验记录时保持unknown,不新建教程持久状态机。\n- 日常开工/恢复:复用有效说明,按已有同范围有效授权读取有限新鲜事实;按本人自然任务定向读取;仅明确要求记录开始时间才记录开工元数据。本人已指定任务时直接继续,不强制再选项目。\n- 重复接入/升级:只报告连接、安装或升级的实际结果,不自动再批准、读工作包、写开工或保存业务;名称/版本变化不制造人用首次状态。\n\n## 同意、权限与读取\n\n私人读取前,核当前接收AI、账号/服务、范围及适用有效同意。本人已明确让当前AI查看指定项目或草堂摘要且目标清楚时,直接按本次任务读取,不再泛问;只有接收方/账号/范围有实际歧义或尚无读取意图时才问一次。拒绝、沉默、含糊或换题不授权读取/写入,不换工具、账号或对话绕过。已有明确且可核的同范围授权沿用,不逐字段反复问;新Agent不能借旧AI的同意。\n\nread_only只能按有效范围读,不提出写入或写类申请。读写权限与各项开关分别核,写成功不保证能读正文。检查各层missing_sections;partial、缺权、失败、未记录、封顶与真空结果分开,null或失败不是0,也不能推断全账号为空。read_workbench控制日程/开工元数据,不作所有内容段的总开关;项目、知识及其草稿仍按各自资格过滤。法律待确认须本人网页处理,不代签、重签或绕过。\n\n## 纯接手与开工\n\n查看工作摘要可使用当前协议支持的CLI `context --handoff` / MCP `get_handoff_context`:只有当前客户端和服务端都支持新协议、本人已授权范围时才调用;纯接手不写业务、开工记录或持久已读游标,正常认证审计另列。协议不支持时明确版本缺口,保留已有显式业务查询;不静默用start替代,不自动升级或重新授权。\n\n既有CLI `context --start` / MCP `get_start_context`会尝试记录开工时间,只在本人明确要求记录开始/开工时间时使用;保留原字段和回执。元数据成功、失败、无权分别报告,不补写、不当业务成果,也不据此断言项目有新活动。公共说明/重复setup不触发开工。\n\n摘要以实际协议coverage、各段status/total/has_more及deferred说明覆盖范围;未提供、缺权或失败不说成空、已读或完整。项目概况、关联知识、连接记录、提醒和目录绑定只有当前协议实际支持且有权时才使用,不承诺旧服务已有。显式项目只覆盖本次,不自动永久绑定或新建项目;远程工作区无可信隔离依据时只显式选择。\n\n## 本机项目定位与通用安装\n\n本人问当前目录关联的项目时,仅在已经明确确认独立本机工作区范围后,可用CLI `project binding --local-workspace` 读取有效定位,再按本次任务定向查询;本轮明确指定项目优先,不自动改长期绑定。没有范围确认或可靠绑定就明确询问所需项目,不扫描目录、不先取全局摘要。MCP无本机目录时使用本次显式项目。\n\n永久关联先用 `project bind <project_id>` 看预览;展示实际账号/服务、目录和项目,只有本人明确同意后才按该预览的摘要执行 `--confirm DIGEST --local-workspace`。包含子目录需单独明确并保留 `--include-subdirs`。`project unbind --local-workspace` 同样先预览再确认,只解除本机关联,不删除草堂项目。参数或摘要不能自证本人同意;移动目录、切账号/实际服务、撤权、归档或不可靠共享/远程环境不沿用旧定位。\n\n名单外Skill宿主可在明确的安全父目录使用 `setup --agent <自己的名字> --skills-dir <绝对父目录>`;与 `--platform` 互斥。HOME外共享或远程导入先用离线 `skills export --output <新ZIP路径>`,按宿主实际支持的导入入口及本人授权处理。导出不含个人身份/凭据,不覆盖既有文件,也不代表宿主已加载;没有Skill仍可通过本文和当前命令帮助使用完整CLI。\n\n## 拟稿、对象关系与回执\n\n写入前列出拟稿、目标和私有范围,等本人确认具体选中项;要求整理/记录先开始拟稿,不等于确认保存。确认后只写选中项;相同内容/目标/范围的有效确认不重复问。公开、删除、归档等沿既有网页确认及开关,AI不代确认,不自动写本人规矩。\n\n项目、知识、进展和待办保原关系与同一ID,多视图不复制;关联不自动升核心、发芽或证明已应用。引用实际可读标题和当前环境的真实来源,不猜ID、链接或下一步;只有全局一条下一步时不套到每个项目。\n\n成功、待审、被拒、unknown及202的明确状态分别报告;只在回执确认时说已保存。精简回执或audit_pending不自行判业务失败。结果未知保留原请求、幂等键、连接及回执,只按权限核原笔,不自动重写、换键或因读不到正文重试;确认保存和原笔核对分开取证。\n\n规则提醒只表示平台可读记录,不等于现实停工;连接历史不证明另一AI或实时执行,归属不明保持中性。本文不启用推送、跨会话监听、任意网页OAuth、自动共享库写入或远程绑定连接器;不自动安装、修改个人配置或扩大权限。\n",
6
+ "sha256": "4e1fa9c1585b0d3d0779f8c2c1458ffba65605d027e07977aad1b4c4e902a2ec",
7
+ "manual_path": "/resources/usage"
8
+ };
9
+ // Static public data only: no identity lookup, private storage or runtime file access.
10
+ export function getAgentOperations() {
11
+ return { ...operations };
12
+ }
@@ -0,0 +1,29 @@
1
+ import { CliError, expectOK, networkFailure } from "./errors.js";
2
+ import { verifiedWorkContext, validWorkContextProjectID } from "./work-context-wire.js";
3
+ export async function readHandoff(ctx, projectId) {
4
+ if (projectId !== undefined && !validWorkContextProjectID(projectId))
5
+ throw new CliError("invalid_arguments", "项目参数须为有效UUID。", "只用已核对的真实项目ID,不猜同名项目。", 2);
6
+ const selected = projectId?.toLowerCase();
7
+ const query = new URLSearchParams({ projection: "work_context_v1", ...(selected ? { project_id: selected } : {}) });
8
+ let result;
9
+ try {
10
+ result = await ctx.api.request("/api/v1/agent/m1/habit/handoff?" + query.toString(), { method: "GET", token: ctx.token });
11
+ }
12
+ catch (error) {
13
+ if (error instanceof CliError && error.code === "network_error")
14
+ throw networkFailure(error, true);
15
+ throw error;
16
+ }
17
+ if (result.status === 404 || result.status === 405) {
18
+ throw new CliError("handoff_unsupported", "当前服务不支持可核实的纯读接手协议,未读到本次内容。", "不要改用开工、自动升级、重新授权或重试;需要旧业务查询时请显式选择原有命令。", 7, { http_status: result.status });
19
+ }
20
+ if (result.status === 401)
21
+ throw new CliError("unauthorized", "草堂授权已失效,未读到本次内容。", "先继续当前工作,请本人核对设备授权;不要自动重新授权。", 3, { http_status: 401 });
22
+ if (result.status >= 500)
23
+ throw new CliError("server_error", "草堂服务暂时不可用,未读到本次内容。", "先继续当前工作,稍后核对服务;不要反复重试或重新授权。", 7, { http_status: result.status });
24
+ expectOK(result, "read");
25
+ const body = verifiedWorkContext(result.body, selected);
26
+ if (result.status !== 200 || !body)
27
+ throw new CliError("invalid_response", "纯读接手响应不完整,无法核实。", "先继续当前工作,不要把未读取的内容解释为没有资料;不改用开工或自动重试。", 7, { http_status: result.status });
28
+ return { ...result, body };
29
+ }
@@ -0,0 +1,42 @@
1
+ import { verifiedHabit } from "./habit-wire.js";
2
+ import { validDate } from "./query-validation.js";
3
+ const MAX_HANDOFF_BYTES = 64 * 1024;
4
+ const deferredSections = [
5
+ "project_overview", "project_next_steps", "knowledge_index",
6
+ "recent_connections", "conditional_reminders", "project_selection"
7
+ ];
8
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
9
+ function utcTimestamp(value) {
10
+ if (typeof value !== "string")
11
+ return false;
12
+ const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?Z$/.exec(value);
13
+ return !!match && validDate(match[1])
14
+ && Number(match[2]) <= 23 && Number(match[3]) <= 59 && Number(match[4]) <= 59;
15
+ }
16
+ export function verifiedHandoff(value) {
17
+ try {
18
+ if (Buffer.byteLength(JSON.stringify(value) ?? "", "utf8") > MAX_HANDOFF_BYTES)
19
+ return;
20
+ const body = record(value);
21
+ if (!body || body.schema_version !== 1 || body.mode !== "handoff"
22
+ || body.coverage !== "existing_habit_sections" || !utcTimestamp(body.as_of))
23
+ return;
24
+ const deferred = body.deferred_sections;
25
+ if (!Array.isArray(deferred) || deferred.length !== deferredSections.length
26
+ || new Set(deferred).size !== deferredSections.length
27
+ || deferred.some(section => !deferredSections.includes(section)))
28
+ return;
29
+ const context = verifiedHabit(body.context, true);
30
+ if (!context || typeof context.owner_id !== "string" || !Array.isArray(context.missing_sections)
31
+ || context.missing_sections.some(gap => record(gap)?.section === "calendar_start" || record(gap)?.reason === "record_failed"))
32
+ return;
33
+ return {
34
+ schema_version: 1, mode: "handoff", as_of: body.as_of,
35
+ coverage: "existing_habit_sections", context,
36
+ deferred_sections: [...deferredSections]
37
+ };
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
@@ -1,3 +1,4 @@
1
+ import { validWorkContextProjectID } from "./work-context-wire.js";
1
2
  import { asRecord, CliError } from "./errors.js";
2
3
  import { commandHelp } from "./command-help.js";
3
4
  export const trimGoSpace = (value) => value.replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "");
@@ -63,8 +64,21 @@ export function validateCommandArguments(command, flags) {
63
64
  return;
64
65
  if (Object.entries(flags).some(([key, value]) => value !== undefined && !help.options.includes(`--${key}`)))
65
66
  throw flagError(command);
67
+ if (help.command === "context" && flags.handoff === true && Object.keys(flags).some(key => key !== "handoff" && key !== "project" && flags[key] !== undefined))
68
+ throw flagError(command);
69
+ if (help.command === "context" && flags.project !== undefined && (flags.handoff !== true || !validWorkContextProjectID(flags.project)))
70
+ throw invalidInput(command, { invalid: ["--project"] });
66
71
  if (help.command === "context" && flags.start === true && Object.keys(flags).some(key => key !== "start" && flags[key] !== undefined))
67
72
  throw flagError(command);
73
+ if (["project bind", "project binding", "project unbind"].includes(help.command)) {
74
+ if (flags.confirm !== undefined && (typeof flags.confirm !== "string" || !/^[a-f0-9]{64}$/.test(flags.confirm)))
75
+ throw invalidInput(command, { invalid: ["--confirm"] });
76
+ for (const flag of ["local-workspace", "include-subdirs"])
77
+ if (flags[flag] !== undefined && flags[flag] !== true)
78
+ throw flagError(command);
79
+ if (help.command === "project bind" && command.length >= 3 && !validWorkContextProjectID(command[2]))
80
+ throw invalidInput(command, { invalid: ["<id>"] });
81
+ }
68
82
  const count = (help.usage.match(/<[^>]+>/g) ?? []).length;
69
83
  const words = help.command.split(" ").length;
70
84
  const positionalCount = help.command === "upload" ? 1 : count;