@ian-pascoe/pi-todo 0.0.0 → 0.1.1
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/package.json +6 -6
- package/src/pi-todo-extension.ts +3 -11
- package/src/todo-list.ts +22 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ian-pascoe/pi-todo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Minimal session-native Todo List for Pi agents.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,10 +32,6 @@
|
|
|
32
32
|
"access": "public",
|
|
33
33
|
"provenance": true
|
|
34
34
|
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"test": "vitest run --config ../../vitest.config.ts --root .",
|
|
37
|
-
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
38
|
-
},
|
|
39
35
|
"peerDependencies": {
|
|
40
36
|
"@earendil-works/pi-ai": "*",
|
|
41
37
|
"@earendil-works/pi-coding-agent": "*",
|
|
@@ -52,5 +48,9 @@
|
|
|
52
48
|
"skills": [
|
|
53
49
|
"./skills"
|
|
54
50
|
]
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"test": "vitest run --config ../../vitest.config.ts --root .",
|
|
54
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
package/src/pi-todo-extension.ts
CHANGED
|
@@ -120,7 +120,7 @@ export default function piTodoExtension(pi: ExtensionAPI): void {
|
|
|
120
120
|
const runTodoAction = (input: TodoActionInput, context: ExtensionContext) => {
|
|
121
121
|
const result = applyTodoAction(state, input);
|
|
122
122
|
if (!result.ok) throw result.error;
|
|
123
|
-
if (result.
|
|
123
|
+
if (result.state !== state) commitTodoState(result.state, context);
|
|
124
124
|
return result;
|
|
125
125
|
};
|
|
126
126
|
const restoreState = (context: ExtensionContext): void => {
|
|
@@ -163,11 +163,7 @@ export default function piTodoExtension(pi: ExtensionAPI): void {
|
|
|
163
163
|
renderResult: (result, { expanded }, theme) => {
|
|
164
164
|
const text = result.content.find((item) => item.type === "text");
|
|
165
165
|
if (!result.details) {
|
|
166
|
-
return new Text(
|
|
167
|
-
theme.fg("error", text?.type === "text" ? text.text : "Todo operation failed"),
|
|
168
|
-
0,
|
|
169
|
-
0,
|
|
170
|
-
);
|
|
166
|
+
return new Text(theme.fg("error", text?.text ?? "Todo operation failed"), 0, 0);
|
|
171
167
|
}
|
|
172
168
|
if (result.details.action === "list") {
|
|
173
169
|
const tasks = result.details.tasks;
|
|
@@ -188,11 +184,7 @@ export default function piTodoExtension(pi: ExtensionAPI): void {
|
|
|
188
184
|
if (remaining > 0) lines.push(theme.fg("dim", `… ${remaining} more`));
|
|
189
185
|
return new Text(lines.join("\n"), 0, 0);
|
|
190
186
|
}
|
|
191
|
-
return new Text(
|
|
192
|
-
theme.fg("success", "✓ ") + theme.fg("muted", text?.type === "text" ? text.text : "Done"),
|
|
193
|
-
0,
|
|
194
|
-
0,
|
|
195
|
-
);
|
|
187
|
+
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", text?.text ?? "Done"), 0, 0);
|
|
196
188
|
},
|
|
197
189
|
});
|
|
198
190
|
|
package/src/todo-list.ts
CHANGED
|
@@ -79,7 +79,6 @@ export class TodoOperationError extends Error {
|
|
|
79
79
|
export type TodoActionResult =
|
|
80
80
|
| {
|
|
81
81
|
readonly ok: true;
|
|
82
|
-
readonly changed: boolean;
|
|
83
82
|
readonly state: TodoStateSnapshot;
|
|
84
83
|
readonly message: string;
|
|
85
84
|
readonly details: TodoToolDetails;
|
|
@@ -136,7 +135,6 @@ export function applyTodoAction(
|
|
|
136
135
|
case "list":
|
|
137
136
|
return {
|
|
138
137
|
ok: true,
|
|
139
|
-
changed: false,
|
|
140
138
|
state,
|
|
141
139
|
message: formatTodoList(state.tasks),
|
|
142
140
|
details: { action: "list", tasks: state.tasks },
|
|
@@ -158,31 +156,43 @@ export function applyTodoAction(
|
|
|
158
156
|
: { id, title, status: input.status ?? "pending" };
|
|
159
157
|
return {
|
|
160
158
|
ok: true,
|
|
161
|
-
changed: true,
|
|
162
159
|
state: { nextId: state.nextId + 1, tasks: [...state.tasks, task] },
|
|
163
160
|
message: `Added Task #${task.id}`,
|
|
164
161
|
details: { action: "add" },
|
|
165
162
|
};
|
|
166
163
|
}
|
|
167
164
|
|
|
168
|
-
case "update":
|
|
165
|
+
case "update":
|
|
166
|
+
case "remove": {
|
|
167
|
+
const action = input.action;
|
|
169
168
|
if (input.id === undefined) {
|
|
170
|
-
return todoOperationFailure(
|
|
169
|
+
return todoOperationFailure(action, `Todo ${action} failed: id is required`);
|
|
171
170
|
}
|
|
172
171
|
const id = parseTodoTaskId(input.id);
|
|
173
172
|
if (id === undefined) {
|
|
174
173
|
return todoOperationFailure(
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
action,
|
|
175
|
+
`Todo ${action} failed: id must be a positive safe integer`,
|
|
177
176
|
);
|
|
178
177
|
}
|
|
179
178
|
const task = state.tasks.find((candidate) => candidate.id === id);
|
|
180
179
|
if (!task) {
|
|
181
180
|
return todoOperationFailure(
|
|
182
|
-
|
|
183
|
-
`Todo
|
|
181
|
+
action,
|
|
182
|
+
`Todo ${action} failed: Task #${input.id} was not found`,
|
|
184
183
|
);
|
|
185
184
|
}
|
|
185
|
+
if (action === "remove") {
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
state: {
|
|
189
|
+
nextId: state.nextId,
|
|
190
|
+
tasks: state.tasks.filter((candidate) => candidate.id !== task.id),
|
|
191
|
+
},
|
|
192
|
+
message: `Removed Task #${task.id}`,
|
|
193
|
+
details: { action: "remove" },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
186
196
|
if (
|
|
187
197
|
input.title === undefined &&
|
|
188
198
|
input.description === undefined &&
|
|
@@ -198,11 +208,7 @@ export function applyTodoAction(
|
|
|
198
208
|
return todoOperationFailure("update", "Todo update failed: title must not be empty");
|
|
199
209
|
}
|
|
200
210
|
const description =
|
|
201
|
-
input.description === undefined
|
|
202
|
-
? task.description
|
|
203
|
-
: input.description === null
|
|
204
|
-
? undefined
|
|
205
|
-
: input.description.trim();
|
|
211
|
+
input.description === undefined ? task.description : input.description?.trim();
|
|
206
212
|
if (input.description !== undefined && input.description !== null && !description) {
|
|
207
213
|
return todoOperationFailure("update", "Todo update failed: description must not be empty");
|
|
208
214
|
}
|
|
@@ -211,7 +217,6 @@ export function applyTodoAction(
|
|
|
211
217
|
: { id: task.id, title, status: input.status ?? task.status };
|
|
212
218
|
return {
|
|
213
219
|
ok: true,
|
|
214
|
-
changed: true,
|
|
215
220
|
state: {
|
|
216
221
|
nextId: state.nextId,
|
|
217
222
|
tasks: state.tasks.map((candidate) =>
|
|
@@ -223,42 +228,11 @@ export function applyTodoAction(
|
|
|
223
228
|
};
|
|
224
229
|
}
|
|
225
230
|
|
|
226
|
-
case "remove": {
|
|
227
|
-
if (input.id === undefined) {
|
|
228
|
-
return todoOperationFailure("remove", "Todo remove failed: id is required");
|
|
229
|
-
}
|
|
230
|
-
const id = parseTodoTaskId(input.id);
|
|
231
|
-
if (id === undefined) {
|
|
232
|
-
return todoOperationFailure(
|
|
233
|
-
"remove",
|
|
234
|
-
"Todo remove failed: id must be a positive safe integer",
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
const task = state.tasks.find((candidate) => candidate.id === id);
|
|
238
|
-
if (!task) {
|
|
239
|
-
return todoOperationFailure(
|
|
240
|
-
"remove",
|
|
241
|
-
`Todo remove failed: Task #${input.id} was not found`,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
ok: true,
|
|
246
|
-
changed: true,
|
|
247
|
-
state: {
|
|
248
|
-
nextId: state.nextId,
|
|
249
|
-
tasks: state.tasks.filter((candidate) => candidate.id !== task.id),
|
|
250
|
-
},
|
|
251
|
-
message: `Removed Task #${task.id}`,
|
|
252
|
-
details: { action: "remove" },
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
231
|
case "clear": {
|
|
257
232
|
const count = state.tasks.length;
|
|
258
233
|
return {
|
|
259
234
|
ok: true,
|
|
260
|
-
|
|
261
|
-
state: createEmptyTodoState(),
|
|
235
|
+
state: count > 0 || state.nextId !== 1 ? createEmptyTodoState() : state,
|
|
262
236
|
message: `Cleared ${count} ${count === 1 ? "Task" : "Tasks"}`,
|
|
263
237
|
details: { action: "clear" },
|
|
264
238
|
};
|
|
@@ -273,10 +247,7 @@ export function todoStatusMarker(status: TodoStatus): "[ ]" | "[>]" | "[x]" {
|
|
|
273
247
|
}
|
|
274
248
|
|
|
275
249
|
function formatTodoDescription(description: string): string {
|
|
276
|
-
return description
|
|
277
|
-
.split("\n")
|
|
278
|
-
.map((line) => ` ${line}`)
|
|
279
|
-
.join("\n");
|
|
250
|
+
return " " + description.replaceAll("\n", "\n ");
|
|
280
251
|
}
|
|
281
252
|
|
|
282
253
|
/** Formats the complete numeric-ID-ordered Todo List for tools and model context. */
|