@topmindspace/topmind-handoff 0.5.2 → 0.6.3
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/CHANGELOG.md +24 -0
- package/README.md +7 -6
- package/SKILL.md +52 -14
- package/assets/handoff-example.md +10 -4
- package/assets/handoff-template.md +3 -3
- package/package.json +1 -1
- package/references/compile.md +18 -7
- package/references/environment.md +2 -2
- package/references/export.md +7 -2
- package/references/import.md +5 -10
- package/references/privacy.md +1 -0
- package/references/spec.md +16 -17
- package/topmind_handoff/admin.py +168 -48
- package/topmind_handoff/cli.py +22 -28
- package/topmind_handoff/compile.py +251 -42
- package/topmind_handoff/model.py +37 -2
- package/topmind_handoff/package.py +57 -75
package/topmind_handoff/admin.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import subprocess
|
|
6
|
-
from datetime import date
|
|
7
|
+
from datetime import date
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from topmind_handoff.compile import VIEW_ORDER as SLICE_ORDER
|
|
@@ -11,18 +12,22 @@ from topmind_handoff.compile import build_compiled, compile_repo, render_status
|
|
|
11
12
|
from topmind_handoff.model import (
|
|
12
13
|
INDEX_BYTE_BUDGET,
|
|
13
14
|
INDEX_LINE_BUDGET,
|
|
14
|
-
REVIEW_DAYS,
|
|
15
|
-
SLICE_TITLE,
|
|
16
15
|
ClaimResult,
|
|
17
16
|
Compiled,
|
|
18
17
|
Op,
|
|
19
18
|
origin_markdown,
|
|
20
19
|
parse_authority,
|
|
20
|
+
parse_day,
|
|
21
|
+
review_suffix,
|
|
21
22
|
safe_origin,
|
|
22
23
|
scenario_filename,
|
|
23
24
|
scenario_names,
|
|
25
|
+
slice_title,
|
|
26
|
+
view_slice_order,
|
|
24
27
|
)
|
|
25
28
|
|
|
29
|
+
_SAFE_NAME = re.compile(r"[A-Za-z0-9_-]+")
|
|
30
|
+
|
|
26
31
|
|
|
27
32
|
class PublicRepoRefused(RuntimeError):
|
|
28
33
|
"""Raised when init is asked to write a public memory repo."""
|
|
@@ -32,6 +37,124 @@ def _nl(text: str) -> str:
|
|
|
32
37
|
return text if text.endswith("\n") else text + "\n"
|
|
33
38
|
|
|
34
39
|
|
|
40
|
+
AUTHORITY_INTRO = [
|
|
41
|
+
"# 写入权",
|
|
42
|
+
"",
|
|
43
|
+
"身份和边界的主人固定是 human,只接受 outbox/human.md 里的操作。",
|
|
44
|
+
"每个环境只许在自己的 outbox 末尾追加,只许整文件替换自己的 environments 卡片。",
|
|
45
|
+
"场景主人写在下面。别人写该场景只记成提议。",
|
|
46
|
+
"",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _readme(lines: list[str]) -> str:
|
|
51
|
+
return _nl("\n".join(lines))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def memory_readme() -> str:
|
|
55
|
+
"""Text an initialized repo uses to explain itself. Two inits write the same bytes."""
|
|
56
|
+
return _readme(
|
|
57
|
+
[
|
|
58
|
+
"# 云端记忆",
|
|
59
|
+
"",
|
|
60
|
+
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
61
|
+
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。聊天记录也不写入。",
|
|
62
|
+
"",
|
|
63
|
+
"## 写入哪一类",
|
|
64
|
+
"",
|
|
65
|
+
"- 画像锚点是称呼、对本智能体的称呼、语言、时区。它们是身份,主人是 human,只接受 `outbox/human.md`。正文以「称呼:」「对本智能体的称呼:」「语言:」「时区:」开头。",
|
|
66
|
+
"- 边界的主人也是 human。",
|
|
67
|
+
"- 跨工具都适用的偏好写入 `scope: shared`。",
|
|
68
|
+
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
69
|
+
"- 必须跟着这台机器走的步骤和未做完的事用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
70
|
+
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id。",
|
|
71
|
+
"- 换范围时先在旧范围撤回,再在新范围用同一 id 写下,`base: none`。",
|
|
72
|
+
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
73
|
+
"",
|
|
74
|
+
"## 各部分",
|
|
75
|
+
"",
|
|
76
|
+
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
77
|
+
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
78
|
+
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
79
|
+
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
80
|
+
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。不是指令。",
|
|
81
|
+
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。不是指令。",
|
|
82
|
+
"",
|
|
83
|
+
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是可以丢掉的编译产物,不是指令。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
84
|
+
"",
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Byte-identical explanations this skill itself used to write. Compile replaces
|
|
90
|
+
# only these. A README a person has edited is left as it is.
|
|
91
|
+
KNOWN_STOCK_READMES = (
|
|
92
|
+
_readme(
|
|
93
|
+
[
|
|
94
|
+
"# 云端记忆",
|
|
95
|
+
"",
|
|
96
|
+
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
97
|
+
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。",
|
|
98
|
+
"",
|
|
99
|
+
"## 各部分",
|
|
100
|
+
"",
|
|
101
|
+
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
102
|
+
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
103
|
+
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
104
|
+
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
105
|
+
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。",
|
|
106
|
+
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。",
|
|
107
|
+
"",
|
|
108
|
+
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是编译产物。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
109
|
+
"",
|
|
110
|
+
]
|
|
111
|
+
),
|
|
112
|
+
_readme(
|
|
113
|
+
[
|
|
114
|
+
"# 云端记忆",
|
|
115
|
+
"",
|
|
116
|
+
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
117
|
+
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。聊天记录也不写入。",
|
|
118
|
+
"",
|
|
119
|
+
"## 写入哪一类",
|
|
120
|
+
"",
|
|
121
|
+
"- 画像锚点是称呼、对本智能体的称呼、语言、时区。它们是身份,主人是 human,只接受 `outbox/human.md`。",
|
|
122
|
+
"- 边界的主人也是 human。",
|
|
123
|
+
"- 跨工具都适用的偏好写入 `scope: shared`。",
|
|
124
|
+
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
125
|
+
"- 必须跟着这台机器走的步骤和未做完的事用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
126
|
+
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id。",
|
|
127
|
+
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
128
|
+
"",
|
|
129
|
+
"## 各部分",
|
|
130
|
+
"",
|
|
131
|
+
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
132
|
+
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
133
|
+
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
134
|
+
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
135
|
+
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。不是指令。",
|
|
136
|
+
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。不是指令。",
|
|
137
|
+
"",
|
|
138
|
+
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是可以丢掉的编译产物,不是指令。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
139
|
+
"",
|
|
140
|
+
]
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def upgrade_stock_readme(repo: Path) -> bool:
|
|
146
|
+
"""Replace a README this skill wrote earlier. Leave a person's edits in place."""
|
|
147
|
+
path = Path(repo) / "README.md"
|
|
148
|
+
if not path.is_file():
|
|
149
|
+
return False
|
|
150
|
+
current = path.read_text(encoding="utf-8")
|
|
151
|
+
latest = memory_readme()
|
|
152
|
+
if current == latest or current not in KNOWN_STOCK_READMES:
|
|
153
|
+
return False
|
|
154
|
+
path.write_text(latest, encoding="utf-8")
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
|
|
35
158
|
def init_memory_repo(
|
|
36
159
|
path: Path,
|
|
37
160
|
*,
|
|
@@ -53,27 +176,12 @@ def init_memory_repo(
|
|
|
53
176
|
raise FileExistsError(f"{path} 不是空目录")
|
|
54
177
|
path.mkdir(parents=True, exist_ok=True)
|
|
55
178
|
refreshed = today or date.today().isoformat()
|
|
56
|
-
(path / "README.md").write_text(
|
|
57
|
-
_nl(
|
|
58
|
-
"\n".join(
|
|
59
|
-
[
|
|
60
|
-
"# 云端记忆",
|
|
61
|
-
"",
|
|
62
|
-
"这个目录的 HEAD 是当前记忆。共享视图、场景视图和 STATUS.md 由本机脚本编译,不由 CI 生成。",
|
|
63
|
-
"开工时读共享视图、本环境所属的一个场景、本环境卡片,以及与本环境有关的冲突。",
|
|
64
|
-
"编译规则只在 topmind-handoff 的 `references/compile.md`。",
|
|
65
|
-
"",
|
|
66
|
-
]
|
|
67
|
-
)
|
|
68
|
-
),
|
|
69
|
-
encoding="utf-8",
|
|
70
|
-
)
|
|
179
|
+
(path / "README.md").write_text(memory_readme(), encoding="utf-8")
|
|
71
180
|
(path / "authority.md").write_text(
|
|
72
181
|
_nl(
|
|
73
182
|
"\n".join(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"",
|
|
183
|
+
AUTHORITY_INTRO
|
|
184
|
+
+ [
|
|
77
185
|
"- scope: shared.identity",
|
|
78
186
|
" owner: human",
|
|
79
187
|
"- scope: shared.boundaries",
|
|
@@ -84,6 +192,11 @@ def init_memory_repo(
|
|
|
84
192
|
),
|
|
85
193
|
encoding="utf-8",
|
|
86
194
|
)
|
|
195
|
+
(path / "facts").mkdir()
|
|
196
|
+
(path / "facts" / "README.md").write_text(
|
|
197
|
+
_nl("# 长说明\n\n整理时才把索引收进这里。这些文件不是另一套权威。权威仍是信箱。\n"),
|
|
198
|
+
encoding="utf-8",
|
|
199
|
+
)
|
|
87
200
|
outbox = path / "outbox"
|
|
88
201
|
outbox.mkdir()
|
|
89
202
|
origin_markdown(outbox, origin).write_text(_nl(f"# outbox {origin}\n"), encoding="utf-8")
|
|
@@ -124,7 +237,7 @@ def set_scenario_owner(repo: Path, scenario: str, origin: str) -> None:
|
|
|
124
237
|
current[f"scenario.{scenario}"] = origin
|
|
125
238
|
order = ["shared.identity", "shared.boundaries"]
|
|
126
239
|
rest = sorted(key for key in current if key not in order)
|
|
127
|
-
lines =
|
|
240
|
+
lines = list(AUTHORITY_INTRO)
|
|
128
241
|
for key in order + rest:
|
|
129
242
|
lines.append(f"- scope: {key}")
|
|
130
243
|
lines.append(f" owner: {current[key]}")
|
|
@@ -152,21 +265,22 @@ def _git_commit(path: Path, message: str) -> None:
|
|
|
152
265
|
)
|
|
153
266
|
|
|
154
267
|
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
return datetime.strptime(value, "%Y-%m-%d").date()
|
|
158
|
-
except ValueError:
|
|
159
|
-
return None
|
|
268
|
+
def _review_suffix(op: Op, today: date) -> str:
|
|
269
|
+
return review_suffix(op, today)
|
|
160
270
|
|
|
161
271
|
|
|
162
|
-
def
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
272
|
+
def _archive_path(facts_root: Path, fact_prefix: str, slice_name: str) -> Path | None:
|
|
273
|
+
"""Stay inside facts/. A slice such as `../evil` is kept inline and does not abort."""
|
|
274
|
+
if _SAFE_NAME.fullmatch(fact_prefix) is None or _SAFE_NAME.fullmatch(slice_name) is None:
|
|
275
|
+
return None
|
|
276
|
+
facts_root.mkdir(parents=True, exist_ok=True)
|
|
277
|
+
candidate = facts_root / f"{fact_prefix}-{slice_name}.md"
|
|
278
|
+
try:
|
|
279
|
+
if candidate.resolve().parent != facts_root.resolve():
|
|
280
|
+
return None
|
|
281
|
+
except OSError:
|
|
282
|
+
return None
|
|
283
|
+
return candidate
|
|
170
284
|
|
|
171
285
|
|
|
172
286
|
def _write_slice_archive(
|
|
@@ -177,16 +291,15 @@ def _write_slice_archive(
|
|
|
177
291
|
rich: list[str],
|
|
178
292
|
) -> None:
|
|
179
293
|
"""Keep every collapsed index line, including facts that have no detail."""
|
|
180
|
-
facts_root
|
|
294
|
+
dest = _archive_path(facts_root, fact_prefix, slice_name)
|
|
295
|
+
if dest is None:
|
|
296
|
+
return
|
|
181
297
|
rich_lines = set(rich)
|
|
182
298
|
body = list(rich)
|
|
183
299
|
body.extend(line for line in lines if line not in rich_lines)
|
|
184
300
|
if not body:
|
|
185
301
|
body = list(lines)
|
|
186
|
-
(
|
|
187
|
-
_nl("# " + slice_name + "\n\n" + "\n".join(body)),
|
|
188
|
-
encoding="utf-8",
|
|
189
|
-
)
|
|
302
|
+
dest.write_text(_nl("# " + slice_name + "\n\n" + "\n".join(body)), encoding="utf-8")
|
|
190
303
|
|
|
191
304
|
|
|
192
305
|
def _results_for(compiled: Compiled, scope: str) -> list[ClaimResult]:
|
|
@@ -213,9 +326,11 @@ def _compact_scope(title: str, results: list[ClaimResult], today: date, facts_ro
|
|
|
213
326
|
|
|
214
327
|
def render(section_map: dict[str, list[str]]) -> str:
|
|
215
328
|
lines = [f"# {title}", ""]
|
|
216
|
-
for slice_name in
|
|
217
|
-
lines.append(f"## {SLICE_TITLE[slice_name]}")
|
|
329
|
+
for slice_name in view_slice_order(section_map):
|
|
218
330
|
body = section_map.get(slice_name) or []
|
|
331
|
+
if slice_name not in SLICE_ORDER and not body:
|
|
332
|
+
continue
|
|
333
|
+
lines.append(f"## {slice_title(slice_name)}")
|
|
219
334
|
lines.extend(body or ["(无)"])
|
|
220
335
|
lines.append("")
|
|
221
336
|
lines.append("## 冲突")
|
|
@@ -234,14 +349,19 @@ def _compact_scope(title: str, results: list[ClaimResult], today: date, facts_ro
|
|
|
234
349
|
for slice_name, block in details.items():
|
|
235
350
|
if not block:
|
|
236
351
|
continue
|
|
237
|
-
facts_root
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
)
|
|
352
|
+
dest = _archive_path(facts_root, fact_prefix, slice_name)
|
|
353
|
+
if dest is None:
|
|
354
|
+
continue
|
|
355
|
+
dest.write_text(_nl("# " + slice_name + "\n\n" + "\n".join(block)), encoding="utf-8")
|
|
241
356
|
|
|
242
357
|
text = render(sections)
|
|
243
358
|
# Collapse the largest slices until the index fits the startup budget.
|
|
244
359
|
collapsible = [name for name in ("fact", "preference", "project", "identity", "boundaries")]
|
|
360
|
+
collapsible.extend(
|
|
361
|
+
name
|
|
362
|
+
for name in sections
|
|
363
|
+
if name not in SLICE_ORDER and _SAFE_NAME.fullmatch(name) and name not in collapsible
|
|
364
|
+
)
|
|
245
365
|
while (text.count("\n") > INDEX_LINE_BUDGET or len(text.encode("utf-8")) > INDEX_BYTE_BUDGET) and any(
|
|
246
366
|
len(sections.get(name) or []) > 1 for name in collapsible
|
|
247
367
|
):
|
|
@@ -286,7 +406,7 @@ def _mark_stale(card_path: Path, today: date) -> None:
|
|
|
286
406
|
def _card_refreshed(raw: str) -> date | None:
|
|
287
407
|
for line in raw.splitlines():
|
|
288
408
|
if line.startswith("refreshed:"):
|
|
289
|
-
return
|
|
409
|
+
return parse_day(line.split(":", 1)[1].strip())
|
|
290
410
|
return None
|
|
291
411
|
|
|
292
412
|
|
|
@@ -330,7 +450,7 @@ def organize_repo(repo: Path, today: date | None = None) -> Compiled:
|
|
|
330
450
|
_mark_stale(path, today)
|
|
331
451
|
# Cards may now be stale; rebuild status from the same outbox tips.
|
|
332
452
|
compiled = build_compiled(repo)
|
|
333
|
-
(repo / "STATUS.md").write_text(render_status(compiled), encoding="utf-8")
|
|
453
|
+
(repo / "STATUS.md").write_text(render_status(compiled, today=today), encoding="utf-8")
|
|
334
454
|
after = {path.name: path.read_bytes() for path in sorted(outbox.glob("*.md"))} if outbox.is_dir() else {}
|
|
335
455
|
if before != after:
|
|
336
456
|
raise RuntimeError("整理改写了信箱")
|
package/topmind_handoff/cli.py
CHANGED
|
@@ -8,19 +8,18 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
from topmind_handoff.admin import PublicRepoRefused, init_memory_repo, organize_repo, set_scenario_owner
|
|
10
10
|
from topmind_handoff.compile import compile_repo, startup_read, status_is_valid
|
|
11
|
-
from topmind_handoff.package import
|
|
11
|
+
from topmind_handoff.package import render_package
|
|
12
12
|
|
|
13
13
|
USAGE = """\
|
|
14
14
|
用法:
|
|
15
|
-
python3 scripts/compile_memory.py <记忆目录>
|
|
15
|
+
python3 scripts/compile_memory.py <记忆目录> [--today YYYY-MM-DD]
|
|
16
16
|
python3 scripts/compile_memory.py --check <记忆目录>
|
|
17
17
|
python3 scripts/compile_memory.py --init <目录> --private --origin <origin_id> [--role 场景] [--today YYYY-MM-DD]
|
|
18
18
|
python3 scripts/compile_memory.py --init <目录> --public --origin <origin_id>
|
|
19
19
|
python3 scripts/compile_memory.py --organize <记忆目录> [--today YYYY-MM-DD]
|
|
20
|
-
python3 scripts/compile_memory.py --render <记忆目录> --origin <origin_id> [--out 文件]
|
|
21
|
-
python3 scripts/compile_memory.py --render-legacy --out 文件
|
|
20
|
+
python3 scripts/compile_memory.py --render <记忆目录> --origin <origin_id> [--out 文件] [--today YYYY-MM-DD]
|
|
22
21
|
python3 scripts/compile_memory.py --own <记忆目录> --scenario <场景> --origin <origin_id>
|
|
23
|
-
python3 scripts/compile_memory.py --startup <记忆目录> --origin <origin_id>
|
|
22
|
+
python3 scripts/compile_memory.py --startup <记忆目录> --origin <origin_id> [--today YYYY-MM-DD]
|
|
24
23
|
"""
|
|
25
24
|
|
|
26
25
|
|
|
@@ -42,8 +41,6 @@ def main(argv: list[str]) -> int:
|
|
|
42
41
|
return _organize(argv[1:])
|
|
43
42
|
if argv[0] == "--render":
|
|
44
43
|
return _render(argv[1:])
|
|
45
|
-
if argv[0] == "--render-legacy":
|
|
46
|
-
return _render_legacy(argv[1:])
|
|
47
44
|
if argv[0] == "--own":
|
|
48
45
|
return _own(argv[1:])
|
|
49
46
|
if argv[0] == "--startup":
|
|
@@ -51,9 +48,7 @@ def main(argv: list[str]) -> int:
|
|
|
51
48
|
if argv[0].startswith("--"):
|
|
52
49
|
print(USAGE)
|
|
53
50
|
return 2
|
|
54
|
-
|
|
55
|
-
print(f"compiled {argv[0]}")
|
|
56
|
-
return 0
|
|
51
|
+
return _compile(argv)
|
|
57
52
|
|
|
58
53
|
|
|
59
54
|
def _init(argv: list[str]) -> int:
|
|
@@ -81,6 +76,17 @@ def _init(argv: list[str]) -> int:
|
|
|
81
76
|
return 0
|
|
82
77
|
|
|
83
78
|
|
|
79
|
+
def _compile(argv: list[str]) -> int:
|
|
80
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
81
|
+
parser.add_argument("path")
|
|
82
|
+
parser.add_argument("--today")
|
|
83
|
+
args = parser.parse_args(argv)
|
|
84
|
+
today = _day(args.today) if args.today else None
|
|
85
|
+
compile_repo(Path(args.path), today)
|
|
86
|
+
print(f"compiled {args.path}")
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
84
90
|
def _organize(argv: list[str]) -> int:
|
|
85
91
|
parser = argparse.ArgumentParser(add_help=False)
|
|
86
92
|
parser.add_argument("path")
|
|
@@ -97,9 +103,11 @@ def _render(argv: list[str]) -> int:
|
|
|
97
103
|
parser.add_argument("path")
|
|
98
104
|
parser.add_argument("--origin", required=True)
|
|
99
105
|
parser.add_argument("--out")
|
|
106
|
+
parser.add_argument("--today")
|
|
100
107
|
args = parser.parse_args(argv)
|
|
108
|
+
today = _day(args.today) if args.today else None
|
|
101
109
|
try:
|
|
102
|
-
text = render_package(Path(args.path), args.origin)
|
|
110
|
+
text = render_package(Path(args.path), args.origin, today=today)
|
|
103
111
|
except ValueError as exc:
|
|
104
112
|
print(str(exc))
|
|
105
113
|
return 1
|
|
@@ -130,28 +138,14 @@ def _startup(argv: list[str]) -> int:
|
|
|
130
138
|
parser = argparse.ArgumentParser(add_help=False)
|
|
131
139
|
parser.add_argument("path")
|
|
132
140
|
parser.add_argument("--origin", required=True)
|
|
141
|
+
parser.add_argument("--today")
|
|
133
142
|
args = parser.parse_args(argv)
|
|
143
|
+
today = _day(args.today) if args.today else None
|
|
134
144
|
try:
|
|
135
|
-
print(startup_read(Path(args.path), args.origin), end="")
|
|
145
|
+
print(startup_read(Path(args.path), args.origin, today=today), end="")
|
|
136
146
|
except ValueError as exc:
|
|
137
147
|
print(str(exc))
|
|
138
148
|
return 1
|
|
139
149
|
return 0
|
|
140
150
|
|
|
141
151
|
|
|
142
|
-
def _render_legacy(argv: list[str]) -> int:
|
|
143
|
-
parser = argparse.ArgumentParser(add_help=False)
|
|
144
|
-
parser.add_argument("--out", required=True)
|
|
145
|
-
parser.add_argument("--mode", default="migration")
|
|
146
|
-
args = parser.parse_args(argv)
|
|
147
|
-
text = render_legacy_package(
|
|
148
|
-
[
|
|
149
|
-
{"section": "1. 这个人是谁(Identity)", "text": "称呼:示例", "id": "anchor-name"},
|
|
150
|
-
{"section": "1. 这个人是谁(Identity)", "text": "时区:Asia/Shanghai", "id": "anchor-tz"},
|
|
151
|
-
{"section": "5. 事实与记忆(Facts & memories)", "text": "已撤回:示例条目", "id": "pref-vim"},
|
|
152
|
-
],
|
|
153
|
-
mode=args.mode,
|
|
154
|
-
)
|
|
155
|
-
Path(args.out).write_text(text, encoding="utf-8")
|
|
156
|
-
print(args.out)
|
|
157
|
-
return 0
|