@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.
- package/extension/src/discover.ts +10 -3
- package/extension/src/index.ts +6 -5
- package/extension/src/packed.ts +28 -6
- package/extension/src/profile.ts +123 -43
- package/extension/src/resource-config.ts +55 -17
- package/extension/src/security-tui.ts +9 -2
- package/extension/src/setup-command.ts +10 -2
- package/extension/src/tool-output.ts +50 -18
- package/extension/src/tools.ts +46 -31
- package/extension/src/tui.ts +240 -184
- package/package.json +3 -3
|
@@ -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 ?? [])
|
|
153
|
-
|
|
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 (
|
|
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 (
|
|
203
|
-
|
|
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")
|
|
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
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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(
|
|
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)
|
|
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));
|
package/extension/src/tools.ts
CHANGED
|
@@ -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 {
|
|
5
|
-
import {
|
|
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
|
|
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) {
|
|
128
|
-
|
|
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) {
|
|
148
|
-
|
|
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 =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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) {
|
|
189
|
-
|
|
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) {
|
|
201
|
-
|
|
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) {
|
|
213
|
-
|
|
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
|
},
|