@yijiancaotang/cli 1.0.0 → 1.1.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.
- package/README.md +41 -9
- package/dist/auth.js +7 -4
- package/dist/calendar-contract.js +358 -0
- package/dist/calendar-help.js +12 -0
- package/dist/calendar.js +64 -0
- package/dist/capability-result.js +5 -0
- package/dist/cli.js +12 -3
- package/dist/command-help.js +10 -5
- package/dist/commands.js +6 -0
- package/dist/doctor.js +11 -4
- package/dist/habit-read.js +3 -1
- package/dist/habit-wire.js +36 -5
- package/dist/http.js +11 -1
- package/dist/installation.js +307 -37
- package/dist/local-commands.js +107 -14
- package/dist/local-records.js +149 -0
- package/dist/managed-skill-baseline.js +35 -0
- package/dist/missing-sections.js +1 -0
- package/dist/output.js +3 -1
- package/dist/platforms.js +51 -12
- package/dist/query-search.js +3 -2
- package/dist/reviewed-managed-bundle-baseline.js +356 -0
- package/dist/schedule-contract.js +289 -0
- package/dist/schedule-help.js +35 -0
- package/dist/schedule-start-wire.js +80 -0
- package/dist/schedule.js +70 -0
- package/dist/skill-assets.js +1 -1
- package/dist/web-citations.js +1 -1
- package/dist/write-routing.js +26 -0
- package/package.json +1 -1
- package/skills/caotang-context/SKILL.md +5 -0
- package/skills/caotang-first-connect/SKILL.md +18 -6
- package/skills/caotang-project-progress/SKILL.md +6 -2
- package/skills/caotang-save-knowledge/SKILL.md +6 -4
- package/skills/caotang-schedule/SKILL.md +54 -0
- package/skills/caotang-search/SKILL.md +11 -0
- package/skills/caotang-workday/SKILL.md +56 -9
- package/skills/manifest.json +28 -6
|
@@ -38,6 +38,8 @@ export function commandAcceptsSwitch(args, capability) {
|
|
|
38
38
|
eligible = op === "create" || op === "update" ? ["write_project"] : ["read_projects", "read_drafts"];
|
|
39
39
|
break;
|
|
40
40
|
case "search":
|
|
41
|
+
eligible = ["read_projects", "read_knowledge", "read_workbench"];
|
|
42
|
+
break;
|
|
41
43
|
case "activity":
|
|
42
44
|
eligible = ["read_projects", "read_knowledge"];
|
|
43
45
|
break;
|
|
@@ -47,6 +49,9 @@ export function commandAcceptsSwitch(args, capability) {
|
|
|
47
49
|
case "checkins":
|
|
48
50
|
eligible = ["read_projects"];
|
|
49
51
|
break;
|
|
52
|
+
case "context":
|
|
53
|
+
eligible = ["read_workbench"];
|
|
54
|
+
break;
|
|
50
55
|
case "source":
|
|
51
56
|
case "upload":
|
|
52
57
|
eligible = ["write_knowledge_sources"];
|
package/dist/cli.js
CHANGED
|
@@ -15,17 +15,21 @@ import { ApiClient } from "./http.js";
|
|
|
15
15
|
import { explicitUnknownWrite, redact, renderError, renderResult } from "./output.js";
|
|
16
16
|
import { localDoctor, localSetup, localVersion, manualUpdateInstructions, selectedPlatforms } from "./local-commands.js";
|
|
17
17
|
import { commandHelp } from "./command-help.js";
|
|
18
|
+
import { scheduleCommandUsages } from "./schedule-help.js";
|
|
19
|
+
import { calendarCommandUsages } from "./calendar-help.js";
|
|
20
|
+
import { WRITE_ROUTING_HELP } from "./write-routing.js";
|
|
18
21
|
import { flagError, validateCommandArguments } from "./input-validation.js";
|
|
19
22
|
const help = {
|
|
20
23
|
brand: "一间草堂",
|
|
21
24
|
usage: "caotang --agent <你的名字> <command> [--env production|test] [--human]",
|
|
25
|
+
write_routing: WRITE_ROUTING_HELP,
|
|
22
26
|
advance_example: { command: "caotang advance CARD_ID --input -", input: { internalization_status: "applied", source_type: "project", source_id: "PROJECT_ID", evidence_summary: "经本人确认,这张卡实际用于该项目的登录回归检查。" } },
|
|
23
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|-"],
|
|
24
28
|
platform_scope: "kimi-code、kimi-work(Kimi.app工作模式)和workbuddy仅支持macOS本机安装;目录发现不代表真实AI端到端通过。Kimi Code和Kimi工作模式是独立入口,不互相代替。",
|
|
25
29
|
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"],
|
|
26
30
|
command_help: "每个命令用 --help 查看自己的字段和固定 JSON 示例;写入 JSON 支持 --input -。knowledge 不带参数是知识列表;记录一件事完成了,用 progress add。source add 的 source_title 选填;progress add 的 visibility 省略时默认 private。",
|
|
27
31
|
shared_connection: "每条命令都带 --agent <你的名字>,只有这个 AI 的连接会被读取、复用或断开。auth logout 先经本人确认,再吊销这个 AI 的连接;其它 AI 不受影响。重新接入使用相同名字 setup --agent <你的名字>,由本人在网页批准。",
|
|
28
|
-
query: "search [query]
|
|
32
|
+
query: "search [query]统一搜索项目、项目进展、知识、手写进展、待办标题(pending/completed/cancelled,归read_workbench)和当天总结正文(归read_projects);省略或空白词按日期浏览,多个词全部命中、不分顺序和大小写,按字面匹配而非语义搜索。--status/--topic/--publication-status/--project只筛知识分支,--project不会限制其它类型;定向项目进展用progress list ID,仅知识用knowledge --q。搜索返回kind/snippet等统一结果项,不再是旧知识DTO;search、projects、knowledge可加--core-only只读筛选;结果is_core按项目已有标记、知识发芽及以上(applied/reviewed/templated)分别判断,它不是新的可写星标。只看核心项目默认不含已归档;显式 projects --core-only --status archived 可查已归档核心项目。只看核心不含项目进展、手写进展、待办和当天总结;待办不继承所属项目核心标记。AI知识分支默认published,显式--publication-status all仍受read_drafts限制;搜索至少需read_projects/read_knowledge/read_workbench之一。要查核心项目的进展,先查核心项目,再用progress list ID。项目、知识、search、progress list、checkins支持北京时间含首尾日期与limit/offset分页,返回total、has_more、limit、offset;默认20/0,日期列表最多50条,pending最多100条。项目/知识/待办默认updated_at,可created_at;进展固定created_at。当天总结仅返date/confirmed_at,按业务日期筛选、按confirmed_at排序,不伪造created_at/updated_at,date_field不改变总结日期口径。pending默认有效待确认,--status all查历史。",
|
|
29
33
|
activity: "activity仅接受必填起止日期与limit/offset,日期真实且北京时间含首尾最多一个日历年,闰日周年归一到3月1日。days是全范围日统计,不是当前页;items/total只含可读明细,默认20/0、最多50条。null和未完整覆盖不能解释为0,草稿受限不计入;只有符合资格的知识应用进展可匿名计数,不返身份/正文。不支持q、date-field、项目/长势/核心筛选。",
|
|
30
34
|
knowledge_growth: `机器枚举保持不变;可读知识返回growth,真实变化返回from_growth/to_growth,安全逐级提示返回current_growth/next_growth(中文名、说明、level/total)。未返回或未知状态不推断,不补发请求。五级长势:${KNOWLEDGE_GROWTH_HELP}`,
|
|
31
35
|
growth_confirmation: "用户说某张卡已经用上时,可以主动问一次:“你把这张卡用在这个项目,做了这件事,要记为发芽吗?”复述已知事实,只问缺的;未来打算、沉默、拒绝或只报项目名都不是同意。没有项目或尚未关联,先问要不要新建或关联,不同意就不推进。播种到发芽先说清“先生根、再发芽”,本人明确同意两步后才逐级调用;第一步未确认成功不发第二步。第二步明确失败只报最后确认的级别;待审或结果未知说明哪个结果未确认,不自动重试。 从生根进入发芽必须带 source_type=project、已关联的本人项目 source_id、一句本人确认的实际使用说明 evidence_summary;同时需要写知识长势和写项目进展权限。状态、应用证据、项目时间线一起写入。网页从发芽以下直接跳到更高级也要填写这一表单。退级、同级、已发芽后的推进不要求补项目或说明;不回填历史。 link --core 是兼容关联参数,不会设置项目或知识的核心标记。",
|
|
@@ -35,12 +39,17 @@ const help = {
|
|
|
35
39
|
safety: "不要将令牌放入参数。写入不会自动重试;待审核202退出0,不是业务完成;结果未知的写入退出6,不执行依赖步骤。202需按回执区分待处理与write_outcome_unknown(写入结果未确认,不保证后台队列,请先在网页核对)。"
|
|
36
40
|
};
|
|
37
41
|
const usageError = () => new CliError("invalid_arguments", "命令或参数无效。", "运行 --help,写入数据使用 --input file 或 --input -。");
|
|
42
|
+
help.commands.push(...scheduleCommandUsages);
|
|
43
|
+
help.commands.push(...calendarCommandUsages);
|
|
38
44
|
const parserOptions = {
|
|
39
45
|
agent: { type: "string" }, env: { type: "string" }, human: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" }, platform: { type: "string" },
|
|
40
46
|
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" },
|
|
41
47
|
start: { type: "boolean" }, "start-date": { type: "string" }, "end-date": { type: "string" }, "date-field": { type: "string" }, "core-only": { type: "boolean" },
|
|
42
48
|
"idempotency-key": { type: "string" }, card: { type: "string" }, "source-title": { type: "string" }, summary: { type: "string" }, core: { type: "boolean" },
|
|
43
|
-
"device-name": { type: "string" }, "client-name": { type: "string" }, "no-open": { type: "boolean" }
|
|
49
|
+
"device-name": { type: "string" }, "client-name": { type: "string" }, "no-open": { type: "boolean" },
|
|
50
|
+
view: { type: "string" }, "group-by": { type: "string" }, revision: { type: "string" },
|
|
51
|
+
"actor-kind": { type: "string" }, "actor-name": { type: "string" }, "actor-source-id": { type: "string" },
|
|
52
|
+
kind: { type: "string" }, "only-core": { type: "string" }, cursor: { type: "string" }
|
|
44
53
|
};
|
|
45
54
|
// Node's strict parser treats a separate negative numeric value as an option.
|
|
46
55
|
// Normalize only offset values, so both syntaxes reach the same range check;
|
|
@@ -168,7 +177,7 @@ export async function runCLI(args, options = {}) {
|
|
|
168
177
|
throw usageError();
|
|
169
178
|
positionals = ["version"];
|
|
170
179
|
}
|
|
171
|
-
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"].includes(positionals[0] ?? ""))
|
|
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] ?? ""))
|
|
172
181
|
throw usageError();
|
|
173
182
|
validateCommandArguments(positionals, flags);
|
|
174
183
|
const localCommand = ["setup", "doctor", "version", "update"].includes(positionals[0]);
|
package/dist/command-help.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { KNOWLEDGE_GROWTH_STEPS } from "./generated/knowledge-growth.js";
|
|
2
|
+
import { scheduleCommandHelp } from "./schedule-help.js";
|
|
3
|
+
import { calendarCommandHelp } from "./calendar-help.js";
|
|
4
|
+
import { WRITE_ROUTING_NOT_THIS } from "./write-routing.js";
|
|
2
5
|
const dates = ["--start-date", "--end-date", "--date-field"];
|
|
3
6
|
const page = ["--limit", "--offset"];
|
|
4
7
|
const knowledge = [...page, "--core-only", "--q", "--project", "--topic", "--status", "--publication-status", ...dates];
|
|
@@ -34,14 +37,14 @@ const help = {
|
|
|
34
37
|
"project update": write("project update", " <id>", "修改项目标题或简介,至少提供一个字段。", {
|
|
35
38
|
title: "选填,去首尾空白后 2-80 个 Unicode 字符。", summary: "选填,去首尾空白后最多 280 字符;只有当前状态为 draft 的项目可清空,最终由服务端按当前状态校验。",
|
|
36
39
|
}, [], { summary: "本人确认的新简介" }),
|
|
37
|
-
"progress add": write("progress add", " <id>",
|
|
40
|
+
"progress add": write("progress add", " <id>", `${WRITE_ROUTING_NOT_THIS.progress} 记录一件事完成了,用 progress add。`, {
|
|
38
41
|
body: "必填,2-360 个 Unicode 字符。", update_type: "选填:progress、knowledge_applied、cognition、stage;默认 progress。", visibility: privateOnly,
|
|
39
42
|
}, ["body"], { body: "已完成本人确认的工作", visibility: "private" }, true),
|
|
40
|
-
"cognition set": write("cognition set", " <id>",
|
|
43
|
+
"cognition set": write("cognition set", " <id>", `${WRITE_ROUTING_NOT_THIS.cognition} 更新项目认知摘要。`, { cognition_summary: "必填字符串,最多 1000 字符,可为空。" }, ["cognition_summary"], { cognition_summary: "本人确认的新认识" }),
|
|
41
44
|
"stage set": write("stage set", " <id>", "更新项目阶段;至少提供一个字段,不是记录已完成工作的入口。", {
|
|
42
45
|
stage: "选填,非空,最多 30 字符。", stages: "选填,1-8 个非空阶段,每项最多 30 字符。", status: "选填:draft、active、paused。",
|
|
43
46
|
}, [], { stage: "验证" }),
|
|
44
|
-
"knowledge add": write("knowledge add", "",
|
|
47
|
+
"knowledge add": write("knowledge add", "", `${WRITE_ROUTING_NOT_THIS.knowledge} 保存本人确认的知识分析;intent 不是选填。`, {
|
|
45
48
|
title: "必填,2-160 字符。", intent: "必填,2-500 字符;用户没说时可拟一句用途并告知本人,确认后保存。",
|
|
46
49
|
summary: "必填,1-4000 字符;分析正文不堆网址。", source_type: "必填:manual、douyin、xiaohongshu、forum、book、publisher_preview、expert、agent、web;不补默认值。",
|
|
47
50
|
source_label: "选填,最多 300 字符。", topic: "选填,最多 120 字符。", linked_project_id: "选填,关联项目 ID。",
|
|
@@ -61,7 +64,7 @@ const help = {
|
|
|
61
64
|
project: read("project", " <id>", "读取指定项目详情。"),
|
|
62
65
|
knowledge: read("knowledge", "", "knowledge 不带参数是知识列表;记录一件事完成了,用 progress add。", knowledge, "默认 published;草稿用 --publication-status draft,需要读取知识和读取草稿权限。"),
|
|
63
66
|
"knowledge show": read("knowledge show", " <id>", "读取指定知识详情。"),
|
|
64
|
-
search: { ...read("search", "", "
|
|
67
|
+
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 "项目名称"' } },
|
|
65
68
|
"progress list": read("progress list", " <id>", "读取指定项目的进展。", [...page, ...dates]),
|
|
66
69
|
checkins: read("checkins", "", "列出手写进展。", [...page, ...dates]),
|
|
67
70
|
context: read("context", "", "读取工作台上下文;--start 读取新工作对话的短上下文。", ["--start", "--limit", "--since"], "--start 不与 --limit 或 --since 混用;短读不覆盖账号全部内容,不据此判断账号为空。"),
|
|
@@ -87,12 +90,14 @@ for (const operation of ["delete", "archive", "visibility"]) {
|
|
|
87
90
|
...(operation === "visibility" ? { visibility: "必填:public 或 private;这是申请,不是直接公开。" } : {}),
|
|
88
91
|
}, ["target_type", "target_id", "reason", ...(operation === "visibility" ? ["visibility"] : [])], { target_type: "project", target_id: id, reason: "本人确认的申请", ...(operation === "visibility" ? { visibility: "private" } : {}) });
|
|
89
92
|
}
|
|
93
|
+
Object.assign(help, scheduleCommandHelp);
|
|
94
|
+
Object.assign(help, calendarCommandHelp);
|
|
90
95
|
export function commandHelp(positionals) {
|
|
91
96
|
const [first, second] = positionals;
|
|
92
97
|
const pair = `${first} ${second}`;
|
|
93
98
|
if (Object.hasOwn(help, pair))
|
|
94
99
|
return help[pair];
|
|
95
|
-
if (["auth", "request", "progress", "source", "cognition", "stage"].includes(first))
|
|
100
|
+
if (["auth", "request", "progress", "source", "cognition", "stage", "schedule", "daily-summary"].includes(first))
|
|
96
101
|
return undefined;
|
|
97
102
|
if (first === "knowledge" && second !== undefined)
|
|
98
103
|
return undefined;
|
package/dist/commands.js
CHANGED
|
@@ -13,6 +13,8 @@ import { readHabit } from "./habit-read.js";
|
|
|
13
13
|
import { citationOrigin, webCitations } from "./web-citations.js";
|
|
14
14
|
import { KNOWLEDGE_GROWTH_STEPS, knowledgeGrowthMetadata } from "./generated/knowledge-growth.js";
|
|
15
15
|
import { inputDetails, invalidInput, validateInput, validateCommandArguments } from "./input-validation.js";
|
|
16
|
+
import { executeSchedule } from "./schedule.js";
|
|
17
|
+
import { executeCalendar } from "./calendar.js";
|
|
16
18
|
const root = "/api/v1/agent";
|
|
17
19
|
// Go limits display text by Unicode code points, not UTF-16 code units.
|
|
18
20
|
const runeLength = (value) => [...value].length;
|
|
@@ -323,6 +325,10 @@ async function dispatch(p, flags, ctx) {
|
|
|
323
325
|
return invalid();
|
|
324
326
|
}
|
|
325
327
|
export async function executeCommand(positionals, flags, ctx) {
|
|
328
|
+
if (positionals[0] === "schedule")
|
|
329
|
+
return executeSchedule(positionals, flags, ctx);
|
|
330
|
+
if (["calendar", "timeline", "daily-summary"].includes(positionals[0]))
|
|
331
|
+
return executeCalendar(positionals, flags, ctx);
|
|
326
332
|
try {
|
|
327
333
|
validateCommandArguments(positionals, flags);
|
|
328
334
|
const result = await dispatch(positionals, flags, ctx);
|
package/dist/doctor.js
CHANGED
|
@@ -12,11 +12,13 @@ const localMessages = {
|
|
|
12
12
|
platform_detection_failed: ["无法完成平台程序检查。", "请检查本地程序目录后重新运行只读检查,不要启动客户端。"],
|
|
13
13
|
command_missing: ["一间草堂命令行尚未完整安装。", "请核对本地安装结果,再由本人决定是否运行 setup。"],
|
|
14
14
|
command_modified: ["已有命令行与当前候选不一致。", "请保留现有文件,由本人核对安装来源和差异,不要自动覆盖或删除。"],
|
|
15
|
+
command_outdated: ["已核对为旧版受管命令行,需要更新。", "沿用原来的名字运行 npx @yijiancaotang/cli@latest setup --agent <你的名字> 更新,不需要重新批准连接。"],
|
|
15
16
|
command_not_on_path: ["当前找不到可直接使用的一间草堂命令。", "请先核对安装是否完整;有已核验的完整路径时使用它,不修改 shell 或 PATH 配置。"],
|
|
16
17
|
command_version_unknown: ["无法确认本地命令行版本。", "请核对本地安装版本,不要自动升级或执行未知程序。"],
|
|
17
18
|
no_platform_detected: ["尚未识别到支持的 AI 客户端程序。", "请由本人确认 Claude Code、Codex、OpenClaw,或 macOS 上 Kimi Code、Kimi 工作模式、WorkBuddy 的程序安装位置后重新检查。"],
|
|
18
19
|
skills_missing: ["已识别平台的技能包不完整。", "请按当前安装包的技能清单核对所选平台,再由本人决定是否运行 setup。"],
|
|
19
20
|
skills_modified: ["已识别平台的技能内容与本包不一致。", "请保留现有技能,由本人核对差异;不要自动覆盖或删除。"],
|
|
21
|
+
skills_outdated: ["已核对为这个名字的旧版受管技能,需要更新。", "沿用原来的名字运行 npx @yijiancaotang/cli@latest setup --agent <你的名字> 更新,不需要重新批准连接。"],
|
|
20
22
|
skills_unsafe: ["已识别平台的技能目录未通过安全检查。", "请由本人检查技能目录的属主、权限和符号链接,不要自动修复或提权。"],
|
|
21
23
|
};
|
|
22
24
|
const hasLocalCode = (code) => Object.hasOwn(localMessages, code);
|
|
@@ -152,17 +154,20 @@ export async function doctor(ctx) {
|
|
|
152
154
|
const ids = PLATFORM_IDS;
|
|
153
155
|
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))
|
|
154
156
|
addLocal("local_inspection_failed");
|
|
157
|
+
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))
|
|
158
|
+
addLocal("local_inspection_failed");
|
|
155
159
|
const platforms = ids.map((id) => {
|
|
156
160
|
const platform = Array.isArray(local?.platforms) ? local.platforms.find((candidate) => candidate?.id === id) : undefined;
|
|
157
161
|
const detected = platform?.detected === true;
|
|
158
162
|
const skills = Array.isArray(platform?.skills) ? platform.skills : [];
|
|
159
163
|
let status = "not_checked";
|
|
160
164
|
if (detected) {
|
|
161
|
-
status = skills.some((skill) => !skill || !skillNames.includes(skill.name) || skill.status === "unsafe" || !["installed", "missing", "modified"].includes(skill.status)) ? "unsafe"
|
|
165
|
+
status = skills.some((skill) => !skill || !skillNames.includes(skill.name) || skill.status === "unsafe" || !["installed", "outdated", "missing", "modified"].includes(skill.status)) ? "unsafe"
|
|
162
166
|
: skills.some((skill) => skill.status === "modified") ? "modified"
|
|
163
|
-
: !completeSkillInventory(skills.map(skill => skill.name)) || skills.some((skill) => skill.status
|
|
167
|
+
: !completeSkillInventory(skills.map(skill => skill.name)) || skills.some((skill) => skill.status === "missing") ? "missing"
|
|
168
|
+
: skills.some((skill) => skill.status === "outdated") ? "outdated" : "installed";
|
|
164
169
|
if (status !== "installed")
|
|
165
|
-
addLocal(status === "unsafe" ? "skills_unsafe" : status === "modified" ? "skills_modified" : "skills_missing");
|
|
170
|
+
addLocal(status === "unsafe" ? "skills_unsafe" : status === "modified" ? "skills_modified" : status === "outdated" ? "skills_outdated" : "skills_missing");
|
|
166
171
|
}
|
|
167
172
|
// Report aggregate skill state, not unchecked names or filesystem paths from the inspector.
|
|
168
173
|
const installed = detected ? new Set(skills.filter((skill) => skill?.status === "installed" && skillNames.includes(skill.name)).map((skill) => skill.name)).size : 0;
|
|
@@ -210,5 +215,7 @@ export async function doctor(ctx) {
|
|
|
210
215
|
authExit = failed.exitCode;
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
|
-
|
|
218
|
+
const skillPlatforms = platforms.filter(platform => platform.skills.status === "installed" || local?.skill_platforms?.includes(platform.id)).map(platform => platform.id);
|
|
219
|
+
const supplement = ctx.agent && skillPlatforms.length === 0 ? ["可以用 setup --platform <平台> 补装技能,不影响现有连接。", "仅检查了已知默认目录和本次显式目录参数;若以前使用自定义目录,请保留原来的目录参数再检查,不能据此判断其它位置是否装过。"] : [];
|
|
220
|
+
return { result: { status: 200, body: { readiness_scope: "local_cli_and_skills", ...(ctx.agent ? { agent_name: ctx.agent.name, own_connection: auth.status === "valid", skill_platform: skillPlatforms[0] ?? "generic", skill_platforms: skillPlatforms, skill_search_scope: "known_paths_only" } : {}), end_to_end_verified: false, command, platforms, auth, network, issues, next_actions: [...new Set([...issues.map((value) => value.next_action), ...supplement])], ready: issues.length === 0 && auth.status === "valid" } }, exitCode: authExit || localExit };
|
|
214
221
|
}
|
package/dist/habit-read.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asRecord, CliError, expectOK, networkFailure } from "./errors.js";
|
|
2
|
-
import { verifiedHabit } from "./habit-wire.js";
|
|
2
|
+
import { verifiedHabit, habitWorkbenchDisabled } from "./habit-wire.js";
|
|
3
3
|
export async function readHabit(ctx, start) {
|
|
4
4
|
let result;
|
|
5
5
|
try {
|
|
@@ -14,6 +14,8 @@ export async function readHabit(ctx, start) {
|
|
|
14
14
|
throw new CliError("unauthorized", "草堂授权已失效,未读到本次内容。", "先继续当前工作,请本人核对设备授权;不要自动重新授权。", 3, { http_status: 401 });
|
|
15
15
|
if (result.status >= 500)
|
|
16
16
|
throw new CliError("server_error", "草堂服务暂时不可用,未读到本次内容。", "先继续当前工作,稍后核对服务;不要反复重试或重新授权。", 7, { http_status: result.status });
|
|
17
|
+
if (start && result.status === 403 && habitWorkbenchDisabled(result.body))
|
|
18
|
+
throw new CliError("capability_disabled", "“读取工作台”开关未开启,未读到本次开工内容。", "请本人到网页的 Agent 管理核对开关;本次不记开工,不换入口、重新授权或反复重试。", 4, { http_status: 403, required_capability: "read_workbench" });
|
|
17
19
|
expectOK(result, "read");
|
|
18
20
|
const body = verifiedHabit(result.body, start);
|
|
19
21
|
if (result.status !== 200 || !body)
|
package/dist/habit-wire.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dailySummaryResult } from "./calendar-contract.js";
|
|
1
2
|
import { validDate as validDay } from "./query-validation.js";
|
|
2
3
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
3
4
|
const list = (value, n) => { if (!Array.isArray(value) || value.length > n)
|
|
@@ -25,8 +26,10 @@ function day(value) { const r = record(value); if (!validDay(r.date))
|
|
|
25
26
|
throw Error(); return { kind: p.kind, id: id(p.id), summary: str(p.summary, 120), ...(p.project_id === undefined ? {} : { project_id: id(p.project_id) }) }; }); if (items.length > total)
|
|
26
27
|
throw Error(); return { date: r.date, total, items }; }
|
|
27
28
|
function step(value) { const r = record(value); return { id: id(r.id), project_id: id(r.project_id), summary: str(r.summary, 120), created_at: str(r.created_at, 64) }; }
|
|
28
|
-
function gap(value) { const r = record(value); if (typeof r.reason !== "string" || !["capability_disabled", "read_failed", "community_disabled"].includes(r.reason))
|
|
29
|
-
throw Error();
|
|
29
|
+
function gap(value) { const r = record(value), section = key(r.section); if (typeof r.reason !== "string" || !["capability_disabled", "read_failed", "community_disabled", "record_failed"].includes(r.reason))
|
|
30
|
+
throw Error(); if (r.reason === "record_failed" && (section !== "calendar_start" || r.required_capability !== "read_workbench"))
|
|
31
|
+
throw Error(); if (section === "calendar_start" && (r.required_capability !== "read_workbench" || !["capability_disabled", "record_failed"].includes(r.reason)))
|
|
32
|
+
throw Error(); return { section, ...(r.required_capability === undefined ? {} : { required_capability: key(r.required_capability) }), reason: r.reason }; }
|
|
30
33
|
function official(value) { const r = record(value), total = count(r.unread_count), title = str(r.latest_title, 120); if ((total === 0 && title !== "") || (total > 0 && !title))
|
|
31
34
|
throw Error(); return { unread_count: total, latest_title: title }; }
|
|
32
35
|
// Project the bounded wire fields rather than forwarding unknown content or
|
|
@@ -43,14 +46,42 @@ export function verifiedHabit(value, start) {
|
|
|
43
46
|
return;
|
|
44
47
|
const rules = r.rules === null ? null : list(r.rules, 20).map(rule), p = r.pending === null ? null : pending(r.pending), days = r.recent_days === null ? null : list(r.recent_days, 3).map(day), next = r.next_step === null ? null : step(r.next_step), gaps = list(r.missing_sections, 32).map(gap);
|
|
45
48
|
const owner = typeof r.owner_id === "string" && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}(?![\s\S])/i.test(r.owner_id) ? r.owner_id : undefined;
|
|
46
|
-
const
|
|
49
|
+
const schedules = r.schedule_items === undefined ? undefined : r.schedule_items === null ? null : verifiedScheduleStart(r.schedule_items);
|
|
50
|
+
const summary = r.last_daily_summary === undefined ? undefined : r.last_daily_summary === null ? null : dailySummaryResult(r.last_daily_summary);
|
|
51
|
+
const incomplete = rules === null || p === null || days === null || schedules === null || gaps.some(g => g.reason !== "community_disabled" && g.reason !== "record_failed");
|
|
52
|
+
const onlyStartRecordGap = gaps.length > 0 && gaps.every(g => g.section === "calendar_start" && g.reason === "record_failed");
|
|
47
53
|
if ((incomplete || gaps.length > 0) && !r.partial)
|
|
48
54
|
return;
|
|
49
|
-
const contradicted = r.account_empty === true && (!!rules?.length || !!p?.total || !!days?.length || !!next);
|
|
55
|
+
const contradicted = r.account_empty === true && (!!rules?.length || !!p?.total || !!days?.length || !!next || !!schedules?.total_stored || !!summary);
|
|
50
56
|
return { ...(owner ? { owner_id: owner } : {}), ...(r.official_notices === undefined ? {} : { official_notices: r.official_notices === null ? null : official(r.official_notices) }), rules, pending: p, recent_days: days, next_step: next,
|
|
51
|
-
account_empty: !owner || r.partial || incomplete || contradicted ? null : r.account_empty, partial: r.partial, missing_sections: gaps };
|
|
57
|
+
...(schedules === undefined ? {} : { schedule_items: schedules }), ...(summary === undefined ? {} : { last_daily_summary: summary }), account_empty: !owner || (r.partial && !onlyStartRecordGap) || incomplete || contradicted ? null : r.account_empty, partial: r.partial, missing_sections: gaps };
|
|
52
58
|
}
|
|
53
59
|
catch {
|
|
54
60
|
return undefined;
|
|
55
61
|
}
|
|
56
62
|
}
|
|
63
|
+
import { verifiedScheduleStart } from "./schedule-start-wire.js";
|
|
64
|
+
// A closed content switch is diagnosed only from an exact, non-conflicting
|
|
65
|
+
// start receipt; bare 403 and unrelated capabilities remain unknown.
|
|
66
|
+
export function habitWorkbenchDisabled(value) {
|
|
67
|
+
const body = record(value);
|
|
68
|
+
if (!body)
|
|
69
|
+
return false;
|
|
70
|
+
const code = body.code ?? body.error;
|
|
71
|
+
return code === "capability_disabled" && (body.code === undefined || body.code === code) && (body.error === undefined || body.error === code)
|
|
72
|
+
&& body.required_capability === "read_workbench" && (!Object.hasOwn(body, "missing_sections") || Array.isArray(body.missing_sections) && body.missing_sections.length === 0);
|
|
73
|
+
}
|
|
74
|
+
// Derived only from validated protocol metadata; upstream free text is ignored.
|
|
75
|
+
export function calendarStartNotice(value) {
|
|
76
|
+
const body = record(value), rows = body?.missing_sections;
|
|
77
|
+
if (!Array.isArray(rows))
|
|
78
|
+
return "";
|
|
79
|
+
const reasons = new Set(rows.slice(0, 32).filter(v => record(v)?.section === "calendar_start" && record(v)?.required_capability === "read_workbench").map(v => record(v)?.reason));
|
|
80
|
+
if (reasons.size !== 1)
|
|
81
|
+
return "";
|
|
82
|
+
if (reasons.has("record_failed"))
|
|
83
|
+
return "这次没记上开工时间,已读内容仍可使用;不自动重试。";
|
|
84
|
+
if (reasons.has("capability_disabled"))
|
|
85
|
+
return "读取工作台开关未开启,这次未记录开工时间;其余内容按原权限返回。";
|
|
86
|
+
return "";
|
|
87
|
+
}
|
package/dist/http.js
CHANGED
|
@@ -3,6 +3,8 @@ import { isPagedQueryPath, isQueryParameterError, isProjectIDParameterError } fr
|
|
|
3
3
|
import { isSearchParameterError } from "./query-search.js";
|
|
4
4
|
import { isDailyParameterError } from "./query-daily.js";
|
|
5
5
|
import { isCreatePath } from "./create-bundle.js";
|
|
6
|
+
import { isScheduleHeaderPath, scheduleHeaders } from "./schedule-contract.js";
|
|
7
|
+
import { isCalendarHeaderPath, calendarHeaders } from "./calendar-contract.js";
|
|
6
8
|
const maxJSONBytes = 4 * 1024 * 1024;
|
|
7
9
|
const invalidResponse = () => new CliError("invalid_response", "一间草堂返回了无法确认的响应。", "请先核对网页中的实际结果,不要自动重复写入。", 7);
|
|
8
10
|
const invalidRequest = () => new CliError("invalid_request", "请求参数无效。", "请检查命令帮助和输入数据。");
|
|
@@ -100,7 +102,15 @@ export class ApiClient {
|
|
|
100
102
|
}
|
|
101
103
|
if (options.body && typeof options.body === "object" && "device_code" in options.body && typeof options.body.device_code === "string")
|
|
102
104
|
this.secrets.add(options.body.device_code);
|
|
103
|
-
if (
|
|
105
|
+
if (isScheduleHeaderPath(url.pathname, method)) {
|
|
106
|
+
for (const [name, value] of Object.entries(scheduleHeaders(url.pathname, method, options.headers)))
|
|
107
|
+
headers.set(name, value);
|
|
108
|
+
}
|
|
109
|
+
else if (isCalendarHeaderPath(url.pathname, method)) {
|
|
110
|
+
for (const [name, value] of Object.entries(calendarHeaders(url.pathname, method, options.headers)))
|
|
111
|
+
headers.set(name, value);
|
|
112
|
+
}
|
|
113
|
+
else if (options.headers !== undefined) {
|
|
104
114
|
if (!options.headers || Object.keys(options.headers).some((key) => key !== "Idempotency-Key"))
|
|
105
115
|
throw invalidRequest();
|
|
106
116
|
const key = options.headers["Idempotency-Key"];
|