task-ledger 1.3.12 → 1.3.14

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/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: task-ledger
3
3
  description: 本地任务台账,跨会话记录在办任务的归属、进度与起止。当用户要登记一件新的事(新建任务 / 建个坑 / 这活我领了)、记录已有任务的进展(接着做 / 记一条进展 / 这轮先记下)、查看任务状态或历史(现在该做什么 / 这活谁在做 / 上周做过什么)、给任务结案或退回(做完了 / 验收通过 / 不做了 / 退回待认领)、整理标签或检查台账一致性时加载;只是改代码、讨论方案时不要用。
4
- version: 1.3.12
4
+ version: 1.3.14
5
5
  allowed-tools: Read,Edit,Bash,Grep
6
6
  ---
7
7
 
@@ -45,7 +45,7 @@ allowed-tools: Read,Edit,Bash,Grep
45
45
  | `--claim <uuid>` | 认领名册里已有的 uuid(名字一起复制) |
46
46
  | `--new --name "…"` | 新建身份:生成 uuid + 名字 |
47
47
 
48
- 干什么:写本机身份文件(`~/.taskledger/identity.json`),**一台机器只一次**,与台账无关。在终端里**裸跑 `init` 即交互式选择框**:读当前仓库名册,用**上下箭头**移动、**Enter** 认领某个身份,末尾「+ 新建身份」可输入新用户名自建;名册为空则直接让你输入用户名;全程要真终端。`--claim <uuid>` / `--new --name "<名字>"` 也只接受真终端,agent 的无终端调用会被脚本拒绝。
48
+ 干什么:写本机身份文件(`~/.taskledger/identity.json`),**一台机器只一次**,与台账无关。在终端里**裸跑 `init` 即交互式选择框**:读当前仓库名册,用**上下箭头**移动、**Enter** 认领某个身份,末尾「+ 新建身份」可输入新用户名自建;名册为空则直接让你输入用户名;选定或输入名字后需 **Y/N 确认**才写入;**ESC 或 Ctrl-C** 可随时取消。全程要真终端。`--claim <uuid>` / `--new --name "<名字>"` 也只接受真终端,agent 的无终端调用会被脚本拒绝。
49
49
 
50
50
  ### new — 建任务
51
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/pool.json + owners/<hash>.json shards + owners.json index, history, worklog) driven by a CLI script. Standalone install for CodeBuddy and other agents; also bundled inside the dsh-task-ledger DSH plugin.",
5
5
  "keywords": [
6
6
  "skill",
@@ -307,13 +307,15 @@ def _select_identity(opts: list[dict], *, title: str) -> tuple[str, int | None]:
307
307
  rows.append(
308
308
  "\x1b[7m ❯ " + label + " \x1b[0m" if i == sel else " " + label
309
309
  )
310
- rows += ["", "\x1b[2m ↑/↓ 移动 Enter 确认 Ctrl-C 退出\x1b[0m"]
310
+ rows += ["", "\x1b[2m ↑/↓ 移动 Enter 确认 ESC / Ctrl-C 退出\x1b[0m"]
311
311
  return rows
312
312
 
313
313
  try:
314
314
  fd = sys.stdin.fileno()
315
315
  if not sys.stdin.isatty():
316
316
  raise OSError("not a tty")
317
+ import os
318
+ import select
317
319
  import termios
318
320
  import tty
319
321
 
@@ -325,39 +327,42 @@ def _select_identity(opts: list[dict], *, title: str) -> tuple[str, int | None]:
325
327
  tty.setraw(fd)
326
328
  sys.stdout.write("\x1b[?25l") # 隐藏光标
327
329
  while True:
328
- rows = _frame(sel)
329
330
  if drew:
330
- sys.stdout.write(f"\x1b[{height}A") # 回到本块第一行
331
+ sys.stdout.write("\x1b[1A" * height) # 相对上移回块顶
331
332
  else:
332
- sys.stdout.write("\r\n") # 从新行开始,避开 shell 提示
333
+ sys.stdout.write("\n") # 另起一行,不覆盖 shell 提示
333
334
  drew = True
334
- for row in rows:
335
- # raw 模式下 \n 不回行首,必须显式 \r\n,否则逐行右移
336
- sys.stdout.write("\r\x1b[K" + row + "\r\n")
335
+ # promptui 同款:每行「清整行 + 回车 + 内容 + LF」
336
+ for row in _frame(sel):
337
+ sys.stdout.write("\x1b[2K\r" + row + "\n")
337
338
  sys.stdout.flush()
338
339
 
339
- ch = sys.stdin.read(1)
340
- if ch == "\x1b":
341
- nxt = sys.stdin.read(1)
342
- if nxt in ("[", "O"):
343
- k = sys.stdin.read(1)
344
- if k == "A":
340
+ ch = os.read(fd, 1)
341
+ if ch == b"\x1b":
342
+ # 区分「单独 ESC(退出)」与「方向键 ESC [ A/B」:短暂等待后续字节
343
+ if not select.select([fd], [], [], 0.05)[0]:
344
+ result = ("cancel", None)
345
+ break
346
+ nxt = os.read(fd, 1)
347
+ if nxt in (b"[", b"O"):
348
+ k = os.read(fd, 1)
349
+ if k == b"A":
345
350
  sel = (sel - 1) % n
346
- elif k == "B":
351
+ elif k == b"B":
347
352
  sel = (sel + 1) % n
348
- elif ch in ("\r", "\n"):
353
+ elif ch in (b"\r", b"\n"):
349
354
  result = (
350
355
  ("new", None) if entries[sel].get("new") else ("claim", sel)
351
356
  )
352
357
  break
353
- elif ch == "\x03": # Ctrl-C
358
+ elif ch == b"\x03": # Ctrl-C
354
359
  result = ("cancel", None)
355
360
  break
356
361
  finally:
357
362
  sys.stdout.write("\x1b[?25h") # 显示光标
358
363
  if drew:
359
- # 原地擦掉菜单块(不滚动主屏),光标留在块首,结果从这里打印
360
- sys.stdout.write(f"\x1b[{height}A\r\x1b[J")
364
+ # promptui 同款擦除:逐行「上移 + 清整行 + 回车」,不动主屏滚动
365
+ sys.stdout.write("\x1b[1A\x1b[2K\r" * height)
361
366
  termios.tcsetattr(fd, termios.TCSADRAIN, old)
362
367
  sys.stdout.flush()
363
368
  return result
@@ -384,6 +389,20 @@ def _select_identity(opts: list[dict], *, title: str) -> tuple[str, int | None]:
384
389
  return ("cancel", None)
385
390
 
386
391
 
392
+ def _confirm(question: str) -> bool:
393
+ """Y/N 确认:Y 返回 True,N / 空 / 中断返回 False。"""
394
+ while True:
395
+ try:
396
+ ans = input(question + " [Y/N]:").strip().lower()
397
+ except (EOFError, KeyboardInterrupt):
398
+ return False
399
+ if ans in ("y", "yes"):
400
+ return True
401
+ if ans in ("n", "no"):
402
+ return False
403
+ print("请输入 Y 或 N。")
404
+
405
+
387
406
  def _run_init(args: argparse.Namespace) -> int:
388
407
  """init 不进 runner、不解析台账、不碰 git:只写机器身份文件。
389
408
 
@@ -439,11 +458,16 @@ def _run_init(args: argparse.Namespace) -> int:
439
458
  if not prompt_name:
440
459
  print("\x1b[2m✕ 未输入用户名,已取消。\x1b[0m")
441
460
  return 0
461
+ if not _confirm(f"您确认要用「{prompt_name}」这个名称新建账户吗?"):
462
+ print("\x1b[2m✕ 已取消,未做任何更改。\x1b[0m")
463
+ return 0
442
464
  res = init_identity(repo, claim=None, new=True, name=prompt_name)
443
465
  else:
444
- res = init_identity(
445
- repo, claim=opts[idx]["uuid"], new=False, name=""
446
- )
466
+ chosen = opts[idx]
467
+ if not _confirm(f"您确认是要使用「@{chosen['name']}」这个身份吗?"):
468
+ print("\x1b[2m✕ 已取消,未做任何更改。\x1b[0m")
469
+ return 0
470
+ res = init_identity(repo, claim=chosen["uuid"], new=False, name="")
447
471
  else:
448
472
  # 名册空:交互输入名字;非终端交给用户去终端跑(init_identity 会报需要名字)
449
473
  prompt_name = input("输入用户名:").strip() if tty else ""