@danypops/pi-packed 0.4.0 → 0.5.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 +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 +1 -1
- package/src/cli.ts +23 -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
package/src/cli.ts
CHANGED
|
@@ -35,6 +35,7 @@ usage:
|
|
|
35
35
|
packed search <query> [--offline] [--json] search npm (or the local mirror with --offline)
|
|
36
36
|
packed info <name> [--json] package details
|
|
37
37
|
packed updates [--json] updates per the local mirror
|
|
38
|
+
packed update <source> [--approve] [--json] update one configured package through Pi
|
|
38
39
|
packed mirror [--json] sync upstream into the local SQLite index
|
|
39
40
|
packed installed [--json] installed pi packages
|
|
40
41
|
packed catalog [--json] local package index (apt-cache stats)
|
|
@@ -98,6 +99,7 @@ const PACKAGE_COMMAND_OPERATIONS: Record<string, PackageOperation | undefined> =
|
|
|
98
99
|
installed: "installed",
|
|
99
100
|
catalog: "catalog",
|
|
100
101
|
updates: "updates",
|
|
102
|
+
update: "update",
|
|
101
103
|
mirror: "mirror",
|
|
102
104
|
install: "install",
|
|
103
105
|
remove: "remove",
|
|
@@ -237,8 +239,27 @@ const commands: Record<string, { usage: string; run: Command }> = {
|
|
|
237
239
|
},
|
|
238
240
|
},
|
|
239
241
|
|
|
242
|
+
update: {
|
|
243
|
+
usage: "packed update <configured-source> [--approve] [--json]",
|
|
244
|
+
async run(_rest, d, flags, pos) {
|
|
245
|
+
const source = pos[0] ?? "";
|
|
246
|
+
if (!SOURCE_RE.test(source)) return usageErr(`usage: ${commands["update"]!.usage}\n`);
|
|
247
|
+
try {
|
|
248
|
+
const output = await d.inst.update(source, { approved: flags.approved });
|
|
249
|
+
return flags.json
|
|
250
|
+
? ok(`${JSON.stringify({ ok: true, source, output, reloadRequired: true })}\n`)
|
|
251
|
+
: ok(`${output}\nReload Pi with /reload to activate the updated package.\n`);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
254
|
+
return flags.json
|
|
255
|
+
? fail(`${JSON.stringify({ ok: false, source, error: message, reloadRequired: false })}\n`)
|
|
256
|
+
: fail(`${message}\n`);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
|
|
240
261
|
remove: {
|
|
241
|
-
usage: "packed remove <name> [--json] (bare npm name, e.g. pi-lsp or @scope/pkg)",
|
|
262
|
+
usage: "packed remove <name> [--approve] [--json] (bare npm name, e.g. pi-lsp or @scope/pkg)",
|
|
242
263
|
async run(_rest, d, flags, pos) {
|
|
243
264
|
const name = pos[0] ?? "";
|
|
244
265
|
if (!NAME_RE.test(name)) return usageErr(`usage: ${commands["remove"]!.usage}\n`);
|
|
@@ -299,7 +320,7 @@ export async function cliRun(args: string[], d: CliDeps): Promise<CliResult> {
|
|
|
299
320
|
if (!cmd) return usageErr(`unknown command "${name}"\n${USAGE}`);
|
|
300
321
|
const { flags, pos } = parseFlags(rest);
|
|
301
322
|
try {
|
|
302
|
-
const validMutationInput = name === "install"
|
|
323
|
+
const validMutationInput = name === "install" || name === "update"
|
|
303
324
|
? SOURCE_RE.test(pos[0] ?? "")
|
|
304
325
|
: name === "remove" ? NAME_RE.test(pos[0] ?? "")
|
|
305
326
|
: 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: [] });
|