@danypops/pi-packed 0.19.4 → 0.19.6

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.
@@ -1,3 +1,4 @@
1
+ import type { PackageSummary as Pkg, PackageInfo as PkgInfo } from "@danypops/packed/protocol";
1
2
  import type { AgentToolResult, Theme } from "@earendil-works/pi-coding-agent";
2
3
  import { Text, truncateToWidth } from "@earendil-works/pi-tui";
3
4
  import {
@@ -10,7 +11,6 @@ import {
10
11
  TOOL_DETAILS_MAX_SERIALIZED_CHARACTERS,
11
12
  TOOL_MODEL_CONTENT_MAX_CHARACTERS,
12
13
  } from "./constants.js";
13
- import type { PackageInfo as PkgInfo, PackageSummary as Pkg } from "@danypops/packed/protocol";
14
14
 
15
15
  const DETAILS_VERSION = 2 as const;
16
16
  const MUTATION_OPERATIONS = new Set(["install", "update", "remove"]);
@@ -149,8 +149,12 @@ export function createInfoDetails(info: PkgInfo): InfoToolDetails {
149
149
  ...(info.homepage ? { homepage: safePackageTarget(info.homepage) } : {}),
150
150
  ...(info.repository ? { repository: safePackageTarget(info.repository) } : {}),
151
151
  ...(info.license ? { license: bounded(info.license, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS) } : {}),
152
- keywords: (info.keywords ?? []).slice(0, TOOL_DETAILS_MAX_KEYWORDS).map((keyword) => bounded(keyword, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)),
153
- capabilities: Object.keys(info.pi ?? {}).slice(0, TOOL_DETAILS_MAX_CAPABILITIES).map((name) => bounded(name, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)),
152
+ keywords: (info.keywords ?? [])
153
+ .slice(0, TOOL_DETAILS_MAX_KEYWORDS)
154
+ .map((keyword) => bounded(keyword, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)),
155
+ capabilities: Object.keys(info.pi ?? {})
156
+ .slice(0, TOOL_DETAILS_MAX_CAPABILITIES)
157
+ .map((name) => bounded(name, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)),
154
158
  ...(info.publication?.provenanceUrl ? { provenance: safePackageTarget(info.publication.provenanceUrl) } : {}),
155
159
  trustedPublisher: info.publication?.trustedPublisher ?? "unknown",
156
160
  },
@@ -191,7 +195,15 @@ export function parsePackageToolDetails(value: unknown): PackageToolDetails | un
191
195
  const candidate = value as Record<string, unknown>;
192
196
  if (candidate.version !== DETAILS_VERSION || typeof candidate.kind !== "string") return undefined;
193
197
  if (candidate.kind === "search") {
194
- if (candidate.operation !== "search" || !isShortString(candidate.query, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS) || typeof candidate.total !== "number" || !Number.isSafeInteger(candidate.total) || candidate.total < 0 || typeof candidate.truncated !== "boolean") return undefined;
198
+ if (
199
+ candidate.operation !== "search" ||
200
+ !isShortString(candidate.query, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS) ||
201
+ typeof candidate.total !== "number" ||
202
+ !Number.isSafeInteger(candidate.total) ||
203
+ candidate.total < 0 ||
204
+ typeof candidate.truncated !== "boolean"
205
+ )
206
+ return undefined;
195
207
  if (!Array.isArray(candidate.items) || candidate.items.length > TOOL_DETAILS_MAX_PACKAGES) return undefined;
196
208
  if (!candidate.items.every((item) => isPackageSummary(item))) return undefined;
197
209
  return value as SearchToolDetails;
@@ -199,8 +211,19 @@ export function parsePackageToolDetails(value: unknown): PackageToolDetails | un
199
211
  if (candidate.kind === "info") {
200
212
  if (candidate.operation !== "info" || !candidate.package || typeof candidate.package !== "object") return undefined;
201
213
  const pkg = candidate.package as Record<string, unknown>;
202
- if (!isPackageSummary(pkg) || !Array.isArray(pkg.keywords) || pkg.keywords.length > TOOL_DETAILS_MAX_KEYWORDS || !pkg.keywords.every((item) => isShortString(item, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS))) return undefined;
203
- if (!Array.isArray(pkg.capabilities) || pkg.capabilities.length > TOOL_DETAILS_MAX_CAPABILITIES || !pkg.capabilities.every((item) => isShortString(item, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS))) return undefined;
214
+ if (
215
+ !isPackageSummary(pkg) ||
216
+ !Array.isArray(pkg.keywords) ||
217
+ pkg.keywords.length > TOOL_DETAILS_MAX_KEYWORDS ||
218
+ !pkg.keywords.every((item) => isShortString(item, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS))
219
+ )
220
+ return undefined;
221
+ if (
222
+ !Array.isArray(pkg.capabilities) ||
223
+ pkg.capabilities.length > TOOL_DETAILS_MAX_CAPABILITIES ||
224
+ !pkg.capabilities.every((item) => isShortString(item, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS))
225
+ )
226
+ return undefined;
204
227
  for (const field of ["homepage", "repository", "license", "provenance"] as const) {
205
228
  if (pkg[field] !== undefined && !isShortString(pkg[field])) return undefined;
206
229
  }
@@ -210,7 +233,8 @@ export function parsePackageToolDetails(value: unknown): PackageToolDetails | un
210
233
  if (candidate.kind === "mutation") {
211
234
  if (typeof candidate.operation !== "string" || !MUTATION_OPERATIONS.has(candidate.operation)) return undefined;
212
235
  if (typeof candidate.status !== "string" || !MUTATION_STATUSES.has(candidate.status)) return undefined;
213
- if (!isShortString(candidate.target) || !isShortString(candidate.output) || typeof candidate.reloadRequired !== "boolean") return undefined;
236
+ if (!isShortString(candidate.target) || !isShortString(candidate.output) || typeof candidate.reloadRequired !== "boolean")
237
+ return undefined;
214
238
  return value as MutationToolDetails;
215
239
  }
216
240
  } catch {
@@ -222,15 +246,21 @@ export function parsePackageToolDetails(value: unknown): PackageToolDetails | un
222
246
  function isPackageSummary(value: unknown): boolean {
223
247
  if (!value || typeof value !== "object") return false;
224
248
  const item = value as Record<string, unknown>;
225
- return isShortString(item.name, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)
226
- && isShortString(item.version, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)
227
- && isShortString(item.description, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS)
228
- && typeof item.shape === "string" && PACKAGE_SHAPES.has(item.shape)
229
- && typeof item.verified === "boolean";
249
+ return (
250
+ isShortString(item.name, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS) &&
251
+ isShortString(item.version, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS) &&
252
+ isShortString(item.description, TOOL_DETAILS_MAX_DESCRIPTION_CHARACTERS) &&
253
+ typeof item.shape === "string" &&
254
+ PACKAGE_SHAPES.has(item.shape) &&
255
+ typeof item.verified === "boolean"
256
+ );
230
257
  }
231
258
 
232
259
  function contentFallback(result: AgentToolResult<unknown>): string {
233
- return result.content.filter((item) => item.type === "text").map((item) => item.text).join("\n");
260
+ return result.content
261
+ .filter((item) => item.type === "text")
262
+ .map((item) => item.text)
263
+ .join("\n");
234
264
  }
235
265
 
236
266
  export function renderPackageToolCall(label: string, args: Record<string, unknown>, theme: Theme) {
@@ -255,9 +285,13 @@ export function renderPackageToolResult(
255
285
  const heading = `${theme.bold(String(details.total))} packages · showing ${details.items.length}${details.truncated ? " · bounded" : ""}`;
256
286
  const rows = shown.map((pkg) => {
257
287
  const evidence = pkg.verified ? ` [verified ${pkg.shape}]` : " [keyword candidate]";
258
- return truncateToWidth(`${theme.fg("accent", `${pkg.name}@${pkg.version}`)}${theme.fg("muted", evidence)}${options.expanded && pkg.description ? ` — ${pkg.description}` : ""}`, safeWidth);
288
+ return truncateToWidth(
289
+ `${theme.fg("accent", `${pkg.name}@${pkg.version}`)}${theme.fg("muted", evidence)}${options.expanded && pkg.description ? ` — ${pkg.description}` : ""}`,
290
+ safeWidth,
291
+ );
259
292
  });
260
- if (!options.expanded && details.items.length > shown.length) rows.push(theme.fg("muted", `… ${details.items.length - shown.length} more`));
293
+ if (!options.expanded && details.items.length > shown.length)
294
+ rows.push(theme.fg("muted", `… ${details.items.length - shown.length} more`));
261
295
  return [truncateToWidth(heading, safeWidth), ...rows];
262
296
  }
263
297
  if (details.kind === "info") {
@@ -276,9 +310,7 @@ export function renderPackageToolResult(
276
310
  return lines.filter(Boolean).map((line) => truncateToWidth(line, safeWidth));
277
311
  }
278
312
  const statusColor = details.status === "succeeded" ? "success" : "warning";
279
- const lines = [
280
- `${theme.fg(statusColor, details.status === "succeeded" ? "✓" : "○")} ${details.operation} ${details.target}`,
281
- ];
313
+ const lines = [`${theme.fg(statusColor, details.status === "succeeded" ? "✓" : "○")} ${details.operation} ${details.target}`];
282
314
  if (options.expanded && details.output) lines.push(details.output);
283
315
  if (details.reloadRequired) lines.push(theme.fg("warning", "Reload Pi with /reload to activate the update."));
284
316
  return lines.map((line) => truncateToWidth(line, safeWidth));
@@ -1,17 +1,17 @@
1
1
  /** Agent-facing package tools over the authenticated daemon. */
2
2
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { Type } from "typebox";
4
- import { packagePermissionDecision, type PackageOperation } from "./permission.js";
5
- import { InstallServiceError, PI_COMMAND_NAME, type Natives } from "./packed.js";
4
+ import { InstallServiceError, type Natives, PI_COMMAND_NAME } from "./packed.js";
5
+ import { type PackageOperation, packagePermissionDecision } from "./permission.js";
6
6
  import { reloadWarning } from "./reload.js";
7
7
  import {
8
8
  createInfoDetails,
9
9
  createModelContent,
10
10
  createMutationDetails,
11
11
  createSearchDetails,
12
+ type PackageToolDetails,
12
13
  renderPackageToolCall,
13
14
  renderPackageToolResult,
14
- type PackageToolDetails,
15
15
  } from "./tool-output.js";
16
16
 
17
17
  function text(value: string, details: PackageToolDetails) {
@@ -44,7 +44,9 @@ export async function approvePackageOperation(
44
44
  `${operation[0]!.toUpperCase()}${operation.slice(1)} Pi package`,
45
45
  `Run: ${command}\n\nThis operation can execute package code or mutate Pi settings/install roots. ${reloadWarning(operation)} Continue?`,
46
46
  );
47
- return approved ? { allowed: true, approved: true } : { allowed: false, approved: false, reason: "cancelled", message: `${operation} cancelled by user.` };
47
+ return approved
48
+ ? { allowed: true, approved: true }
49
+ : { allowed: false, approved: false, reason: "cancelled", message: `${operation} cancelled by user.` };
48
50
  }
49
51
 
50
52
  export async function installPackageWithPolicy(
@@ -57,7 +59,7 @@ export async function installPackageWithPolicy(
57
59
  const output = approval.message ?? "install denied";
58
60
  return text(output, createMutationDetails("install", source, approval.reason ?? "denied", output));
59
61
  }
60
- let output = await natives.install(source, approval.approved) || `Installed ${source}. Reload with /reload to activate.`;
62
+ let output = (await natives.install(source, approval.approved)) || `Installed ${source}. Reload with /reload to activate.`;
61
63
  // Piggybacks on the same approval already granted for install -- both are
62
64
  // the same code-execution mutation tier, not a new consent surface. Silent
63
65
  // for the overwhelmingly common case (most Pi packages aren't daemons at
@@ -77,11 +79,7 @@ export async function installPackageWithPolicy(
77
79
  return text(output, createMutationDetails("install", source, "succeeded", output));
78
80
  }
79
81
 
80
- export async function updatePackageWithPolicy(
81
- source: string,
82
- natives: Pick<Natives, "security" | "update">,
83
- ctx: ApprovalContext,
84
- ) {
82
+ export async function updatePackageWithPolicy(source: string, natives: Pick<Natives, "security" | "update">, ctx: ApprovalContext) {
85
83
  const approval = await approvePackageOperation("update", `pi update --extension ${source}`, natives, ctx);
86
84
  if (!approval.allowed) {
87
85
  const output = approval.message ?? "update denied";
@@ -101,17 +99,13 @@ export async function updatePackageWithPolicy(
101
99
  return text(message, createMutationDetails("update", source, "succeeded", outcome.output || message, true));
102
100
  }
103
101
 
104
- export async function removePackageWithPolicy(
105
- name: string,
106
- natives: Pick<Natives, "security" | "remove">,
107
- ctx: ApprovalContext,
108
- ) {
102
+ export async function removePackageWithPolicy(name: string, natives: Pick<Natives, "security" | "remove">, ctx: ApprovalContext) {
109
103
  const approval = await approvePackageOperation("remove", `pi remove npm:${name}`, natives, ctx);
110
104
  if (!approval.allowed) {
111
105
  const output = approval.message ?? "remove denied";
112
106
  return text(output, createMutationDetails("remove", name, approval.reason ?? "denied", output));
113
107
  }
114
- const output = await natives.remove(name, approval.approved) || `Removed ${name}. Reload with /reload to deactivate.`;
108
+ const output = (await natives.remove(name, approval.approved)) || `Removed ${name}. Reload with /reload to deactivate.`;
115
109
  return text(output, createMutationDetails("remove", name, "succeeded", output));
116
110
  }
117
111
 
@@ -124,8 +118,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
124
118
  query: Type.String({ description: "Search terms, e.g. 'lsp' or 'telegram'" }),
125
119
  limit: Type.Optional(Type.Number({ description: "Max results (default 10, max 50)" })),
126
120
  }),
127
- renderCall(args, theme) { return renderPackageToolCall("Search packages", args, theme); },
128
- renderResult(result, options, theme, context) { return renderPackageToolResult(result, options, theme, context); },
121
+ renderCall(args, theme) {
122
+ return renderPackageToolCall("Search packages", args, theme);
123
+ },
124
+ renderResult(result, options, theme, context) {
125
+ return renderPackageToolResult(result, options, theme, context);
126
+ },
129
127
  async execute(_id, params) {
130
128
  try {
131
129
  const response = await natives.search(params.query, params.limit ?? 10);
@@ -144,8 +142,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
144
142
  label: "Pi Package Info",
145
143
  description: `Show bounded metadata and declared Pi resources for one package. Special case: querying ${PI_COMMAND_NAME} (Pi itself) also reports the locally running version against the latest published release -- a different question from, and in addition to, the generic npm registry metadata every other package gets.`,
146
144
  parameters: Type.Object({ name: Type.String({ description: "npm package name" }) }),
147
- renderCall(args, theme) { return renderPackageToolCall("Package info", args, theme); },
148
- renderResult(result, options, theme, context) { return renderPackageToolResult(result, options, theme, context); },
145
+ renderCall(args, theme) {
146
+ return renderPackageToolCall("Package info", args, theme);
147
+ },
148
+ renderResult(result, options, theme, context) {
149
+ return renderPackageToolResult(result, options, theme, context);
150
+ },
149
151
  async execute(_id, params) {
150
152
  try {
151
153
  const info = await natives.info(params.name);
@@ -165,11 +167,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
165
167
  if (params.name === PI_COMMAND_NAME) {
166
168
  const status = await natives.piStatus().catch(() => undefined);
167
169
  if (status?.current) {
168
- const comparison = status.latest === undefined
169
- ? "latest release unknown"
170
- : status.upToDate === false
171
- ? `behind -- latest is ${status.latest}, run pi update --self`
172
- : `up to date with the latest ${status.latest}`;
170
+ const comparison =
171
+ status.latest === undefined
172
+ ? "latest release unknown"
173
+ : status.upToDate === false
174
+ ? `behind -- latest is ${status.latest}, run pi update --self`
175
+ : `up to date with the latest ${status.latest}`;
173
176
  lines.push(`--- Pi runtime (not npm registry data) ---`, `currently running: ${status.current}`, comparison);
174
177
  }
175
178
  }
@@ -185,8 +188,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
185
188
  label: "Pi Package Install",
186
189
  description: "Install a Pi package through the authenticated daemon. Operation-aware approval is secure by default.",
187
190
  parameters: Type.Object({ source: Type.String({ description: "npm:, git:, or https source" }) }),
188
- renderCall(args, theme) { return renderPackageToolCall("Install package", args, theme); },
189
- renderResult(result, options, theme, context) { return renderPackageToolResult(result, options, theme, context); },
191
+ renderCall(args, theme) {
192
+ return renderPackageToolCall("Install package", args, theme);
193
+ },
194
+ renderResult(result, options, theme, context) {
195
+ return renderPackageToolResult(result, options, theme, context);
196
+ },
190
197
  async execute(_id, params, _signal, _onUpdate, ctx) {
191
198
  return installPackageWithPolicy(params.source, natives, ctx);
192
199
  },
@@ -197,8 +204,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
197
204
  label: "Pi Package Update",
198
205
  description: "Update one configured Pi package through Pi's documented update command. Operation-aware approval is secure by default.",
199
206
  parameters: Type.Object({ source: Type.String({ description: "configured npm:, git:, or https source" }) }),
200
- renderCall(args, theme) { return renderPackageToolCall("Update package", args, theme); },
201
- renderResult(result, options, theme, context) { return renderPackageToolResult(result, options, theme, context); },
207
+ renderCall(args, theme) {
208
+ return renderPackageToolCall("Update package", args, theme);
209
+ },
210
+ renderResult(result, options, theme, context) {
211
+ return renderPackageToolResult(result, options, theme, context);
212
+ },
202
213
  async execute(_id, params, _signal, _onUpdate, ctx) {
203
214
  return updatePackageWithPolicy(params.source, natives, ctx);
204
215
  },
@@ -209,8 +220,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
209
220
  label: "Pi Package Remove",
210
221
  description: "Remove an installed npm Pi package through the authenticated daemon. Operation-aware approval is secure by default.",
211
222
  parameters: Type.Object({ name: Type.String({ description: "bare npm name, e.g. pi-lsp or @scope/pkg" }) }),
212
- renderCall(args, theme) { return renderPackageToolCall("Remove package", args, theme); },
213
- renderResult(result, options, theme, context) { return renderPackageToolResult(result, options, theme, context); },
223
+ renderCall(args, theme) {
224
+ return renderPackageToolCall("Remove package", args, theme);
225
+ },
226
+ renderResult(result, options, theme, context) {
227
+ return renderPackageToolResult(result, options, theme, context);
228
+ },
214
229
  async execute(_id, params, _signal, _onUpdate, ctx) {
215
230
  return removePackageWithPolicy(params.name, natives, ctx);
216
231
  },