@leo-alvarenga/pi-todo-list 0.4.2 → 0.5.0

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/README.md CHANGED
@@ -11,7 +11,7 @@ The agent manages tasks with a `todo` tool while you watch them live in a panel
11
11
  self-blocks, dependency cycles) are rejected before any state change.
12
12
  - **Live TUI panel** above the input editor — a header line with a
13
13
  collapse/expand chevron and per-status counters, Nerd Font status glyphs
14
- ( pending, 󱥸 in-progress,  completed), blocked-task hints, and a hardcoded
14
+ ( pending, 󱥸 in-progress,  completed), blocked-task hints, and a hardcoded
15
15
  8-row budget with a `… +N more` summary line. The panel hides itself
16
16
  entirely while the list is empty.
17
17
  - **Toggle with `Alt+T`** — collapsed by default; collapsed shows just the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo-alvarenga/pi-todo-list",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Session-aware todo list overlay for Pi: todo tool, /todos command, and a live TUI panel",
5
5
  "license": "MIT",
6
6
  "repository": "github.com/leo-alvarenga/pi-mono",
@@ -10,4 +10,4 @@ Track work with the todo tools unless the user explicitly says not to:
10
10
  - `todo add` — record each item the user wants tracked (text or texts).
11
11
  - `todo update` — set `status: completed` as items finish; update status on every progress change (in-progress, blocked, …).
12
12
  - `todo remove` — drop tasks that are no longer relevant.
13
- - "complete everything" / "done with all" → `todo_complete_all`, not a loop of `todo update`.
13
+ - "complete everything" / "done with all" → `todo_complete_all`, not a loop of `todo update`.
package/src/command.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { Text } from "@earendil-works/pi-tui";
3
3
 
4
- import { groupByStatus } from "./core";
4
+ import { applyAction, groupByStatus } from "./core";
5
5
  import { REPORT_ENTRY } from "./constants";
6
6
  import type { TodoStore } from "./state";
7
7
  import type { Todo } from "./types";
@@ -42,6 +42,25 @@ export function registerTodosCommand(pi: ExtensionAPI, store: TodoStore): void {
42
42
  },
43
43
  });
44
44
 
45
+ pi.registerCommand("todos-clear", {
46
+ description: "Manually clears all todos",
47
+ handler: async (_args, ctx) => {
48
+ const state = store.getState(ctx);
49
+ if (!state.todos.length) return;
50
+
51
+ const result = applyAction(state, {
52
+ action: "clear",
53
+ });
54
+
55
+ if (!result.ok) {
56
+ ctx.ui.notify("Could not clear todos", "error");
57
+ return;
58
+ }
59
+
60
+ store.commit(ctx, result.state);
61
+ },
62
+ });
63
+
45
64
  pi.registerEntryRenderer<{ todos: Todo[] }>(
46
65
  REPORT_ENTRY,
47
66
  (entry, _options, theme) => {
package/src/core.ts CHANGED
@@ -170,7 +170,8 @@ function clear(): ActionResult {
170
170
 
171
171
  function addMany(state: TodoState, texts: string[]): ActionResult {
172
172
  const cleaned = texts.map(cleanText);
173
- if (cleaned.some((t) => !t)) return { ok: false, error: "text required for add" };
173
+ if (cleaned.some((t) => !t))
174
+ return { ok: false, error: "text required for add" };
174
175
  if (cleaned.some((t) => t && t.length > MAX_TEXT_LENGTH)) return textError();
175
176
 
176
177
  let nextId = state.nextId;
@@ -224,7 +225,8 @@ export function applyAction(
224
225
  ): ActionResult {
225
226
  switch (params.action) {
226
227
  case "add":
227
- if (params.texts !== undefined && params.texts.length > 0) return addMany(state, params.texts);
228
+ if (params.texts !== undefined && params.texts.length > 0)
229
+ return addMany(state, params.texts);
228
230
  return add(state, params.text ?? "");
229
231
 
230
232
  case "update":
@@ -235,7 +237,8 @@ export function applyAction(
235
237
  return update(state, params.id, params);
236
238
 
237
239
  case "remove":
238
- if (params.ids !== undefined && params.ids.length > 0) return removeMany(state, params.ids);
240
+ if (params.ids !== undefined && params.ids.length > 0)
241
+ return removeMany(state, params.ids);
239
242
  if (params.id === undefined) {
240
243
  return { ok: false, error: "id required for remove" };
241
244
  }
package/src/index.ts CHANGED
@@ -23,4 +23,4 @@ export default function (pi: ExtensionAPI): void {
23
23
  pi.on("session_shutdown", (_event, ctx) => {
24
24
  if (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);
25
25
  });
26
- }
26
+ }
package/src/tool.ts CHANGED
@@ -10,12 +10,32 @@ import type { TodoDetails, TodoState } from "./types";
10
10
 
11
11
  const TodoParams = Type.Object({
12
12
  action: StringEnum(["add", "update", "remove", "list", "clear"] as const),
13
- text: Type.Optional(Type.String({ description: "Task text (required for add; optional for update)" })),
14
- texts: Type.Optional(Type.Array(Type.String(), { description: "Task texts; add all at once (optional; used with action=add)" })),
15
- id: Type.Optional(Type.Number({ description: "Task id (required for remove; alternative to ids)" })),
16
- ids: Type.Optional(Type.Array(Type.Number(), { description: "Task ids; remove all at once (optional; used with action=remove)" })),
13
+ text: Type.Optional(
14
+ Type.String({
15
+ description: "Task text (required for add; optional for update)",
16
+ }),
17
+ ),
18
+ texts: Type.Optional(
19
+ Type.Array(Type.String(), {
20
+ description:
21
+ "Task texts; add all at once (optional; used with action=add)",
22
+ }),
23
+ ),
24
+ id: Type.Optional(
25
+ Type.Number({
26
+ description: "Task id (required for remove; alternative to ids)",
27
+ }),
28
+ ),
29
+ ids: Type.Optional(
30
+ Type.Array(Type.Number(), {
31
+ description:
32
+ "Task ids; remove all at once (optional; used with action=remove)",
33
+ }),
34
+ ),
17
35
  status: Type.Optional(StringEnum([...STATUSES] as const)),
18
- blockedBy: Type.Optional(Type.Array(Type.Number(), { description: "Task ids this task depends on" })),
36
+ blockedBy: Type.Optional(
37
+ Type.Array(Type.Number(), { description: "Task ids this task depends on" }),
38
+ ),
19
39
  });
20
40
 
21
41
  export function registerTodoTool(pi: ExtensionAPI, store: TodoStore): void {
@@ -25,7 +45,8 @@ export function registerTodoTool(pi: ExtensionAPI, store: TodoStore): void {
25
45
  parameters: TodoParams,
26
46
  description:
27
47
  "Manage the session todo list. Actions: add (text or texts), update (id, optional status/text/blockedBy), remove (id or ids), list, clear",
28
- promptSnippet: "Manage the session todo list: add/update/remove/list/clear tasks",
48
+ promptSnippet:
49
+ "Manage the session todo list: add/update/remove/list/clear tasks",
29
50
  promptGuidelines: [
30
51
  "Always track the work using the todo tools.",
31
52
  "Record each item the user wants tracked with todo add.",
@@ -33,7 +54,7 @@ export function registerTodoTool(pi: ExtensionAPI, store: TodoStore): void {
33
54
  "When the user says 'complete everything' / 'done with all', use todo_complete_all instead of looping todo update.",
34
55
  ],
35
56
 
36
- async execute(toolCallId, params, _signal, _onUpdate, ctx) {
57
+ async execute(_, params, _signal, _onUpdate, ctx) {
37
58
  const current = store.getState(ctx);
38
59
  const result = applyAction(current, params);
39
60
 
@@ -52,29 +73,41 @@ export function registerTodoTool(pi: ExtensionAPI, store: TodoStore): void {
52
73
  store.commit(ctx, result.state);
53
74
  return {
54
75
  content: [{ type: "text", text: result.text }],
55
- details: { action: params.action, todos: [...result.state.todos], nextId: result.state.nextId } satisfies TodoDetails,
76
+ details: {
77
+ action: params.action,
78
+ todos: [...result.state.todos],
79
+ nextId: result.state.nextId,
80
+ } satisfies TodoDetails,
56
81
  };
57
82
  },
58
83
 
59
84
  renderCall(args, theme, _context) {
60
- let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action);
85
+ let text =
86
+ theme.fg("toolTitle", theme.bold("todo ")) +
87
+ theme.fg("muted", args.action);
61
88
  if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`;
62
- if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`;
89
+ if (args.id !== undefined)
90
+ text += ` ${theme.fg("accent", `#${args.id}`)}`;
63
91
  return new Text(text, 0, 0);
64
92
  },
65
93
 
66
94
  renderResult(result, _options, theme, _context) {
67
95
  const details = result.details as TodoDetails | undefined;
68
- if (details?.error) return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
96
+ if (details?.error)
97
+ return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
69
98
  const text = result.content[0];
70
99
  const msg = text?.type === "text" ? text.text : "";
71
- if (details?.action === "list") return new Text(theme.fg("muted", msg), 0, 0);
100
+ if (details?.action === "list")
101
+ return new Text(theme.fg("muted", msg), 0, 0);
72
102
  return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0);
73
103
  },
74
104
  });
75
105
  }
76
106
 
77
- export function registerTodoCompleteAllTool(pi: ExtensionAPI, store: TodoStore): void {
107
+ export function registerTodoCompleteAllTool(
108
+ pi: ExtensionAPI,
109
+ store: TodoStore,
110
+ ): void {
78
111
  pi.registerTool({
79
112
  name: "todo_complete_all",
80
113
  label: "Complete All Todos",
@@ -86,26 +119,40 @@ export function registerTodoCompleteAllTool(pi: ExtensionAPI, store: TodoStore):
86
119
 
87
120
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
88
121
  const current = store.getState(ctx);
122
+
89
123
  const n = current.todos.length;
90
124
  const cleared: TodoState = { todos: [], nextId: 1 };
125
+
91
126
  store.commit(ctx, cleared);
127
+
92
128
  return {
93
- content: [{ type: "text", text: `All ${n} todos completed and cleared` }],
94
- details: { action: "clear", todos: [], nextId: 1 } satisfies TodoDetails,
129
+ content: [
130
+ { type: "text", text: `All ${n} todos completed and cleared` },
131
+ ],
132
+ details: {
133
+ action: "clear",
134
+ todos: [],
135
+ nextId: 1,
136
+ } satisfies TodoDetails,
95
137
  };
96
138
  },
97
139
 
98
140
  renderCall(_args, theme) {
99
- return new Text(theme.fg("toolTitle", theme.bold("todo_complete_all")), 0, 0);
141
+ return new Text(
142
+ theme.fg("toolTitle", theme.bold("todo_complete_all")),
143
+ 0,
144
+ 0,
145
+ );
100
146
  },
101
147
 
102
148
  renderResult(result, _options, theme) {
103
149
  const text = result.content[0];
104
150
  return new Text(
105
- theme.fg("success", "✓ ") + theme.fg("muted", text?.type === "text" ? text.text : ""),
151
+ theme.fg("success", "✓ ") +
152
+ theme.fg("muted", text?.type === "text" ? text.text : ""),
106
153
  0,
107
154
  0,
108
155
  );
109
156
  },
110
157
  });
111
- }
158
+ }