@docyrus/docyrus 0.0.40 → 0.0.42
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 +7 -0
- package/main.js +3708 -1173
- package/main.js.map +4 -4
- package/package.json +8 -7
- package/resources/pi-agent/extensions/control.ts +0 -8
- package/resources/pi-agent/extensions/knowledge.ts +0 -7
- package/resources/pi-agent/extensions/loop.ts +1 -5
- package/resources/pi-agent/extensions/pi-bash-live-view/package.json +1 -1
- package/resources/pi-agent/extensions/pi-custom-compaction/events/register-events.ts +0 -10
- package/resources/pi-agent/extensions/pi-custom-compaction/package.json +4 -4
- package/resources/pi-agent/extensions/plan.ts +95 -4
- package/resources/pi-agent/extensions/prompt-editor.ts +0 -18
- package/resources/pi-agent/extensions/prompt-url-widget.ts +0 -4
- package/resources/pi-agent/extensions/review.ts +0 -4
- package/resources/pi-agent/extensions/tasks.ts +497 -0
- package/resources/pi-agent/extensions/todos.ts +102 -2
- package/resources/pi-agent/skills/agent-browser/SKILL.md +779 -0
- package/resources/pi-agent/skills/agent-browser/references/authentication.md +303 -0
- package/resources/pi-agent/skills/agent-browser/references/commands.md +295 -0
- package/resources/pi-agent/skills/agent-browser/references/profiling.md +120 -0
- package/resources/pi-agent/skills/agent-browser/references/proxy-support.md +194 -0
- package/resources/pi-agent/skills/agent-browser/references/session-management.md +193 -0
- package/resources/pi-agent/skills/agent-browser/references/snapshot-refs.md +219 -0
- package/resources/pi-agent/skills/agent-browser/references/video-recording.md +173 -0
- package/resources/pi-agent/skills/agent-browser/templates/authenticated-session.sh +105 -0
- package/resources/pi-agent/skills/agent-browser/templates/capture-workflow.sh +69 -0
- package/resources/pi-agent/skills/agent-browser/templates/form-automation.sh +62 -0
- package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +73 -0
- package/resources/pi-agent/skills/grill-me/SKILL.md +109 -0
- package/server-loader.js +24270 -3381
- package/server-loader.js.map +4 -4
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import type {
|
|
5
|
+
ExtensionAPI,
|
|
6
|
+
ExtensionCommandContext,
|
|
7
|
+
} from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import type { Theme } from "@mariozechner/pi-tui";
|
|
9
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
10
|
+
|
|
11
|
+
type ITasksAction =
|
|
12
|
+
| "show"
|
|
13
|
+
| "get"
|
|
14
|
+
| "create-feature"
|
|
15
|
+
| "create-task"
|
|
16
|
+
| "set-status"
|
|
17
|
+
| "create-linked-todo";
|
|
18
|
+
|
|
19
|
+
interface ICliEnvironment {
|
|
20
|
+
executable: string;
|
|
21
|
+
entryPath: string;
|
|
22
|
+
scope: "local" | "global";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ICommandResult {
|
|
26
|
+
code: number | null;
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface IProjectTaskSummary {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
summary: string;
|
|
35
|
+
type: string;
|
|
36
|
+
assignee: string;
|
|
37
|
+
status: string;
|
|
38
|
+
acceptanceCriteria: string[];
|
|
39
|
+
featureId: string;
|
|
40
|
+
sectionId: string;
|
|
41
|
+
linkedTodoCount: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface IProjectFeatureSummary {
|
|
45
|
+
id: string;
|
|
46
|
+
title: string;
|
|
47
|
+
slug: string;
|
|
48
|
+
summary: string;
|
|
49
|
+
sectionId: string;
|
|
50
|
+
status: string;
|
|
51
|
+
taskCount: number;
|
|
52
|
+
tasks: IProjectTaskSummary[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface IProjectSectionSummary {
|
|
56
|
+
sectionId: string;
|
|
57
|
+
heading: string;
|
|
58
|
+
filePath: string;
|
|
59
|
+
status: string;
|
|
60
|
+
featureCount: number;
|
|
61
|
+
taskCount: number;
|
|
62
|
+
features: IProjectFeatureSummary[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface IProjectPlanShowPayload {
|
|
66
|
+
hierarchy: {
|
|
67
|
+
sections: IProjectSectionSummary[];
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const TaskParams = Type.Object({
|
|
72
|
+
action: StringEnum([
|
|
73
|
+
"show",
|
|
74
|
+
"get",
|
|
75
|
+
"create-feature",
|
|
76
|
+
"create-task",
|
|
77
|
+
"set-status",
|
|
78
|
+
"create-linked-todo",
|
|
79
|
+
] as const),
|
|
80
|
+
taskId: Type.Optional(Type.String({ description: "Canonical task id" })),
|
|
81
|
+
featureId: Type.Optional(Type.String({ description: "Canonical feature id" })),
|
|
82
|
+
sectionId: Type.Optional(Type.String({ description: "Knowledge section id" })),
|
|
83
|
+
title: Type.Optional(Type.String({ description: "Feature or task title" })),
|
|
84
|
+
summary: Type.Optional(Type.String({ description: "Feature or task summary" })),
|
|
85
|
+
slug: Type.Optional(Type.String({ description: "Feature slug" })),
|
|
86
|
+
type: Type.Optional(Type.String({ description: "Task type" })),
|
|
87
|
+
assignee: Type.Optional(Type.String({ description: "Task assignee" })),
|
|
88
|
+
status: Type.Optional(Type.String({ description: "Task status" })),
|
|
89
|
+
acceptanceCriteria: Type.Optional(Type.Array(Type.String({ description: "Acceptance criterion" }))),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function readCliEnvironment(env: NodeJS.ProcessEnv = process.env): ICliEnvironment {
|
|
93
|
+
const executable = env.DOCYRUS_CLI_EXECUTABLE?.trim();
|
|
94
|
+
const entryPath = env.DOCYRUS_CLI_ENTRY?.trim();
|
|
95
|
+
const scope = env.DOCYRUS_CLI_SCOPE?.trim() as "local" | "global" | undefined;
|
|
96
|
+
if (!executable || !entryPath || (scope !== "local" && scope !== "global")) {
|
|
97
|
+
throw new Error("Missing Docyrus CLI runtime env. Expected DOCYRUS_CLI_EXECUTABLE, DOCYRUS_CLI_ENTRY, and DOCYRUS_CLI_SCOPE.");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
executable,
|
|
102
|
+
entryPath,
|
|
103
|
+
scope,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildCommandArgs(environment: ICliEnvironment, args: string[], useJson = false): string[] {
|
|
108
|
+
const scopedArgs = environment.scope === "global" ? ["-g", ...args] : args;
|
|
109
|
+
return [environment.entryPath, ...scopedArgs, ...(useJson ? ["--json"] : [])];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function summarizeFailure(result: ICommandResult): string {
|
|
113
|
+
const stderr = result.stderr.trim();
|
|
114
|
+
if (stderr) {
|
|
115
|
+
return stderr;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const stdout = result.stdout.trim();
|
|
119
|
+
if (stdout) {
|
|
120
|
+
return stdout.split(/\r?\n/gu).at(-1) || stdout;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof result.code === "number") {
|
|
124
|
+
return `Command exited with code ${result.code}.`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return "Command failed.";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runCli(environment: ICliEnvironment, args: string[], cwd: string, useJson = false): Promise<ICommandResult> {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
execFile(environment.executable, buildCommandArgs(environment, args, useJson), {
|
|
133
|
+
cwd,
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
env: {
|
|
136
|
+
...process.env,
|
|
137
|
+
},
|
|
138
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
139
|
+
}, (error, stdout, stderr) => {
|
|
140
|
+
if (error && typeof (error as NodeJS.ErrnoException).code === "string" && (error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
141
|
+
reject(error);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
resolve({
|
|
146
|
+
code: typeof (error as { code?: number } | null)?.code === "number" ? (error as { code?: number }).code || null : null,
|
|
147
|
+
stdout,
|
|
148
|
+
stderr,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function runCliJson<TValue>(environment: ICliEnvironment, args: string[], cwd: string): Promise<TValue> {
|
|
155
|
+
const result = await runCli(environment, args, cwd, true);
|
|
156
|
+
const output = result.stdout.trim() || result.stderr.trim();
|
|
157
|
+
if (!output) {
|
|
158
|
+
throw new Error("Docyrus CLI produced no JSON output.");
|
|
159
|
+
}
|
|
160
|
+
if (result.code !== 0) {
|
|
161
|
+
throw new Error(summarizeFailure(result));
|
|
162
|
+
}
|
|
163
|
+
return JSON.parse(output) as TValue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function formatTaskSummary(task: IProjectTaskSummary): string {
|
|
167
|
+
return `${task.id} · ${task.title} · ${task.status} · ${task.assignee} · ${task.type}${task.linkedTodoCount > 0 ? ` · ${task.linkedTodoCount} linked todo(s)` : ""}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function formatHierarchySummary(payload: IProjectPlanShowPayload): string {
|
|
171
|
+
const populatedSections = payload.hierarchy.sections.filter((section) => section.features.length > 0);
|
|
172
|
+
if (populatedSections.length === 0) {
|
|
173
|
+
return "No project-plan features or tasks yet.";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const lines: string[] = [];
|
|
177
|
+
for (const section of populatedSections) {
|
|
178
|
+
lines.push(`${section.heading} (${section.status})`);
|
|
179
|
+
for (const feature of section.features) {
|
|
180
|
+
lines.push(` - ${feature.title} (${feature.status})`);
|
|
181
|
+
for (const task of feature.tasks) {
|
|
182
|
+
lines.push(` * ${formatTaskSummary(task)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function selectFromOptions(
|
|
191
|
+
ctx: ExtensionCommandContext,
|
|
192
|
+
title: string,
|
|
193
|
+
options: Array<{ label: string; value: string }>,
|
|
194
|
+
): Promise<string | undefined> {
|
|
195
|
+
const selected = await ctx.ui.select(title, options.map((option) => option.label));
|
|
196
|
+
if (selected === undefined) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return options.find((option) => option.label === selected)?.value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function tasksCommandHandler(pi: ExtensionAPI, ctx: ExtensionCommandContext, rawArgs: string): Promise<void> {
|
|
204
|
+
const environment = readCliEnvironment();
|
|
205
|
+
const payload = await runCliJson<IProjectPlanShowPayload>(environment, ["project-plan", "show"], ctx.cwd);
|
|
206
|
+
const summary = formatHierarchySummary(payload);
|
|
207
|
+
|
|
208
|
+
if (!ctx.hasUI) {
|
|
209
|
+
process.stdout.write(summary.endsWith("\n") ? summary : `${summary}\n`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const query = rawArgs.trim().toLowerCase();
|
|
214
|
+
const sections = payload.hierarchy.sections.filter((section) => section.features.length > 0)
|
|
215
|
+
.filter((section) => !query || section.heading.toLowerCase().includes(query) || section.sectionId.toLowerCase().includes(query));
|
|
216
|
+
if (sections.length === 0) {
|
|
217
|
+
ctx.ui.notify("No project-plan sections with tasks were found.", "info");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const selectedSectionId = await selectFromOptions(ctx, "Project Sections", sections.map((section) => ({
|
|
222
|
+
label: `${section.heading} (${section.status})`,
|
|
223
|
+
value: section.sectionId,
|
|
224
|
+
})));
|
|
225
|
+
if (!selectedSectionId) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const section = sections.find((item) => item.sectionId === selectedSectionId);
|
|
230
|
+
if (!section) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const selectedFeatureId = await selectFromOptions(ctx, "Features", section.features.map((feature) => ({
|
|
235
|
+
label: `${feature.title} (${feature.status})`,
|
|
236
|
+
value: feature.id,
|
|
237
|
+
})));
|
|
238
|
+
if (!selectedFeatureId) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const feature = section.features.find((item) => item.id === selectedFeatureId);
|
|
243
|
+
if (!feature) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const selectedTaskId = await selectFromOptions(ctx, "Tasks", feature.tasks.map((task) => ({
|
|
248
|
+
label: formatTaskSummary(task),
|
|
249
|
+
value: task.id,
|
|
250
|
+
})));
|
|
251
|
+
if (!selectedTaskId) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const task = feature.tasks.find((item) => item.id === selectedTaskId);
|
|
256
|
+
if (!task) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const actionOptions = [
|
|
261
|
+
{ label: "View", value: "view" },
|
|
262
|
+
{ label: "Mark in progress", value: "in_progress" },
|
|
263
|
+
{ label: "Mark blocked", value: "blocked" },
|
|
264
|
+
{ label: "Mark done", value: "done" },
|
|
265
|
+
...(task.assignee === "agent" ? [{ label: "Create linked todo", value: "linked-todo" }] : []),
|
|
266
|
+
...(task.assignee === "agent" ? [{ label: "Work on task", value: "work" }] : []),
|
|
267
|
+
];
|
|
268
|
+
const selectedAction = await selectFromOptions(ctx, "Task Action", actionOptions);
|
|
269
|
+
if (!selectedAction) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (selectedAction === "view") {
|
|
274
|
+
ctx.ui.notify([
|
|
275
|
+
`${task.title}`,
|
|
276
|
+
`status: ${task.status}`,
|
|
277
|
+
`assignee: ${task.assignee}`,
|
|
278
|
+
`type: ${task.type}`,
|
|
279
|
+
...(task.summary ? [`summary: ${task.summary}`] : []),
|
|
280
|
+
`linkedTodos: ${task.linkedTodoCount}`,
|
|
281
|
+
].join("\n"), "info");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (selectedAction === "linked-todo") {
|
|
286
|
+
const linkedTodo = await runCliJson<{ id: string }>(environment, [
|
|
287
|
+
"project-plan",
|
|
288
|
+
"create-linked-todo",
|
|
289
|
+
"--taskId",
|
|
290
|
+
task.id,
|
|
291
|
+
], ctx.cwd);
|
|
292
|
+
ctx.ui.notify(`Created linked todo ${linkedTodo.id} for ${task.id}`, "info");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (selectedAction === "work") {
|
|
297
|
+
if (task.linkedTodoCount === 0) {
|
|
298
|
+
try {
|
|
299
|
+
await runCliJson(environment, [
|
|
300
|
+
"project-plan",
|
|
301
|
+
"create-linked-todo",
|
|
302
|
+
"--taskId",
|
|
303
|
+
task.id,
|
|
304
|
+
], ctx.cwd);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
ctx.ui.setEditorText(`work on task ${task.id} "${task.title}"`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
await runCliJson(environment, [
|
|
315
|
+
"project-plan",
|
|
316
|
+
"set-task-status",
|
|
317
|
+
"--taskId",
|
|
318
|
+
task.id,
|
|
319
|
+
"--status",
|
|
320
|
+
selectedAction,
|
|
321
|
+
], ctx.cwd);
|
|
322
|
+
ctx.ui.notify(`Updated ${task.id} to ${selectedAction}`, "info");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export default function tasksExtension(pi: ExtensionAPI) {
|
|
326
|
+
pi.registerTool({
|
|
327
|
+
name: "project_task",
|
|
328
|
+
label: "Project Task",
|
|
329
|
+
description: "Manage the canonical repo-tracked project-plan graph and linked local subtasks.",
|
|
330
|
+
parameters: TaskParams,
|
|
331
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
332
|
+
const environment = readCliEnvironment();
|
|
333
|
+
const action = params.action as ITasksAction;
|
|
334
|
+
|
|
335
|
+
switch (action) {
|
|
336
|
+
case "show": {
|
|
337
|
+
const payload = await runCliJson<IProjectPlanShowPayload>(environment, ["project-plan", "show"], ctx.cwd);
|
|
338
|
+
const text = formatHierarchySummary(payload);
|
|
339
|
+
return {
|
|
340
|
+
content: [{ type: "text", text }],
|
|
341
|
+
details: payload,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
case "get": {
|
|
346
|
+
if (!params.taskId) {
|
|
347
|
+
return {
|
|
348
|
+
content: [{ type: "text", text: "taskId required" }],
|
|
349
|
+
isError: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const payload = await runCliJson<Record<string, unknown>>(environment, [
|
|
353
|
+
"project-plan",
|
|
354
|
+
"get-task",
|
|
355
|
+
"--taskId",
|
|
356
|
+
String(params.taskId),
|
|
357
|
+
], ctx.cwd);
|
|
358
|
+
return {
|
|
359
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
360
|
+
details: payload,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
case "create-feature": {
|
|
365
|
+
if (!params.sectionId || !params.title) {
|
|
366
|
+
return {
|
|
367
|
+
content: [{ type: "text", text: "sectionId and title required" }],
|
|
368
|
+
isError: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const args = [
|
|
372
|
+
"project-plan",
|
|
373
|
+
"upsert-feature",
|
|
374
|
+
"--sectionId",
|
|
375
|
+
String(params.sectionId),
|
|
376
|
+
"--title",
|
|
377
|
+
String(params.title),
|
|
378
|
+
];
|
|
379
|
+
if (params.slug) {
|
|
380
|
+
args.push("--slug", String(params.slug));
|
|
381
|
+
}
|
|
382
|
+
if (params.summary) {
|
|
383
|
+
args.push("--summary", String(params.summary));
|
|
384
|
+
}
|
|
385
|
+
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
386
|
+
return {
|
|
387
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
388
|
+
details: payload,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
case "create-task": {
|
|
393
|
+
if (!params.featureId || !params.title || !params.type || !params.assignee) {
|
|
394
|
+
return {
|
|
395
|
+
content: [{ type: "text", text: "featureId, title, type, and assignee required" }],
|
|
396
|
+
isError: true,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const args = [
|
|
400
|
+
"project-plan",
|
|
401
|
+
"upsert-task",
|
|
402
|
+
"--featureId",
|
|
403
|
+
String(params.featureId),
|
|
404
|
+
"--title",
|
|
405
|
+
String(params.title),
|
|
406
|
+
"--type",
|
|
407
|
+
String(params.type),
|
|
408
|
+
"--assignee",
|
|
409
|
+
String(params.assignee),
|
|
410
|
+
];
|
|
411
|
+
if (params.sectionId) {
|
|
412
|
+
args.push("--sectionId", String(params.sectionId));
|
|
413
|
+
}
|
|
414
|
+
if (params.summary) {
|
|
415
|
+
args.push("--summary", String(params.summary));
|
|
416
|
+
}
|
|
417
|
+
if (params.status) {
|
|
418
|
+
args.push("--status", String(params.status));
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(params.acceptanceCriteria) && params.acceptanceCriteria.length > 0) {
|
|
421
|
+
args.push("--acceptanceCriteria", JSON.stringify(params.acceptanceCriteria));
|
|
422
|
+
}
|
|
423
|
+
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
424
|
+
return {
|
|
425
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
426
|
+
details: payload,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
case "set-status": {
|
|
431
|
+
if (!params.taskId || !params.status) {
|
|
432
|
+
return {
|
|
433
|
+
content: [{ type: "text", text: "taskId and status required" }],
|
|
434
|
+
isError: true,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const payload = await runCliJson<Record<string, unknown>>(environment, [
|
|
438
|
+
"project-plan",
|
|
439
|
+
"set-task-status",
|
|
440
|
+
"--taskId",
|
|
441
|
+
String(params.taskId),
|
|
442
|
+
"--status",
|
|
443
|
+
String(params.status),
|
|
444
|
+
], ctx.cwd);
|
|
445
|
+
return {
|
|
446
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
447
|
+
details: payload,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
case "create-linked-todo": {
|
|
452
|
+
if (!params.taskId) {
|
|
453
|
+
return {
|
|
454
|
+
content: [{ type: "text", text: "taskId required" }],
|
|
455
|
+
isError: true,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const args = [
|
|
459
|
+
"project-plan",
|
|
460
|
+
"create-linked-todo",
|
|
461
|
+
"--taskId",
|
|
462
|
+
String(params.taskId),
|
|
463
|
+
];
|
|
464
|
+
if (params.title) {
|
|
465
|
+
args.push("--title", String(params.title));
|
|
466
|
+
}
|
|
467
|
+
if (params.summary) {
|
|
468
|
+
args.push("--body", String(params.summary));
|
|
469
|
+
}
|
|
470
|
+
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
471
|
+
return {
|
|
472
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
473
|
+
details: payload,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
renderCall(args, theme: Theme) {
|
|
479
|
+
return new Text(
|
|
480
|
+
`${theme.fg("toolTitle", theme.bold("project task "))}${theme.fg("muted", String(args.action || "show"))}`,
|
|
481
|
+
0,
|
|
482
|
+
0,
|
|
483
|
+
);
|
|
484
|
+
},
|
|
485
|
+
renderResult(result, _options, _theme) {
|
|
486
|
+
const text = result.content[0];
|
|
487
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
pi.registerCommand("tasks", {
|
|
492
|
+
description: "Browse project-plan sections, features, and tasks",
|
|
493
|
+
handler: async(args, ctx) => {
|
|
494
|
+
await tasksCommandHandler(pi, ctx, args);
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
}
|