@danypops/pi-packed 0.4.0 → 0.5.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.
- package/README.md +3 -1
- package/extension/src/index.ts +1 -1
- package/extension/src/packed.ts +2 -0
- package/extension/src/tools.ts +25 -0
- package/extension/src/tui.ts +4 -4
- package/package.json +4 -1
- package/src/cli.ts +24 -2
- package/src/client.ts +18 -0
- package/src/install.ts +4 -0
- package/src/ports.ts +1 -0
- package/src/service.ts +23 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ inside the supervised Bun daemon.
|
|
|
10
10
|
|
|
11
11
|
```text
|
|
12
12
|
┌─ Pi extension (Node-compatible) ──────────────┐
|
|
13
|
-
│ pkg_search · pkg_info · pkg_install
|
|
13
|
+
│ pkg_search · pkg_info · pkg_install/update/remove│
|
|
14
14
|
│ /packages · /packed permission settings │
|
|
15
15
|
│ operation-aware approval · no Bun/SQLite access │
|
|
16
16
|
└──────────────────┬────────────────────────────┘
|
|
@@ -46,6 +46,7 @@ Packages execute arbitrary code and mutate Pi settings/install roots. One daemon
|
|
|
46
46
|
| `packed search <q> [--offline] [--limit N] [--json]` | Search npm or the local mirror, scoped to `keywords:pi-package` |
|
|
47
47
|
| `packed info <name> [--json]` | Show version, repository, Pi manifest, size, and license |
|
|
48
48
|
| `packed updates [--json]` | Show drift from the local mirror |
|
|
49
|
+
| `packed update <source> [--approve] [--json]` | Update one configured source through `pi update --extension` |
|
|
49
50
|
| `packed mirror [--json]` | Refresh the SQLite package index |
|
|
50
51
|
| `packed installed [--json]` | Read Pi's installed package declarations |
|
|
51
52
|
| `packed catalog [--json]` | Inspect the local package index |
|
|
@@ -81,6 +82,7 @@ The daemon listens on loopback only.
|
|
|
81
82
|
| `GET` | `/updates` |
|
|
82
83
|
| `GET` | `/catalog` |
|
|
83
84
|
| `POST` | `/install` with `{ "source": "...", "approved": true }` |
|
|
85
|
+
| `POST` | `/update` with `{ "source": "...", "approved": true }` |
|
|
84
86
|
| `POST` | `/remove` with `{ "name": "...", "approved": true }` |
|
|
85
87
|
|
|
86
88
|
State defaults to `~/.cache/pi-packed/` and contains `token`, `port`,
|
package/extension/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi-packed — Pi extension seam.
|
|
3
3
|
*
|
|
4
|
-
* Thin by design: registers agent tools (pkg_search/pkg_info/pkg_install),
|
|
4
|
+
* Thin by design: registers agent tools (pkg_search/pkg_info/pkg_install/pkg_update/pkg_remove),
|
|
5
5
|
* the /packages command, and a session_start update notification. ALL logic
|
|
6
6
|
* lives in the Bun service (src/): registry access, caching, watcher,
|
|
7
7
|
* catalog sync, install execution.
|
package/extension/src/packed.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface Natives {
|
|
|
30
30
|
setMutationApproval(value: MutationApproval, approved?: boolean): Promise<SecuritySettings>;
|
|
31
31
|
install(source: string, approved?: boolean): Promise<string>;
|
|
32
32
|
remove(name: string, approved?: boolean): Promise<string>;
|
|
33
|
+
update(source: string, approved?: boolean): Promise<string>;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export type PackageDaemonConnector = () => Promise<PackageDaemonPort>;
|
|
@@ -65,5 +66,6 @@ export async function createNatives(connect: PackageDaemonConnector = connectDef
|
|
|
65
66
|
setMutationApproval: (value, approved) => call((daemon) => daemon.setMutationApproval(value, approved)),
|
|
66
67
|
install: (source, approved) => call((daemon) => daemon.install(source, approved)),
|
|
67
68
|
remove: (name, approved) => call((daemon) => daemon.remove(name, approved)),
|
|
69
|
+
update: (source, approved) => call((daemon) => daemon.update(source, approved)),
|
|
68
70
|
};
|
|
69
71
|
}
|
package/extension/src/tools.ts
CHANGED
|
@@ -51,6 +51,21 @@ export async function installPackageWithPolicy(
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export async function updatePackageWithPolicy(
|
|
55
|
+
source: string,
|
|
56
|
+
natives: Pick<Natives, "security" | "update">,
|
|
57
|
+
ctx: ApprovalContext,
|
|
58
|
+
) {
|
|
59
|
+
try {
|
|
60
|
+
const approval = await approvePackageOperation("update", `pi update --extension ${source}`, natives, ctx);
|
|
61
|
+
if (!approval.allowed) return text(approval.message ?? "update denied");
|
|
62
|
+
const out = await natives.update(source, approval.approved);
|
|
63
|
+
return text(`${out || `Updated ${source}.`} Reload with /reload to activate.`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return text(`update failed: ${error instanceof Error ? error.message : error}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
export async function removePackageWithPolicy(
|
|
55
70
|
name: string,
|
|
56
71
|
natives: Pick<Natives, "security" | "remove">,
|
|
@@ -121,6 +136,16 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
|
|
|
121
136
|
},
|
|
122
137
|
});
|
|
123
138
|
|
|
139
|
+
pi.registerTool({
|
|
140
|
+
name: "pkg_update",
|
|
141
|
+
label: "Pi Package Update",
|
|
142
|
+
description: "Update one configured Pi package through Pi's documented update command. Operation-aware approval is secure by default.",
|
|
143
|
+
parameters: Type.Object({ source: Type.String({ description: "configured npm:, git:, or https source" }) }),
|
|
144
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
145
|
+
return updatePackageWithPolicy(params.source, natives, ctx);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
124
149
|
pi.registerTool({
|
|
125
150
|
name: "pkg_remove",
|
|
126
151
|
label: "Pi Package Remove",
|
package/extension/src/tui.ts
CHANGED
|
@@ -72,10 +72,10 @@ export async function showPackages(ctx: ExtensionCommandContext, natives: Native
|
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
74
|
ctx.ui.notify(`Updating ${row.name}…`, "info");
|
|
75
|
-
await natives.
|
|
76
|
-
ctx.ui.notify(`Updated ${row.name} to ${row.latest}
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
await natives.update(`npm:${row.name}`, approval.approved);
|
|
76
|
+
ctx.ui.notify(`Updated ${row.name} to ${row.latest}; reloading Pi resources`, "info");
|
|
77
|
+
await ctx.reload();
|
|
78
|
+
return;
|
|
79
79
|
} catch (e) {
|
|
80
80
|
ctx.ui.notify(`update failed: ${e instanceof Error ? e.message : e}`, "error");
|
|
81
81
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-packed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Package service for the Pi agent: search/info/install/updates + /packages TUI, backed by a long-running Bun service",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"packed": "src/cli.ts"
|
|
8
|
+
},
|
|
6
9
|
"keywords": [
|
|
7
10
|
"pi-package"
|
|
8
11
|
],
|
package/src/cli.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
1
2
|
/**
|
|
2
3
|
* cli.ts — second driving adapter (humans drive the same hexagon ports).
|
|
3
4
|
* cliRun is pure: ({code, out}) in, no I/O — the entry point prints.
|
|
@@ -35,6 +36,7 @@ usage:
|
|
|
35
36
|
packed search <query> [--offline] [--json] search npm (or the local mirror with --offline)
|
|
36
37
|
packed info <name> [--json] package details
|
|
37
38
|
packed updates [--json] updates per the local mirror
|
|
39
|
+
packed update <source> [--approve] [--json] update one configured package through Pi
|
|
38
40
|
packed mirror [--json] sync upstream into the local SQLite index
|
|
39
41
|
packed installed [--json] installed pi packages
|
|
40
42
|
packed catalog [--json] local package index (apt-cache stats)
|
|
@@ -98,6 +100,7 @@ const PACKAGE_COMMAND_OPERATIONS: Record<string, PackageOperation | undefined> =
|
|
|
98
100
|
installed: "installed",
|
|
99
101
|
catalog: "catalog",
|
|
100
102
|
updates: "updates",
|
|
103
|
+
update: "update",
|
|
101
104
|
mirror: "mirror",
|
|
102
105
|
install: "install",
|
|
103
106
|
remove: "remove",
|
|
@@ -237,8 +240,27 @@ const commands: Record<string, { usage: string; run: Command }> = {
|
|
|
237
240
|
},
|
|
238
241
|
},
|
|
239
242
|
|
|
243
|
+
update: {
|
|
244
|
+
usage: "packed update <configured-source> [--approve] [--json]",
|
|
245
|
+
async run(_rest, d, flags, pos) {
|
|
246
|
+
const source = pos[0] ?? "";
|
|
247
|
+
if (!SOURCE_RE.test(source)) return usageErr(`usage: ${commands["update"]!.usage}\n`);
|
|
248
|
+
try {
|
|
249
|
+
const output = await d.inst.update(source, { approved: flags.approved });
|
|
250
|
+
return flags.json
|
|
251
|
+
? ok(`${JSON.stringify({ ok: true, source, output, reloadRequired: true })}\n`)
|
|
252
|
+
: ok(`${output}\nReload Pi with /reload to activate the updated package.\n`);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
255
|
+
return flags.json
|
|
256
|
+
? fail(`${JSON.stringify({ ok: false, source, error: message, reloadRequired: false })}\n`)
|
|
257
|
+
: fail(`${message}\n`);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
|
|
240
262
|
remove: {
|
|
241
|
-
usage: "packed remove <name> [--json] (bare npm name, e.g. pi-lsp or @scope/pkg)",
|
|
263
|
+
usage: "packed remove <name> [--approve] [--json] (bare npm name, e.g. pi-lsp or @scope/pkg)",
|
|
242
264
|
async run(_rest, d, flags, pos) {
|
|
243
265
|
const name = pos[0] ?? "";
|
|
244
266
|
if (!NAME_RE.test(name)) return usageErr(`usage: ${commands["remove"]!.usage}\n`);
|
|
@@ -299,7 +321,7 @@ export async function cliRun(args: string[], d: CliDeps): Promise<CliResult> {
|
|
|
299
321
|
if (!cmd) return usageErr(`unknown command "${name}"\n${USAGE}`);
|
|
300
322
|
const { flags, pos } = parseFlags(rest);
|
|
301
323
|
try {
|
|
302
|
-
const validMutationInput = name === "install"
|
|
324
|
+
const validMutationInput = name === "install" || name === "update"
|
|
303
325
|
? SOURCE_RE.test(pos[0] ?? "")
|
|
304
326
|
: name === "remove" ? NAME_RE.test(pos[0] ?? "")
|
|
305
327
|
: name === "security" ? (pos[0] === undefined || pos[0] === "always" || pos[0] === "never")
|
package/src/client.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface PackageDaemonPort {
|
|
|
21
21
|
setMutationApproval(value: MutationApproval, approved?: boolean): Promise<SecuritySettings>;
|
|
22
22
|
install(source: string, approved?: boolean): Promise<string>;
|
|
23
23
|
remove(name: string, approved?: boolean): Promise<string>;
|
|
24
|
+
update(source: string, approved?: boolean): Promise<string>;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
interface MutationResponse {
|
|
@@ -113,6 +114,15 @@ export class PackageDaemonClient implements PackageDaemonPort {
|
|
|
113
114
|
if (!result.ok) throw new PackageDaemonError(result.output || `failed to remove ${name}`, "remove");
|
|
114
115
|
return result.output;
|
|
115
116
|
}
|
|
117
|
+
|
|
118
|
+
async update(source: string, approved = false): Promise<string> {
|
|
119
|
+
const result = await this.request<MutationResponse>("/update", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: JSON.stringify({ source, approved }),
|
|
122
|
+
});
|
|
123
|
+
if (!result.ok) throw new PackageDaemonError(result.output || `failed to update ${source}`, "update");
|
|
124
|
+
return result.output;
|
|
125
|
+
}
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
export class PackageDaemonInstaller implements Installer {
|
|
@@ -128,6 +138,10 @@ export class PackageDaemonInstaller implements Installer {
|
|
|
128
138
|
}
|
|
129
139
|
return this.client.remove(source.slice(4), options?.approved);
|
|
130
140
|
}
|
|
141
|
+
|
|
142
|
+
update(source: string, options?: { approved?: boolean }): Promise<string> {
|
|
143
|
+
return this.client.update(source, options?.approved);
|
|
144
|
+
}
|
|
131
145
|
}
|
|
132
146
|
|
|
133
147
|
export class DaemonBackedSecurity {
|
|
@@ -150,6 +164,10 @@ export class DaemonBackedInstaller implements Installer {
|
|
|
150
164
|
async remove(source: string, options?: { approved?: boolean }): Promise<string> {
|
|
151
165
|
return new PackageDaemonInstaller(await connectPackageDaemon(this.stateDirectory)).remove(source, options);
|
|
152
166
|
}
|
|
167
|
+
|
|
168
|
+
async update(source: string, options?: { approved?: boolean }): Promise<string> {
|
|
169
|
+
return (await connectPackageDaemon(this.stateDirectory)).update(source, options?.approved);
|
|
170
|
+
}
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
export class DaemonRegistry implements Registry {
|
package/src/install.ts
CHANGED
|
@@ -30,4 +30,8 @@ export class ExecInstaller implements Installer {
|
|
|
30
30
|
remove(source: string, _options?: { approved?: boolean }): Promise<string> {
|
|
31
31
|
return this.run(["remove", source]);
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
update(source: string, _options?: { approved?: boolean }): Promise<string> {
|
|
35
|
+
return this.run(["update", "--extension", source]);
|
|
36
|
+
}
|
|
33
37
|
}
|
package/src/ports.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface Registry {
|
|
|
41
41
|
export interface Installer {
|
|
42
42
|
install(source: string, options?: { approved?: boolean }): Promise<string>;
|
|
43
43
|
remove(source: string, options?: { approved?: boolean }): Promise<string>;
|
|
44
|
+
update(source: string, options?: { approved?: boolean }): Promise<string>;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export interface InstalledPkg {
|
package/src/service.ts
CHANGED
|
@@ -166,6 +166,29 @@ export function createApp(deps: Deps): { fetch: (req: Request) => Promise<Respon
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
if (path === "/update" && req.method === "POST") {
|
|
170
|
+
let source = "";
|
|
171
|
+
let approved = false;
|
|
172
|
+
try {
|
|
173
|
+
const body = (await req.json()) as { source?: unknown; approved?: unknown };
|
|
174
|
+
source = String(body.source ?? "");
|
|
175
|
+
approved = body.approved === true;
|
|
176
|
+
} catch {
|
|
177
|
+
/* fall through to validation */
|
|
178
|
+
}
|
|
179
|
+
if (!SOURCE_RE.test(source)) {
|
|
180
|
+
return err(400, "invalid source; want a configured npm:, git:, or https package source");
|
|
181
|
+
}
|
|
182
|
+
const denied = authorize("update", approved);
|
|
183
|
+
if (denied) return denied;
|
|
184
|
+
try {
|
|
185
|
+
const output = await deps.inst.update(source, { approved });
|
|
186
|
+
return json({ ok: true, source, output, reloadRequired: true });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return json({ ok: false, source, output: error instanceof Error ? error.message : String(error), reloadRequired: false });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
169
192
|
if (path === "/updates" && req.method === "GET") {
|
|
170
193
|
const snap = await loadUpdates(deps.stateDir);
|
|
171
194
|
return json(snap ?? { updates: [] });
|