@zhushanwen/pi-todo 0.1.3 → 0.1.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +54 -54
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-todo",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI-driven todo list for Pi — stateful task management with session persistence and /todos command.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -41,12 +41,12 @@ const VALID_STATUSES = ["pending", "in_progress", "completed"] as const;
41
41
 
42
42
  const TodoParams = Type.Object({
43
43
  action: StringEnum(["list", "add", "update", "delete", "clear"] as const),
44
- text: Type.Optional(Type.String({ description: "Todo 文本(update 时使用)" })),
45
- id: Type.Optional(Type.Number({ description: "Todo IDupdate 时使用)" })),
46
- texts: Type.Optional(Type.Array(Type.String(), { description: "Todo 文本列表(add 时使用)" })),
47
- ids: Type.Optional(Type.Array(Type.Number(), { description: "Todo ID 列表(delete 时使用)" })),
44
+ text: Type.Optional(Type.String({ description: "Todo text (for update action)" })),
45
+ id: Type.Optional(Type.Number({ description: "Todo ID (for update action)" })),
46
+ texts: Type.Optional(Type.Array(Type.String(), { description: "Todo text list (for add action)" })),
47
+ ids: Type.Optional(Type.Array(Type.Number(), { description: "Todo ID list (for delete action)" })),
48
48
  status: Type.Optional(
49
- StringEnum(VALID_STATUSES, { description: "目标状态(update 时使用)" }),
49
+ StringEnum(VALID_STATUSES, { description: "Target status (for update action)" }),
50
50
  ),
51
51
  });
52
52
 
@@ -93,11 +93,11 @@ class TodoListComponent {
93
93
  lines.push("");
94
94
 
95
95
  if (this.todos.length === 0) {
96
- lines.push(truncateToWidth(` ${th.fg("dim", "\u6682\u65e0 todo\u3002\u8ba9 agent \u6dfb\u52a0\u4e00\u4e9b\uff01")}`, width));
96
+ lines.push(truncateToWidth(` ${th.fg("dim", "No todos yet. Ask the agent to add some!")}`, width));
97
97
  } else {
98
98
  const completed = this.todos.filter((t) => t.status === "completed").length;
99
99
  const total = this.todos.length;
100
- lines.push(truncateToWidth(` ${th.fg("muted", `${completed}/${total} \u5df2\u5b8c\u6210`)}`, width));
100
+ lines.push(truncateToWidth(` ${th.fg("muted", `${completed}/${total} completed`)}`, width));
101
101
  lines.push("");
102
102
 
103
103
  for (const todo of this.todos) {
@@ -114,7 +114,7 @@ class TodoListComponent {
114
114
  }
115
115
 
116
116
  lines.push("");
117
- lines.push(truncateToWidth(` ${th.fg("dim", "\u6309 Escape \u5173\u95ed")}`, width));
117
+ lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width));
118
118
  lines.push("");
119
119
 
120
120
  this.cachedWidth = width;
@@ -192,7 +192,7 @@ function buildRender(todoList: Todo[]): TodoDetails["_render"] {
192
192
  const total = todoList.length;
193
193
  return {
194
194
  type: "task-list" as const,
195
- summary: `${completed}/${total} 已完成`,
195
+ summary: `${completed}/${total} completed`,
196
196
  data: {
197
197
  items: todoList.map((t) => ({ id: t.id, text: t.text, status: t.status })),
198
198
  meta: {},
@@ -211,9 +211,9 @@ const TODO_REMINDER_INTERVAL = 10;
211
211
 
212
212
  function buildTodoListText(todoList: Todo[], options: { expanded: boolean }, theme: Theme): string {
213
213
  if (todoList.length === 0) {
214
- return theme.fg("dim", "\u6682\u65e0 todo");
214
+ return theme.fg("dim", "No todos");
215
215
  }
216
- let listText = theme.fg("muted", `${todoList.length} \u9879 todo\uff1a`);
216
+ let listText = theme.fg("muted", `${todoList.length} todos:`);
217
217
  const display = options.expanded ? todoList : todoList.slice(0, MAX_COLLAPSED_ITEMS);
218
218
  for (const t of display) {
219
219
  const status = getDisplayStatus(t);
@@ -228,7 +228,7 @@ function buildTodoListText(todoList: Todo[], options: { expanded: boolean }, the
228
228
  listText += `\n${mark} ${theme.fg("accent", `#${t.id}`)} ${itemText}`;
229
229
  }
230
230
  if (!options.expanded && todoList.length > MAX_COLLAPSED_ITEMS) {
231
- listText += `\n${theme.fg("dim", `... \u8fd8\u6709 ${todoList.length - MAX_COLLAPSED_ITEMS} \u9879`)}`;
231
+ listText += `\n${theme.fg("dim", `... ${todoList.length - MAX_COLLAPSED_ITEMS} more`)}`;
232
232
  }
233
233
  return listText;
234
234
  }
@@ -244,7 +244,7 @@ function renderTodoResult(result: unknown, options: { expanded: boolean }, theme
244
244
  }
245
245
 
246
246
  if (details.error) {
247
- return new Text(theme.fg("error", `\u9519\u8bef: ${details.error}`), 0, 0);
247
+ return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
248
248
  }
249
249
 
250
250
  const todoList = details.todos;
@@ -281,7 +281,7 @@ function renderTodoResult(result: unknown, options: { expanded: boolean }, theme
281
281
  default: {
282
282
  const text = r.content[0];
283
283
  const msg = text?.type === "text" ? (text.text ?? "") : "";
284
- return new Text(theme.fg("dim", msg || "\u5b8c\u6210"), 0, 0);
284
+ return new Text(theme.fg("dim", msg || "Done"), 0, 0);
285
285
  }
286
286
  }
287
287
  }
@@ -334,14 +334,14 @@ export default function (pi: ExtensionAPI) {
334
334
  return `[${mark}] #${t.id}: ${t.text}`;
335
335
  })
336
336
  .join("\n")
337
- : "\u6682\u65e0 todo";
337
+ : "No todos";
338
338
  break;
339
339
  }
340
340
 
341
341
  case "add": {
342
342
  if (!params.texts || params.texts.length === 0) {
343
343
  return {
344
- content: [{ type: "text" as const, text: "\u9519\u8bef\uff1aadd \u9700\u8981 texts \u53c2\u6570\uff08\u975e\u7a7a\u6570\u7ec4\uff09" }],
344
+ content: [{ type: "text" as const, text: "Error: add requires texts parameter (non-empty array)" }],
345
345
  details: {
346
346
  action: "add" as const,
347
347
  todos: [...todos],
@@ -354,7 +354,7 @@ export default function (pi: ExtensionAPI) {
354
354
  const trimmed = params.texts.map((t) => t.trim()).filter((t) => t.length > 0);
355
355
  if (trimmed.length === 0) {
356
356
  return {
357
- content: [{ type: "text" as const, text: "\u9519\u8bef\uff1atexts \u4e2d\u81f3\u5c11\u9700\u8981\u4e00\u4e2a\u975e\u7a7a\u5b57\u7b26\u4e32" }],
357
+ content: [{ type: "text" as const, text: "Error: texts must contain at least one non-empty string" }],
358
358
  details: {
359
359
  action: "add" as const,
360
360
  todos: [...todos],
@@ -369,7 +369,7 @@ export default function (pi: ExtensionAPI) {
369
369
  todos.push({ id: nextId++, text: t, status: "pending" });
370
370
  }
371
371
  const endId = nextId - 1;
372
- resultText = `\u5df2\u6dfb\u52a0 ${trimmed.length} \u9879 todo (#${startId}-#${endId})`;
372
+ resultText = `Added ${trimmed.length} todos (#${startId}-#${endId})`;
373
373
  // v3: 新增 todo 表示未全部完成
374
374
  allCompletedAtCount = null;
375
375
  break;
@@ -378,7 +378,7 @@ export default function (pi: ExtensionAPI) {
378
378
  case "update": {
379
379
  if (params.id === undefined) {
380
380
  return {
381
- content: [{ type: "text" as const, text: "\u9519\u8bef\uff1aupdate \u9700\u8981 id \u53c2\u6570" }],
381
+ content: [{ type: "text" as const, text: "Error: update requires id parameter" }],
382
382
  details: {
383
383
  action: "update" as const,
384
384
  todos: [...todos],
@@ -390,7 +390,7 @@ export default function (pi: ExtensionAPI) {
390
390
  }
391
391
  if (params.status === undefined && params.text === undefined) {
392
392
  return {
393
- content: [{ type: "text" as const, text: "\u9519\u8bef\uff1aupdate \u81f3\u5c11\u9700\u8981 status \u6216 text \u53c2\u6570" }],
393
+ content: [{ type: "text" as const, text: "Error: update requires at least status or text parameter" }],
394
394
  details: {
395
395
  action: "update" as const,
396
396
  todos: [...todos],
@@ -402,7 +402,7 @@ export default function (pi: ExtensionAPI) {
402
402
  }
403
403
  if (params.text !== undefined && params.text === "") {
404
404
  return {
405
- content: [{ type: "text" as const, text: "\u9519\u8bef\uff1atext \u4e0d\u80fd\u4e3a\u7a7a\u5b57\u7b26\u4e32" }],
405
+ content: [{ type: "text" as const, text: "Error: text cannot be empty string" }],
406
406
  details: {
407
407
  action: "update" as const,
408
408
  todos: [...todos],
@@ -420,7 +420,7 @@ export default function (pi: ExtensionAPI) {
420
420
  content: [
421
421
  {
422
422
  type: "text" as const,
423
- text: `\u9519\u8bef\uff1astatus \u53ea\u63a5\u53d7 ${VALID_STATUSES.join(" / ")}`,
423
+ text: `Error: status only accepts ${VALID_STATUSES.join(" / ")}`,
424
424
  },
425
425
  ],
426
426
  details: {
@@ -436,7 +436,7 @@ export default function (pi: ExtensionAPI) {
436
436
  const todo = todos.find((t) => t.id === params.id);
437
437
  if (!todo) {
438
438
  return {
439
- content: [{ type: "text" as const, text: `Todo #${params.id} \u4e0d\u5b58\u5728` }],
439
+ content: [{ type: "text" as const, text: `Todo #${params.id} not found` }],
440
440
  details: {
441
441
  action: "update" as const,
442
442
  todos: [...todos],
@@ -463,13 +463,13 @@ export default function (pi: ExtensionAPI) {
463
463
  todo.text = params.text;
464
464
  }
465
465
 
466
- const parts: string[] = [`\u5df2\u66f4\u65b0 todo #${todo.id}`];
467
- if (params.status !== undefined) parts.push(`\u72b6\u6001 \u2192 ${params.status}`);
468
- if (params.text !== undefined) parts.push(`\u6587\u672c \u2192 "${todo.text}"`);
469
- resultText = parts.join("\uff0c");
466
+ const parts: string[] = [`Updated todo #${todo.id}`];
467
+ if (params.status !== undefined) parts.push(`status ${params.status}`);
468
+ if (params.text !== undefined) parts.push(`text "${todo.text}"`);
469
+ resultText = parts.join(", ");
470
470
 
471
471
  if (isLastCompletion) {
472
- resultText += "\n\n\u6240\u6709 todo \u5df2\u5b8c\u6210\u3002\u8bf7\u603b\u7ed3\u5de5\u4f5c\u6210\u679c\u3002";
472
+ resultText += "\n\nAll todos completed. Please summarize your work.";
473
473
  }
474
474
 
475
475
  // v3: 检查是否所有 todo 已完成
@@ -485,7 +485,7 @@ export default function (pi: ExtensionAPI) {
485
485
  case "delete": {
486
486
  if (!params.ids || params.ids.length === 0) {
487
487
  return {
488
- content: [{ type: "text" as const, text: "\u9519\u8bef\uff1adelete \u9700\u8981 ids \u53c2\u6570\uff08\u975e\u7a7a\u6570\u7ec4\uff09" }],
488
+ content: [{ type: "text" as const, text: "Error: delete requires ids parameter (non-empty array)" }],
489
489
  details: {
490
490
  action: "delete" as const,
491
491
  todos: [...todos],
@@ -500,7 +500,7 @@ export default function (pi: ExtensionAPI) {
500
500
  if (missing.length > 0) {
501
501
  const missingStr = missing.map((id) => `#${id}`).join(", ");
502
502
  return {
503
- content: [{ type: "text" as const, text: `\u9519\u8bef\uff1aTodo ${missingStr} \u4e0d\u5b58\u5728` }],
503
+ content: [{ type: "text" as const, text: `Error: Todo ${missingStr} not found` }],
504
504
  details: {
505
505
  action: "delete" as const,
506
506
  todos: [...todos],
@@ -518,7 +518,7 @@ export default function (pi: ExtensionAPI) {
518
518
  removedIds.push(id);
519
519
  }
520
520
  }
521
- resultText = `\u5df2\u5220\u9664 ${removedIds.length} \u9879 (#${removedIds.join(", #")})\uff0c\u5269\u4f59 ${todos.length} \u9879`;
521
+ resultText = `Deleted ${removedIds.length} items (#${removedIds.join(", #")}), ${todos.length} remaining`;
522
522
  break;
523
523
  }
524
524
 
@@ -526,7 +526,7 @@ export default function (pi: ExtensionAPI) {
526
526
  const count = todos.length;
527
527
  todos = [];
528
528
  nextId = 1;
529
- resultText = count > 0 ? `\u5df2\u6e05\u7a7a ${count} \u9879 todo` : "\u6682\u65e0 todo\uff0c\u65e0\u9700\u6e05\u7a7a";
529
+ resultText = count > 0 ? `Cleared ${count} todos` : "No todos to clear";
530
530
  // v3: 手动清空后重置
531
531
  allCompletedAtCount = null;
532
532
  break;
@@ -534,7 +534,7 @@ export default function (pi: ExtensionAPI) {
534
534
 
535
535
  default:
536
536
  return {
537
- content: [{ type: "text" as const, text: `\u672a\u77e5 action: ${params.action}` }],
537
+ content: [{ type: "text" as const, text: `Unknown action: ${params.action}` }],
538
538
  details: {
539
539
  action: "list" as const,
540
540
  todos: [...todos],
@@ -630,7 +630,7 @@ export default function (pi: ExtensionAPI) {
630
630
  return {
631
631
  message: {
632
632
  customType: "todo-auto-clear",
633
- content: `所有 ${count} todo 已完成,列表已自动清空。`,
633
+ content: `All ${count} todos completed, list auto-cleared.`,
634
634
  display: true,
635
635
  },
636
636
  };
@@ -646,7 +646,7 @@ export default function (pi: ExtensionAPI) {
646
646
  return {
647
647
  message: {
648
648
  customType: "todo-verification-nudge",
649
- content: "你刚完成了 3+ 个任务但没有验证步骤。建议在总结前添加验证任务。",
649
+ content: "You completed 3+ tasks without a verification step. Consider adding a verification task before summarizing.",
650
650
  display: true,
651
651
  },
652
652
  };
@@ -663,7 +663,7 @@ export default function (pi: ExtensionAPI) {
663
663
  return {
664
664
  message: {
665
665
  customType: "todo-reminder",
666
- content: "Todo 工具最近没有被使用。如果你在处理任务,建议使用它来跟踪进度。",
666
+ content: "The todo tool hasn't been used recently. If working on tasks, consider using it to track progress.",
667
667
  display: true,
668
668
  },
669
669
  };
@@ -681,23 +681,23 @@ export default function (pi: ExtensionAPI) {
681
681
  name: "todo",
682
682
  label: "Todo",
683
683
  description:
684
- "\u7ba1\u7406 todo \u6e05\u5355\u3002" +
685
- "\n\n\u53ef\u7528 action\uff1a" +
686
- "\n- list\uff1a\u67e5\u770b\u6240\u6709 todo" +
687
- "\n- add\uff1a\u6279\u91cf\u6dfb\u52a0 todo\uff08\u9700\u8981 texts \u6570\u7ec4\uff09" +
688
- "\n- update\uff1a\u66f4\u65b0 todo\uff08\u9700\u8981 id\uff0c\u53ef\u9009 status/text\uff09" +
689
- "\n- delete\uff1a\u6279\u91cf\u5220\u9664 todo\uff08\u9700\u8981 ids \u6570\u7ec4\uff09" +
690
- "\n- clear\uff1a\u6e05\u7a7a\u6240\u6709 todo \u5e76\u91cd\u7f6e ID",
691
- promptSnippet: "\u8f7b\u91cf\u7ea7\u4efb\u52a1\u6e05\u5355\u3002\u591a\u6b65\u9aa4\u5de5\u4f5c\u65f6\u8ffd\u8e2a\u8fdb\u5ea6\uff0c\u4e0d\u5fc5\u7b49 /goal \u6a21\u5f0f",
684
+ "Manage a todo list." +
685
+ "\n\nAvailable actions:" +
686
+ "\n- list: View all todos" +
687
+ "\n- add: Batch add todos (requires texts array)" +
688
+ "\n- update: Update a todo (requires id, optional status/text)" +
689
+ "\n- delete: Batch delete todos (requires ids array)" +
690
+ "\n- clear: Clear all todos and reset IDs",
691
+ promptSnippet: "Lightweight task list for tracking progress on multi-step work, without requiring /goal mode",
692
692
  promptGuidelines: [
693
- "[使用场景] 多步骤任务(3+步)、需要追踪进度、用户明确要求时使用 todo",
694
- "[不适用] 单步操作、任务简单可直接完成、已在用 goal_manager ",
695
- "[时机] 开始工作前创建,完成时立即标记",
696
- "[状态] 同一时间最多一个 in_progress,完成后立即标记 completed",
697
- "[粒度] 一个 todo 对应一个可验证的工作单元,3-8 项为宜",
698
- "[完成] 所有 todo 完成后会自动清空(保留 2 轮后)",
699
- "[验证] 完成 3+ 任务时建议添加验证步骤",
700
- "[定位] 不要用 todo 替代 goal_manager,两者定位不同",
693
+ "[Usage] Use for multi-step tasks (3+ steps), progress tracking, or when explicitly requested",
694
+ "[Not for] Single-step operations, trivial tasks, or when goal_manager is already active",
695
+ "[Timing] Create before starting work, mark completed immediately when done",
696
+ "[Status] At most one in_progress at a time; mark completed immediately",
697
+ "[Granularity] One todo per verifiable work unit, 3-8 items ideal",
698
+ "[Completion] All todos auto-clear when completed (retained for 2 turns)",
699
+ "[Verification] When completing 3+ tasks, consider adding a verification step",
700
+ "[Scope] Do not use todo as a substitute for goal_manager — they serve different purposes",
701
701
  ],
702
702
  parameters: TodoParams,
703
703
 
@@ -732,10 +732,10 @@ export default function (pi: ExtensionAPI) {
732
732
 
733
733
  // ── Command: /todos ─────────────────────────────────
734
734
  pi.registerCommand("todos", {
735
- description: "\u67e5\u770b\u5f53\u524d\u5206\u652f\u7684\u6240\u6709 todo",
735
+ description: "View all todos for the current branch",
736
736
  handler: async (_args: string | undefined, ctx: ExtensionCommandContext) => {
737
737
  if (!ctx.hasUI) {
738
- ctx.ui.notify("/todos \u9700\u8981\u4ea4\u4e92\u6a21\u5f0f", "error");
738
+ ctx.ui.notify("/todos requires interactive mode", "error");
739
739
  return;
740
740
  }
741
741