@sakanano-moe/agenthelm 6.5.3 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/bin/agenthelm.js +1 -1
  2. package/package.json +2 -2
  3. package/runtime/index.json +80 -0
  4. package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +19 -0
  5. package/runtime/plugins/agent-pack/.mcp.json +12 -0
  6. package/runtime/plugins/agent-pack/hooks/hooks.json +16 -0
  7. package/runtime/plugins/agent-pack/mcp.json +14 -0
  8. package/runtime/plugins/agent-pack/plugin.json +20 -0
  9. package/runtime/plugins/agent-pack/references/capability-config.md +124 -0
  10. package/runtime/{agenthelm.py → plugins/agent-pack/runtime/agenthelm.py} +42 -1
  11. package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +388 -0
  12. package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +46 -0
  13. package/runtime/plugins/agents-init/.codex-plugin/plugin.json +22 -0
  14. package/runtime/plugins/agents-init/content.json +31 -0
  15. package/runtime/plugins/agents-init/plugin.json +19 -0
  16. package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +376 -0
  17. package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +119 -0
  18. package/runtime/plugins/agents-init/skills/agents-init/index.html +357 -0
  19. package/runtime/plugins/agents-init/skills/agents-init/launcher.sh +93 -0
  20. package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +171 -0
  21. package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +1190 -0
  22. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +678 -0
  23. package/runtime/plugins/agents-init/skills/agents-init/skill.json +122 -0
  24. package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +26 -0
  25. package/runtime/plugins/opinion-manager/catalog/README.md +15 -0
  26. package/runtime/plugins/opinion-manager/catalog/rules/.gitkeep +0 -0
  27. package/runtime/plugins/opinion-manager/catalog/rules/om.align-requirements/versions/1.0.0.json +11 -0
  28. package/runtime/plugins/opinion-manager/catalog/rules/om.assumptions-authority/versions/1.0.0.json +11 -0
  29. package/runtime/plugins/opinion-manager/catalog/rules/om.batch-questions/versions/1.0.0.json +11 -0
  30. package/runtime/plugins/opinion-manager/catalog/rules/om.bilingual-terms/versions/1.0.0.json +11 -0
  31. package/runtime/plugins/opinion-manager/catalog/rules/om.calculations/versions/1.0.0.json +11 -0
  32. package/runtime/plugins/opinion-manager/catalog/rules/om.chinese-response/versions/1.0.0.json +11 -0
  33. package/runtime/plugins/opinion-manager/catalog/rules/om.clean-deliverable/versions/1.0.0.json +11 -0
  34. package/runtime/plugins/opinion-manager/catalog/rules/om.complete-options/versions/1.0.0.json +11 -0
  35. package/runtime/plugins/opinion-manager/catalog/rules/om.conclusion-first/versions/1.0.0.json +11 -0
  36. package/runtime/plugins/opinion-manager/catalog/rules/om.concrete-titles/versions/1.0.0.json +11 -0
  37. package/runtime/plugins/opinion-manager/catalog/rules/om.consistent-items/versions/1.0.0.json +11 -0
  38. package/runtime/plugins/opinion-manager/catalog/rules/om.device-readable/versions/1.0.0.json +11 -0
  39. package/runtime/plugins/opinion-manager/catalog/rules/om.editable-diagrams/versions/1.0.0.json +11 -0
  40. package/runtime/plugins/opinion-manager/catalog/rules/om.evidence-inference/versions/1.0.0.json +11 -0
  41. package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-chinese/versions/1.0.0.json +11 -0
  42. package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-project-language/versions/1.0.0.json +11 -0
  43. package/runtime/plugins/opinion-manager/catalog/rules/om.markdown-structure/versions/1.0.0.json +11 -0
  44. package/runtime/plugins/opinion-manager/catalog/rules/om.portable-files/versions/1.0.0.json +11 -0
  45. package/runtime/plugins/opinion-manager/catalog/rules/om.report-context/versions/1.0.0.json +11 -0
  46. package/runtime/plugins/opinion-manager/catalog/rules/om.report-formats/versions/1.0.0.json +11 -0
  47. package/runtime/plugins/opinion-manager/catalog/rules/om.report-review/versions/1.0.0.json +11 -0
  48. package/runtime/plugins/opinion-manager/catalog/rules/om.response-brief/versions/1.0.0.json +11 -0
  49. package/runtime/plugins/opinion-manager/catalog/rules/om.response-dense/versions/1.0.0.json +11 -0
  50. package/runtime/plugins/opinion-manager/catalog/rules/om.rules-tools/versions/1.0.0.json +11 -0
  51. package/runtime/plugins/opinion-manager/catalog/rules/om.semantic-wrap/versions/1.0.0.json +11 -0
  52. package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-proportional/versions/1.0.0.json +11 -0
  53. package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-three-level/versions/1.0.0.json +11 -0
  54. package/runtime/plugins/opinion-manager/catalog/rules/om.tables-compact/versions/1.0.0.json +11 -0
  55. package/runtime/plugins/opinion-manager/catalog/rules/om.tables-list-first/versions/1.0.0.json +11 -0
  56. package/runtime/plugins/opinion-manager/catalog/rules/om.test-evidence/versions/1.0.0.json +11 -0
  57. package/runtime/plugins/opinion-manager/catalog/rules/om.versioned-files/versions/1.0.0.json +11 -0
  58. package/runtime/plugins/opinion-manager/catalog/templates/.gitkeep +0 -0
  59. package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/compact-tables/versions/1.0.0.json +21 -0
  60. package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/list-first/versions/1.0.0.json +21 -0
  61. package/runtime/plugins/opinion-manager/catalog/templates/development-standard/chinese-maintenance/versions/1.0.0.json +15 -0
  62. package/runtime/plugins/opinion-manager/catalog/templates/development-standard/project-language/versions/1.0.0.json +15 -0
  63. package/runtime/plugins/opinion-manager/catalog/templates/response-style/brief-chinese/versions/1.0.0.json +19 -0
  64. package/runtime/plugins/opinion-manager/catalog/templates/response-style/dense-chinese/versions/1.0.0.json +19 -0
  65. package/runtime/plugins/opinion-manager/catalog/templates/scenario/concise-project/versions/1.0.0.json +39 -0
  66. package/runtime/plugins/opinion-manager/catalog/templates/scenario/reviewed-project/versions/1.0.0.json +39 -0
  67. package/runtime/plugins/opinion-manager/catalog/templates/work-method/proportional/versions/1.0.0.json +20 -0
  68. package/runtime/plugins/opinion-manager/catalog/templates/work-method/three-level/versions/1.0.0.json +20 -0
  69. package/runtime/plugins/opinion-manager/content.json +63 -0
  70. package/runtime/plugins/opinion-manager/plugin.json +18 -0
  71. package/runtime/plugins/opinion-manager/references/lock.schema.json +14 -0
  72. package/runtime/plugins/opinion-manager/references/onboarding.md +57 -0
  73. package/runtime/plugins/opinion-manager/references/profile.schema.json +56 -0
  74. package/runtime/plugins/opinion-manager/references/rule.schema.json +16 -0
  75. package/runtime/plugins/opinion-manager/references/template.schema.json +71 -0
  76. package/runtime/plugins/opinion-manager/references/versioned-template.schema.json +19 -0
  77. package/runtime/plugins/opinion-manager/references/versioning.md +263 -0
  78. package/runtime/plugins/opinion-manager/runtime/constants.py +7 -0
  79. package/runtime/plugins/opinion-manager/runtime/context.py +180 -0
  80. package/runtime/plugins/opinion-manager/runtime/lifecycle.py +395 -0
  81. package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +419 -0
  82. package/runtime/plugins/opinion-manager/runtime/versioned.py +705 -0
  83. package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +62 -0
  84. package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +4 -0
  85. package/runtime/plugins/opinion-manager/templates/.gitkeep +1 -0
  86. package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +18 -0
  87. package/runtime/plugins/project-orchestrator/content.json +37 -0
  88. package/runtime/plugins/project-orchestrator/plugin.json +19 -0
  89. package/runtime/plugins/project-orchestrator/references/asset-synonyms.example.json +8 -0
  90. package/runtime/plugins/project-orchestrator/references/case-workspaces.md +159 -0
  91. package/runtime/plugins/project-orchestrator/references/case.schema.json +31 -0
  92. package/runtime/plugins/project-orchestrator/runtime/assets.py +1288 -0
  93. package/runtime/plugins/project-orchestrator/runtime/case.py +557 -0
  94. package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +350 -0
  95. package/runtime/plugins/project-orchestrator/runtime/memory.py +363 -0
  96. package/runtime/plugins/project-orchestrator/runtime/project_base.py +107 -0
  97. package/runtime/plugins/project-orchestrator/runtime/project_cli.py +599 -0
  98. package/runtime/plugins/project-orchestrator/runtime/project_demo.py +504 -0
  99. package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +470 -0
  100. package/runtime/plugins/project-orchestrator/runtime/project_menu.py +394 -0
  101. package/runtime/plugins/project-orchestrator/runtime/project_real.py +522 -0
  102. package/runtime/plugins/project-orchestrator/runtime/project_write.py +329 -0
  103. package/runtime/plugins/project-orchestrator/runtime/wf.py +439 -0
  104. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +230 -0
  105. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +4 -0
  106. package/runtime/plugins/project-orchestrator/templates/CASE.md +39 -0
  107. /package/runtime/{agent_pack_config.py → plugins/agent-pack/runtime/agent_pack_config.py} +0 -0
  108. /package/runtime/{mcp_env_launcher.py → plugins/agent-pack/runtime/mcp_env_launcher.py} +0 -0
  109. /package/runtime/{mcp_server.py → plugins/agent-pack/runtime/mcp_server.py} +0 -0
@@ -0,0 +1,394 @@
1
+ """项目入口的交互菜单:按想完成的任务组织,每一步都能继续操作或回车返回。
2
+
3
+ 只在终端里使用;脚本与 CI 请用 project_cli 的子命令,它们从不等待输入。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import re
8
+
9
+ import project_cli as cli
10
+ from project_cli import S, EntryError
11
+
12
+ MENU = (
13
+ ("1", "找资料", "按关键词、日期、类型查找并打开", "find"),
14
+ ("2", "看进展", "浏览事项、下一动作与等待对象", "progress"),
15
+ ("3", "项目指南", "这个项目做什么、资料在哪、怎样开始", "guide"),
16
+ ("4", "检查项目与环境", "依赖、插件、记录与索引", "health"),
17
+ None,
18
+ ("5", "修复与更新", "重建索引、安装与更新", "fix"),
19
+ ("6", "记录事项", "新建、追加记录、更新状态", "record"),
20
+ ("7", "项目工具", "项目自行声明的操作", "tools"),
21
+ )
22
+ ACTION_RE = re.compile(r"^([ofcvr])\s*(\d+)$")
23
+ RECORD_ACTIONS = (("1", "追加一条记录", "log"), ("2", "更新下一动作", "next_action"),
24
+ ("3", "改为等待(填写在等谁或等什么)", "waiting"), ("4", "关闭(标为已完成)", "close"),
25
+ ("5", "恢复(标为进行中)", "reopen"), ("6", "登记资料或交付物", "deliverable"))
26
+
27
+
28
+ class Back(Exception):
29
+ pass
30
+
31
+
32
+ class Quit(Exception):
33
+ pass
34
+
35
+
36
+ def ask(prompt: str) -> str:
37
+ try:
38
+ line = input(S.bold(S.cyan(f"\n{prompt} › "))).strip()
39
+ except EOFError:
40
+ raise Quit from None
41
+ if line in ("q", "quit", "退出"):
42
+ raise Quit
43
+ if line in ("", "b", "返回"):
44
+ raise Back
45
+ return line
46
+
47
+
48
+ def choose(prompt: str, default: str) -> str:
49
+ """Ask with a default: an empty answer means the default instead of going back."""
50
+ try:
51
+ return ask(prompt)
52
+ except Back:
53
+ return default
54
+
55
+
56
+ def show(lines: list[str]) -> None:
57
+ print("\n".join(lines))
58
+
59
+
60
+ def fail(error: Exception) -> None:
61
+ code = getattr(error, "code", "usage")
62
+ show(cli.render_error(EntryError(str(error), code, getattr(error, "next_steps", []))))
63
+
64
+
65
+ def sub(screen, *args) -> None:
66
+ """Run a nested screen; its "回车返回" comes back here instead of to the home page."""
67
+ try:
68
+ screen(*args)
69
+ except Back:
70
+ pass
71
+
72
+
73
+ def confirm(prompt: str = "输入 yes 执行,回车取消") -> bool:
74
+ try:
75
+ return ask(prompt).lower() in ("yes", "y", "是")
76
+ except Back:
77
+ print(S.dim("已取消,没有写入。"))
78
+ return False
79
+
80
+
81
+ # ---------------------------------------------------------------- screens
82
+
83
+ def home(provider) -> None:
84
+ print()
85
+ show(cli.render_overview(provider))
86
+ capabilities = {item["id"]: item for item in provider.capabilities()}
87
+ health = provider.overview()["health"]
88
+ print()
89
+ for entry in MENU:
90
+ if entry is None:
91
+ print(S.dim(" ─────────── 维护"))
92
+ continue
93
+ key, title, hint, capability = entry
94
+ state = capabilities[capability]
95
+ if capability == "health" and any(health.values()):
96
+ hint = " · ".join(f"{health[k]} 项{label}" for k, label in cli.LEVELS if health.get(k))
97
+ if state["state"] != "available":
98
+ hint = S.yellow(f"不可用:{state['reason']}")
99
+ print(f" {S.bold(S.cyan(key))} {cli.pad(title, 16)}{S.dim(hint) if state['state'] == 'available' else hint}")
100
+ print(S.dim("\n 输入序号;也可以直接输入关键词查资料。 ? 帮助 · q 退出"))
101
+
102
+
103
+ def find_screen(provider, first: str | None = None) -> None:
104
+ show(cli.header(provider, "找资料"))
105
+ print(S.dim("输入关键词,可带日期(2025-12、10月)与 type:pdf、in:片段;回车返回"))
106
+ result = None
107
+ pending = first
108
+ while True:
109
+ line, pending = (pending, None) if pending else (ask("查找"), None)
110
+ match = ACTION_RE.match(line) or re.match(r"^()(\d+)$", line)
111
+ if match and result is not None:
112
+ key, number = match.group(1) or "o", int(match.group(2))
113
+ try:
114
+ item = cli._act_target(result, number)
115
+ except EntryError as exc:
116
+ fail(exc)
117
+ continue
118
+ if key == "v":
119
+ show(cli.render_versions(item) if item["versions"] else [S.dim("这份资料没有其他版本")])
120
+ elif key == "r":
121
+ show(cli.render_refs(provider.refs(item["path"])))
122
+ else:
123
+ print(provider.act({"o": "open", "f": "reveal", "c": "copy"}[key], item["path"])["message"])
124
+ continue
125
+ terms, ext, container = [], None, None
126
+ for token in line.split():
127
+ if token.startswith("type:"):
128
+ ext = token[5:]
129
+ elif token.startswith("in:"):
130
+ container = token[3:]
131
+ else:
132
+ terms.append(token)
133
+ try:
134
+ result = provider.find(terms, ext=ext, container=container)
135
+ except EntryError as exc:
136
+ fail(exc)
137
+ print(S.dim("输入 5 可直接去「修复与更新」。"))
138
+ if ask("下一步") != "5":
139
+ return
140
+ sub(fix_screen, provider, ["rebuild-index"] if exc.code == "index-broken" else None)
141
+ if provider.index_state()["status"] == "broken":
142
+ return
143
+ pending = line # 修好了:用同一个词再查一次
144
+ continue
145
+ show(cli.render_find(result))
146
+ if result["results"]:
147
+ print(S.dim("o1 打开 · f1 显示所在目录 · c1 复制路径 · v1 看版本 · r1 看引用 · 继续输入词再查 · 回车返回"))
148
+
149
+
150
+ def item_screen(provider, item: dict) -> None:
151
+ show(cli.render_item(item))
152
+ while True:
153
+ print(S.dim("o 打开权威记录 · d1 打开交付物 · f 显示所在目录 · w 为它记录 · 回车返回"))
154
+ line = ask("事项")
155
+ if line == "o":
156
+ print(provider.act("open", item["path"])["message"])
157
+ elif line == "f":
158
+ print(provider.act("reveal", item["path"])["message"])
159
+ elif line == "w":
160
+ sub(record_screen, provider, item["id"])
161
+ elif re.fullmatch(r"d\d+", line) and 1 <= int(line[1:]) <= len(item["deliverables"]):
162
+ print(provider.act("open", item["deliverables"][int(line[1:]) - 1])["message"])
163
+ else:
164
+ print(S.yellow("没有这个操作。"))
165
+
166
+
167
+ def progress_screen(provider) -> None:
168
+ term, expand = None, False
169
+ while True:
170
+ show(cli.header(provider, "看进展"))
171
+ lines, numbered = cli.render_items(provider.items(term=term), expand_closed=expand)
172
+ show(([S.dim(f"筛选:{term}(输入 * 清除)")] if term else []) + lines)
173
+ print(S.dim("序号看详情 · 输入词查找(标题、别名、旧编号)· all 展开已完成 · 回车返回"))
174
+ line = ask("进展")
175
+ if line.isdigit() and 1 <= int(line) <= len(numbered):
176
+ sub(item_screen, provider, provider.item(numbered[int(line) - 1]["id"]))
177
+ elif line == "all":
178
+ expand = True
179
+ elif line == "*":
180
+ term = None
181
+ else:
182
+ term = line
183
+ matches = provider.items(term=term)["items"]
184
+ if len(matches) == 1:
185
+ sub(item_screen, provider, provider.item(matches[0]["id"]))
186
+ term = None
187
+ elif not matches:
188
+ print(S.yellow(f"没有事项匹配“{line}”。"))
189
+ term = None
190
+ else:
191
+ expand = True
192
+
193
+
194
+ def guide_screen(provider) -> None:
195
+ show(cli.header(provider, "项目指南") + cli.render_guide(provider.guide()))
196
+ ask("回车返回")
197
+
198
+
199
+ def health_screen(provider) -> None:
200
+ show(cli.header(provider, "检查项目与环境"))
201
+ lines, numbered = cli.render_health(provider.health())
202
+ show(lines)
203
+ while True:
204
+ print(S.dim("序号去处理 · 回车返回"))
205
+ line = ask("检查")
206
+ if not (line.isdigit() and 1 <= int(line) <= len(numbered)):
207
+ print(S.yellow("没有这一项。"))
208
+ continue
209
+ finding = numbered[int(line) - 1]
210
+ actions = [step["action"] for step in finding["next"] if step["action"]]
211
+ if not actions:
212
+ print(S.dim("这一项不能在菜单里直接处理:") + finding["next"][0]["label"])
213
+ continue
214
+ verb, _, target = actions[0].partition(":")
215
+ if verb == "fix":
216
+ sub(fix_screen, provider, [target])
217
+ elif verb == "open":
218
+ print(provider.act("open", target)["message"])
219
+
220
+
221
+ def fix_screen(provider, preset: list[str] | None = None) -> None:
222
+ show(cli.header(provider, "修复与更新"))
223
+ print(S.dim("流程:检查 → 选择处理项 → 查看影响 → 确认执行 → 验证结果"))
224
+ available = provider.fix_plan([])["steps"]
225
+ if not available:
226
+ print(S.green("检查完成:没有需要处理的项。"))
227
+ return
228
+ if preset:
229
+ ids = [step["id"] for step in available if step["id"] in preset]
230
+ titles = [step["title"] for step in available if step["id"] in ids]
231
+ print(S.bold("1–2. 检查并选中:") + ("、".join(titles) or S.yellow("这一项已处理或当前不可处理")))
232
+ if not ids:
233
+ return
234
+ else:
235
+ print(S.bold("1. 检查结果"))
236
+ for number, step in enumerate(available, 1):
237
+ blocked = S.yellow(f"({step['blocked']})") if step["blocked"] else ""
238
+ print(f" {S.bold(S.cyan(str(number)))} {step['title']}{blocked}")
239
+ line = ask("2. 选择处理项(序号,空格分隔;all 全选)")
240
+ picks = range(1, len(available) + 1) if line == "all" else \
241
+ [int(token) for token in line.split() if token.isdigit() and 1 <= int(token) <= len(available)]
242
+ ids = [available[number - 1]["id"] for number in picks]
243
+ if not ids:
244
+ print(S.yellow("没有选中任何项。"))
245
+ return
246
+ choices: dict[str, str] = {}
247
+ if "add-entry" in ids and not provider.overview().get("launcher"):
248
+ name = choose("启动器文件名(回车用 menu)", "")
249
+ if name:
250
+ choices["entry-name"] = name
251
+ plan = provider.fix_plan(ids, dict(choices))
252
+ print(S.bold("3. 查看影响"))
253
+ show(cli.render_plan(plan))
254
+ asked = False
255
+ for step in plan["steps"]:
256
+ for item in step["custom"]:
257
+ if step["blocked"]:
258
+ continue
259
+ answer = choose(f"{item['path']} {item['state']}:k 不改(默认)/ a 采用", "k") \
260
+ if item["path"] == "README.md" else \
261
+ choose(f"{item['path']} {item['state']}:k 保留你的版本(默认)/ a 采用新模板", "k")
262
+ choices[item["path"]] = "accept" if answer == "a" else "keep"
263
+ asked = True
264
+ if asked:
265
+ plan = provider.fix_plan(ids, choices)
266
+ print(S.bold("按你的选择,最终将:"))
267
+ show(cli.render_plan(plan, choices))
268
+ if all(step["blocked"] for step in plan["steps"]):
269
+ print(S.yellow("所选项目前都不能执行,只能预览。"))
270
+ return
271
+ print(S.bold("4. 确认执行"))
272
+ if not confirm():
273
+ return
274
+ print(S.bold("5. 验证结果"))
275
+ show(cli.render_apply(provider.apply_fix(plan, choices)))
276
+
277
+
278
+ def record_screen(provider, target: str | None = None) -> None:
279
+ show(cli.header(provider, "记录事项"))
280
+ capability = next(item for item in provider.capabilities() if item["id"] == "record")
281
+ if capability["state"] != "available":
282
+ try:
283
+ provider.record_preview(None, {})
284
+ except EntryError as exc:
285
+ fail(exc)
286
+ return
287
+ if target is None:
288
+ line = ask("n 新建 · 或输入事项(编号、名称、旧编号)")
289
+ target = None if line == "n" else line
290
+ if target is None:
291
+ change = {"title": ask("标题"), "goal": ask("目标")}
292
+ try:
293
+ change["next_action"] = ask("下一动作(回车跳过)")
294
+ except Back:
295
+ change["next_action"] = ""
296
+ else:
297
+ try:
298
+ item = provider.item(target)
299
+ except EntryError as exc:
300
+ fail(exc)
301
+ return
302
+ print(f"{S.bold(item['title'])} [{cli.GROUP_LABEL.get(item['group'], item['status'])}]")
303
+ for key, title, _ in RECORD_ACTIONS:
304
+ print(f" {S.bold(S.cyan(key))} {title}")
305
+ choice = dict((key, field) for key, _, field in RECORD_ACTIONS).get(ask("要做什么"))
306
+ if choice is None:
307
+ print(S.yellow("没有这个操作。"))
308
+ return
309
+ target = item["id"]
310
+ if choice == "close":
311
+ change = {"status": "done"}
312
+ elif choice == "reopen":
313
+ change = {"status": "active"}
314
+ if not item["next_action"]:
315
+ change["next_action"] = ask("恢复后的下一动作")
316
+ elif choice == "waiting":
317
+ change = {"status": "waiting", "waiting_for": ask("在等谁或等什么")}
318
+ elif choice == "deliverable":
319
+ change = {"add_deliverable": [ask("文件路径(相对项目根目录,可从「找资料」c1 复制)")]}
320
+ else:
321
+ change = {choice: ask("内容")}
322
+ try:
323
+ preview = provider.record_preview(target, change)
324
+ except EntryError as exc:
325
+ fail(exc)
326
+ return
327
+ show(cli.render_preview(preview))
328
+ if not confirm("输入 yes 写入,回车取消"):
329
+ return
330
+ try:
331
+ print(S.green(provider.record_apply(preview)["verify"]))
332
+ except EntryError as exc:
333
+ fail(exc)
334
+
335
+
336
+ def tools_screen(provider) -> None:
337
+ tools = provider.tools()
338
+ show(cli.header(provider, "项目工具") + cli.render_tools(tools))
339
+ while tools:
340
+ line = ask("序号查看并执行 · 回车返回")
341
+ if not (line.isdigit() and 1 <= int(line) <= len(tools)):
342
+ print(S.yellow("没有这一项。"))
343
+ continue
344
+ tool = tools[int(line) - 1]
345
+ if tool["state"] != "available":
346
+ print(S.yellow(f"“{tool['title']}”当前不可用:{tool['reason']}"))
347
+ continue
348
+ show(cli.render_tools([tool]))
349
+ if confirm():
350
+ print(provider.run_tool(tool["id"])["message"])
351
+ if not tools:
352
+ ask("回车返回")
353
+
354
+
355
+ HELP = f"""{S.bold('怎么用')}
356
+ 首页输入序号进入;直接输入词就是找资料。各页回车返回上一级,q 随时退出。
357
+ 找资料结果:{S.cyan('o1')} 打开 · {S.cyan('f1')} 显示所在目录 · {S.cyan('c1')} 复制路径 · {S.cyan('v1')} 看版本 · {S.cyan('r1')} 看引用
358
+ 写入类操作(修复、记录、工具)都先显示影响,输入 yes 才执行。
359
+ {S.bold('不进菜单也可以')}
360
+ ./<启动器> find 词 · progress · show 事项 · guide · health · fix · record · tools
361
+ 加 --output json 得到结构化结果;脚本里从不等待输入。完整说明:--help"""
362
+
363
+ SCREENS = {"1": find_screen, "2": progress_screen, "3": guide_screen, "4": health_screen,
364
+ "5": fix_screen, "6": record_screen, "7": tools_screen}
365
+
366
+
367
+ def run(provider) -> int:
368
+ try:
369
+ import readline # noqa: F401 line editing and history
370
+ except ImportError:
371
+ pass
372
+ try:
373
+ while True:
374
+ home(provider)
375
+ try:
376
+ line = ask("选择")
377
+ except Back:
378
+ continue
379
+ try:
380
+ if line in SCREENS:
381
+ SCREENS[line](provider)
382
+ elif line in ("?", "help", "帮助"):
383
+ print(HELP)
384
+ else:
385
+ find_screen(provider, line)
386
+ except Back:
387
+ continue
388
+ except EntryError as exc:
389
+ fail(exc)
390
+ except Quit:
391
+ return cli.EXIT_OK
392
+ except KeyboardInterrupt:
393
+ print()
394
+ return cli.EXIT_INTERRUPTED