@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.
- package/package.json +1 -1
- package/runtime/index.json +6 -6
- package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/agent-pack/hooks/hooks.json +32 -0
- package/runtime/plugins/agent-pack/plugin.json +1 -1
- package/runtime/plugins/agent-pack/references/local-only.md +126 -0
- package/runtime/plugins/agent-pack/references/nudge.en.md +58 -0
- package/runtime/plugins/agent-pack/references/nudge.md +58 -0
- package/runtime/plugins/agent-pack/runtime/agent_pack_nudge.py +573 -0
- package/runtime/plugins/agent-pack/runtime/agenthelm.py +419 -107
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +77 -53
- package/runtime/plugins/agent-pack/runtime/i18n_agent_pack.py +78 -0
- package/runtime/plugins/agent-pack/runtime/local_only.py +1130 -0
- package/runtime/plugins/agent-pack/runtime/locale/en.json +385 -0
- package/runtime/plugins/agent-pack/runtime/mcp_server.py +25 -0
- package/runtime/plugins/agent-pack/runtime/nudge_text.py +107 -0
- package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +9 -1
- package/runtime/plugins/agents-init/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/agents-init/content.json +17 -2
- package/runtime/plugins/agents-init/plugin.json +1 -1
- package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +47 -0
- package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +39 -1
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +404 -70
- package/runtime/plugins/agents-init/skills/agents-init/scripts/adopt.py +596 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/i18n_init.py +78 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +245 -72
- package/runtime/plugins/agents-init/skills/agents-init/scripts/locale/en.json +210 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/regions.py +99 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/scaffold.py +1081 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_adopt.py +551 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +46 -2
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_scaffold.py +555 -0
- package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +66 -0
- package/runtime/plugins/agents-init/skills/agents-init/skill.json +1 -1
- package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/references/inbox.en.md +48 -0
- package/runtime/plugins/opinion-manager/references/inbox.md +48 -0
- package/runtime/plugins/opinion-manager/runtime/context.py +19 -18
- package/runtime/plugins/opinion-manager/runtime/i18n_opinion.py +78 -0
- package/runtime/plugins/opinion-manager/runtime/inbox.py +397 -0
- package/runtime/plugins/opinion-manager/runtime/inbox_text.py +101 -0
- package/runtime/plugins/opinion-manager/runtime/lifecycle.py +45 -44
- package/runtime/plugins/opinion-manager/runtime/locale/en.json +184 -0
- package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +55 -50
- package/runtime/plugins/opinion-manager/runtime/versioned.py +84 -83
- package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +15 -2
- package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/content.json +30 -3
- package/runtime/plugins/project-orchestrator/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/references/case-workspaces.md +9 -1
- package/runtime/plugins/project-orchestrator/references/case.schema.json +1 -0
- package/runtime/plugins/project-orchestrator/references/sop-interview.md +83 -0
- package/runtime/plugins/project-orchestrator/references/sop.md +156 -0
- package/runtime/plugins/project-orchestrator/runtime/assets.py +52 -57
- package/runtime/plugins/project-orchestrator/runtime/case.py +139 -80
- package/runtime/plugins/project-orchestrator/runtime/case_structure.py +71 -0
- package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +47 -46
- package/runtime/plugins/project-orchestrator/runtime/i18n_orchestrator.py +78 -0
- package/runtime/plugins/project-orchestrator/runtime/locale/en.json +1409 -0
- package/runtime/plugins/project-orchestrator/runtime/memory.py +44 -43
- package/runtime/plugins/project-orchestrator/runtime/private_paths.py +119 -0
- package/runtime/plugins/project-orchestrator/runtime/project_base.py +37 -19
- package/runtime/plugins/project-orchestrator/runtime/project_cli.py +267 -174
- package/runtime/plugins/project-orchestrator/runtime/project_demo.py +130 -48
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +524 -134
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +159 -85
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +191 -106
- package/runtime/plugins/project-orchestrator/runtime/project_write.py +58 -55
- package/runtime/plugins/project-orchestrator/runtime/sop.py +1670 -0
- package/runtime/plugins/project-orchestrator/runtime/sop_structure.py +226 -0
- package/runtime/plugins/project-orchestrator/runtime/wf.py +74 -70
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +22 -0
- package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.en.md +57 -0
- package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.md +57 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.en.md +77 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.md +77 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.en.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.en.md +87 -0
- package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.md +87 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.en.md +75 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.md +75 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.en.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.md +12 -0
- package/runtime/plugins/project-orchestrator/templates/CASE.en.md +39 -0
|
@@ -13,9 +13,11 @@ from pathlib import Path
|
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
15
|
import context
|
|
16
|
+
import inbox
|
|
16
17
|
import lifecycle
|
|
17
18
|
import versioned
|
|
18
19
|
from constants import KNOWN_PLACEHOLDERS
|
|
20
|
+
from i18n_opinion import _ # noqa: E402
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
|
|
@@ -33,37 +35,37 @@ class OpinionError(RuntimeError):
|
|
|
33
35
|
def read_json_object(path: Path) -> dict[str, Any]:
|
|
34
36
|
metadata = path.lstat()
|
|
35
37
|
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
|
|
36
|
-
raise OpinionError(
|
|
38
|
+
raise OpinionError(_("拒绝读取非普通文件:{0}").format(path))
|
|
37
39
|
value = json.loads(path.read_text(encoding="utf-8"))
|
|
38
40
|
if not isinstance(value, dict):
|
|
39
|
-
raise OpinionError(
|
|
41
|
+
raise OpinionError(_("JSON 根节点必须是对象:{0}").format(path))
|
|
40
42
|
return value
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
def validate_rule(rule: Any, template_id: str) -> dict[str, Any]:
|
|
44
46
|
if not isinstance(rule, dict):
|
|
45
|
-
raise OpinionError(
|
|
47
|
+
raise OpinionError(_("模板 {0} 的 rules 项必须是对象").format(template_id))
|
|
46
48
|
required = {"id", "title", "level", "scopes", "text"}
|
|
47
49
|
missing = required - set(rule)
|
|
48
50
|
if missing:
|
|
49
|
-
raise OpinionError(
|
|
51
|
+
raise OpinionError(_("规则缺少字段 {0}:{1}").format(sorted(missing), template_id))
|
|
50
52
|
unknown = set(rule) - required
|
|
51
53
|
if unknown:
|
|
52
|
-
raise OpinionError(
|
|
54
|
+
raise OpinionError(_("规则包含未知字段 {0}:{1}").format(sorted(unknown), template_id))
|
|
53
55
|
rule_id = rule["id"]
|
|
54
56
|
if not isinstance(rule_id, str) or not ID_PATTERN.fullmatch(rule_id):
|
|
55
|
-
raise OpinionError(
|
|
57
|
+
raise OpinionError(_("规则编号无效:{0!r}").format(rule_id))
|
|
56
58
|
if rule["level"] not in {"required", "preferred"}:
|
|
57
|
-
raise OpinionError(
|
|
59
|
+
raise OpinionError(_("规则 level 无效:{0}").format(rule_id))
|
|
58
60
|
if not isinstance(rule["title"], str) or not rule["title"].strip():
|
|
59
|
-
raise OpinionError(
|
|
61
|
+
raise OpinionError(_("规则标题为空:{0}").format(rule_id))
|
|
60
62
|
if not isinstance(rule["text"], str) or not rule["text"].strip():
|
|
61
|
-
raise OpinionError(
|
|
63
|
+
raise OpinionError(_("规则正文为空:{0}").format(rule_id))
|
|
62
64
|
scopes = rule["scopes"]
|
|
63
65
|
if not isinstance(scopes, list) or not scopes or not all(
|
|
64
66
|
isinstance(scope, str) and scope.strip() for scope in scopes
|
|
65
67
|
) or len(scopes) != len(set(scopes)):
|
|
66
|
-
raise OpinionError(
|
|
68
|
+
raise OpinionError(_("规则 scopes 必须是非空字符串数组:{0}").format(rule_id))
|
|
67
69
|
return rule
|
|
68
70
|
|
|
69
71
|
|
|
@@ -71,77 +73,77 @@ def validate_template(value: dict[str, Any], path: Path) -> dict[str, Any]:
|
|
|
71
73
|
required = {"id", "version", "name", "description", "scopes", "rules", "questions"}
|
|
72
74
|
missing = required - set(value)
|
|
73
75
|
if missing:
|
|
74
|
-
raise OpinionError(
|
|
76
|
+
raise OpinionError(_("模板缺少字段 {0}:{1}").format(sorted(missing), path))
|
|
75
77
|
unknown = set(value) - required
|
|
76
78
|
if unknown:
|
|
77
|
-
raise OpinionError(
|
|
79
|
+
raise OpinionError(_("模板包含未知字段 {0}:{1}").format(sorted(unknown), path))
|
|
78
80
|
template_id = value["id"]
|
|
79
81
|
if not isinstance(template_id, str) or not ID_PATTERN.fullmatch(template_id):
|
|
80
|
-
raise OpinionError(
|
|
82
|
+
raise OpinionError(_("模板编号无效:{0!r}").format(template_id))
|
|
81
83
|
if path.stem != template_id:
|
|
82
|
-
raise OpinionError(
|
|
84
|
+
raise OpinionError(_("模板文件名必须与编号一致:{0}").format(path.name))
|
|
83
85
|
if not isinstance(value["version"], str) or not VERSION_PATTERN.fullmatch(value["version"]):
|
|
84
|
-
raise OpinionError(
|
|
86
|
+
raise OpinionError(_("模板版本必须使用三段数字:{0}").format(template_id))
|
|
85
87
|
for field in ("name", "description"):
|
|
86
88
|
if not isinstance(value[field], str) or not value[field].strip():
|
|
87
|
-
raise OpinionError(
|
|
89
|
+
raise OpinionError(_("模板字段 {0} 不能为空:{1}").format(field, template_id))
|
|
88
90
|
if not isinstance(value["scopes"], list) or not value["scopes"] or not all(
|
|
89
91
|
isinstance(scope, str) and scope.strip() for scope in value["scopes"]
|
|
90
92
|
) or len(value["scopes"]) != len(set(value["scopes"])):
|
|
91
|
-
raise OpinionError(
|
|
93
|
+
raise OpinionError(_("模板 scopes 必须是非空数组:{0}").format(template_id))
|
|
92
94
|
rules = value["rules"]
|
|
93
95
|
if not isinstance(rules, list) or not rules:
|
|
94
|
-
raise OpinionError(
|
|
96
|
+
raise OpinionError(_("模板 rules 必须是非空数组:{0}").format(template_id))
|
|
95
97
|
rule_ids: set[str] = set()
|
|
96
98
|
for rule in rules:
|
|
97
99
|
validated = validate_rule(rule, template_id)
|
|
98
100
|
if validated["id"] in rule_ids:
|
|
99
|
-
raise OpinionError(
|
|
101
|
+
raise OpinionError(_("模板内存在重复规则编号:{0}").format(validated['id']))
|
|
100
102
|
rule_ids.add(validated["id"])
|
|
101
103
|
questions = value["questions"]
|
|
102
104
|
if not isinstance(questions, list):
|
|
103
|
-
raise OpinionError(
|
|
105
|
+
raise OpinionError(_("模板 questions 必须是数组:{0}").format(template_id))
|
|
104
106
|
question_ids: set[str] = set()
|
|
105
107
|
for question in questions:
|
|
106
108
|
if not isinstance(question, dict) or set(question) != {"id", "prompt", "options"}:
|
|
107
|
-
raise OpinionError(
|
|
109
|
+
raise OpinionError(_("模板问题结构无效:{0}").format(template_id))
|
|
108
110
|
if not isinstance(question["id"], str) or not ID_PATTERN.fullmatch(question["id"]):
|
|
109
|
-
raise OpinionError(
|
|
111
|
+
raise OpinionError(_("模板问题编号无效:{0}").format(template_id))
|
|
110
112
|
if question["id"] in question_ids:
|
|
111
|
-
raise OpinionError(
|
|
113
|
+
raise OpinionError(_("模板问题编号重复:{0}").format(question['id']))
|
|
112
114
|
question_ids.add(question["id"])
|
|
113
115
|
if not isinstance(question["prompt"], str) or not question["prompt"].strip():
|
|
114
|
-
raise OpinionError(
|
|
116
|
+
raise OpinionError(_("模板问题正文为空:{0}").format(template_id))
|
|
115
117
|
if not isinstance(question["options"], list) or len(question["options"]) < 2:
|
|
116
|
-
raise OpinionError(
|
|
118
|
+
raise OpinionError(_("模板问题至少需要两个选项:{0}").format(template_id))
|
|
117
119
|
for option in question["options"]:
|
|
118
120
|
if not isinstance(option, dict) or set(option) != {"label", "recommend"}:
|
|
119
|
-
raise OpinionError(
|
|
121
|
+
raise OpinionError(_("模板问题选项结构无效:{0}").format(template_id))
|
|
120
122
|
if not isinstance(option["label"], str) or not option["label"].strip():
|
|
121
|
-
raise OpinionError(
|
|
123
|
+
raise OpinionError(_("模板问题选项标签为空:{0}").format(template_id))
|
|
122
124
|
recommendations = option["recommend"]
|
|
123
125
|
if not isinstance(recommendations, list) or not recommendations or not all(
|
|
124
126
|
isinstance(recommendation, str) and recommendation in rule_ids
|
|
125
127
|
for recommendation in recommendations
|
|
126
128
|
) or len(recommendations) != len(set(recommendations)):
|
|
127
|
-
raise OpinionError(
|
|
129
|
+
raise OpinionError(_("模板问题引用了未知规则:{0}").format(template_id))
|
|
128
130
|
return value
|
|
129
131
|
|
|
130
132
|
|
|
131
133
|
def load_catalog() -> OrderedDict[str, dict[str, Any]]:
|
|
132
134
|
if not TEMPLATE_ROOT.is_dir() or TEMPLATE_ROOT.is_symlink():
|
|
133
|
-
raise OpinionError(
|
|
135
|
+
raise OpinionError(_("模板目录不可用:{0}").format(TEMPLATE_ROOT))
|
|
134
136
|
catalog: OrderedDict[str, dict[str, Any]] = OrderedDict()
|
|
135
137
|
global_rules: dict[str, dict[str, Any]] = {}
|
|
136
138
|
for path in sorted(TEMPLATE_ROOT.glob("*.json")):
|
|
137
139
|
template = validate_template(read_json_object(path), path)
|
|
138
140
|
template_id = template["id"]
|
|
139
141
|
if template_id in catalog:
|
|
140
|
-
raise OpinionError(
|
|
142
|
+
raise OpinionError(_("模板编号重复:{0}").format(template_id))
|
|
141
143
|
for rule in template["rules"]:
|
|
142
144
|
current = global_rules.get(rule["id"])
|
|
143
145
|
if current is not None and current != rule:
|
|
144
|
-
raise OpinionError(
|
|
146
|
+
raise OpinionError(_("相同规则编号的内容不一致:{0}").format(rule['id']))
|
|
145
147
|
global_rules[rule["id"]] = rule
|
|
146
148
|
catalog[template_id] = template
|
|
147
149
|
return catalog
|
|
@@ -150,10 +152,10 @@ def load_catalog() -> OrderedDict[str, dict[str, Any]]:
|
|
|
150
152
|
def parse_template_spec(spec: str, catalog: OrderedDict[str, dict[str, Any]]) -> str:
|
|
151
153
|
template_id, separator, version = spec.partition("@")
|
|
152
154
|
if template_id not in catalog:
|
|
153
|
-
raise OpinionError(
|
|
155
|
+
raise OpinionError(_("未知模板:{0}").format(template_id))
|
|
154
156
|
current = catalog[template_id]["version"]
|
|
155
157
|
if separator and version != current:
|
|
156
|
-
raise OpinionError(
|
|
158
|
+
raise OpinionError(_("模板版本不匹配:{0}@{1},当前版本为 {2}").format(template_id, version, current))
|
|
157
159
|
return template_id
|
|
158
160
|
|
|
159
161
|
|
|
@@ -170,10 +172,10 @@ def read_custom_file(path: Path | None) -> str | None:
|
|
|
170
172
|
return None
|
|
171
173
|
metadata = path.lstat()
|
|
172
174
|
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
|
|
173
|
-
raise OpinionError(
|
|
175
|
+
raise OpinionError(_("自定义规则必须来自普通文件:{0}").format(path))
|
|
174
176
|
content = path.read_text(encoding="utf-8").strip()
|
|
175
177
|
if not content:
|
|
176
|
-
raise OpinionError("自定义规则文件为空")
|
|
178
|
+
raise OpinionError(_("自定义规则文件为空"))
|
|
177
179
|
return content
|
|
178
180
|
|
|
179
181
|
|
|
@@ -197,7 +199,7 @@ def select_rules(
|
|
|
197
199
|
indexed = rule_index(catalog)
|
|
198
200
|
for rule_id in rule_ids:
|
|
199
201
|
if rule_id not in indexed:
|
|
200
|
-
raise OpinionError(
|
|
202
|
+
raise OpinionError(_("未知规则:{0}").format(rule_id))
|
|
201
203
|
template_id, rule = indexed[rule_id]
|
|
202
204
|
source_versions[template_id] = catalog[template_id]["version"]
|
|
203
205
|
if rule_id not in selected_ids:
|
|
@@ -292,28 +294,28 @@ def write_atomic(path: Path, content: str) -> None:
|
|
|
292
294
|
def apply_opinion(project: Path, content: str, *, replace: bool) -> str:
|
|
293
295
|
root = project.expanduser().resolve()
|
|
294
296
|
if not root.is_dir():
|
|
295
|
-
raise OpinionError(
|
|
297
|
+
raise OpinionError(_("项目目录不存在:{0}").format(root))
|
|
296
298
|
destination = root / OPINION_FILE
|
|
297
299
|
if destination.is_symlink():
|
|
298
|
-
raise OpinionError(
|
|
300
|
+
raise OpinionError(_("拒绝写入符号链接:{0}").format(destination))
|
|
299
301
|
if destination.exists():
|
|
300
302
|
if not destination.is_file():
|
|
301
|
-
raise OpinionError(
|
|
303
|
+
raise OpinionError(_("目标路径不是普通文件:{0}").format(destination))
|
|
302
304
|
current = destination.read_text(encoding="utf-8")
|
|
303
305
|
if current == content:
|
|
304
306
|
return "unchanged"
|
|
305
307
|
if not replace and not known_placeholder(current):
|
|
306
|
-
raise OpinionError("OPINION.md 已包含内容;预览差异并明确使用 --replace")
|
|
308
|
+
raise OpinionError(_("OPINION.md 已包含内容;预览差异并明确使用 --replace"))
|
|
307
309
|
write_atomic(destination, content)
|
|
308
310
|
if destination.read_text(encoding="utf-8") != content:
|
|
309
|
-
raise OpinionError("写入后核对 OPINION.md 失败")
|
|
311
|
+
raise OpinionError(_("写入后核对 OPINION.md 失败"))
|
|
310
312
|
return "written"
|
|
311
313
|
|
|
312
314
|
|
|
313
315
|
def catalog_markdown(catalog: OrderedDict[str, dict[str, Any]]) -> str:
|
|
314
|
-
lines = ["# Opinion 模板目录", ""]
|
|
316
|
+
lines = [_("# Opinion 模板目录"), ""]
|
|
315
317
|
if not catalog:
|
|
316
|
-
lines.append("当前没有已批准的公开模板。")
|
|
318
|
+
lines.append(_("当前没有已批准的公开模板。"))
|
|
317
319
|
return "\n".join(lines).rstrip() + "\n"
|
|
318
320
|
for template in catalog.values():
|
|
319
321
|
identity = f"{template['family']}/{template['variant']}" if "family" in template else template["id"]
|
|
@@ -322,7 +324,7 @@ def catalog_markdown(catalog: OrderedDict[str, dict[str, Any]]) -> str:
|
|
|
322
324
|
"",
|
|
323
325
|
template["description"],
|
|
324
326
|
"",
|
|
325
|
-
|
|
327
|
+
_("规则数量:{0};适用场景:{1}。").format(len(template['rules']), _('、').join(template['scopes'])),
|
|
326
328
|
"",
|
|
327
329
|
))
|
|
328
330
|
return "\n".join(lines).rstrip() + "\n"
|
|
@@ -336,13 +338,13 @@ def catalog_payload(catalog: OrderedDict[str, dict[str, Any]]) -> dict[str, Any]
|
|
|
336
338
|
|
|
337
339
|
|
|
338
340
|
def build_parser() -> argparse.ArgumentParser:
|
|
339
|
-
parser = argparse.ArgumentParser(description="管理版本化 Opinion 模板并生成统一 OPINION.md")
|
|
341
|
+
parser = argparse.ArgumentParser(description=_("管理版本化 Opinion 模板并生成统一 OPINION.md"))
|
|
340
342
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
341
343
|
|
|
342
|
-
catalog = subparsers.add_parser("catalog", help="查看模板、规则和引导问题")
|
|
344
|
+
catalog = subparsers.add_parser("catalog", help=_("查看模板、规则和引导问题"))
|
|
343
345
|
catalog.add_argument("--output", choices=("json", "markdown"), default="markdown")
|
|
344
346
|
|
|
345
|
-
compose = subparsers.add_parser("compose", help="组合模板、规则和自定义内容")
|
|
347
|
+
compose = subparsers.add_parser("compose", help=_("组合模板、规则和自定义内容"))
|
|
346
348
|
compose.add_argument("--project", type=Path, required=True)
|
|
347
349
|
compose.add_argument("--template", action="append", default=[])
|
|
348
350
|
compose.add_argument("--rule", action="append", default=[])
|
|
@@ -353,6 +355,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
353
355
|
versioned.add_commands(subparsers, compose, catalog)
|
|
354
356
|
context.add_command(subparsers)
|
|
355
357
|
lifecycle.add_command(subparsers)
|
|
358
|
+
inbox.add_command(subparsers)
|
|
356
359
|
return parser
|
|
357
360
|
|
|
358
361
|
|
|
@@ -361,6 +364,8 @@ def execute(args: argparse.Namespace) -> tuple[int, str]:
|
|
|
361
364
|
return context.execute(args)
|
|
362
365
|
if args.command == "lifecycle":
|
|
363
366
|
return lifecycle.execute(args)
|
|
367
|
+
if args.command == "inbox":
|
|
368
|
+
return inbox.execute(args)
|
|
364
369
|
if versioned.handles(args):
|
|
365
370
|
return versioned.execute(args)
|
|
366
371
|
catalog = load_catalog()
|
|
@@ -376,11 +381,11 @@ def execute(args: argparse.Namespace) -> tuple[int, str]:
|
|
|
376
381
|
|
|
377
382
|
lock_path = args.project.expanduser() / "opinion.lock.json"
|
|
378
383
|
if lock_path.exists() or lock_path.is_symlink():
|
|
379
|
-
raise OpinionError("项目已经使用版本锁定;请用 compose --profile 更新")
|
|
384
|
+
raise OpinionError(_("项目已经使用版本锁定;请用 compose --profile 更新"))
|
|
380
385
|
|
|
381
386
|
custom = read_custom_file(args.custom_file)
|
|
382
387
|
if not args.template and not args.rule and custom is None:
|
|
383
|
-
raise OpinionError("至少选择一个模板、一个规则或一份自定义规则文件")
|
|
388
|
+
raise OpinionError(_("至少选择一个模板、一个规则或一份自定义规则文件"))
|
|
384
389
|
selected, sources = select_rules(catalog, args.template, args.rule)
|
|
385
390
|
mode = selection_mode(args.template, args.rule, custom)
|
|
386
391
|
content, metadata = render_opinion(catalog, selected, sources, custom, mode)
|
|
@@ -409,7 +414,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
409
414
|
if getattr(args, "output", "markdown") == "json":
|
|
410
415
|
output = json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False, indent=2) + "\n"
|
|
411
416
|
else:
|
|
412
|
-
output =
|
|
417
|
+
output = _("错误:{0}\n").format(exc)
|
|
413
418
|
code = 2
|
|
414
419
|
sys.stdout.write(output)
|
|
415
420
|
return code
|