@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 +1 -1
- package/package.json +1 -1
- package/skills/track-progress.md +1 -1
- package/src/command.ts +20 -1
- package/src/core.ts +6 -3
- package/src/index.ts +1 -1
- package/src/tool.ts +65 -18
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
|
-
(
|
|
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
package/skills/track-progress.md
CHANGED
|
@@ -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))
|
|
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)
|
|
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)
|
|
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
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(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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: {
|
|
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 =
|
|
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)
|
|
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)
|
|
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")
|
|
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(
|
|
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: [
|
|
94
|
-
|
|
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(
|
|
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", "✓ ") +
|
|
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
|
+
}
|