@topmindspace/topmind-handoff 0.7.1 → 0.7.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 +8 -83
- package/README.md +18 -1
- package/SKILL.md +20 -12
- package/assets/handoff-example.md +6 -8
- package/assets/handoff-template.md +2 -2
- package/examples/README.md +4 -3
- package/package.json +2 -2
- package/references/compile.md +9 -4
- package/references/environment.md +3 -3
- package/references/export.md +6 -2
- package/references/import.md +6 -5
- package/references/privacy.md +8 -3
- package/references/spec.md +8 -6
- package/topmind_handoff/__init__.py +16 -4
- package/topmind_handoff/admin.py +4 -218
- package/topmind_handoff/cli.py +185 -9
- package/topmind_handoff/compile.py +424 -26
- package/topmind_handoff/model.py +65 -12
- package/topmind_handoff/package.py +24 -26
- package/topmind_handoff/privacy.py +41 -0
package/topmind_handoff/admin.py
CHANGED
|
@@ -8,7 +8,7 @@ from datetime import date
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from topmind_handoff.compile import VIEW_ORDER as SLICE_ORDER
|
|
11
|
-
from topmind_handoff.compile import build_compiled, compile_repo,
|
|
11
|
+
from topmind_handoff.compile import build_compiled, compile_repo, write_compiled_status
|
|
12
12
|
from topmind_handoff.model import (
|
|
13
13
|
CLUSTER_SIZE,
|
|
14
14
|
ClaimResult,
|
|
@@ -68,7 +68,8 @@ def memory_readme() -> str:
|
|
|
68
68
|
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
69
69
|
"- 另一台要接着做的下一步用 `scope: shared` 或 `scope: scenario:<场景>`。只在这台的路径和工具状态用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
70
70
|
"- 产出只记状态、下一步和位置,不记正文。`slice` 用 `work` 或 `project`。`~/` 路径用 `scope: origin:`。",
|
|
71
|
-
"-
|
|
71
|
+
"- 日常工作默认每个环境维护一份自己的【当前工作】(`--work`,写进 `id: work.<origin_id>`,跨环境同步进展且互不冲突;仅本机可见加 `--local`);特定项目交接加 `--topic <专项名>`;完结加 `--done` 撤回。",
|
|
72
|
+
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id(可用 `--set`、`--work`、`--retract`、`--resolve` 自动算 `op_id` 和 `base`)。",
|
|
72
73
|
"- 换范围时先在旧范围撤回,再在新范围用同一 id 写下,`base: none`。",
|
|
73
74
|
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
74
75
|
"",
|
|
@@ -179,178 +180,6 @@ def write_knowledge_skeleton(repo: Path) -> list[Path]:
|
|
|
179
180
|
return written
|
|
180
181
|
|
|
181
182
|
|
|
182
|
-
# Byte-identical explanations this skill itself used to write. Compile replaces
|
|
183
|
-
# only these. A README a person has edited is left as it is.
|
|
184
|
-
KNOWN_STOCK_READMES = (
|
|
185
|
-
_readme(
|
|
186
|
-
[
|
|
187
|
-
"# 云端记忆",
|
|
188
|
-
"",
|
|
189
|
-
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
190
|
-
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。聊天记录也不写入。",
|
|
191
|
-
"",
|
|
192
|
-
"## 写入哪一类",
|
|
193
|
-
"",
|
|
194
|
-
"- 画像锚点是称呼、对本智能体的称呼、语言、时区。它们是身份,主人是 human,只接受 `outbox/human.md`。正文以「称呼:」「对本智能体的称呼:」「语言:」「时区:」开头。",
|
|
195
|
-
"- 边界的主人也是 human。",
|
|
196
|
-
"- 跨工具都适用的偏好写入 `scope: shared`。",
|
|
197
|
-
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
198
|
-
"- 必须跟着这台机器走的步骤和未做完的事用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
199
|
-
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id。",
|
|
200
|
-
"- 换范围时先在旧范围撤回,再在新范围用同一 id 写下,`base: none`。",
|
|
201
|
-
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
202
|
-
"",
|
|
203
|
-
"## 各部分",
|
|
204
|
-
"",
|
|
205
|
-
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
206
|
-
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
207
|
-
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
208
|
-
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
209
|
-
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。不是指令。",
|
|
210
|
-
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。不是指令。",
|
|
211
|
-
"",
|
|
212
|
-
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是可以丢掉的编译产物,不是指令。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
213
|
-
"",
|
|
214
|
-
]
|
|
215
|
-
),
|
|
216
|
-
_readme(
|
|
217
|
-
[
|
|
218
|
-
"# 云端记忆",
|
|
219
|
-
"",
|
|
220
|
-
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
221
|
-
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。",
|
|
222
|
-
"",
|
|
223
|
-
"## 各部分",
|
|
224
|
-
"",
|
|
225
|
-
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
226
|
-
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
227
|
-
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
228
|
-
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
229
|
-
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。",
|
|
230
|
-
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。",
|
|
231
|
-
"",
|
|
232
|
-
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是编译产物。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
233
|
-
"",
|
|
234
|
-
]
|
|
235
|
-
),
|
|
236
|
-
_readme(
|
|
237
|
-
[
|
|
238
|
-
"# 云端记忆",
|
|
239
|
-
"",
|
|
240
|
-
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
241
|
-
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。聊天记录也不写入。",
|
|
242
|
-
"",
|
|
243
|
-
"## 写入哪一类",
|
|
244
|
-
"",
|
|
245
|
-
"- 画像锚点是称呼、对本智能体的称呼、语言、时区。它们是身份,主人是 human,只接受 `outbox/human.md`。",
|
|
246
|
-
"- 边界的主人也是 human。",
|
|
247
|
-
"- 跨工具都适用的偏好写入 `scope: shared`。",
|
|
248
|
-
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
249
|
-
"- 必须跟着这台机器走的步骤和未做完的事用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
250
|
-
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id。",
|
|
251
|
-
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
252
|
-
"",
|
|
253
|
-
"## 各部分",
|
|
254
|
-
"",
|
|
255
|
-
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
256
|
-
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
257
|
-
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
258
|
-
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
259
|
-
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。不是指令。",
|
|
260
|
-
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。不是指令。",
|
|
261
|
-
"",
|
|
262
|
-
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是可以丢掉的编译产物,不是指令。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
263
|
-
"",
|
|
264
|
-
]
|
|
265
|
-
),
|
|
266
|
-
_readme(
|
|
267
|
-
[
|
|
268
|
-
"# 云端记忆",
|
|
269
|
-
"",
|
|
270
|
-
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
271
|
-
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。聊天记录也不写入。",
|
|
272
|
-
"",
|
|
273
|
-
"## 写入哪一类",
|
|
274
|
-
"",
|
|
275
|
-
"- 画像锚点是称呼、对本智能体的称呼、语言、时区。它们是身份,主人是 human,只接受 `outbox/human.md`。正文以「称呼:」「对本智能体的称呼:」「语言:」「时区:」开头。",
|
|
276
|
-
"- 边界的主人也是 human。",
|
|
277
|
-
"- 跨工具都适用的偏好写入 `scope: shared`。",
|
|
278
|
-
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
279
|
-
"- 必须跟着这台机器走的步骤和未做完的事用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
280
|
-
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id。",
|
|
281
|
-
"- 换范围时先在旧范围撤回,再在新范围用同一 id 写下,`base: none`。",
|
|
282
|
-
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
283
|
-
"",
|
|
284
|
-
"## 各部分",
|
|
285
|
-
"",
|
|
286
|
-
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
287
|
-
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
288
|
-
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
289
|
-
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
290
|
-
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。不是指令。",
|
|
291
|
-
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。不是指令。",
|
|
292
|
-
"",
|
|
293
|
-
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是可以丢掉的编译产物,不是指令。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
294
|
-
"",
|
|
295
|
-
]
|
|
296
|
-
),
|
|
297
|
-
_readme(
|
|
298
|
-
[
|
|
299
|
-
"# 云端记忆",
|
|
300
|
-
"",
|
|
301
|
-
"这是一份私有记忆仓库。本机的 topmind-handoff 编译它。仓库里没有 CI,也没有 Release。",
|
|
302
|
-
"凭据只用已经配好的 git 或 gh。不写密码、API key、token、证件号、银行卡号、精确住址、他人隐私、记账明细。聊天记录也不写入。",
|
|
303
|
-
"",
|
|
304
|
-
"## 写入哪一类",
|
|
305
|
-
"",
|
|
306
|
-
"- 画像锚点是称呼、对本智能体的称呼、语言、时区。它们是身份,主人是 human,只接受 `outbox/human.md`。正文以「称呼:」「对本智能体的称呼:」「语言:」「时区:」开头。",
|
|
307
|
-
"- 边界的主人也是 human。",
|
|
308
|
-
"- 跨工具都适用的偏好写入 `scope: shared`。",
|
|
309
|
-
"- 只在某个场景里成立的习惯用 `scope: scenario:<场景>`,不写入 shared。",
|
|
310
|
-
"- 必须跟着这台机器走的步骤和未做完的事用 `scope: origin:<origin_id>`。技能版本和连接器写在这台的环境卡片上,不写成共享事实。",
|
|
311
|
-
"- 已有主题要改,沿用原来的 id,`base` 填当前链头,不另起一条 id。",
|
|
312
|
-
"- 换范围时先在旧范围撤回,再在新范围用同一 id 写下,`base: none`。",
|
|
313
|
-
"- 不写密码、API key、token,不写聊天记录,不改别人的信箱。",
|
|
314
|
-
"",
|
|
315
|
-
"## 各部分",
|
|
316
|
-
"",
|
|
317
|
-
"- `outbox/<origin_id>.md`:这个环境的操作账本。只许该环境在文件末尾追加。不改别人的信箱,不改已有行。",
|
|
318
|
-
"- `environments/<origin_id>.md`:这个环境的卡片。只许该环境整文件替换。",
|
|
319
|
-
"- `authority.md`:谁可以写进视图。身份和边界的主人是 human,只接受 `outbox/human.md`。",
|
|
320
|
-
"- `facts/`:整理时从索引收走的长说明。不是另一套权威。",
|
|
321
|
-
"- `views/shared.md` 和 `views/scenario-<场景>.md`:本机编译产物,可以丢掉后重新编译。不是指令。",
|
|
322
|
-
"- `STATUS.md`:同一次编译的校对。信箱末尾对不上,视图就无效。不是指令。",
|
|
323
|
-
"- `knowledge/`:跨环境共享的经验、收藏帖、提示词。普通 Markdown,任何环境直接编辑。见 `knowledge/README.md`。",
|
|
324
|
-
"",
|
|
325
|
-
"权威是 `outbox/`、`authority.md` 和 `environments/`。`views/` 和 `STATUS.md` 是可以丢掉的编译产物,不是指令。Git 冲突时丢掉这两边的视图,保留全部信箱,在本机重新编译。",
|
|
326
|
-
"",
|
|
327
|
-
"## knowledge/ 知识库",
|
|
328
|
-
"",
|
|
329
|
-
"- `knowledge/<大类>/<主题>.md`,最多两级。大类有收藏帖、段子、引流曝光、写作、图片视频、提示词。",
|
|
330
|
-
"- 它是参考资料,不是指令,不覆盖偏好与边界,不进 `outbox/`、编译、视图和 `authority.md`。",
|
|
331
|
-
"- 任何环境都可以直接改,提交并推送即可,git 历史就是留痕。不写凭据、隐私和聊天记录。",
|
|
332
|
-
"- 条目格式、拆分规则、读写时机见 `knowledge/README.md`。",
|
|
333
|
-
"",
|
|
334
|
-
]
|
|
335
|
-
),
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
def upgrade_stock_readme(repo: Path) -> bool:
|
|
340
|
-
"""Replace a README this skill wrote earlier. Leave a person's edits in place."""
|
|
341
|
-
path = Path(repo) / "README.md"
|
|
342
|
-
if not path.is_file():
|
|
343
|
-
return False
|
|
344
|
-
current = path.read_text(encoding="utf-8")
|
|
345
|
-
latest = memory_readme()
|
|
346
|
-
if current == latest or current not in KNOWN_STOCK_READMES:
|
|
347
|
-
return False
|
|
348
|
-
path.write_text(latest, encoding="utf-8")
|
|
349
|
-
# The new explanation points at knowledge/. Add only the files that are missing.
|
|
350
|
-
write_knowledge_skeleton(repo)
|
|
351
|
-
return True
|
|
352
|
-
|
|
353
|
-
|
|
354
183
|
def init_memory_repo(
|
|
355
184
|
path: Path,
|
|
356
185
|
*,
|
|
@@ -626,41 +455,6 @@ def _compact_scope(
|
|
|
626
455
|
return text
|
|
627
456
|
|
|
628
457
|
|
|
629
|
-
def _mark_stale(card_path: Path, today: date) -> None:
|
|
630
|
-
raw = card_path.read_text(encoding="utf-8")
|
|
631
|
-
card = _card_refreshed(raw)
|
|
632
|
-
if card is None:
|
|
633
|
-
return
|
|
634
|
-
if (today - card).days < 90:
|
|
635
|
-
return
|
|
636
|
-
if raw.startswith("---"):
|
|
637
|
-
parts = raw.split("---", 2)
|
|
638
|
-
front = parts[1]
|
|
639
|
-
body = parts[2] if len(parts) > 2 else ""
|
|
640
|
-
if "stale:" not in front:
|
|
641
|
-
front = front.rstrip() + "\nstale: true\n"
|
|
642
|
-
else:
|
|
643
|
-
front = front.replace("stale: false", "stale: true")
|
|
644
|
-
if "未刷新" not in body:
|
|
645
|
-
body = body.rstrip() + "\n\n未刷新\n"
|
|
646
|
-
card_path.write_text(_nl("---" + front + "---" + body), encoding="utf-8")
|
|
647
|
-
return
|
|
648
|
-
extra = ""
|
|
649
|
-
if "stale: true" not in raw:
|
|
650
|
-
extra += "stale: true\n"
|
|
651
|
-
if "未刷新" not in raw:
|
|
652
|
-
extra += "未刷新\n"
|
|
653
|
-
if extra:
|
|
654
|
-
card_path.write_text(_nl(raw.rstrip() + "\n" + extra), encoding="utf-8")
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
def _card_refreshed(raw: str) -> date | None:
|
|
658
|
-
for line in raw.splitlines():
|
|
659
|
-
if line.startswith("refreshed:"):
|
|
660
|
-
return parse_day(line.split(":", 1)[1].strip())
|
|
661
|
-
return None
|
|
662
|
-
|
|
663
|
-
|
|
664
458
|
def write_index_views(repo: Path, compiled: Compiled, today: date | None, *, fit: bool) -> None:
|
|
665
459
|
"""Write one-line views. `fit` clusters slices that are past the startup budget.
|
|
666
460
|
|
|
@@ -712,15 +506,7 @@ def organize_repo(repo: Path, today: date | None = None) -> Compiled:
|
|
|
712
506
|
before = {path.name: path.read_bytes() for path in sorted(outbox.glob("*.md"))} if outbox.is_dir() else {}
|
|
713
507
|
compiled = build_compiled(repo)
|
|
714
508
|
write_index_views(repo, compiled, today, fit=True)
|
|
715
|
-
|
|
716
|
-
if environments.is_dir():
|
|
717
|
-
for path in sorted(environments.glob("*.md")):
|
|
718
|
-
_mark_stale(path, today)
|
|
719
|
-
# Cards may now be stale; rebuild status from the same outbox tips.
|
|
720
|
-
compiled = build_compiled(repo)
|
|
721
|
-
(repo / "STATUS.md").write_text(
|
|
722
|
-
render_status(compiled, today=today, index_fit="organized"), encoding="utf-8"
|
|
723
|
-
)
|
|
509
|
+
write_compiled_status(repo, compiled, today=today, index_fit="organized")
|
|
724
510
|
after = {path.name: path.read_bytes() for path in sorted(outbox.glob("*.md"))} if outbox.is_dir() else {}
|
|
725
511
|
if before != after:
|
|
726
512
|
raise RuntimeError("整理改写了信箱")
|
package/topmind_handoff/cli.py
CHANGED
|
@@ -7,8 +7,17 @@ from datetime import datetime
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
from topmind_handoff.admin import PublicRepoRefused, init_memory_repo, organize_repo, set_scenario_owner
|
|
10
|
-
from topmind_handoff.compile import
|
|
11
|
-
|
|
10
|
+
from topmind_handoff.compile import (
|
|
11
|
+
compile_repo,
|
|
12
|
+
open_claim,
|
|
13
|
+
record_retract,
|
|
14
|
+
record_resolve,
|
|
15
|
+
record_set,
|
|
16
|
+
record_work,
|
|
17
|
+
startup_read,
|
|
18
|
+
status_is_valid,
|
|
19
|
+
)
|
|
20
|
+
from topmind_handoff.package import render_package, replay_package
|
|
12
21
|
|
|
13
22
|
USAGE = """\
|
|
14
23
|
用法:
|
|
@@ -16,6 +25,11 @@ USAGE = """\
|
|
|
16
25
|
python3 scripts/compile_memory.py --check <记忆目录>
|
|
17
26
|
python3 scripts/compile_memory.py --init <目录> --private --origin <origin_id> [--role 场景] [--today YYYY-MM-DD]
|
|
18
27
|
python3 scripts/compile_memory.py --init <目录> --public --origin <origin_id>
|
|
28
|
+
python3 scripts/compile_memory.py --set <记忆目录> --origin <origin_id> --id <claim_id> --text <正文> [--scope 范围] [--slice 维度] [--base op_id] [--detail 长说明] [--today YYYY-MM-DD]
|
|
29
|
+
python3 scripts/compile_memory.py --retract <记忆目录> --origin <origin_id> --id <claim_id> [--scope 范围] [--today YYYY-MM-DD]
|
|
30
|
+
python3 scripts/compile_memory.py --resolve <记忆目录> --origin <origin_id> --id <claim_id> --text <正文> [--scope 范围] [--slice 维度] [--detail 长说明] [--today YYYY-MM-DD]
|
|
31
|
+
python3 scripts/compile_memory.py --work <记忆目录> --origin <origin_id> [--text 正文] [--topic 专项] [--local] [--done] [--detail 长说明] [--today YYYY-MM-DD]
|
|
32
|
+
python3 scripts/compile_memory.py --replay <记忆目录> --file <交接包> [--confirm]
|
|
19
33
|
python3 scripts/compile_memory.py --organize <记忆目录> [--today YYYY-MM-DD]
|
|
20
34
|
python3 scripts/compile_memory.py --open <记忆目录> --id <claim_id>
|
|
21
35
|
python3 scripts/compile_memory.py --render <记忆目录> --origin <origin_id> [--out 文件] [--today YYYY-MM-DD]
|
|
@@ -25,7 +39,10 @@ USAGE = """\
|
|
|
25
39
|
|
|
26
40
|
|
|
27
41
|
def _day(value: str):
|
|
28
|
-
|
|
42
|
+
try:
|
|
43
|
+
return datetime.strptime(value, "%Y-%m-%d").date()
|
|
44
|
+
except ValueError as exc:
|
|
45
|
+
raise ValueError(f"日期格式无效(应为 YYYY-MM-DD):{value}") from exc
|
|
29
46
|
|
|
30
47
|
|
|
31
48
|
def main(argv: list[str]) -> int:
|
|
@@ -33,11 +50,24 @@ def main(argv: list[str]) -> int:
|
|
|
33
50
|
print(USAGE)
|
|
34
51
|
return 0 if argv else 2
|
|
35
52
|
if argv[0] == "--check":
|
|
53
|
+
if len(argv) != 2:
|
|
54
|
+
print(USAGE)
|
|
55
|
+
return 2
|
|
36
56
|
ok, message = status_is_valid(Path(argv[1]))
|
|
37
57
|
print(message)
|
|
38
58
|
return 0 if ok else 1
|
|
39
59
|
if argv[0] == "--init":
|
|
40
60
|
return _init(argv[1:])
|
|
61
|
+
if argv[0] == "--set":
|
|
62
|
+
return _set(argv[1:])
|
|
63
|
+
if argv[0] == "--retract":
|
|
64
|
+
return _retract(argv[1:])
|
|
65
|
+
if argv[0] == "--resolve":
|
|
66
|
+
return _resolve(argv[1:])
|
|
67
|
+
if argv[0] == "--work":
|
|
68
|
+
return _work(argv[1:])
|
|
69
|
+
if argv[0] == "--replay":
|
|
70
|
+
return _replay(argv[1:])
|
|
41
71
|
if argv[0] == "--organize":
|
|
42
72
|
return _organize(argv[1:])
|
|
43
73
|
if argv[0] == "--open":
|
|
@@ -84,8 +114,12 @@ def _compile(argv: list[str]) -> int:
|
|
|
84
114
|
parser.add_argument("path")
|
|
85
115
|
parser.add_argument("--today")
|
|
86
116
|
args = parser.parse_args(argv)
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
try:
|
|
118
|
+
today = _day(args.today) if args.today else None
|
|
119
|
+
compile_repo(Path(args.path), today)
|
|
120
|
+
except ValueError as exc:
|
|
121
|
+
print(str(exc))
|
|
122
|
+
return 1
|
|
89
123
|
print(f"compiled {args.path}")
|
|
90
124
|
return 0
|
|
91
125
|
|
|
@@ -95,8 +129,12 @@ def _organize(argv: list[str]) -> int:
|
|
|
95
129
|
parser.add_argument("path")
|
|
96
130
|
parser.add_argument("--today")
|
|
97
131
|
args = parser.parse_args(argv)
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
try:
|
|
133
|
+
today = _day(args.today) if args.today else None
|
|
134
|
+
organize_repo(Path(args.path), today)
|
|
135
|
+
except ValueError as exc:
|
|
136
|
+
print(str(exc))
|
|
137
|
+
return 1
|
|
100
138
|
print(f"organized {args.path}")
|
|
101
139
|
return 0
|
|
102
140
|
|
|
@@ -121,8 +159,8 @@ def _render(argv: list[str]) -> int:
|
|
|
121
159
|
parser.add_argument("--out")
|
|
122
160
|
parser.add_argument("--today")
|
|
123
161
|
args = parser.parse_args(argv)
|
|
124
|
-
today = _day(args.today) if args.today else None
|
|
125
162
|
try:
|
|
163
|
+
today = _day(args.today) if args.today else None
|
|
126
164
|
text = render_package(Path(args.path), args.origin, today=today)
|
|
127
165
|
except ValueError as exc:
|
|
128
166
|
print(str(exc))
|
|
@@ -157,8 +195,8 @@ def _startup(argv: list[str]) -> int:
|
|
|
157
195
|
parser.add_argument("--also")
|
|
158
196
|
parser.add_argument("--today")
|
|
159
197
|
args = parser.parse_args(argv)
|
|
160
|
-
today = _day(args.today) if args.today else None
|
|
161
198
|
try:
|
|
199
|
+
today = _day(args.today) if args.today else None
|
|
162
200
|
print(startup_read(Path(args.path), args.origin, today=today, also=args.also), end="")
|
|
163
201
|
except ValueError as exc:
|
|
164
202
|
print(str(exc))
|
|
@@ -166,3 +204,141 @@ def _startup(argv: list[str]) -> int:
|
|
|
166
204
|
return 0
|
|
167
205
|
|
|
168
206
|
|
|
207
|
+
def _set(argv: list[str]) -> int:
|
|
208
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
209
|
+
parser.add_argument("path")
|
|
210
|
+
parser.add_argument("--origin", required=True)
|
|
211
|
+
parser.add_argument("--id", required=True, dest="claim_id")
|
|
212
|
+
parser.add_argument("--text", required=True)
|
|
213
|
+
parser.add_argument("--scope", default="shared")
|
|
214
|
+
parser.add_argument("--slice", dest="slice_name")
|
|
215
|
+
parser.add_argument("--base")
|
|
216
|
+
parser.add_argument("--detail", default="")
|
|
217
|
+
parser.add_argument("--today")
|
|
218
|
+
args = parser.parse_args(argv)
|
|
219
|
+
try:
|
|
220
|
+
today = _day(args.today) if args.today else None
|
|
221
|
+
op_id = record_set(
|
|
222
|
+
Path(args.path),
|
|
223
|
+
origin=args.origin,
|
|
224
|
+
claim_id=args.claim_id,
|
|
225
|
+
text=args.text,
|
|
226
|
+
scope=args.scope,
|
|
227
|
+
slice_name=args.slice_name,
|
|
228
|
+
base=args.base,
|
|
229
|
+
detail=args.detail,
|
|
230
|
+
today=today,
|
|
231
|
+
)
|
|
232
|
+
except ValueError as exc:
|
|
233
|
+
print(str(exc))
|
|
234
|
+
return 1
|
|
235
|
+
print(f"set {op_id} ({args.scope} {args.claim_id})")
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _retract(argv: list[str]) -> int:
|
|
240
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
241
|
+
parser.add_argument("path")
|
|
242
|
+
parser.add_argument("--origin", required=True)
|
|
243
|
+
parser.add_argument("--id", required=True, dest="claim_id")
|
|
244
|
+
parser.add_argument("--scope", default="shared")
|
|
245
|
+
parser.add_argument("--text", default="已撤回")
|
|
246
|
+
parser.add_argument("--today")
|
|
247
|
+
args = parser.parse_args(argv)
|
|
248
|
+
try:
|
|
249
|
+
today = _day(args.today) if args.today else None
|
|
250
|
+
op_id = record_retract(
|
|
251
|
+
Path(args.path),
|
|
252
|
+
origin=args.origin,
|
|
253
|
+
claim_id=args.claim_id,
|
|
254
|
+
scope=args.scope,
|
|
255
|
+
text=args.text,
|
|
256
|
+
today=today,
|
|
257
|
+
)
|
|
258
|
+
except ValueError as exc:
|
|
259
|
+
print(str(exc))
|
|
260
|
+
return 1
|
|
261
|
+
print(f"retracted {op_id} ({args.scope} {args.claim_id})")
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _resolve(argv: list[str]) -> int:
|
|
266
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
267
|
+
parser.add_argument("path")
|
|
268
|
+
parser.add_argument("--origin", required=True)
|
|
269
|
+
parser.add_argument("--id", required=True, dest="claim_id")
|
|
270
|
+
parser.add_argument("--text", required=True)
|
|
271
|
+
parser.add_argument("--scope", default="shared")
|
|
272
|
+
parser.add_argument("--slice", dest="slice_name")
|
|
273
|
+
parser.add_argument("--detail", default="")
|
|
274
|
+
parser.add_argument("--today")
|
|
275
|
+
args = parser.parse_args(argv)
|
|
276
|
+
try:
|
|
277
|
+
today = _day(args.today) if args.today else None
|
|
278
|
+
op_id = record_resolve(
|
|
279
|
+
Path(args.path),
|
|
280
|
+
origin=args.origin,
|
|
281
|
+
claim_id=args.claim_id,
|
|
282
|
+
text=args.text,
|
|
283
|
+
scope=args.scope,
|
|
284
|
+
slice_name=args.slice_name,
|
|
285
|
+
detail=args.detail,
|
|
286
|
+
today=today,
|
|
287
|
+
)
|
|
288
|
+
except ValueError as exc:
|
|
289
|
+
print(str(exc))
|
|
290
|
+
return 1
|
|
291
|
+
print(f"resolved {op_id} ({args.scope} {args.claim_id})")
|
|
292
|
+
return 0
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _work(argv: list[str]) -> int:
|
|
296
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
297
|
+
parser.add_argument("path")
|
|
298
|
+
parser.add_argument("--origin", required=True)
|
|
299
|
+
parser.add_argument("--text", default="")
|
|
300
|
+
parser.add_argument("--topic")
|
|
301
|
+
parser.add_argument("--scope")
|
|
302
|
+
parser.add_argument("--local", action="store_true")
|
|
303
|
+
parser.add_argument("--done", action="store_true")
|
|
304
|
+
parser.add_argument("--detail", default="")
|
|
305
|
+
parser.add_argument("--today")
|
|
306
|
+
args = parser.parse_args(argv)
|
|
307
|
+
try:
|
|
308
|
+
today = _day(args.today) if args.today else None
|
|
309
|
+
op_id = record_work(
|
|
310
|
+
Path(args.path),
|
|
311
|
+
origin=args.origin,
|
|
312
|
+
text=args.text,
|
|
313
|
+
topic=args.topic,
|
|
314
|
+
scope=args.scope,
|
|
315
|
+
local=args.local,
|
|
316
|
+
done=args.done,
|
|
317
|
+
detail=args.detail,
|
|
318
|
+
today=today,
|
|
319
|
+
)
|
|
320
|
+
except ValueError as exc:
|
|
321
|
+
print(str(exc))
|
|
322
|
+
return 1
|
|
323
|
+
action = "done" if args.done else "work"
|
|
324
|
+
print(f"{action} {op_id}")
|
|
325
|
+
return 0
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _replay(argv: list[str]) -> int:
|
|
329
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
330
|
+
parser.add_argument("path")
|
|
331
|
+
parser.add_argument("--file", required=True, dest="pkg_file")
|
|
332
|
+
parser.add_argument("--confirm", action="store_true")
|
|
333
|
+
args = parser.parse_args(argv)
|
|
334
|
+
try:
|
|
335
|
+
pkg_text = Path(args.pkg_file).read_text(encoding="utf-8")
|
|
336
|
+
added = replay_package(Path(args.path), pkg_text, confirmed=args.confirm)
|
|
337
|
+
except (ValueError, OSError) as exc:
|
|
338
|
+
print(str(exc))
|
|
339
|
+
return 1
|
|
340
|
+
mode = "replayed" if args.confirm else "preview"
|
|
341
|
+
print(f"{mode}: {', '.join(added) if added else 'none'}")
|
|
342
|
+
return 0
|
|
343
|
+
|
|
344
|
+
|