@aliou/pi-dev-kit 0.4.9 → 0.6.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 +60 -0
- package/package.json +63 -2
- package/src/commands/index.ts +6 -0
- package/src/commands/update.ts +144 -0
- package/src/index.ts +8 -0
- package/src/prompts/setup-demo.md +35 -0
- package/src/skills/demo-setup/SKILL.md +217 -0
- package/src/skills/pi-extension/SKILL.md +154 -0
- package/src/skills/pi-extension/references/additional-apis.md +304 -0
- package/src/skills/pi-extension/references/commands.md +100 -0
- package/src/skills/pi-extension/references/components.md +166 -0
- package/src/skills/pi-extension/references/documentation.md +54 -0
- package/src/skills/pi-extension/references/hooks.md +244 -0
- package/src/skills/pi-extension/references/messages.md +169 -0
- package/src/skills/pi-extension/references/modes.md +156 -0
- package/src/skills/pi-extension/references/providers.md +134 -0
- package/src/skills/pi-extension/references/publish.md +139 -0
- package/src/skills/pi-extension/references/state.md +56 -0
- package/src/skills/pi-extension/references/structure.md +522 -0
- package/src/skills/pi-extension/references/testing.md +183 -0
- package/src/skills/pi-extension/references/tools.md +948 -0
- package/src/tools/changelog-tool.ts +484 -0
- package/src/tools/docs-tool.ts +181 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/package-manager-tool.ts +194 -0
- package/src/tools/utils.ts +38 -0
- package/src/tools/version-tool.ts +70 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ToolBody, ToolCallHeader, ToolFooter } from "@aliou/pi-utils-ui";
|
|
4
|
+
import type {
|
|
5
|
+
AgentToolResult,
|
|
6
|
+
ExtensionAPI,
|
|
7
|
+
ExtensionContext,
|
|
8
|
+
Theme,
|
|
9
|
+
ToolRenderResultOptions,
|
|
10
|
+
} from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { keyHint, VERSION } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
13
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
14
|
+
import { findPiInstallation } from "./utils";
|
|
15
|
+
|
|
16
|
+
const GITHUB_RAW_CHANGELOG_URL =
|
|
17
|
+
"https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/CHANGELOG.md";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Params
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const ChangelogParamsSchema = Type.Object({
|
|
24
|
+
version: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description:
|
|
27
|
+
"Specific version to get changelog for. If not provided, returns latest version.",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
type ChangelogParams = Static<typeof ChangelogParamsSchema>;
|
|
33
|
+
|
|
34
|
+
const ChangelogVersionsParamsSchema = Type.Object({});
|
|
35
|
+
type ChangelogVersionsParams = Record<string, never>;
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Types
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
interface ChangelogEntry {
|
|
42
|
+
version: string;
|
|
43
|
+
content: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ChangelogDetails {
|
|
47
|
+
changelog?: ChangelogEntry;
|
|
48
|
+
source?: "local" | "github";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ChangelogVersionsDetails {
|
|
52
|
+
versions?: string[];
|
|
53
|
+
source?: "local" | "github";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Parsing
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
interface ParsedChangelog {
|
|
61
|
+
entries: Array<{ version: string; content: string }>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseChangelogEntries(changelogContent: string): ParsedChangelog {
|
|
65
|
+
const lines = changelogContent.split("\n");
|
|
66
|
+
const entries: Array<{
|
|
67
|
+
version: string;
|
|
68
|
+
content: string;
|
|
69
|
+
lineStart: number;
|
|
70
|
+
lineEnd: number;
|
|
71
|
+
}> = [];
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < lines.length; i++) {
|
|
74
|
+
const line = lines[i];
|
|
75
|
+
if (!line) continue;
|
|
76
|
+
const versionMatch = line.trim().match(/^#+\s*(?:\[([^\]]+)\]|([^[\s]+))/);
|
|
77
|
+
if (versionMatch) {
|
|
78
|
+
const version = versionMatch[1] || versionMatch[2];
|
|
79
|
+
if (version && /^v?\d+\.\d+/.test(version)) {
|
|
80
|
+
entries.push({ version, content: "", lineStart: i, lineEnd: -1 });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < entries.length; i++) {
|
|
86
|
+
const entry = entries[i];
|
|
87
|
+
if (!entry) continue;
|
|
88
|
+
const nextEntry = entries[i + 1];
|
|
89
|
+
const nextStart = nextEntry ? nextEntry.lineStart : lines.length;
|
|
90
|
+
entry.lineEnd = nextStart;
|
|
91
|
+
|
|
92
|
+
const contentLines = lines.slice(entry.lineStart + 1, entry.lineEnd);
|
|
93
|
+
const rawContent = contentLines.join("\n").trim();
|
|
94
|
+
|
|
95
|
+
const cleanContent = rawContent
|
|
96
|
+
.replace(/^-+$|^=+$|^\*+$|^#+$/gm, "")
|
|
97
|
+
.trim();
|
|
98
|
+
if (!cleanContent || cleanContent.length < 10) {
|
|
99
|
+
entry.content =
|
|
100
|
+
"[Empty changelog entry - no details provided for this version]";
|
|
101
|
+
} else {
|
|
102
|
+
entry.content = rawContent;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { entries };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function findChangelogEntry(
|
|
110
|
+
changelogContent: string,
|
|
111
|
+
requestedVersion?: string,
|
|
112
|
+
): ChangelogEntry {
|
|
113
|
+
const { entries } = parseChangelogEntries(changelogContent);
|
|
114
|
+
if (entries.length === 0) {
|
|
115
|
+
throw new Error("No version entries found in changelog");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (requestedVersion) {
|
|
119
|
+
const normalizedRequested = requestedVersion.replace(/^v/, "");
|
|
120
|
+
const entry = entries.find(
|
|
121
|
+
(e) =>
|
|
122
|
+
e.version === requestedVersion ||
|
|
123
|
+
e.version === `v${normalizedRequested}` ||
|
|
124
|
+
e.version.replace(/^v/, "") === normalizedRequested,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (entry) {
|
|
128
|
+
return { version: entry.version, content: entry.content };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const allVersions = entries.map((e) => e.version);
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Version ${requestedVersion} not found. Available versions: ${allVersions.join(", ")}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const latest = entries[0];
|
|
138
|
+
if (!latest) {
|
|
139
|
+
throw new Error("No version entries found in changelog");
|
|
140
|
+
}
|
|
141
|
+
return { version: latest.version, content: latest.content };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Helpers
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function isNewerThanInstalled(requestedVersion: string): boolean {
|
|
149
|
+
const normalize = (v: string) => v.replace(/^v/, "");
|
|
150
|
+
const req = normalize(requestedVersion);
|
|
151
|
+
const installed = normalize(VERSION);
|
|
152
|
+
if (req === installed) return false;
|
|
153
|
+
|
|
154
|
+
const reqParts = req.split(".").map(Number);
|
|
155
|
+
const instParts = installed.split(".").map(Number);
|
|
156
|
+
for (let i = 0; i < Math.max(reqParts.length, instParts.length); i++) {
|
|
157
|
+
const r = reqParts[i] ?? 0;
|
|
158
|
+
const inst = instParts[i] ?? 0;
|
|
159
|
+
if (r > inst) return true;
|
|
160
|
+
if (r < inst) return false;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function fetchGithubChangelog(): Promise<string> {
|
|
166
|
+
try {
|
|
167
|
+
const res = await fetch(GITHUB_RAW_CHANGELOG_URL);
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Failed to fetch changelog from GitHub: ${res.status} ${res.statusText}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return await res.text();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof Error && error.message.includes("Failed to fetch")) {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Failed to fetch changelog from GitHub: ${error instanceof Error ? error.message : String(error)}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function readLocalChangelog(): { content: string; piPath: string } {
|
|
185
|
+
const piPath = findPiInstallation();
|
|
186
|
+
if (!piPath) {
|
|
187
|
+
throw new Error("Could not locate Pi installation");
|
|
188
|
+
}
|
|
189
|
+
const changelogPath = path.join(piPath, "CHANGELOG.md");
|
|
190
|
+
if (!fs.existsSync(changelogPath)) {
|
|
191
|
+
throw new Error(`Changelog file not found at ${changelogPath}`);
|
|
192
|
+
}
|
|
193
|
+
return { content: fs.readFileSync(changelogPath, "utf-8"), piPath };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Max lines shown when collapsed. */
|
|
197
|
+
const COLLAPSED_LINES = 8;
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Render helpers
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
function renderChangelogContent(
|
|
204
|
+
content: string,
|
|
205
|
+
theme: Theme,
|
|
206
|
+
maxLines?: number,
|
|
207
|
+
): string[] {
|
|
208
|
+
const allLines = content.split("\n");
|
|
209
|
+
const truncated = maxLines != null && allLines.length > maxLines;
|
|
210
|
+
const linesToRender = truncated ? allLines.slice(0, maxLines) : allLines;
|
|
211
|
+
|
|
212
|
+
const out: string[] = [];
|
|
213
|
+
for (const line of linesToRender) {
|
|
214
|
+
if (line.trim().startsWith("###")) {
|
|
215
|
+
out.push(theme.fg("warning", line));
|
|
216
|
+
} else if (line.trim().startsWith("##")) {
|
|
217
|
+
out.push(theme.fg("accent", line));
|
|
218
|
+
} else if (line.trim().startsWith("#")) {
|
|
219
|
+
out.push(theme.fg("accent", theme.bold(line)));
|
|
220
|
+
} else if (line.trim().startsWith("-") || line.trim().startsWith("*")) {
|
|
221
|
+
out.push(theme.fg("dim", line));
|
|
222
|
+
} else {
|
|
223
|
+
out.push(line);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (truncated) {
|
|
228
|
+
out.push(theme.fg("muted", "..."));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// pi_changelog
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
export function setupChangelogTool(pi: ExtensionAPI) {
|
|
239
|
+
pi.registerTool<typeof ChangelogParamsSchema, ChangelogDetails>({
|
|
240
|
+
name: "pi_changelog",
|
|
241
|
+
label: "Pi Changelog",
|
|
242
|
+
description:
|
|
243
|
+
"Get changelog entry for a Pi version. Returns latest by default. Use pi_changelog_versions to list all available versions.",
|
|
244
|
+
promptSnippet: `pi_changelog version="1.2.3" // Get changelog for specific version
|
|
245
|
+
pi_changelog // Get latest changelog`,
|
|
246
|
+
promptGuidelines: [
|
|
247
|
+
"Use this tool to check what's new in a Pi version",
|
|
248
|
+
"Use pi_changelog_versions first to list available versions",
|
|
249
|
+
"Leave version empty to get the latest changelog",
|
|
250
|
+
],
|
|
251
|
+
|
|
252
|
+
parameters: ChangelogParamsSchema,
|
|
253
|
+
|
|
254
|
+
async execute(
|
|
255
|
+
_toolCallId: string,
|
|
256
|
+
params: ChangelogParams,
|
|
257
|
+
_signal: AbortSignal | undefined,
|
|
258
|
+
_onUpdate: unknown,
|
|
259
|
+
_ctx: ExtensionContext,
|
|
260
|
+
): Promise<AgentToolResult<ChangelogDetails>> {
|
|
261
|
+
// Newer than installed -> fetch from GitHub
|
|
262
|
+
if (params.version && isNewerThanInstalled(params.version)) {
|
|
263
|
+
const githubContent = await fetchGithubChangelog();
|
|
264
|
+
const changelog = findChangelogEntry(githubContent, params.version);
|
|
265
|
+
|
|
266
|
+
const message = `Changelog for ${changelog.version} (from GitHub)\n\n## ${changelog.version}\n\n${changelog.content}`;
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: "text", text: message }],
|
|
269
|
+
details: {
|
|
270
|
+
changelog,
|
|
271
|
+
source: "github",
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Local
|
|
277
|
+
const local = readLocalChangelog();
|
|
278
|
+
const changelog = findChangelogEntry(local.content, params.version);
|
|
279
|
+
|
|
280
|
+
const message = `Changelog for ${changelog.version}\n\n## ${changelog.version}\n\n${changelog.content}`;
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: "text", text: message }],
|
|
283
|
+
details: {
|
|
284
|
+
changelog,
|
|
285
|
+
source: "local",
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
renderCall(args: ChangelogParams, theme: Theme) {
|
|
291
|
+
return new ToolCallHeader(
|
|
292
|
+
{
|
|
293
|
+
toolName: "Pi Changelog",
|
|
294
|
+
mainArg: args.version ? `v${args.version}` : "latest",
|
|
295
|
+
},
|
|
296
|
+
theme,
|
|
297
|
+
);
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
renderResult(
|
|
301
|
+
result: AgentToolResult<ChangelogDetails>,
|
|
302
|
+
options: ToolRenderResultOptions,
|
|
303
|
+
theme: Theme,
|
|
304
|
+
) {
|
|
305
|
+
const { details } = result;
|
|
306
|
+
|
|
307
|
+
// Check for missing expected fields to detect errors
|
|
308
|
+
if (!details?.changelog) {
|
|
309
|
+
const text = result.content[0];
|
|
310
|
+
return new Text(
|
|
311
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
312
|
+
0,
|
|
313
|
+
0,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const fields: Array<
|
|
318
|
+
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
319
|
+
> = [];
|
|
320
|
+
|
|
321
|
+
const lines: string[] = [];
|
|
322
|
+
|
|
323
|
+
if (options.expanded) {
|
|
324
|
+
// Expanded view: show full changelog content
|
|
325
|
+
lines.push(
|
|
326
|
+
theme.fg(
|
|
327
|
+
"accent",
|
|
328
|
+
theme.bold(`Version: ${details.changelog.version}`),
|
|
329
|
+
),
|
|
330
|
+
"",
|
|
331
|
+
);
|
|
332
|
+
lines.push(...renderChangelogContent(details.changelog.content, theme));
|
|
333
|
+
fields.push(new Text(lines.join("\n"), 0, 0));
|
|
334
|
+
} else {
|
|
335
|
+
// Collapsed view: show version + first few lines of changelog + expand hint
|
|
336
|
+
lines.push(
|
|
337
|
+
theme.fg(
|
|
338
|
+
"accent",
|
|
339
|
+
theme.bold(`Version: ${details.changelog.version}`),
|
|
340
|
+
),
|
|
341
|
+
"",
|
|
342
|
+
);
|
|
343
|
+
lines.push(
|
|
344
|
+
...renderChangelogContent(
|
|
345
|
+
details.changelog.content,
|
|
346
|
+
theme,
|
|
347
|
+
COLLAPSED_LINES,
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
lines.push(
|
|
351
|
+
"",
|
|
352
|
+
theme.fg("muted", `${keyHint("app.tools.expand", "to expand")}`),
|
|
353
|
+
);
|
|
354
|
+
fields.push(new Text(lines.join("\n"), 0, 0));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Footer: show source tag only
|
|
358
|
+
const footer = new ToolFooter(theme, {
|
|
359
|
+
items: [
|
|
360
|
+
{
|
|
361
|
+
label: "source",
|
|
362
|
+
value: details.source ?? "local",
|
|
363
|
+
tone: "accent",
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return new ToolBody(
|
|
369
|
+
{
|
|
370
|
+
fields,
|
|
371
|
+
footer,
|
|
372
|
+
},
|
|
373
|
+
options,
|
|
374
|
+
theme,
|
|
375
|
+
);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// -------------------------------------------------------------------------
|
|
380
|
+
// pi_changelog_versions
|
|
381
|
+
// -------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
pi.registerTool<
|
|
384
|
+
typeof ChangelogVersionsParamsSchema,
|
|
385
|
+
ChangelogVersionsDetails
|
|
386
|
+
>({
|
|
387
|
+
name: "pi_changelog_versions",
|
|
388
|
+
label: "Pi Changelog Versions",
|
|
389
|
+
description: "List all available Pi changelog versions",
|
|
390
|
+
promptSnippet: `pi_changelog_versions // List all available versions`,
|
|
391
|
+
|
|
392
|
+
parameters: ChangelogVersionsParamsSchema,
|
|
393
|
+
|
|
394
|
+
async execute(
|
|
395
|
+
_toolCallId: string,
|
|
396
|
+
_params: ChangelogVersionsParams,
|
|
397
|
+
_signal: AbortSignal | undefined,
|
|
398
|
+
_onUpdate: unknown,
|
|
399
|
+
_ctx: ExtensionContext,
|
|
400
|
+
): Promise<AgentToolResult<ChangelogVersionsDetails>> {
|
|
401
|
+
const local = readLocalChangelog();
|
|
402
|
+
const { entries } = parseChangelogEntries(local.content);
|
|
403
|
+
|
|
404
|
+
if (entries.length === 0) {
|
|
405
|
+
throw new Error("No version entries found in changelog");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const versions = entries.map((e) => e.version);
|
|
409
|
+
const message = `${versions.length} versions available:\n${versions.join(", ")}`;
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
content: [{ type: "text", text: message }],
|
|
413
|
+
details: {
|
|
414
|
+
versions,
|
|
415
|
+
source: "local",
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
renderCall(_args: ChangelogVersionsParams, theme: Theme) {
|
|
421
|
+
return new ToolCallHeader({ toolName: "Pi Changelog Versions" }, theme);
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
renderResult(
|
|
425
|
+
result: AgentToolResult<ChangelogVersionsDetails>,
|
|
426
|
+
options: ToolRenderResultOptions,
|
|
427
|
+
theme: Theme,
|
|
428
|
+
) {
|
|
429
|
+
const { details } = result;
|
|
430
|
+
|
|
431
|
+
// Check for missing expected fields to detect errors
|
|
432
|
+
if (!details?.versions) {
|
|
433
|
+
const text = result.content[0];
|
|
434
|
+
return new Text(
|
|
435
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
436
|
+
0,
|
|
437
|
+
0,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const fields: Array<
|
|
442
|
+
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
443
|
+
> = [];
|
|
444
|
+
|
|
445
|
+
const lines: string[] = [
|
|
446
|
+
theme.fg("accent", `${details.versions.length} versions available:`),
|
|
447
|
+
"",
|
|
448
|
+
];
|
|
449
|
+
const cols = 6;
|
|
450
|
+
const maxLen = Math.max(
|
|
451
|
+
...details.versions.map((version) => version.length),
|
|
452
|
+
);
|
|
453
|
+
const colWidth = maxLen + 2;
|
|
454
|
+
for (let i = 0; i < details.versions.length; i += cols) {
|
|
455
|
+
const row = details.versions
|
|
456
|
+
.slice(i, i + cols)
|
|
457
|
+
.map((version) => version.padEnd(colWidth))
|
|
458
|
+
.join("");
|
|
459
|
+
lines.push(theme.fg("dim", row));
|
|
460
|
+
}
|
|
461
|
+
fields.push(new Text(lines.join("\n"), 0, 0));
|
|
462
|
+
|
|
463
|
+
// Footer: just show version count
|
|
464
|
+
const footer = new ToolFooter(theme, {
|
|
465
|
+
items: [
|
|
466
|
+
{
|
|
467
|
+
label: "count",
|
|
468
|
+
value: String(details.versions.length),
|
|
469
|
+
tone: "accent",
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
return new ToolBody(
|
|
475
|
+
{
|
|
476
|
+
fields,
|
|
477
|
+
footer,
|
|
478
|
+
},
|
|
479
|
+
options,
|
|
480
|
+
theme,
|
|
481
|
+
);
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ToolBody, ToolCallHeader, ToolFooter } from "@aliou/pi-utils-ui";
|
|
4
|
+
import type {
|
|
5
|
+
AgentToolResult,
|
|
6
|
+
ExtensionAPI,
|
|
7
|
+
ExtensionContext,
|
|
8
|
+
Theme,
|
|
9
|
+
ToolRenderResultOptions,
|
|
10
|
+
} from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { keyHint } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
13
|
+
import { Type } from "@sinclair/typebox";
|
|
14
|
+
import { findPiInstallation } from "./utils";
|
|
15
|
+
|
|
16
|
+
const DocsParamsSchema = Type.Object({});
|
|
17
|
+
type DocsParams = Record<string, never>;
|
|
18
|
+
|
|
19
|
+
interface DocsDetails {
|
|
20
|
+
/** Relative paths from the pi install root, markdown only. */
|
|
21
|
+
docFiles?: string[];
|
|
22
|
+
installPath?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function listFilesRecursive(dir: string, prefix = ""): string[] {
|
|
26
|
+
const results: string[] = [];
|
|
27
|
+
if (!fs.existsSync(dir)) return results;
|
|
28
|
+
|
|
29
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
results.push(...listFilesRecursive(path.join(dir, entry.name), rel));
|
|
34
|
+
} else {
|
|
35
|
+
results.push(rel);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function setupDocsTool(pi: ExtensionAPI) {
|
|
42
|
+
pi.registerTool<typeof DocsParamsSchema, DocsDetails>({
|
|
43
|
+
name: "pi_docs",
|
|
44
|
+
label: "Pi Documentation",
|
|
45
|
+
description:
|
|
46
|
+
"List Pi markdown documentation files (README, docs/, examples/)",
|
|
47
|
+
|
|
48
|
+
promptSnippet: "List Pi documentation files",
|
|
49
|
+
promptGuidelines: [
|
|
50
|
+
"Use to discover available Pi documentation",
|
|
51
|
+
"Returns markdown files from README.md, docs/, and examples/",
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
parameters: DocsParamsSchema,
|
|
55
|
+
|
|
56
|
+
async execute(
|
|
57
|
+
_toolCallId: string,
|
|
58
|
+
_params: DocsParams,
|
|
59
|
+
_signal: AbortSignal | undefined,
|
|
60
|
+
_onUpdate: unknown,
|
|
61
|
+
_ctx: ExtensionContext,
|
|
62
|
+
): Promise<AgentToolResult<DocsDetails>> {
|
|
63
|
+
const piPath = findPiInstallation();
|
|
64
|
+
if (!piPath) {
|
|
65
|
+
throw new Error("Could not locate running Pi installation directory");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const readmePath = path.join(piPath, "README.md");
|
|
69
|
+
const docsDir = path.join(piPath, "docs");
|
|
70
|
+
const examplesDir = path.join(piPath, "examples");
|
|
71
|
+
|
|
72
|
+
const docFiles: string[] = [];
|
|
73
|
+
|
|
74
|
+
if (fs.existsSync(readmePath)) {
|
|
75
|
+
docFiles.push("README.md");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(docsDir)) {
|
|
79
|
+
for (const file of listFilesRecursive(docsDir)) {
|
|
80
|
+
if (file.endsWith(".md")) {
|
|
81
|
+
docFiles.push(`docs/${file}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (fs.existsSync(examplesDir)) {
|
|
87
|
+
for (const file of listFilesRecursive(examplesDir)) {
|
|
88
|
+
if (file.endsWith(".md")) {
|
|
89
|
+
docFiles.push(`examples/${file}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (docFiles.length === 0) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`No markdown documentation found in Pi installation at ${piPath}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Content sent to LLM: full relative paths so it can read them.
|
|
101
|
+
const lines = docFiles.map((rel) => `${path.join(piPath, rel)} (${rel})`);
|
|
102
|
+
const message = `${docFiles.length} markdown files:\n${lines.join("\n")}`;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: message }],
|
|
106
|
+
details: {
|
|
107
|
+
docFiles,
|
|
108
|
+
installPath: piPath,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
renderCall(_args: DocsParams, theme: Theme) {
|
|
114
|
+
return new ToolCallHeader({ toolName: "Pi Docs" }, theme);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
renderResult(
|
|
118
|
+
result: AgentToolResult<DocsDetails>,
|
|
119
|
+
options: ToolRenderResultOptions,
|
|
120
|
+
theme: Theme,
|
|
121
|
+
) {
|
|
122
|
+
const { details } = result;
|
|
123
|
+
const { isPartial } = options;
|
|
124
|
+
|
|
125
|
+
// Handle isPartial first (this tool doesn't stream, but keep the pattern)
|
|
126
|
+
if (isPartial) {
|
|
127
|
+
return new Text(theme.fg("dim", "Loading..."), 0, 0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check for missing expected fields in details to detect errors
|
|
131
|
+
if (!details || !details.docFiles) {
|
|
132
|
+
const text = result.content[0];
|
|
133
|
+
return new Text(
|
|
134
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
135
|
+
0,
|
|
136
|
+
0,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { docFiles } = details;
|
|
141
|
+
const fields: Array<
|
|
142
|
+
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
143
|
+
> = [];
|
|
144
|
+
|
|
145
|
+
if (options.expanded) {
|
|
146
|
+
// Expanded view: show full file list
|
|
147
|
+
const lines: string[] = [];
|
|
148
|
+
lines.push(
|
|
149
|
+
theme.fg("accent", `${docFiles.length} markdown files:`),
|
|
150
|
+
"",
|
|
151
|
+
);
|
|
152
|
+
for (const rel of docFiles) {
|
|
153
|
+
lines.push(theme.fg("dim", ` ${rel}`));
|
|
154
|
+
}
|
|
155
|
+
fields.push(new Text(lines.join("\n"), 0, 0));
|
|
156
|
+
} else {
|
|
157
|
+
// Collapsed view: show file count + expand hint
|
|
158
|
+
fields.push({
|
|
159
|
+
label: "Files",
|
|
160
|
+
value:
|
|
161
|
+
theme.fg("accent", `${docFiles.length} markdown files`) +
|
|
162
|
+
` (${keyHint("app.tools.expand", "to expand")})`,
|
|
163
|
+
showCollapsed: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Only show footer if there are items worth showing
|
|
168
|
+
const footer = new ToolFooter(theme, {
|
|
169
|
+
items: [
|
|
170
|
+
{
|
|
171
|
+
label: "docs",
|
|
172
|
+
value: String(docFiles.length),
|
|
173
|
+
tone: "accent",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return new ToolBody({ fields, footer }, options, theme);
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { setupChangelogTool } from "./changelog-tool";
|
|
3
|
+
import { setupDocsTool } from "./docs-tool";
|
|
4
|
+
import { setupPackageManagerTool } from "./package-manager-tool";
|
|
5
|
+
import { setupVersionTool } from "./version-tool";
|
|
6
|
+
|
|
7
|
+
export function setupTools(pi: ExtensionAPI) {
|
|
8
|
+
setupPackageManagerTool(pi);
|
|
9
|
+
setupVersionTool(pi);
|
|
10
|
+
setupDocsTool(pi);
|
|
11
|
+
setupChangelogTool(pi);
|
|
12
|
+
}
|