@danypops/pi-packed 0.21.13 → 0.21.15

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/dist/client.d.ts CHANGED
@@ -42,6 +42,22 @@ export interface PackedExtensionClient {
42
42
  piStatus(): Promise<PiStatus>;
43
43
  }
44
44
  export declare function resolvePackedClientPaths(options?: PackedPathOptions): PackedClientPaths;
45
+ export interface PackedVehicleClientTarget {
46
+ /** Base URL for the daemon's Vehicle-projected surface (see service.ts's createApp, which mounts
47
+ * createVehicleHttpApp() at /vehicle/* on this same port, alongside /api/v1/ops) -- @danypops/
48
+ * vehicle-client's RemoteVehicleClient mounts its own /vehicle/manifest, /vehicle/invoke,
49
+ * /vehicle/cancel routes under this. */
50
+ baseUrl: string;
51
+ token: string;
52
+ }
53
+ /**
54
+ * Narrow surface for a Vehicle-projected operation consumer -- same daemon, same handle file, same
55
+ * Bearer token every other Packed RPC call already uses (connectPackedClient reads both the same
56
+ * way). Returns undefined rather than throwing when the daemon has never started -- no handle/token
57
+ * on disk yet -- matching resolvePushChannelTarget/resolveVehicleClientTarget's own tolerance in
58
+ * @danypops/papyrus for the identical condition.
59
+ */
60
+ export declare function resolveVehicleClientTarget(paths?: PackedClientPaths): PackedVehicleClientTarget | undefined;
45
61
  export type FetchTransport = (request: Request) => Promise<Response>;
46
62
  export declare class PackedClient implements PackedExtensionClient {
47
63
  private readonly rpc;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../service/src/public/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../service/src/public/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import{spawn as g}from"node:child_process";import{accessSync as A,constants as f,readFileSync as S}from"node:fs";import{delimiter as I,dirname as y,join as O,resolve as k}from"node:path";import{fileURLToPath as P}from"node:url";class j{baseUrl;token;opsPath;transport;label;constructor(z,J,G){this.baseUrl=z;this.token=J;this.label=G.label,this.opsPath=G.opsPath??"/api/v1/ops",this.transport=G.transport??fetch}authedRequest(z,J={}){return this.transport(new Request(`${this.baseUrl}${z}`,{...J,headers:{...J.headers,authorization:`Bearer ${this.token}`}}))}async call(z,J){let G=await this.authedRequest(this.opsPath,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({op:z,input:J})}),Z=await G.json();if(!G.ok)throw Error(Z.error??`${this.label} operation failed with HTTP ${G.status}`);return Z.result}async operations(){let z=await this.authedRequest(this.opsPath),J=await z.json();if(!z.ok)throw Error(J.error??`${this.label} discovery failed with HTTP ${z.status}`);return J.operations??[]}async ready(){let z=await this.authedRequest("/ready");if(z.status===503)return!1;if(!z.ok)throw Error(`${this.label} readiness check failed with HTTP ${z.status}`);return!0}async health(){let z=await this.authedRequest("/health"),J=await z.json();if(!z.ok||J.ok!==!0||typeof J.version!=="string")throw Error(J.error??`${this.label} health check failed`);return{ok:!0,version:J.version}}}import{chmodSync as r,existsSync as p,mkdirSync as o,readFileSync as C,renameSync as n,rmSync as t,writeFileSync as a}from"node:fs";import{homedir as b,tmpdir as N}from"node:os";import{dirname as zz,join as W,win32 as Y}from"node:path";var _="127.0.0.1";function q(z,J={}){let G=J.platform??process.platform,Z=J.home??b();if(G==="darwin")return L(z,Z);if(G==="win32")return M(z,J.env??process.env,Z);return R(z,J,Z)}function R(z,J,G){let Z=J.env??process.env,$=J.uid??process.getuid?.()??0,K=Z.XDG_DATA_HOME??W(G,".local","share"),Q=Z.XDG_STATE_HOME??W(G,".local","state"),x=Z.XDG_RUNTIME_DIR??W("/run","user",String($)),D=Z.XDG_CONFIG_HOME??W(G,".config");return{database:W(K,z.stateDirectoryName,z.databaseFilename),token:W(Q,z.stateDirectoryName,z.tokenFilename),handle:W(x,z.stateDirectoryName,z.handleFilename),serviceDescriptor:W(D,"systemd","user",z.systemdUnitName)}}function L(z,J){let G=W(J,"Library"),Z=W(G,"Application Support",z.stateDirectoryName);return{database:W(Z,z.databaseFilename),token:W(Z,z.tokenFilename),handle:W(N(),z.stateDirectoryName,z.handleFilename),serviceDescriptor:W(Z,z.systemdUnitName)}}function M(z,J,G){let Z=J.LOCALAPPDATA??Y.join(G,"AppData","Local"),$=J.APPDATA??Y.join(G,"AppData","Roaming"),K=Y.join(Z,z.stateDirectoryName,"Data");return{database:Y.join(K,z.databaseFilename),token:Y.join(K,z.tokenFilename),handle:Y.join(Z,"Temp",z.stateDirectoryName,z.handleFilename),serviceDescriptor:Y.join($,z.stateDirectoryName,"Config",z.systemdUnitName)}}function X(z){try{let J=JSON.parse(C(z,"utf8"));if(J.host!==_||!Number.isInteger(J.port)||J.port<1||J.port>65535||!Number.isInteger(J.pid))return null;return J}catch{return null}}import{execFileSync as E}from"node:child_process";import{fileURLToPath as w}from"node:url";function H(z,J){let G=J.runCommand(process.execPath,[J.armadaCliPath,"status","--json"]);if(!G.ok)return!1;try{return JSON.parse(G.output).vehicles?.some(($)=>$.name===z)??!1}catch{return!1}}function B(){return{armadaCliPath:w(import.meta.resolve("@danypops/armada/cli")),runCommand:(z,J,G)=>{try{return{ok:!0,output:E(z,J,{encoding:"utf8",input:G,stdio:[G===void 0?"ignore":"pipe","pipe","pipe"]})}}catch(Z){let $=Z;return{ok:!1,output:($.stdout??"")+($.stderr??$.message)}}}}}class U extends Error{notADaemon;constructor(z,J){super(z);this.notADaemon=J;this.name="InstallServiceError"}}var u="pi-packed",F="pi-packed.service";function T(z={}){let J=z.env??process.env;if(J.PI_PACKED_HOME){let Z=k(J.PI_PACKED_HOME);return{token:`${Z}/token`,handle:`${Z}/handle.json`,serviceDescriptor:`${Z}/${F}`}}let G=q({stateDirectoryName:"pi-packed",databaseFilename:"packages.db",tokenFilename:"token",handleFilename:"handle.json",systemdUnitName:F},z);return{token:G.token,handle:G.handle,serviceDescriptor:G.serviceDescriptor}}function v(){return H(u,B())}function h(z,J=30000){return(G)=>z(new Request(G,{signal:AbortSignal.any([G.signal,AbortSignal.timeout(J)])}))}class V{rpc;constructor(z,J,G=fetch){this.rpc=new j(z,J,{label:"Packed",transport:h(G)})}call(z,J){return this.rpc.call(z,J)}health(){return this.rpc.health()}search(z,J,G=!1){return this.call("package.search",{query:z,limit:J,offline:G})}info(z){return this.call("package.info",{name:z})}installed(){return this.call("package.installed",{})}async updates(){return(await this.call("package.updates",{})).updates}security(){return this.call("package.security.get",{})}setMutationApproval(z,J=!1){return this.call("package.security.set",{mutationApproval:z,approved:J})}async install(z,J=!1){let G=await this.call("package.install",{source:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async installService(z,J=!1){let G=await this.call("package.install_service",{source:z,approved:J});if(!G.ok)throw new U(G.output,G.notADaemon);return{output:G.output,spec:G.spec}}async remove(z,J=!1){let G=await this.call("package.remove",{name:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async update(z,J=!1){let G=await this.call("package.update",{source:z,approved:J});if(!G.ok)throw Error(G.output);return{output:G.output,reloadRequired:G.reloadRequired??!0,alreadyUpToDate:G.alreadyUpToDate??!1,pinned:G.pinned??!1,previousVersion:G.previousVersion,currentVersion:G.currentVersion}}setupPlan(z,J=!1){return this.call("setup.plan",{manifestPath:z,prune:J})}setupApply(z,J=!1,G=!1){return this.call("setup.apply",{manifestPath:z,approved:J,prune:G})}listResources(z){return this.call("resources.list",{projectRoot:z})}async toggleResource(z,J,G,Z,$,K=!1){let Q=await this.call("resources.toggle",{source:z,field:J,path:G,enabled:Z,projectRoot:$,approved:K});if(!Q.ok)throw Error(Q.output);return Q.output}piStatus(){return this.call("pi.status",{})}}async function m(z=T(),J=fetch){let G=X(z.handle);if(!G)throw Error("Packed daemon is not running");let Z;try{Z=S(z.token,"utf8").trim()}catch{throw Error("Packed daemon token is unavailable")}if(!/^[a-f0-9]{64}$/.test(Z))throw Error("Packed daemon token is invalid");let $=new V(`http://${G.host}:${G.port}`,Z,J);return await $.health(),$}async function d(z){try{return await z.connect()}catch{}let J=z.retryAttempts??30,G=z.retryDelayMs??100,Z=z.isServiceInstalled();if(!Z)z.spawn();for(let K=0;K<J;K++){await z.sleep(G);try{return await z.connect()}catch{}}let $=J*G/1000;throw Error(Z?`The managed Packed service is registered but unreachable after ${$} seconds. Its supervisor may have stopped it or it may be failing; restart the managed service and inspect its status and logs. Packed will not auto-spawn a competing process.`:`Packed daemon did not become ready within ${$} seconds`)}function c(z=process.env){if(z.PI_PACKED_PI_BIN||z.PI_BIN)return;let J=z.PATH;if(!J)return;let G=process.platform==="win32"?["pi.exe","pi.cmd","pi.bat","pi"]:["pi"];for(let Z of J.split(I)){if(!Z)continue;for(let $ of G){let K=O(Z,$);try{return A(K,f.X_OK),K}catch{}}}return}async function Hz(z=T(),J=fetch){return d({connect:()=>m(z,J),isServiceInstalled:()=>v(),spawn:()=>{let G=process.env.PI_PACKED_BIN,Z=G??process.env.PI_PACKED_BUN??"bun",$=G?["serve"]:[O(y(P(import.meta.url)),"../service/src/cli/cli.ts"),"serve"],K=c(),Q=g(Z,$,{detached:!0,stdio:"ignore",env:{...process.env,DAEMON_KIT_LAUNCH_PROVENANCE:"auto-spawn",...K?{PI_BIN:K}:{}}});Q.once("error",()=>{}),Q.unref()},sleep:(G)=>new Promise((Z)=>setTimeout(Z,G))})}export{c as resolvePiBinForSpawn,T as resolvePackedClientPaths,Hz as ensurePackedClient,d as ensureClient,m as connectPackedClient,V as PackedClient,U as InstallServiceError};
1
+ import{spawn as A}from"node:child_process";import{accessSync as f,constants as S,readFileSync as U}from"node:fs";import{delimiter as I,dirname as y,join as T,resolve as k}from"node:path";import{fileURLToPath as P}from"node:url";class j{baseUrl;token;opsPath;transport;label;constructor(z,J,G){this.baseUrl=z;this.token=J;this.label=G.label,this.opsPath=G.opsPath??"/api/v1/ops",this.transport=G.transport??fetch}authedRequest(z,J={}){return this.transport(new Request(`${this.baseUrl}${z}`,{...J,headers:{...J.headers,authorization:`Bearer ${this.token}`}}))}async call(z,J){let G=await this.authedRequest(this.opsPath,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({op:z,input:J})}),Z=await G.json();if(!G.ok)throw Error(Z.error??`${this.label} operation failed with HTTP ${G.status}`);return Z.result}async operations(){let z=await this.authedRequest(this.opsPath),J=await z.json();if(!z.ok)throw Error(J.error??`${this.label} discovery failed with HTTP ${z.status}`);return J.operations??[]}async ready(){let z=await this.authedRequest("/ready");if(z.status===503)return!1;if(!z.ok)throw Error(`${this.label} readiness check failed with HTTP ${z.status}`);return!0}async health(){let z=await this.authedRequest("/health"),J=await z.json();if(!z.ok||J.ok!==!0||typeof J.version!=="string")throw Error(J.error??`${this.label} health check failed`);return{ok:!0,version:J.version}}}import{chmodSync as r,existsSync as p,mkdirSync as o,readFileSync as C,renameSync as n,rmSync as t,writeFileSync as a}from"node:fs";import{homedir as N,tmpdir as _}from"node:os";import{dirname as zz,join as W,win32 as Y}from"node:path";var R="127.0.0.1";function H(z,J={}){let G=J.platform??process.platform,Z=J.home??N();if(G==="darwin")return M(z,Z);if(G==="win32")return E(z,J.env??process.env,Z);return L(z,J,Z)}function L(z,J,G){let Z=J.env??process.env,$=J.uid??process.getuid?.()??0,K=Z.XDG_DATA_HOME??W(G,".local","share"),Q=Z.XDG_STATE_HOME??W(G,".local","state"),D=Z.XDG_RUNTIME_DIR??W("/run","user",String($)),b=Z.XDG_CONFIG_HOME??W(G,".config");return{database:W(K,z.stateDirectoryName,z.databaseFilename),token:W(Q,z.stateDirectoryName,z.tokenFilename),handle:W(D,z.stateDirectoryName,z.handleFilename),serviceDescriptor:W(b,"systemd","user",z.systemdUnitName)}}function M(z,J){let G=W(J,"Library"),Z=W(G,"Application Support",z.stateDirectoryName);return{database:W(Z,z.databaseFilename),token:W(Z,z.tokenFilename),handle:W(_(),z.stateDirectoryName,z.handleFilename),serviceDescriptor:W(Z,z.systemdUnitName)}}function E(z,J,G){let Z=J.LOCALAPPDATA??Y.join(G,"AppData","Local"),$=J.APPDATA??Y.join(G,"AppData","Roaming"),K=Y.join(Z,z.stateDirectoryName,"Data");return{database:Y.join(K,z.databaseFilename),token:Y.join(K,z.tokenFilename),handle:Y.join(Z,"Temp",z.stateDirectoryName,z.handleFilename),serviceDescriptor:Y.join($,z.stateDirectoryName,"Config",z.systemdUnitName)}}function q(z){try{let J=JSON.parse(C(z,"utf8"));if(J.host!==R||!Number.isInteger(J.port)||J.port<1||J.port>65535||!Number.isInteger(J.pid))return null;return J}catch{return null}}import{execFileSync as w}from"node:child_process";import{fileURLToPath as g}from"node:url";function B(z,J){let G=J.runCommand(process.execPath,[J.armadaCliPath,"status","--json"]);if(!G.ok)return!1;try{return JSON.parse(G.output).vehicles?.some(($)=>$.name===z)??!1}catch{return!1}}function O(){return{armadaCliPath:g(import.meta.resolve("@danypops/armada/cli")),runCommand:(z,J,G)=>{try{return{ok:!0,output:w(z,J,{encoding:"utf8",input:G,stdio:[G===void 0?"ignore":"pipe","pipe","pipe"]})}}catch(Z){let $=Z;return{ok:!1,output:($.stdout??"")+($.stderr??$.message)}}}}}class V extends Error{notADaemon;constructor(z,J){super(z);this.notADaemon=J;this.name="InstallServiceError"}}var u="pi-packed",F="pi-packed.service";function X(z={}){let J=z.env??process.env;if(J.PI_PACKED_HOME){let Z=k(J.PI_PACKED_HOME);return{token:`${Z}/token`,handle:`${Z}/handle.json`,serviceDescriptor:`${Z}/${F}`}}let G=H({stateDirectoryName:"pi-packed",databaseFilename:"packages.db",tokenFilename:"token",handleFilename:"handle.json",systemdUnitName:F},z);return{token:G.token,handle:G.handle,serviceDescriptor:G.serviceDescriptor}}function Hz(z=X()){let J=q(z.handle);if(!J)return;let G;try{G=U(z.token,"utf8").trim()}catch{return}if(!/^[a-f0-9]{64}$/.test(G))return;return{baseUrl:`http://${J.host}:${J.port}`,token:G}}function v(){return B(u,O())}function h(z,J=30000){return(G)=>z(new Request(G,{signal:AbortSignal.any([G.signal,AbortSignal.timeout(J)])}))}class x{rpc;constructor(z,J,G=fetch){this.rpc=new j(z,J,{label:"Packed",transport:h(G)})}call(z,J){return this.rpc.call(z,J)}health(){return this.rpc.health()}search(z,J,G=!1){return this.call("package.search",{query:z,limit:J,offline:G})}info(z){return this.call("package.info",{name:z})}installed(){return this.call("package.installed",{})}async updates(){return(await this.call("package.updates",{})).updates}security(){return this.call("package.security.get",{})}setMutationApproval(z,J=!1){return this.call("package.security.set",{mutationApproval:z,approved:J})}async install(z,J=!1){let G=await this.call("package.install",{source:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async installService(z,J=!1){let G=await this.call("package.install_service",{source:z,approved:J});if(!G.ok)throw new V(G.output,G.notADaemon);return{output:G.output,spec:G.spec}}async remove(z,J=!1){let G=await this.call("package.remove",{name:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async update(z,J=!1){let G=await this.call("package.update",{source:z,approved:J});if(!G.ok)throw Error(G.output);return{output:G.output,reloadRequired:G.reloadRequired??!0,alreadyUpToDate:G.alreadyUpToDate??!1,pinned:G.pinned??!1,previousVersion:G.previousVersion,currentVersion:G.currentVersion}}setupPlan(z,J=!1){return this.call("setup.plan",{manifestPath:z,prune:J})}setupApply(z,J=!1,G=!1){return this.call("setup.apply",{manifestPath:z,approved:J,prune:G})}listResources(z){return this.call("resources.list",{projectRoot:z})}async toggleResource(z,J,G,Z,$,K=!1){let Q=await this.call("resources.toggle",{source:z,field:J,path:G,enabled:Z,projectRoot:$,approved:K});if(!Q.ok)throw Error(Q.output);return Q.output}piStatus(){return this.call("pi.status",{})}}async function m(z=X(),J=fetch){let G=q(z.handle);if(!G)throw Error("Packed daemon is not running");let Z;try{Z=U(z.token,"utf8").trim()}catch{throw Error("Packed daemon token is unavailable")}if(!/^[a-f0-9]{64}$/.test(Z))throw Error("Packed daemon token is invalid");let $=new x(`http://${G.host}:${G.port}`,Z,J);return await $.health(),$}async function c(z){try{return await z.connect()}catch{}let J=z.retryAttempts??30,G=z.retryDelayMs??100,Z=z.isServiceInstalled();if(!Z)z.spawn();for(let K=0;K<J;K++){await z.sleep(G);try{return await z.connect()}catch{}}let $=J*G/1000;throw Error(Z?`The managed Packed service is registered but unreachable after ${$} seconds. Its supervisor may have stopped it or it may be failing; restart the managed service and inspect its status and logs. Packed will not auto-spawn a competing process.`:`Packed daemon did not become ready within ${$} seconds`)}function d(z=process.env){if(z.PI_PACKED_PI_BIN||z.PI_BIN)return;let J=z.PATH;if(!J)return;let G=process.platform==="win32"?["pi.exe","pi.cmd","pi.bat","pi"]:["pi"];for(let Z of J.split(I)){if(!Z)continue;for(let $ of G){let K=T(Z,$);try{return f(K,S.X_OK),K}catch{}}}return}async function Bz(z=X(),J=fetch){return c({connect:()=>m(z,J),isServiceInstalled:()=>v(),spawn:()=>{let G=process.env.PI_PACKED_BIN,Z=G??process.env.PI_PACKED_BUN??"bun",$=G?["serve"]:[T(y(P(import.meta.url)),"../service/src/cli/cli.ts"),"serve"],K=d(),Q=A(Z,$,{detached:!0,stdio:"ignore",env:{...process.env,DAEMON_KIT_LAUNCH_PROVENANCE:"auto-spawn",...K?{PI_BIN:K}:{}}});Q.once("error",()=>{}),Q.unref()},sleep:(G)=>new Promise((Z)=>setTimeout(Z,G))})}export{Hz as resolveVehicleClientTarget,d as resolvePiBinForSpawn,X as resolvePackedClientPaths,Bz as ensurePackedClient,c as ensureClient,m as connectPackedClient,x as PackedClient,V as InstallServiceError};
@@ -8,11 +8,17 @@ export interface PublicationEvidence {
8
8
  provenanceUrl?: string;
9
9
  trustedPublisher: "verified" | "not-verified" | "unknown";
10
10
  }
11
+ export interface DownloadObservations {
12
+ weekly?: number;
13
+ monthly?: number;
14
+ observedAt: string;
15
+ }
11
16
  export interface PackageSummary {
12
17
  name: string;
13
18
  version: string;
14
19
  description?: string;
15
20
  date?: string;
21
+ downloads?: DownloadObservations;
16
22
  packageEvidence?: PackageEvidence;
17
23
  publication?: PublicationEvidence;
18
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../service/src/public/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../service/src/public/protocol.ts"],"names":[],"mappings":"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"}
@@ -7,9 +7,52 @@
7
7
  */
8
8
  import type { PackageOperation } from "./permission.js";
9
9
 
10
+ /**
11
+ * Packed's own npm package name -- the one target where install/update/
12
+ * remove has a real, confirmed hazard plain /reload cannot fix: this
13
+ * extension's own already-loaded code (e.g. vehicle-target.ts importing
14
+ * @danypops/pi-packed/client) keeps a stale reference to the pre-mutation
15
+ * module even after /reload re-evaluates the extension's own source files.
16
+ * Confirmed live: updating pi-packed mid-session, then /reload, threw
17
+ * "resolveVehicleClientTarget is not a function" -- a brand-new process
18
+ * importing the exact same on-disk files resolved it fine. Node/Bun cache
19
+ * an ES module by resolved file path for the life of the process; an
20
+ * in-place npm overwrite never invalidates an already-loaded entry, and
21
+ * /reload only re-executes the extension's own entry file, not every
22
+ * transitively-cached dependency. Only a full process restart re-imports
23
+ * everything fresh.
24
+ */
25
+ export const PACKED_NPM_PACKAGE_NAME = "@danypops/pi-packed";
26
+
27
+ /**
28
+ * True when a pkg_install/pkg_update source (npm:@danypops/pi-packed,
29
+ * npm:@danypops/pi-packed@1.2.3, git:/https: sources never match) or a
30
+ * pkg_remove bare name refers to Packed's own npm package. Deliberately
31
+ * narrow -- this is the one case Packed can be CERTAIN has the stale-
32
+ * module-cache hazard above, since it's the package whose own code is
33
+ * running the mutation. Any other package's extension could in principle
34
+ * have the same self-import pattern, but Packed has no way to know that
35
+ * about third-party code, so it never guesses there.
36
+ */
37
+ export function targetsPackedItself(sourceOrName: string): boolean {
38
+ const withoutScheme = sourceOrName.startsWith("npm:") ? sourceOrName.slice(4) : sourceOrName;
39
+ const bare = withoutScheme.startsWith("@") ? withoutScheme.split("@").slice(0, 2).join("@") : withoutScheme.split("@")[0];
40
+ return bare === PACKED_NPM_PACKAGE_NAME;
41
+ }
42
+
43
+ export interface ReloadWarningOptions {
44
+ /** True when the target is Packed's own package -- see targetsPackedItself(). */
45
+ restartRequired?: boolean;
46
+ }
47
+
10
48
  /** Inline warning appended to a mutation's pre-confirmation dialog, before
11
49
  * the operation runs -- the moment the user is actually deciding. */
12
- export function reloadWarning(operation: PackageOperation): string {
50
+ export function reloadWarning(operation: PackageOperation, options: ReloadWarningOptions = {}): string {
51
+ if (options.restartRequired) {
52
+ return operation === "remove"
53
+ ? "Packed's own code is already running inside this Pi process -- deactivating it needs a full restart (exit and relaunch Pi), not just /reload."
54
+ : "Packed's own code is already running inside this Pi process -- its updated code can only be picked up by a full restart (exit and relaunch Pi), not just /reload.";
55
+ }
13
56
  return operation === "remove"
14
57
  ? "This will require a Pi reload (/reload) to deactivate it."
15
58
  : "This will likely require a Pi reload (/reload) to activate its resources.";
@@ -35,7 +78,13 @@ export interface ReloadConfirmContext {
35
78
  * shares identical wording and behavior instead of drifting apart --
36
79
  * reload.ts's whole reason for existing.
37
80
  */
38
- export async function confirmReload(ctx: ReloadConfirmContext): Promise<boolean> {
81
+ export async function confirmReload(ctx: ReloadConfirmContext, options: ReloadWarningOptions = {}): Promise<boolean> {
39
82
  if (!ctx.hasUI) return true;
83
+ if (options.restartRequired) {
84
+ return ctx.ui.confirm(
85
+ "Restart Pi now?",
86
+ "Packed's own code changed and cannot be hot-reloaded from inside itself -- exit and relaunch Pi to pick it up. /reload alone will not work.",
87
+ );
88
+ }
40
89
  return ctx.ui.confirm("Reload Pi now?", "This change only takes effect after a reload.");
41
90
  }
@@ -16,6 +16,7 @@ import { handleSetupCommand } from "./setup-command.js";
16
16
  import { handleResourceConfigCommand } from "./tabs/resource-config.js";
17
17
  import { registerTools } from "./tools.js";
18
18
  import { showPackedPanel } from "./tui.js";
19
+ import { registerPackedVehicle } from "./vehicle-tools.js";
19
20
 
20
21
  // Async factory (pi awaits it): the seam creates authenticated daemon
21
22
  // clients lazily. It never executes Bun-only adapters or opens SQLite.
@@ -34,8 +35,15 @@ export default async function (pi: ExtensionAPI) {
34
35
  },
35
36
  });
36
37
 
37
- // Update check against the local mirror, on Pi's own lifecycle event.
38
+ // registerVehicleTools() (inside registerPackedVehicle) needs
39
+ // pi.getAllTools()/getActiveTools()/setActiveTools() -- Pi's extension runtime only finishes
40
+ // initializing after every extension's top-level factory (this one included) has resolved, so
41
+ // calling it directly from there throws "Extension runtime not initialized" (confirmed live in
42
+ // the identical pi-papyrus/pi-tickets bug). session_start fires only after that initialization
43
+ // completes, and Pi awaits every session_start handler before the model's first turn, so
44
+ // registering here is both safe and still visible on turn one.
38
45
  pi.on("session_start", async (_event, ctx) => {
46
+ await registerPackedVehicle(pi);
39
47
  if (!ctx.hasUI) return;
40
48
  try {
41
49
  const updates = await natives.updates();
@@ -55,3 +55,38 @@ export function formatUpdateNotice(updates: UpdateEntry[]): string {
55
55
  const more = updates.length > 3 ? ` +${updates.length - 3} more` : "";
56
56
  return `${updates.length} package update(s): ${names.join(", ")}${more}`;
57
57
  }
58
+
59
+ const MS_PER_DAY = 86_400_000;
60
+
61
+ /**
62
+ * A compact relative age for the Find tab's Card and the package inspector -- e.g. "today",
63
+ * "12d ago", "5mo ago", "3y ago". `now` is injectable so a test never depends on the real clock.
64
+ * Undefined for anything unparseable, or a date that claims to be in the future (clock skew or
65
+ * bad upstream data -- never worth presenting as "released -3d ago").
66
+ */
67
+ export function formatRelativeDate(iso: string | undefined, now: Date = new Date()): string | undefined {
68
+ if (!iso) return undefined;
69
+ const then = new Date(iso);
70
+ if (Number.isNaN(then.getTime())) return undefined;
71
+ const days = Math.floor((now.getTime() - then.getTime()) / MS_PER_DAY);
72
+ if (days < 0) return undefined;
73
+ if (days === 0) return "today";
74
+ if (days === 1) return "1d ago";
75
+ if (days < 30) return `${days}d ago`;
76
+ const months = Math.floor(days / 30);
77
+ if (months < 12) return `${months}mo ago`;
78
+ return `${Math.floor(days / 365)}y ago`;
79
+ }
80
+
81
+ /**
82
+ * A compact K/M-abbreviated download count -- e.g. 718531784 -> "718.5M", 166075689 -> "166.1M",
83
+ * 950 -> "950". Undefined for anything not a real non-negative finite number (npm's own downloads
84
+ * endpoints are usually reliable, but this is user-facing display text, not a trusted internal
85
+ * value -- never render "NaN" or "-5").
86
+ */
87
+ export function formatDownloadCount(count: number | undefined): string | undefined {
88
+ if (count === undefined || !Number.isFinite(count) || count < 0) return undefined;
89
+ if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
90
+ if (count >= 1_000) return `${(count / 1_000).toFixed(1)}K`;
91
+ return String(Math.floor(count));
92
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { matchesKey, wrapTextWithAnsi } from "@earendil-works/pi-tui";
3
3
  import { buildDetailLines, type Component, type TextMeasure } from "malevich-tui-components";
4
+ import { formatDownloadCount, formatRelativeDate } from "./model.js";
4
5
  import type { Natives, PackageInfo } from "./packed.js";
5
6
  import { sanitizeTerminalText } from "./terminal-text.js";
6
7
 
@@ -60,9 +61,20 @@ export class PackageInspector implements Component {
60
61
  const readmeTruncated = rawReadme.length > README_CHAR_LIMIT;
61
62
  const readme = rawReadme.slice(0, README_CHAR_LIMIT);
62
63
  const detailMeasure: TextMeasure = { ...this.measure, wrapTextWithAnsi };
64
+ const released = formatRelativeDate(info.modified);
65
+ const weekly = formatDownloadCount(info.downloads?.weekly);
66
+ const monthly = formatDownloadCount(info.downloads?.monthly);
63
67
  const details = buildDetailLines(Math.max(1, width), {
64
68
  fields: [
65
69
  { label: "Version", value: sanitizeTerminalText(info.version) },
70
+ // "Released" reflects the latest publish across ANY version (npm's own abbreviated-doc
71
+ // `modified` field -- see HttpRegistry.modifiedAt()), not necessarily this exact pinned
72
+ // version's own publish date; the Find tab's search-result Card shows the exact
73
+ // per-version date instead, since npm's search API already returns that for free.
74
+ ...(released ? [{ label: "Released", value: released }] : []),
75
+ ...(weekly || monthly
76
+ ? [{ label: "Downloads", value: [weekly && `${weekly}/week`, monthly && `${monthly}/month`].filter(Boolean).join(" · ") }]
77
+ : []),
66
78
  ...(info.license ? [{ label: "License", value: sanitizeTerminalText(info.license) }] : []),
67
79
  ...(info.repository ? [{ label: "Repository", value: sanitizeTerminalText(info.repository) }] : []),
68
80
  ],
@@ -13,7 +13,9 @@ import type { ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-a
13
13
  import { rawKeyHint } from "@earendil-works/pi-coding-agent";
14
14
  import { Input, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
15
15
  import { Card, type Component } from "malevich-tui-components";
16
+ import { targetsPackedItself } from "../approval/reload.js";
16
17
  import { cardTheme } from "../menu-theme.js";
18
+ import { formatDownloadCount, formatRelativeDate } from "../model.js";
17
19
  import type { Natives, PackageSummary } from "../packed.js";
18
20
  import { InstallServiceError } from "../packed.js";
19
21
  import type { TabHost } from "../tab-host.js";
@@ -31,7 +33,8 @@ export type InstallOutcome = "installed" | "cancelled" | "failed";
31
33
  * path (installPackageWithPolicy) -- most packages aren't daemons at all. */
32
34
  export async function applyInstall(result: PackageSummary, natives: Natives, ctx: ExtensionCommandContext): Promise<InstallOutcome> {
33
35
  const source = `npm:${result.name}`;
34
- const approval = await approvePackageOperation("install", `pi install ${source}`, natives, ctx);
36
+ const restartRequired = targetsPackedItself(result.name);
37
+ const approval = await approvePackageOperation("install", `pi install ${source}`, natives, ctx, restartRequired);
35
38
  if (!approval.allowed) {
36
39
  ctx.ui.notify(approval.message ?? "install denied", "warning");
37
40
  return "cancelled";
@@ -45,7 +48,16 @@ export async function applyInstall(result: PackageSummary, natives: Natives, ctx
45
48
  ctx.ui.notify(`note: could not register a persistent service for ${result.name}: ${e instanceof Error ? e.message : e}`, "warning");
46
49
  }
47
50
  }
48
- ctx.ui.notify(`${output}; reloading Pi resources.`, "info");
51
+ // ctx.reload() re-evaluates this extension's own entry file, same as /reload -- harmless to
52
+ // call either way, but it cannot bust an already-loaded copy of Packed's own client module,
53
+ // so a self-install/update needs the stronger, honest wording instead of implying reload alone
54
+ // finished the job (see approval/reload.ts's targetsPackedItself()).
55
+ ctx.ui.notify(
56
+ restartRequired
57
+ ? `${output}; restart Pi (exit and relaunch) to activate -- /reload alone will not pick up Packed's own updated code.`
58
+ : `${output}; reloading Pi resources.`,
59
+ "info",
60
+ );
49
61
  await ctx.reload();
50
62
  return "installed";
51
63
  } catch (e) {
@@ -131,7 +143,7 @@ export class FindTab implements Component {
131
143
  const selected = i === this.selectedIndex;
132
144
  const card = new Card({
133
145
  title: theme.bold(`${result.name}@${result.version}`),
134
- content: [sanitizeTerminalText(result.description ?? "No description")],
146
+ content: [sanitizeTerminalText(result.description ?? "No description"), ...this.metaLine(result, theme)],
135
147
  selected,
136
148
  theme: cardTheme(theme),
137
149
  measure: { visibleWidth, truncateToWidth },
@@ -141,6 +153,18 @@ export class FindTab implements Component {
141
153
  return lines;
142
154
  }
143
155
 
156
+ /** "Released 5y ago · 166.1M/week" -- both derived from data the search API already returns
157
+ * inline (see HttpRegistry.searchPage()), so no extra network round trip per result. Empty
158
+ * array (no extra content line) when neither is available, rather than an empty muted line. */
159
+ private metaLine(result: PackageSummary, theme: Theme): string[] {
160
+ const parts: string[] = [];
161
+ const released = formatRelativeDate(result.date);
162
+ if (released) parts.push(`Released ${released}`);
163
+ const weekly = formatDownloadCount(result.downloads?.weekly);
164
+ if (weekly) parts.push(`${weekly}/week`);
165
+ return parts.length > 0 ? [theme.fg("muted", parts.join(" · "))] : [];
166
+ }
167
+
144
168
  handleInput(data: string): void {
145
169
  if (this.busy) return;
146
170
  switch (data) {
@@ -1,6 +1,7 @@
1
1
  import type { PackageSummary as Pkg, PackageInfo as PkgInfo } from "@danypops/pi-packed/protocol";
2
2
  import type { AgentToolResult, Theme } from "@earendil-works/pi-coding-agent";
3
3
  import { Text, truncateToWidth } from "@earendil-works/pi-tui";
4
+ import { targetsPackedItself } from "./approval/reload.js";
4
5
  import {
5
6
  TOOL_COLLAPSED_PACKAGE_PREVIEW,
6
7
  TOOL_DETAILS_MAX_CAPABILITIES,
@@ -59,6 +60,11 @@ export interface MutationToolDetails {
59
60
  status: "succeeded" | "cancelled" | "denied";
60
61
  output: string;
61
62
  reloadRequired: boolean;
63
+ /** Optional (not version-gated) -- see approval/reload.ts's targetsPackedItself().
64
+ * Absent on details serialized before this field existed; the renderer treats
65
+ * absent the same as false, so an older persisted tool result still renders
66
+ * exactly as it always did. */
67
+ restartRequired?: boolean;
62
68
  }
63
69
 
64
70
  export type PackageToolDetails = SearchToolDetails | InfoToolDetails | MutationToolDetails;
@@ -181,6 +187,10 @@ export function createMutationDetails(
181
187
  status,
182
188
  output: safeDisplayText(output, TOOL_DETAILS_MAX_OUTPUT_CHARACTERS),
183
189
  reloadRequired,
190
+ // Derived from the raw (pre-sanitized) target -- targetsPackedItself only ever matches a
191
+ // plain npm name/source, never affected by safePackageTarget's URL-credential redaction.
192
+ // Only meaningful when a reload/restart is actually pending at all.
193
+ ...(status === "succeeded" && targetsPackedItself(target) ? { restartRequired: true } : {}),
184
194
  };
185
195
  }
186
196
 
@@ -235,6 +245,7 @@ export function parsePackageToolDetails(value: unknown): PackageToolDetails | un
235
245
  if (typeof candidate.status !== "string" || !MUTATION_STATUSES.has(candidate.status)) return undefined;
236
246
  if (!isShortString(candidate.target) || !isShortString(candidate.output) || typeof candidate.reloadRequired !== "boolean")
237
247
  return undefined;
248
+ if (candidate.restartRequired !== undefined && typeof candidate.restartRequired !== "boolean") return undefined;
238
249
  return value as MutationToolDetails;
239
250
  }
240
251
  } catch {
@@ -312,7 +323,9 @@ export function renderPackageToolResult(
312
323
  const statusColor = details.status === "succeeded" ? "success" : "warning";
313
324
  const lines = [`${theme.fg(statusColor, details.status === "succeeded" ? "✓" : "○")} ${details.operation} ${details.target}`];
314
325
  if (options.expanded && details.output) lines.push(details.output);
315
- if (details.reloadRequired) lines.push(theme.fg("warning", "Reload Pi with /reload to activate the update."));
326
+ if (details.restartRequired)
327
+ lines.push(theme.fg("warning", "Restart Pi (exit and relaunch) to activate -- Packed's own running code can't hot-reload itself."));
328
+ else if (details.reloadRequired) lines.push(theme.fg("warning", "Reload Pi with /reload to activate the update."));
316
329
  return lines.map((line) => truncateToWidth(line, safeWidth));
317
330
  },
318
331
  invalidate() {},
@@ -2,7 +2,7 @@
2
2
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { Type } from "typebox";
4
4
  import { type PackageOperation, packagePermissionDecision } from "./approval/permission.js";
5
- import { reloadWarning } from "./approval/reload.js";
5
+ import { reloadWarning, targetsPackedItself } from "./approval/reload.js";
6
6
  import { InstallServiceError, type Natives, PI_COMMAND_NAME } from "./packed.js";
7
7
  import {
8
8
  createInfoDetails,
@@ -28,6 +28,10 @@ export async function approvePackageOperation(
28
28
  command: string,
29
29
  natives: Pick<Natives, "security">,
30
30
  ctx: ApprovalContext,
31
+ // True only for a source/name that resolves to Packed's own npm package -- see
32
+ // targetsPackedItself(). Every caller below computes this from the exact string it's
33
+ // about to mutate, so the warning always reflects the real target.
34
+ restartRequired = false,
31
35
  ): Promise<{ allowed: boolean; approved: boolean; reason?: "cancelled" | "denied"; message?: string }> {
32
36
  const settings = await natives.security();
33
37
  const decision = packagePermissionDecision(settings, operation);
@@ -42,7 +46,7 @@ export async function approvePackageOperation(
42
46
  }
43
47
  const approved = await ctx.ui.confirm(
44
48
  `${operation[0]!.toUpperCase()}${operation.slice(1)} Pi package`,
45
- `Run: ${command}\n\nThis operation can execute package code or mutate Pi settings/install roots. ${reloadWarning(operation)} Continue?`,
49
+ `Run: ${command}\n\nThis operation can execute package code or mutate Pi settings/install roots. ${reloadWarning(operation, { restartRequired })} Continue?`,
46
50
  );
47
51
  return approved
48
52
  ? { allowed: true, approved: true }
@@ -54,12 +58,16 @@ export async function installPackageWithPolicy(
54
58
  natives: Pick<Natives, "security" | "install" | "installService">,
55
59
  ctx: ApprovalContext,
56
60
  ) {
57
- const approval = await approvePackageOperation("install", `pi install ${source}`, natives, ctx);
61
+ const restartRequired = targetsPackedItself(source);
62
+ const approval = await approvePackageOperation("install", `pi install ${source}`, natives, ctx, restartRequired);
58
63
  if (!approval.allowed) {
59
64
  const output = approval.message ?? "install denied";
60
65
  return text(output, createMutationDetails("install", source, approval.reason ?? "denied", output));
61
66
  }
62
- let output = (await natives.install(source, approval.approved)) || `Installed ${source}. Reload with /reload to activate.`;
67
+ const activateNote = restartRequired
68
+ ? "Restart Pi (exit and relaunch) to activate -- Packed's own running code can't hot-reload itself."
69
+ : "Reload with /reload to activate.";
70
+ let output = (await natives.install(source, approval.approved)) || `Installed ${source}. ${activateNote}`;
63
71
  // Piggybacks on the same approval already granted for install -- both are
64
72
  // the same code-execution mutation tier, not a new consent surface. Silent
65
73
  // for the overwhelmingly common case (most Pi packages aren't daemons at
@@ -80,7 +88,8 @@ export async function installPackageWithPolicy(
80
88
  }
81
89
 
82
90
  export async function updatePackageWithPolicy(source: string, natives: Pick<Natives, "security" | "update">, ctx: ApprovalContext) {
83
- const approval = await approvePackageOperation("update", `pi update --extension ${source}`, natives, ctx);
91
+ const restartRequired = targetsPackedItself(source);
92
+ const approval = await approvePackageOperation("update", `pi update --extension ${source}`, natives, ctx, restartRequired);
84
93
  if (!approval.allowed) {
85
94
  const output = approval.message ?? "update denied";
86
95
  return text(output, createMutationDetails("update", source, approval.reason ?? "denied", output));
@@ -95,31 +104,42 @@ export async function updatePackageWithPolicy(source: string, natives: Pick<Nati
95
104
  return text(message, createMutationDetails("update", source, "succeeded", outcome.output || message, false));
96
105
  }
97
106
  const transition = outcome.previousVersion && outcome.currentVersion ? ` (${outcome.previousVersion} → ${outcome.currentVersion})` : "";
98
- const message = `${outcome.output || `Updated ${source}.`}${transition} Reload with /reload to activate.`;
107
+ const activateNote = restartRequired
108
+ ? "Restart Pi (exit and relaunch) to activate -- Packed's own running code can't hot-reload itself."
109
+ : "Reload with /reload to activate.";
110
+ const message = `${outcome.output || `Updated ${source}.`}${transition} ${activateNote}`;
99
111
  return text(message, createMutationDetails("update", source, "succeeded", outcome.output || message, true));
100
112
  }
101
113
 
102
114
  export async function removePackageWithPolicy(name: string, natives: Pick<Natives, "security" | "remove">, ctx: ApprovalContext) {
103
- const approval = await approvePackageOperation("remove", `pi remove npm:${name}`, natives, ctx);
115
+ const restartRequired = targetsPackedItself(name);
116
+ const approval = await approvePackageOperation("remove", `pi remove npm:${name}`, natives, ctx, restartRequired);
104
117
  if (!approval.allowed) {
105
118
  const output = approval.message ?? "remove denied";
106
119
  return text(output, createMutationDetails("remove", name, approval.reason ?? "denied", output));
107
120
  }
108
- const output = (await natives.remove(name, approval.approved)) || `Removed ${name}. Reload with /reload to deactivate.`;
121
+ const deactivateNote = restartRequired
122
+ ? "Restart Pi (exit and relaunch) to deactivate -- Packed's own running code can't hot-reload itself."
123
+ : "Reload with /reload to deactivate.";
124
+ const output = (await natives.remove(name, approval.approved)) || `Removed ${name}. ${deactivateNote}`;
109
125
  return text(output, createMutationDetails("remove", name, "succeeded", output));
110
126
  }
111
127
 
112
128
  export function registerTools(pi: ExtensionAPI, natives: Natives): void {
113
129
  pi.registerTool({
114
130
  name: "pkg_search",
115
- label: "Pi Package Search",
116
- description: "Search Pi packages on npm. Bounded read operation; defaults to 10 results and caps at 50.",
131
+ label: "Pi Extension Search",
132
+ description:
133
+ "Find Pi extensions -- npm packages that add tools, skills, prompt templates, or themes to the Pi coding agent -- by keyword. This is NOT a general-purpose npm/software search: it only makes sense for 'is there a Pi extension for X' questions (e.g. an LSP integration, a Telegram tool, a new theme). Matches are npm keyword-search candidates only, not yet confirmed to be real Pi extensions -- use pkg_info on a result to verify it actually declares Pi resources before installing. Bounded read operation; defaults to 10 results and caps at 50.",
117
134
  parameters: Type.Object({
118
- query: Type.String({ description: "Search terms, e.g. 'lsp' or 'telegram'" }),
135
+ query: Type.String({
136
+ description:
137
+ "Keywords describing the Pi extension/capability you want, e.g. 'lsp' or 'telegram' -- not a generic npm package search",
138
+ }),
119
139
  limit: Type.Optional(Type.Number({ description: "Max results (default 10, max 50)" })),
120
140
  }),
121
141
  renderCall(args, theme) {
122
- return renderPackageToolCall("Search packages", args, theme);
142
+ return renderPackageToolCall("Search Pi extensions", args, theme);
123
143
  },
124
144
  renderResult(result, options, theme, context) {
125
145
  return renderPackageToolResult(result, options, theme, context);
@@ -139,11 +159,11 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
139
159
 
140
160
  pi.registerTool({
141
161
  name: "pkg_info",
142
- label: "Pi Package Info",
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.`,
144
- parameters: Type.Object({ name: Type.String({ description: "npm package name" }) }),
162
+ label: "Pi Extension Info",
163
+ description: `Show bounded metadata and declared Pi resources (extensions, skills, prompts, themes) for one Pi extension package -- the way to confirm a pkg_search hit is a genuine Pi extension, not just a coincidental keyword match, before installing it. 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.`,
164
+ parameters: Type.Object({ name: Type.String({ description: "npm package name of the Pi extension (or 'pi' itself)" }) }),
145
165
  renderCall(args, theme) {
146
- return renderPackageToolCall("Package info", args, theme);
166
+ return renderPackageToolCall("Pi extension info", args, theme);
147
167
  },
148
168
  renderResult(result, options, theme, context) {
149
169
  return renderPackageToolResult(result, options, theme, context);
@@ -185,11 +205,12 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
185
205
 
186
206
  pi.registerTool({
187
207
  name: "pkg_install",
188
- label: "Pi Package Install",
189
- description: "Install a Pi package through the authenticated daemon. Operation-aware approval is secure by default.",
190
- parameters: Type.Object({ source: Type.String({ description: "npm:, git:, or https source" }) }),
208
+ label: "Pi Extension Install",
209
+ description:
210
+ "Install a Pi extension (a package that adds tools, skills, prompt templates, or a theme to the Pi coding agent) through the authenticated daemon. Not for installing arbitrary software/npm packages unrelated to Pi. Operation-aware approval is secure by default.",
211
+ parameters: Type.Object({ source: Type.String({ description: "npm:, git:, or https source of the Pi extension" }) }),
191
212
  renderCall(args, theme) {
192
- return renderPackageToolCall("Install package", args, theme);
213
+ return renderPackageToolCall("Install Pi extension", args, theme);
193
214
  },
194
215
  renderResult(result, options, theme, context) {
195
216
  return renderPackageToolResult(result, options, theme, context);
@@ -201,11 +222,14 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
201
222
 
202
223
  pi.registerTool({
203
224
  name: "pkg_update",
204
- label: "Pi Package Update",
205
- description: "Update one configured Pi package through Pi's documented update command. Operation-aware approval is secure by default.",
206
- parameters: Type.Object({ source: Type.String({ description: "configured npm:, git:, or https source" }) }),
225
+ label: "Pi Extension Update",
226
+ description:
227
+ "Update one already-installed Pi extension through Pi's documented update command. Operation-aware approval is secure by default.",
228
+ parameters: Type.Object({
229
+ source: Type.String({ description: "configured npm:, git:, or https source of the installed Pi extension" }),
230
+ }),
207
231
  renderCall(args, theme) {
208
- return renderPackageToolCall("Update package", args, theme);
232
+ return renderPackageToolCall("Update Pi extension", args, theme);
209
233
  },
210
234
  renderResult(result, options, theme, context) {
211
235
  return renderPackageToolResult(result, options, theme, context);
@@ -217,11 +241,14 @@ export function registerTools(pi: ExtensionAPI, natives: Natives): void {
217
241
 
218
242
  pi.registerTool({
219
243
  name: "pkg_remove",
220
- label: "Pi Package Remove",
221
- description: "Remove an installed npm Pi package through the authenticated daemon. Operation-aware approval is secure by default.",
222
- parameters: Type.Object({ name: Type.String({ description: "bare npm name, e.g. pi-lsp or @scope/pkg" }) }),
244
+ label: "Pi Extension Remove",
245
+ description:
246
+ "Remove an installed Pi extension (npm package) through the authenticated daemon. Operation-aware approval is secure by default.",
247
+ parameters: Type.Object({
248
+ name: Type.String({ description: "bare npm name of the installed Pi extension, e.g. pi-lsp or @scope/pkg" }),
249
+ }),
223
250
  renderCall(args, theme) {
224
- return renderPackageToolCall("Remove package", args, theme);
251
+ return renderPackageToolCall("Remove Pi extension", args, theme);
225
252
  },
226
253
  renderResult(result, options, theme, context) {
227
254
  return renderPackageToolResult(result, options, theme, context);
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Test-injectable resolver for the packed daemon's Vehicle-projected surface (/vehicle/*), mirroring
3
+ * @danypops/pi-papyrus's service-client.ts currentVehicleClientTarget() pattern one-to-one: a hermetic
4
+ * test exercising the full extension entrypoint must override this the same way it already overrides
5
+ * the daemon connector, or it would silently start depending on whatever real pi-packed daemon handle
6
+ * happens to exist on the machine running it.
7
+ */
8
+ import { type PackedVehicleClientTarget, resolveVehicleClientTarget } from "@danypops/pi-packed/client";
9
+
10
+ export type { PackedVehicleClientTarget };
11
+
12
+ let resolver: () => PackedVehicleClientTarget | undefined = () => resolveVehicleClientTarget();
13
+
14
+ export function currentVehicleClientTarget(): PackedVehicleClientTarget | undefined {
15
+ return resolver();
16
+ }
17
+
18
+ export function setVehicleClientTargetResolverForTests(value: () => PackedVehicleClientTarget | undefined): void {
19
+ resolver = value;
20
+ }
21
+
22
+ export function resetVehicleClientTargetResolverForTests(): void {
23
+ resolver = () => resolveVehicleClientTarget();
24
+ }