@sakanano-moe/agenthelm 6.7.0 → 6.11.0-rc.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.
Files changed (86) hide show
  1. package/package.json +1 -1
  2. package/runtime/index.json +6 -6
  3. package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +1 -1
  4. package/runtime/plugins/agent-pack/hooks/hooks.json +32 -0
  5. package/runtime/plugins/agent-pack/plugin.json +1 -1
  6. package/runtime/plugins/agent-pack/references/local-only.md +126 -0
  7. package/runtime/plugins/agent-pack/references/nudge.en.md +58 -0
  8. package/runtime/plugins/agent-pack/references/nudge.md +58 -0
  9. package/runtime/plugins/agent-pack/runtime/agent_pack_nudge.py +573 -0
  10. package/runtime/plugins/agent-pack/runtime/agenthelm.py +419 -107
  11. package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +77 -53
  12. package/runtime/plugins/agent-pack/runtime/i18n_agent_pack.py +78 -0
  13. package/runtime/plugins/agent-pack/runtime/local_only.py +1130 -0
  14. package/runtime/plugins/agent-pack/runtime/locale/en.json +385 -0
  15. package/runtime/plugins/agent-pack/runtime/mcp_server.py +25 -0
  16. package/runtime/plugins/agent-pack/runtime/nudge_text.py +107 -0
  17. package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +9 -1
  18. package/runtime/plugins/agents-init/.codex-plugin/plugin.json +1 -1
  19. package/runtime/plugins/agents-init/content.json +17 -2
  20. package/runtime/plugins/agents-init/plugin.json +1 -1
  21. package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +47 -0
  22. package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +39 -1
  23. package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +404 -70
  24. package/runtime/plugins/agents-init/skills/agents-init/scripts/adopt.py +596 -0
  25. package/runtime/plugins/agents-init/skills/agents-init/scripts/i18n_init.py +78 -0
  26. package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +245 -72
  27. package/runtime/plugins/agents-init/skills/agents-init/scripts/locale/en.json +210 -0
  28. package/runtime/plugins/agents-init/skills/agents-init/scripts/regions.py +99 -0
  29. package/runtime/plugins/agents-init/skills/agents-init/scripts/scaffold.py +1081 -0
  30. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_adopt.py +551 -0
  31. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +46 -2
  32. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_scaffold.py +555 -0
  33. package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +66 -0
  34. package/runtime/plugins/agents-init/skills/agents-init/skill.json +1 -1
  35. package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +1 -1
  36. package/runtime/plugins/opinion-manager/plugin.json +1 -1
  37. package/runtime/plugins/opinion-manager/references/inbox.en.md +48 -0
  38. package/runtime/plugins/opinion-manager/references/inbox.md +48 -0
  39. package/runtime/plugins/opinion-manager/runtime/context.py +19 -18
  40. package/runtime/plugins/opinion-manager/runtime/i18n_opinion.py +78 -0
  41. package/runtime/plugins/opinion-manager/runtime/inbox.py +397 -0
  42. package/runtime/plugins/opinion-manager/runtime/inbox_text.py +101 -0
  43. package/runtime/plugins/opinion-manager/runtime/lifecycle.py +45 -44
  44. package/runtime/plugins/opinion-manager/runtime/locale/en.json +184 -0
  45. package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +55 -50
  46. package/runtime/plugins/opinion-manager/runtime/versioned.py +84 -83
  47. package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +15 -2
  48. package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +1 -1
  49. package/runtime/plugins/project-orchestrator/content.json +30 -3
  50. package/runtime/plugins/project-orchestrator/plugin.json +1 -1
  51. package/runtime/plugins/project-orchestrator/references/case-workspaces.md +9 -1
  52. package/runtime/plugins/project-orchestrator/references/case.schema.json +1 -0
  53. package/runtime/plugins/project-orchestrator/references/sop-interview.md +83 -0
  54. package/runtime/plugins/project-orchestrator/references/sop.md +156 -0
  55. package/runtime/plugins/project-orchestrator/runtime/assets.py +52 -57
  56. package/runtime/plugins/project-orchestrator/runtime/case.py +139 -80
  57. package/runtime/plugins/project-orchestrator/runtime/case_structure.py +71 -0
  58. package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +47 -46
  59. package/runtime/plugins/project-orchestrator/runtime/i18n_orchestrator.py +78 -0
  60. package/runtime/plugins/project-orchestrator/runtime/locale/en.json +1409 -0
  61. package/runtime/plugins/project-orchestrator/runtime/memory.py +44 -43
  62. package/runtime/plugins/project-orchestrator/runtime/private_paths.py +119 -0
  63. package/runtime/plugins/project-orchestrator/runtime/project_base.py +37 -19
  64. package/runtime/plugins/project-orchestrator/runtime/project_cli.py +267 -174
  65. package/runtime/plugins/project-orchestrator/runtime/project_demo.py +130 -48
  66. package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +524 -134
  67. package/runtime/plugins/project-orchestrator/runtime/project_menu.py +159 -85
  68. package/runtime/plugins/project-orchestrator/runtime/project_real.py +191 -106
  69. package/runtime/plugins/project-orchestrator/runtime/project_write.py +58 -55
  70. package/runtime/plugins/project-orchestrator/runtime/sop.py +1670 -0
  71. package/runtime/plugins/project-orchestrator/runtime/sop_structure.py +226 -0
  72. package/runtime/plugins/project-orchestrator/runtime/wf.py +74 -70
  73. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +22 -0
  74. package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.en.md +57 -0
  75. package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.md +57 -0
  76. package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.en.md +77 -0
  77. package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.md +77 -0
  78. package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.en.md +11 -0
  79. package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.md +11 -0
  80. package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.en.md +87 -0
  81. package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.md +87 -0
  82. package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.en.md +75 -0
  83. package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.md +75 -0
  84. package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.en.md +11 -0
  85. package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.md +12 -0
  86. package/runtime/plugins/project-orchestrator/templates/CASE.en.md +39 -0
@@ -8,21 +8,23 @@ import re
8
8
 
9
9
  import project_cli as cli
10
10
  from project_cli import S, EntryError
11
+ from i18n_orchestrator import _, N_ # noqa: E402
11
12
 
12
13
  MENU = (
13
- ("1", "找资料", "按关键词、日期、类型查找并打开", "find"),
14
- ("2", "看进展", "浏览事项、下一动作与等待对象", "progress"),
15
- ("3", "项目指南", "这个项目做什么、资料在哪、怎样开始", "guide"),
16
- ("4", "检查项目与环境", "依赖、插件、记录与索引", "health"),
14
+ ("1", N_("找资料"), N_("按关键词、日期、类型查找并打开"), "find"),
15
+ ("2", N_("看进展"), N_("浏览事项、下一动作与等待对象"), "progress"),
16
+ ("3", N_("项目指南"), N_("这个项目做什么、资料在哪、怎样开始"), "guide"),
17
+ ("4", N_("检查项目与环境"), N_("依赖、插件、记录与索引"), "health"),
17
18
  None,
18
- ("5", "修复与更新", "重建索引、安装与更新", "fix"),
19
- ("6", "记录事项", "新建、追加记录、更新状态", "record"),
20
- ("7", "项目工具", "项目自行声明的操作", "tools"),
19
+ ("5", N_("修复与更新"), N_("重建索引、安装与更新"), "fix"),
20
+ ("6", N_("记录事项"), N_("新建、追加记录、更新状态"), "record"),
21
+ ("7", N_("项目工具"), N_("项目自行声明的操作"), "tools"),
22
+ ("8", N_("流程(SOP)"), N_("自己定义的步骤序列:查看、检查所需工具、按它执行"), "sop"),
21
23
  )
22
24
  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"))
25
+ RECORD_ACTIONS = (("1", N_("追加一条记录"), "log"), ("2", N_("更新下一动作"), "next_action"),
26
+ ("3", N_("改为等待(填写在等谁或等什么)"), "waiting"), ("4", N_("关闭(标为已完成)"), "close"),
27
+ ("5", N_("恢复(标为进行中)"), "reopen"), ("6", N_("登记资料或交付物"), "deliverable"))
26
28
 
27
29
 
28
30
  class Back(Exception):
@@ -70,11 +72,11 @@ def sub(screen, *args) -> None:
70
72
  pass
71
73
 
72
74
 
73
- def confirm(prompt: str = "输入 yes 执行,回车取消") -> bool:
75
+ def confirm(prompt: str = _("输入 yes 执行,回车取消")) -> bool:
74
76
  try:
75
77
  return ask(prompt).lower() in ("yes", "y", "是")
76
78
  except Back:
77
- print(S.dim("已取消,没有写入。"))
79
+ print(S.dim(_("已取消,没有写入。")))
78
80
  return False
79
81
 
80
82
 
@@ -88,25 +90,26 @@ def home(provider) -> None:
88
90
  print()
89
91
  for entry in MENU:
90
92
  if entry is None:
91
- print(S.dim(" ─────────── 维护"))
93
+ print(S.dim(_(" ─────────── 维护")))
92
94
  continue
93
95
  key, title, hint, capability = entry
94
- state = capabilities[capability]
96
+ title, hint = _(title), _(hint)
97
+ state = capabilities.get(capability) or {"state": "unavailable", "reason": _("演示项目没有这一项")}
95
98
  if capability == "health" and any(health.values()):
96
- hint = " · ".join(f"{health[k]} 项{label}" for k, label in cli.LEVELS if health.get(k))
99
+ hint = " · ".join(_("{0} 项{1}").format(health[k], _(label)) for k, label in cli.LEVELS if health.get(k))
97
100
  if state["state"] != "available":
98
- hint = S.yellow(f"不可用:{state['reason']}")
101
+ hint = S.yellow(_("不可用:{0}").format(state['reason']))
99
102
  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 退出"))
103
+ print(S.dim(_("\n 输入序号;也可以直接输入关键词查资料。 ? 帮助 · q 退出")))
101
104
 
102
105
 
103
106
  def find_screen(provider, first: str | None = None) -> None:
104
- show(cli.header(provider, "找资料"))
105
- print(S.dim("输入关键词,可带日期(2025-12、10月)与 type:pdf、in:片段;回车返回"))
107
+ show(cli.header(provider, _("找资料")))
108
+ print(S.dim(_("输入关键词,可带日期(2025-12、10月)与 type:pdf、in:片段;回车返回")))
106
109
  result = None
107
110
  pending = first
108
111
  while True:
109
- line, pending = (pending, None) if pending else (ask("查找"), None)
112
+ line, pending = (pending, None) if pending else (ask(_("查找")), None)
110
113
  match = ACTION_RE.match(line) or re.match(r"^()(\d+)$", line)
111
114
  if match and result is not None:
112
115
  key, number = match.group(1) or "o", int(match.group(2))
@@ -116,7 +119,7 @@ def find_screen(provider, first: str | None = None) -> None:
116
119
  fail(exc)
117
120
  continue
118
121
  if key == "v":
119
- show(cli.render_versions(item) if item["versions"] else [S.dim("这份资料没有其他版本")])
122
+ show(cli.render_versions(item) if item["versions"] else [S.dim(_("这份资料没有其他版本"))])
120
123
  elif key == "r":
121
124
  show(cli.render_refs(provider.refs(item["path"])))
122
125
  else:
@@ -134,8 +137,8 @@ def find_screen(provider, first: str | None = None) -> None:
134
137
  result = provider.find(terms, ext=ext, container=container)
135
138
  except EntryError as exc:
136
139
  fail(exc)
137
- print(S.dim("输入 5 可直接去「修复与更新」。"))
138
- if ask("下一步") != "5":
140
+ print(S.dim(_("输入 5 可直接去「修复与更新」。")))
141
+ if ask(_("下一步")) != "5":
139
142
  return
140
143
  sub(fix_screen, provider, ["rebuild-index"] if exc.code == "index-broken" else None)
141
144
  if provider.index_state()["status"] == "broken":
@@ -144,14 +147,14 @@ def find_screen(provider, first: str | None = None) -> None:
144
147
  continue
145
148
  show(cli.render_find(result))
146
149
  if result["results"]:
147
- print(S.dim("o1 打开 · f1 显示所在目录 · c1 复制路径 · v1 看版本 · r1 看引用 · 继续输入词再查 · 回车返回"))
150
+ print(S.dim(_("o1 打开 · f1 显示所在目录 · c1 复制路径 · v1 看版本 · r1 看引用 · 继续输入词再查 · 回车返回")))
148
151
 
149
152
 
150
153
  def item_screen(provider, item: dict) -> None:
151
154
  show(cli.render_item(item))
152
155
  while True:
153
- print(S.dim("o 打开权威记录 · d1 打开交付物 · f 显示所在目录 · w 为它记录 · 回车返回"))
154
- line = ask("事项")
156
+ print(S.dim(_("o 打开权威记录 · d1 打开交付物 · f 显示所在目录 · w 为它记录 · 回车返回")))
157
+ line = ask(_("事项"))
155
158
  if line == "o":
156
159
  print(provider.act("open", item["path"])["message"])
157
160
  elif line == "f":
@@ -161,17 +164,17 @@ def item_screen(provider, item: dict) -> None:
161
164
  elif re.fullmatch(r"d\d+", line) and 1 <= int(line[1:]) <= len(item["deliverables"]):
162
165
  print(provider.act("open", item["deliverables"][int(line[1:]) - 1])["message"])
163
166
  else:
164
- print(S.yellow("没有这个操作。"))
167
+ print(S.yellow(_("没有这个操作。")))
165
168
 
166
169
 
167
170
  def progress_screen(provider) -> None:
168
171
  term, expand = None, False
169
172
  while True:
170
- show(cli.header(provider, "看进展"))
173
+ show(cli.header(provider, _("看进展")))
171
174
  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
+ show(([S.dim(_("筛选:{0}(输入 * 清除)").format(term))] if term else []) + lines)
176
+ print(S.dim(_("序号看详情 · 输入词查找(标题、别名、旧编号)· all 展开已完成 · 回车返回")))
177
+ line = ask(_("进展"))
175
178
  if line.isdigit() and 1 <= int(line) <= len(numbered):
176
179
  sub(item_screen, provider, provider.item(numbered[int(line) - 1]["id"]))
177
180
  elif line == "all":
@@ -185,33 +188,33 @@ def progress_screen(provider) -> None:
185
188
  sub(item_screen, provider, provider.item(matches[0]["id"]))
186
189
  term = None
187
190
  elif not matches:
188
- print(S.yellow(f"没有事项匹配“{line}”。"))
191
+ print(S.yellow(_("没有事项匹配“{0}”。").format(line)))
189
192
  term = None
190
193
  else:
191
194
  expand = True
192
195
 
193
196
 
194
197
  def guide_screen(provider) -> None:
195
- show(cli.header(provider, "项目指南") + cli.render_guide(provider.guide()))
196
- ask("回车返回")
198
+ show(cli.header(provider, _("项目指南")) + cli.render_guide(provider.guide()))
199
+ ask(_("回车返回"))
197
200
 
198
201
 
199
202
  def health_screen(provider) -> None:
200
- show(cli.header(provider, "检查项目与环境"))
203
+ show(cli.header(provider, _("检查项目与环境")))
201
204
  lines, numbered = cli.render_health(provider.health())
202
205
  show(lines)
203
206
  while True:
204
- print(S.dim("序号去处理 · 回车返回"))
205
- line = ask("检查")
207
+ print(S.dim(_("序号去处理 · 回车返回")))
208
+ line = ask(_("检查"))
206
209
  if not (line.isdigit() and 1 <= int(line) <= len(numbered)):
207
- print(S.yellow("没有这一项。"))
210
+ print(S.yellow(_("没有这一项。")))
208
211
  continue
209
212
  finding = numbered[int(line) - 1]
210
213
  actions = [step["action"] for step in finding["next"] if step["action"]]
211
214
  if not actions:
212
- print(S.dim("这一项不能在菜单里直接处理:") + finding["next"][0]["label"])
215
+ print(S.dim(_("这一项不能在菜单里直接处理:")) + finding["next"][0]["label"])
213
216
  continue
214
- verb, _, target = actions[0].partition(":")
217
+ verb, _sep, target = actions[0].partition(":")
215
218
  if verb == "fix":
216
219
  sub(fix_screen, provider, [target])
217
220
  elif verb == "open":
@@ -219,64 +222,82 @@ def health_screen(provider) -> None:
219
222
 
220
223
 
221
224
  def fix_screen(provider, preset: list[str] | None = None) -> None:
222
- show(cli.header(provider, "修复与更新"))
223
- print(S.dim("流程:检查 → 选择处理项 → 查看影响 → 确认执行 → 验证结果"))
225
+ show(cli.header(provider, _("修复与更新")))
226
+ print(S.dim(_("流程:检查 → 选择处理项 → 查看影响 → 确认执行 → 验证结果")))
224
227
  available = provider.fix_plan([])["steps"]
225
228
  if not available:
226
- print(S.green("检查完成:没有需要处理的项。"))
229
+ print(S.green(_("检查完成:没有需要处理的项。")))
227
230
  return
228
231
  if preset:
229
232
  ids = [step["id"] for step in available if step["id"] in preset]
230
233
  titles = [step["title"] for step in available if step["id"] in ids]
231
- print(S.bold("1–2. 检查并选中:") + ("、".join(titles) or S.yellow("这一项已处理或当前不可处理")))
234
+ print(S.bold(_("1–2. 检查并选中:")) + (_("、").join(titles) or S.yellow(_("这一项已处理或当前不可处理"))))
232
235
  if not ids:
233
236
  return
234
237
  else:
235
- print(S.bold("1. 检查结果"))
238
+ print(S.bold(_("1. 检查结果")))
236
239
  for number, step in enumerate(available, 1):
237
- blocked = S.yellow(f"({step['blocked']})") if step["blocked"] else ""
240
+ blocked = S.yellow(_("({0})").format(step['blocked'])) if step["blocked"] else ""
238
241
  print(f" {S.bold(S.cyan(str(number)))} {step['title']}{blocked}")
239
- line = ask("2. 选择处理项(序号,空格分隔;all 全选)")
242
+ line = ask(_("2. 选择处理项(序号,空格分隔;all 全选)"))
240
243
  picks = range(1, len(available) + 1) if line == "all" else \
241
244
  [int(token) for token in line.split() if token.isdigit() and 1 <= int(token) <= len(available)]
242
245
  ids = [available[number - 1]["id"] for number in picks]
243
246
  if not ids:
244
- print(S.yellow("没有选中任何项。"))
247
+ print(S.yellow(_("没有选中任何项。")))
245
248
  return
246
249
  choices: dict[str, str] = {}
247
250
  if "add-entry" in ids and not provider.overview().get("launcher"):
248
- name = choose("启动器文件名(回车用 menu)", "")
251
+ name = choose(_("启动器文件名(回车用 menu)"), "")
249
252
  if name:
250
253
  choices["entry-name"] = name
251
254
  plan = provider.fix_plan(ids, dict(choices))
252
- print(S.bold("3. 查看影响"))
255
+ print(S.bold(_("3. 查看影响")))
253
256
  show(cli.render_plan(plan))
254
257
  asked = False
255
258
  for step in plan["steps"]:
256
259
  for item in step["custom"]:
257
260
  if step["blocked"]:
258
261
  continue
259
- answer = choose(f"{item['path']} {item['state']}:k 不改(默认)/ a 采用", "k") \
262
+ if item.get("options"):
263
+ choices[item["path"]] = _decide_item(item, step["payload"].get("diffs", {}).get(item["path"], ""))
264
+ if choices[item["path"]] == "skip" and "_reason" not in choices:
265
+ choices["_reason"] = choose(_("永久跳过的理由(回车略过;会随项目同步,换一台机器也看得到)"), "")
266
+ asked = True
267
+ continue
268
+ answer = choose(_("{0} {1}:k 不改(默认)/ a 采用").format(item['path'], item['state']), "k") \
260
269
  if item["path"] == "README.md" else \
261
- choose(f"{item['path']} {item['state']}:k 保留你的版本(默认)/ a 采用新模板", "k")
270
+ choose(_("{0} {1}:k 保留你的版本(默认)/ a 采用新模板").format(item['path'], item['state']), "k")
262
271
  choices[item["path"]] = "accept" if answer == "a" else "keep"
263
272
  asked = True
264
273
  if asked:
265
274
  plan = provider.fix_plan(ids, choices)
266
- print(S.bold("按你的选择,最终将:"))
275
+ print(S.bold(_("按你的选择,最终将:")))
267
276
  show(cli.render_plan(plan, choices))
268
277
  if all(step["blocked"] for step in plan["steps"]):
269
- print(S.yellow("所选项目前都不能执行,只能预览。"))
278
+ print(S.yellow(_("所选项目前都不能执行,只能预览。")))
270
279
  return
271
- print(S.bold("4. 确认执行"))
280
+ print(S.bold(_("4. 确认执行")))
272
281
  if not confirm():
273
282
  return
274
- print(S.bold("5. 验证结果"))
283
+ print(S.bold(_("5. 验证结果")))
275
284
  show(cli.render_apply(provider.apply_fix(plan, choices)))
276
285
 
277
286
 
287
+ def _decide_item(item: dict, diff: str) -> str:
288
+ """每项四选一:采用 / 永久跳过 / 稍后 / 查看差异(看完再选)。"""
289
+ while True:
290
+ answer = choose(_("{0}({1}):a 采用(默认)/ s 永久跳过 / l 稍后 / d 查看差异").format(item['path'], item['state']), "a")
291
+ if answer == "d":
292
+ print(S.dim(diff or _("(没有差异内容可显示)")))
293
+ continue
294
+ if answer in ("a", "s", "l"):
295
+ return {"a": "accept", "s": "skip", "l": "later"}[answer]
296
+ print(S.yellow(_("请输入 a、s、l 或 d。")))
297
+
298
+
278
299
  def record_screen(provider, target: str | None = None) -> None:
279
- show(cli.header(provider, "记录事项"))
300
+ show(cli.header(provider, _("记录事项")))
280
301
  capability = next(item for item in provider.capabilities() if item["id"] == "record")
281
302
  if capability["state"] != "available":
282
303
  try:
@@ -285,12 +306,12 @@ def record_screen(provider, target: str | None = None) -> None:
285
306
  fail(exc)
286
307
  return
287
308
  if target is None:
288
- line = ask("n 新建 · 或输入事项(编号、名称、旧编号)")
309
+ line = ask(_("n 新建 · 或输入事项(编号、名称、旧编号)"))
289
310
  target = None if line == "n" else line
290
311
  if target is None:
291
- change = {"title": ask("标题"), "goal": ask("目标")}
312
+ change = {"title": ask(_("标题")), "goal": ask(_("目标"))}
292
313
  try:
293
- change["next_action"] = ask("下一动作(回车跳过)")
314
+ change["next_action"] = ask(_("下一动作(回车跳过)"))
294
315
  except Back:
295
316
  change["next_action"] = ""
296
317
  else:
@@ -299,12 +320,12 @@ def record_screen(provider, target: str | None = None) -> None:
299
320
  except EntryError as exc:
300
321
  fail(exc)
301
322
  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("要做什么"))
323
+ print(f"{S.bold(item['title'])} [{_(cli.GROUP_LABEL.get(item['group'], item['status']))}]")
324
+ for key, title, _field in RECORD_ACTIONS:
325
+ print(f" {S.bold(S.cyan(key))} {_(title)}")
326
+ choice = dict((key, field) for key, _title, field in RECORD_ACTIONS).get(ask(_("要做什么")))
306
327
  if choice is None:
307
- print(S.yellow("没有这个操作。"))
328
+ print(S.yellow(_("没有这个操作。")))
308
329
  return
309
330
  target = item["id"]
310
331
  if choice == "close":
@@ -312,20 +333,20 @@ def record_screen(provider, target: str | None = None) -> None:
312
333
  elif choice == "reopen":
313
334
  change = {"status": "active"}
314
335
  if not item["next_action"]:
315
- change["next_action"] = ask("恢复后的下一动作")
336
+ change["next_action"] = ask(_("恢复后的下一动作"))
316
337
  elif choice == "waiting":
317
- change = {"status": "waiting", "waiting_for": ask("在等谁或等什么")}
338
+ change = {"status": "waiting", "waiting_for": ask(_("在等谁或等什么"))}
318
339
  elif choice == "deliverable":
319
- change = {"add_deliverable": [ask("文件路径(相对项目根目录,可从「找资料」c1 复制)")]}
340
+ change = {"add_deliverable": [ask(_("文件路径(相对项目根目录,可从「找资料」c1 复制)"))]}
320
341
  else:
321
- change = {choice: ask("内容")}
342
+ change = {choice: ask(_("内容"))}
322
343
  try:
323
344
  preview = provider.record_preview(target, change)
324
345
  except EntryError as exc:
325
346
  fail(exc)
326
347
  return
327
348
  show(cli.render_preview(preview))
328
- if not confirm("输入 yes 写入,回车取消"):
349
+ if not confirm(_("输入 yes 写入,回车取消")):
329
350
  return
330
351
  try:
331
352
  print(S.green(provider.record_apply(preview)["verify"]))
@@ -335,33 +356,86 @@ def record_screen(provider, target: str | None = None) -> None:
335
356
 
336
357
  def tools_screen(provider) -> None:
337
358
  tools = provider.tools()
338
- show(cli.header(provider, "项目工具") + cli.render_tools(tools))
359
+ show(cli.header(provider, _("项目工具")) + cli.render_tools(tools))
339
360
  while tools:
340
- line = ask("序号查看并执行 · 回车返回")
361
+ line = ask(_("序号查看并执行 · 回车返回"))
341
362
  if not (line.isdigit() and 1 <= int(line) <= len(tools)):
342
- print(S.yellow("没有这一项。"))
363
+ print(S.yellow(_("没有这一项。")))
343
364
  continue
344
365
  tool = tools[int(line) - 1]
345
366
  if tool["state"] != "available":
346
- print(S.yellow(f"“{tool['title']}”当前不可用:{tool['reason']}"))
367
+ print(S.yellow(_("“{0}”当前不可用:{1}").format(tool['title'], tool['reason'])))
347
368
  continue
348
369
  show(cli.render_tools([tool]))
349
370
  if confirm():
350
371
  print(provider.run_tool(tool["id"])["message"])
351
372
  if not tools:
352
- ask("回车返回")
373
+ ask(_("回车返回"))
374
+
375
+
376
+ def sop_screen(provider) -> None:
377
+ while True:
378
+ code, data, lines = provider.sop(["list", "--all"])
379
+ show(cli.header(provider, _("流程(SOP)")) + lines)
380
+ rows = data["sops"]
381
+ print(S.dim(_("序号看详情 · d 哪些到期了 · n 新建草稿 · 回车返回")))
382
+ line = ask(_("流程"))
383
+ if line == "d":
384
+ show(provider.sop(["due"])[2])
385
+ elif line == "n":
386
+ sub(sop_new_screen, provider)
387
+ elif line.isdigit() and 1 <= int(line) <= len(rows):
388
+ sub(sop_detail_screen, provider, rows[int(line) - 1]["id"])
389
+ else:
390
+ print(S.yellow(_("没有这一项。")))
391
+
392
+
393
+ def sop_new_screen(provider) -> None:
394
+ slug = ask(_("英文短名(小写字母、数字、连字符)"))
395
+ template = choose(_("样例:blank 空白 / video-to-transcript 视频转文字 / weekly-review 周复盘(回车用 blank)"), "blank")
396
+ argv = ["new", "--slug", slug, "--template", template]
397
+ show(provider.sop(argv)[2])
398
+ if confirm():
399
+ show(provider.sop([*argv, "--apply"])[2])
400
+ print(S.dim(_("下一步:让 Agent 带你谈清触发、工具、出错和中间产物的约定,或运行 sop interview ") + slug))
401
+
402
+
403
+ def sop_detail_screen(provider, sop_id: str) -> None:
404
+ show(provider.sop(["show", sop_id])[2])
405
+ actions = {"c": _("执行前检查"), "q": _("还要谈清的事"), "v": _("校验"), "r": _("按它执行(先预览)"), "a": _("启用草稿(先预览)")}
406
+ while True:
407
+ print(S.dim(" · ".join(f"{key} {label}" for key, label in actions.items()) + _(" · 回车返回")))
408
+ line = ask(_("流程"))
409
+ if line == "c":
410
+ show(provider.sop(["preflight", sop_id])[2])
411
+ elif line == "q":
412
+ show(provider.sop(["interview", sop_id])[2])
413
+ elif line == "v":
414
+ show(provider.sop(["validate", sop_id])[2])
415
+ elif line in ("r", "a"):
416
+ argv = ["run", sop_id] if line == "r" else ["activate", sop_id]
417
+ code, _unused, lines = provider.sop(argv)
418
+ show(lines)
419
+ if code == 0 and confirm():
420
+ show(provider.sop([*argv, "--apply"])[2])
421
+ else:
422
+ print(S.yellow(_("没有这个操作。")))
423
+
353
424
 
425
+ def help_text() -> str:
426
+ bold, cyan = S.bold, S.cyan
427
+ return (bold(_("怎么用")) + "\n"
428
+ + _(" 首页输入序号进入;直接输入词就是找资料。各页回车返回上一级,q 随时退出。") + "\n"
429
+ + _(" 找资料结果:{0} 打开 · {1} 显示所在目录 · {2} 复制路径 · {3} 看版本 · {4} 看引用").format(
430
+ cyan("o1"), cyan("f1"), cyan("c1"), cyan("v1"), cyan("r1")) + "\n"
431
+ + _(" 写入类操作(修复、记录、工具)都先显示影响,输入 yes 才执行。") + "\n"
432
+ + bold(_("不进菜单也可以")) + "\n"
433
+ + _(" ./<启动器> find 词 · progress · show 事项 · guide · health · fix · record · tools · sop 子命令") + "\n"
434
+ + _(" 加 --output json 得到结构化结果;脚本里从不等待输入。完整说明:--help"))
354
435
 
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
436
 
363
437
  SCREENS = {"1": find_screen, "2": progress_screen, "3": guide_screen, "4": health_screen,
364
- "5": fix_screen, "6": record_screen, "7": tools_screen}
438
+ "5": fix_screen, "6": record_screen, "7": tools_screen, "8": sop_screen}
365
439
 
366
440
 
367
441
  def run(provider) -> int:
@@ -373,14 +447,14 @@ def run(provider) -> int:
373
447
  while True:
374
448
  home(provider)
375
449
  try:
376
- line = ask("选择")
450
+ line = ask(_("选择"))
377
451
  except Back:
378
452
  continue
379
453
  try:
380
454
  if line in SCREENS:
381
455
  SCREENS[line](provider)
382
456
  elif line in ("?", "help", "帮助"):
383
- print(HELP)
457
+ print(help_text())
384
458
  else:
385
459
  find_screen(provider, line)
386
460
  except Back: