@aliou/pi-dev-kit 0.5.0 → 0.6.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.
@@ -20,7 +20,7 @@ const GITHUB_RAW_CHANGELOG_URL =
20
20
  // Params
21
21
  // ---------------------------------------------------------------------------
22
22
 
23
- const ChangelogParams = Type.Object({
23
+ const ChangelogParamsSchema = Type.Object({
24
24
  version: Type.Optional(
25
25
  Type.String({
26
26
  description:
@@ -29,10 +29,10 @@ const ChangelogParams = Type.Object({
29
29
  ),
30
30
  });
31
31
 
32
- type ChangelogParamsType = Static<typeof ChangelogParams>;
32
+ type ChangelogParams = Static<typeof ChangelogParamsSchema>;
33
33
 
34
- const ChangelogVersionsParams = Type.Object({});
35
- type ChangelogVersionsParamsType = Record<string, never>;
34
+ const ChangelogVersionsParamsSchema = Type.Object({});
35
+ type ChangelogVersionsParams = Record<string, never>;
36
36
 
37
37
  // ---------------------------------------------------------------------------
38
38
  // Types
@@ -44,21 +44,15 @@ interface ChangelogEntry {
44
44
  }
45
45
 
46
46
  interface ChangelogDetails {
47
- success: boolean;
48
- message: string;
49
47
  changelog?: ChangelogEntry;
50
48
  source?: "local" | "github";
51
49
  }
52
50
 
53
51
  interface ChangelogVersionsDetails {
54
- success: boolean;
55
- message: string;
56
52
  versions?: string[];
57
53
  source?: "local" | "github";
58
54
  }
59
55
 
60
- type ExecuteResult = AgentToolResult<ChangelogDetails>;
61
-
62
56
  // ---------------------------------------------------------------------------
63
57
  // Parsing
64
58
  // ---------------------------------------------------------------------------
@@ -115,56 +109,36 @@ function parseChangelogEntries(changelogContent: string): ParsedChangelog {
115
109
  function findChangelogEntry(
116
110
  changelogContent: string,
117
111
  requestedVersion?: string,
118
- ): {
119
- success: boolean;
120
- changelog?: ChangelogEntry;
121
- message: string;
122
- } {
123
- try {
124
- const { entries } = parseChangelogEntries(changelogContent);
125
- if (entries.length === 0) {
126
- return { success: false, message: "No version entries found" };
127
- }
128
-
129
- if (requestedVersion) {
130
- const normalizedRequested = requestedVersion.replace(/^v/, "");
131
- const entry = entries.find(
132
- (e) =>
133
- e.version === requestedVersion ||
134
- e.version === `v${normalizedRequested}` ||
135
- e.version.replace(/^v/, "") === normalizedRequested,
136
- );
137
-
138
- if (entry) {
139
- return {
140
- success: true,
141
- changelog: { version: entry.version, content: entry.content },
142
- message: `Found changelog for version ${entry.version}`,
143
- };
144
- }
112
+ ): ChangelogEntry {
113
+ const { entries } = parseChangelogEntries(changelogContent);
114
+ if (entries.length === 0) {
115
+ throw new Error("No version entries found in changelog");
116
+ }
145
117
 
146
- const allVersions = entries.map((e) => e.version);
147
- return {
148
- success: false,
149
- message: `Version ${requestedVersion} not found. Available: ${allVersions.join(", ")}`,
150
- };
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 };
151
129
  }
152
130
 
153
- const latest = entries[0];
154
- if (!latest) {
155
- return { success: false, message: "No version entries found" };
156
- }
157
- return {
158
- success: true,
159
- changelog: { version: latest.version, content: latest.content },
160
- message: `Latest changelog entry: ${latest.version}`,
161
- };
162
- } catch (error) {
163
- return {
164
- success: false,
165
- message: `Error parsing changelog: ${error instanceof Error ? error.message : String(error)}`,
166
- };
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");
167
140
  }
141
+ return { version: latest.version, content: latest.content };
168
142
  }
169
143
 
170
144
  // ---------------------------------------------------------------------------
@@ -188,21 +162,34 @@ function isNewerThanInstalled(requestedVersion: string): boolean {
188
162
  return false;
189
163
  }
190
164
 
191
- async function fetchGithubChangelog(): Promise<string | null> {
165
+ async function fetchGithubChangelog(): Promise<string> {
192
166
  try {
193
167
  const res = await fetch(GITHUB_RAW_CHANGELOG_URL);
194
- if (!res.ok) return null;
168
+ if (!res.ok) {
169
+ throw new Error(
170
+ `Failed to fetch changelog from GitHub: ${res.status} ${res.statusText}`,
171
+ );
172
+ }
195
173
  return await res.text();
196
- } catch {
197
- return null;
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
+ );
198
181
  }
199
182
  }
200
183
 
201
- function readLocalChangelog(): { content: string; piPath: string } | null {
184
+ function readLocalChangelog(): { content: string; piPath: string } {
202
185
  const piPath = findPiInstallation();
203
- if (!piPath) return null;
186
+ if (!piPath) {
187
+ throw new Error("Could not locate Pi installation");
188
+ }
204
189
  const changelogPath = path.join(piPath, "CHANGELOG.md");
205
- if (!fs.existsSync(changelogPath)) return null;
190
+ if (!fs.existsSync(changelogPath)) {
191
+ throw new Error(`Changelog file not found at ${changelogPath}`);
192
+ }
206
193
  return { content: fs.readFileSync(changelogPath, "utf-8"), piPath };
207
194
  }
208
195
 
@@ -249,110 +236,58 @@ function renderChangelogContent(
249
236
  // ---------------------------------------------------------------------------
250
237
 
251
238
  export function setupChangelogTool(pi: ExtensionAPI) {
252
- pi.registerTool<typeof ChangelogParams, ChangelogDetails>({
239
+ pi.registerTool<typeof ChangelogParamsSchema, ChangelogDetails>({
253
240
  name: "pi_changelog",
254
241
  label: "Pi Changelog",
255
242
  description:
256
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
+ ],
257
251
 
258
- parameters: ChangelogParams,
252
+ parameters: ChangelogParamsSchema,
259
253
 
260
254
  async execute(
261
255
  _toolCallId: string,
262
- params: ChangelogParamsType,
256
+ params: ChangelogParams,
263
257
  _signal: AbortSignal | undefined,
264
258
  _onUpdate: unknown,
265
259
  _ctx: ExtensionContext,
266
- ): Promise<ExecuteResult> {
267
- try {
268
- // Newer than installed -> fetch from GitHub
269
- if (params.version && isNewerThanInstalled(params.version)) {
270
- const githubContent = await fetchGithubChangelog();
271
- if (!githubContent) {
272
- return {
273
- content: [
274
- {
275
- type: "text",
276
- text: `Version ${params.version} is newer than installed (${VERSION}) and GitHub fetch failed.`,
277
- },
278
- ],
279
- details: {
280
- success: false,
281
- message: `Version ${params.version} is newer than installed (${VERSION}) and GitHub fetch failed.`,
282
- },
283
- };
284
- }
285
-
286
- const result = findChangelogEntry(githubContent, params.version);
287
- if (!result.success || !result.changelog) {
288
- return {
289
- content: [{ type: "text", text: result.message }],
290
- details: {
291
- success: false,
292
- message: result.message,
293
- source: "github",
294
- },
295
- };
296
- }
297
-
298
- const message = `${result.message} (from GitHub)\n\n## ${result.changelog.version}\n\n${result.changelog.content}`;
299
- return {
300
- content: [{ type: "text", text: message }],
301
- details: {
302
- success: true,
303
- message: `${result.message} (from GitHub)`,
304
- changelog: result.changelog,
305
- source: "github",
306
- },
307
- };
308
- }
309
-
310
- // Local
311
- const local = readLocalChangelog();
312
- if (!local) {
313
- return {
314
- content: [
315
- {
316
- type: "text",
317
- text: "Could not locate Pi installation or CHANGELOG.md",
318
- },
319
- ],
320
- details: {
321
- success: false,
322
- message: "Could not locate Pi installation or CHANGELOG.md",
323
- },
324
- };
325
- }
326
-
327
- const result = findChangelogEntry(local.content, params.version);
328
- if (!result.success || !result.changelog) {
329
- return {
330
- content: [{ type: "text", text: result.message }],
331
- details: { success: false, message: result.message },
332
- };
333
- }
334
-
335
- const { changelog } = result;
336
- const message = `${result.message}\n\n## ${changelog.version}\n\n${changelog.content}`;
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}`;
337
267
  return {
338
268
  content: [{ type: "text", text: message }],
339
269
  details: {
340
- success: true,
341
- message: result.message,
342
270
  changelog,
343
- source: "local",
271
+ source: "github",
344
272
  },
345
273
  };
346
- } catch (error) {
347
- const message = `Error reading Pi changelog: ${error instanceof Error ? error.message : String(error)}`;
348
- return {
349
- content: [{ type: "text", text: message }],
350
- details: { success: false, message },
351
- };
352
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
+ };
353
288
  },
354
289
 
355
- renderCall(args: ChangelogParamsType, theme: Theme) {
290
+ renderCall(args: ChangelogParams, theme: Theme) {
356
291
  return new ToolCallHeader(
357
292
  {
358
293
  toolName: "Pi Changelog",
@@ -369,7 +304,8 @@ export function setupChangelogTool(pi: ExtensionAPI) {
369
304
  ) {
370
305
  const { details } = result;
371
306
 
372
- if (!details) {
307
+ // Check for missing expected fields to detect errors
308
+ if (!details?.changelog) {
373
309
  const text = result.content[0];
374
310
  return new Text(
375
311
  text?.type === "text" && text.text ? text.text : "No result",
@@ -382,62 +318,57 @@ export function setupChangelogTool(pi: ExtensionAPI) {
382
318
  { label: string; value: string; showCollapsed?: boolean } | Text
383
319
  > = [];
384
320
 
385
- if (!details.success) {
386
- fields.push({
387
- label: "Error",
388
- value: theme.fg("error", details.message),
389
- showCollapsed: true,
390
- });
391
- } else if (!details.changelog) {
392
- fields.push({
393
- label: "Result",
394
- value: theme.fg("success", details.message),
395
- showCollapsed: true,
396
- });
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));
397
334
  } else {
398
- const lines: string[] = [];
399
- const sourceTag =
400
- details.source === "github" ? theme.fg("muted", " (github)") : "";
401
- lines.push(theme.fg("success", details.message) + sourceTag, "");
335
+ // Collapsed view: show version + first few lines of changelog + expand hint
402
336
  lines.push(
403
- theme.fg("accent", `Version: ${details.changelog.version}`),
337
+ theme.fg(
338
+ "accent",
339
+ theme.bold(`Version: ${details.changelog.version}`),
340
+ ),
404
341
  "",
405
342
  );
406
343
  lines.push(
407
344
  ...renderChangelogContent(
408
345
  details.changelog.content,
409
346
  theme,
410
- options.expanded ? undefined : COLLAPSED_LINES,
347
+ COLLAPSED_LINES,
411
348
  ),
412
349
  );
413
-
414
- if (!options.expanded) {
415
- lines.push(
416
- "",
417
- theme.fg("muted", `${keyHint("app.tools.expand", "to expand")}`),
418
- );
419
- }
420
-
350
+ lines.push(
351
+ "",
352
+ theme.fg("muted", `${keyHint("app.tools.expand", "to expand")}`),
353
+ );
421
354
  fields.push(new Text(lines.join("\n"), 0, 0));
422
355
  }
423
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
+
424
368
  return new ToolBody(
425
369
  {
426
370
  fields,
427
- footer: new ToolFooter(theme, {
428
- items: [
429
- {
430
- label: "status",
431
- value: details.success ? "ok" : "error",
432
- tone: details.success ? "success" : "error",
433
- },
434
- {
435
- label: "source",
436
- value: details.source ?? "local",
437
- tone: "accent",
438
- },
439
- ],
440
- }),
371
+ footer,
441
372
  },
442
373
  options,
443
374
  theme,
@@ -449,72 +380,44 @@ export function setupChangelogTool(pi: ExtensionAPI) {
449
380
  // pi_changelog_versions
450
381
  // -------------------------------------------------------------------------
451
382
 
452
- pi.registerTool<typeof ChangelogVersionsParams, ChangelogVersionsDetails>({
383
+ pi.registerTool<
384
+ typeof ChangelogVersionsParamsSchema,
385
+ ChangelogVersionsDetails
386
+ >({
453
387
  name: "pi_changelog_versions",
454
388
  label: "Pi Changelog Versions",
455
389
  description: "List all available Pi changelog versions",
390
+ promptSnippet: `pi_changelog_versions // List all available versions`,
456
391
 
457
- parameters: ChangelogVersionsParams,
392
+ parameters: ChangelogVersionsParamsSchema,
458
393
 
459
394
  async execute(
460
395
  _toolCallId: string,
461
- _params: ChangelogVersionsParamsType,
396
+ _params: ChangelogVersionsParams,
462
397
  _signal: AbortSignal | undefined,
463
398
  _onUpdate: unknown,
464
399
  _ctx: ExtensionContext,
465
400
  ): Promise<AgentToolResult<ChangelogVersionsDetails>> {
466
- try {
467
- const local = readLocalChangelog();
468
- if (!local) {
469
- return {
470
- content: [
471
- {
472
- type: "text",
473
- text: "Could not locate Pi installation or CHANGELOG.md",
474
- },
475
- ],
476
- details: {
477
- success: false,
478
- message: "Could not locate Pi installation or CHANGELOG.md",
479
- },
480
- };
481
- }
482
-
483
- const { entries } = parseChangelogEntries(local.content);
484
- if (entries.length === 0) {
485
- return {
486
- content: [
487
- { type: "text", text: "No version entries found in changelog" },
488
- ],
489
- details: {
490
- success: false,
491
- message: "No version entries found in changelog",
492
- },
493
- };
494
- }
495
-
496
- const versions = entries.map((e) => e.version);
497
- const message = `${versions.length} versions available:\n${versions.join(", ")}`;
401
+ const local = readLocalChangelog();
402
+ const { entries } = parseChangelogEntries(local.content);
498
403
 
499
- return {
500
- content: [{ type: "text", text: message }],
501
- details: {
502
- success: true,
503
- message: `Found ${versions.length} versions`,
504
- versions,
505
- source: "local",
506
- },
507
- };
508
- } catch (error) {
509
- const message = `Error reading changelog: ${error instanceof Error ? error.message : String(error)}`;
510
- return {
511
- content: [{ type: "text", text: message }],
512
- details: { success: false, message },
513
- };
404
+ if (entries.length === 0) {
405
+ throw new Error("No version entries found in changelog");
514
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
+ };
515
418
  },
516
419
 
517
- renderCall(_args: ChangelogVersionsParamsType, theme: Theme) {
420
+ renderCall(_args: ChangelogVersionsParams, theme: Theme) {
518
421
  return new ToolCallHeader({ toolName: "Pi Changelog Versions" }, theme);
519
422
  },
520
423
 
@@ -525,7 +428,8 @@ export function setupChangelogTool(pi: ExtensionAPI) {
525
428
  ) {
526
429
  const { details } = result;
527
430
 
528
- if (!details) {
431
+ // Check for missing expected fields to detect errors
432
+ if (!details?.versions) {
529
433
  const text = result.content[0];
530
434
  return new Text(
531
435
  text?.type === "text" && text.text ? text.text : "No result",
@@ -538,55 +442,39 @@ export function setupChangelogTool(pi: ExtensionAPI) {
538
442
  { label: string; value: string; showCollapsed?: boolean } | Text
539
443
  > = [];
540
444
 
541
- if (!details.success) {
542
- fields.push({
543
- label: "Error",
544
- value: theme.fg("error", details.message),
545
- showCollapsed: true,
546
- });
547
- } else if (!details.versions || details.versions.length === 0) {
548
- fields.push({
549
- label: "Result",
550
- value: theme.fg("warning", "No versions found"),
551
- showCollapsed: true,
552
- });
553
- } else {
554
- const lines: string[] = [
555
- theme.fg("accent", `${details.versions.length} versions available:`),
556
- "",
557
- ];
558
- const cols = 6;
559
- const maxLen = Math.max(
560
- ...details.versions.map((version) => version.length),
561
- );
562
- const colWidth = maxLen + 2;
563
- for (let i = 0; i < details.versions.length; i += cols) {
564
- const row = details.versions
565
- .slice(i, i + cols)
566
- .map((version) => version.padEnd(colWidth))
567
- .join("");
568
- lines.push(theme.fg("dim", row));
569
- }
570
- fields.push(new Text(lines.join("\n"), 0, 0));
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));
571
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
+ });
572
473
 
573
474
  return new ToolBody(
574
475
  {
575
476
  fields,
576
- footer: new ToolFooter(theme, {
577
- items: [
578
- {
579
- label: "status",
580
- value: details.success ? "ok" : "error",
581
- tone: details.success ? "success" : "error",
582
- },
583
- {
584
- label: "versions",
585
- value: String(details.versions?.length ?? 0),
586
- tone: "accent",
587
- },
588
- ],
589
- }),
477
+ footer,
590
478
  },
591
479
  options,
592
480
  theme,