portable-agent-layer 0.73.0 → 0.75.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 CHANGED
@@ -97,6 +97,7 @@ pal cli status # check your setup
97
97
  | `pal cli subagent link <name>` | Install a personal `~/.pal/agents/<name>.md` (merged multi-platform definition) into every installed agent, split per platform |
98
98
  | `pal cli subagent doctor <name>` | Evaluate a subagent definition against the authoring best practices (name/file match, per-platform blocks, model/tools/permission shape, shipped-name collision) |
99
99
  | `pal cli subagent list` | List the user-authored subagents in `~/.pal/agents/` |
100
+ | `pal cli version` | Print the installed PAL version. `-v` and `--version` do the same |
100
101
 
101
102
  ### Target flags
102
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.73.0",
3
+ "version": "0.75.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -17,12 +17,13 @@
17
17
  * doctor Check prerequisites and system health
18
18
  * usage Summarize token usage and cost
19
19
  * ledger <sub> [filters] Query the action ledger (log · show · stats)
20
- * server start|stop|status The control room, a local page over ~/.pal
20
+ * server start|stop|restart|status The control room, a local page over ~/.pal
21
21
  * skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
22
22
  * skill doctor <name|--all> Evaluate one skill, or every installed skill, against the authoring best practices
23
23
  * subagent link <name> Install a personal ~/.pal/agents/<name>.md into installed agents
24
24
  * subagent doctor <name> Evaluate a subagent against the authoring best practices
25
25
  * debug [on|off] Enable / disable verbose hook debug logging
26
+ * version | -v Print the installed PAL version
26
27
  */
27
28
 
28
29
  import { spawnSync } from "node:child_process";
@@ -280,6 +281,11 @@ async function runCli(command: string | undefined, args: string[]) {
280
281
  case "debug":
281
282
  cliDebug(args);
282
283
  break;
284
+ case "version":
285
+ case "-v":
286
+ case "--version":
287
+ showVersion();
288
+ break;
283
289
  case "--help":
284
290
  case "-h":
285
291
  case "help":
@@ -342,7 +348,7 @@ function showHelp() {
342
348
  (search · graph · stats · hubs · find · show · add · ls)
343
349
  pal cli ledger <sub> [filters] Query the action ledger (log · show · stats)
344
350
  e.g. ledger log --project X --since 7d
345
- pal cli server start|stop|status The control room: a local page to open before a terminal
351
+ pal cli server start|stop|restart|status The control room: a local page to open before a terminal
346
352
  pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
347
353
  pal cli skill doctor <name|--all> Evaluate one skill, or every installed skill
348
354
  pal cli skill author-model Print the flagship model that authors skills for the active agent
@@ -351,6 +357,7 @@ function showHelp() {
351
357
  pal cli subagent list List the user-authored subagents in ~/.pal/agents/
352
358
  pal cli subagent author-model Print the flagship model that authors subagents for the active agent
353
359
  pal cli debug [on|off] Enable/disable verbose hook debug logging (persisted)
360
+ pal cli version | -v Print the installed PAL version
354
361
 
355
362
  Environment:
356
363
  PAL_HOME Override user state directory (default: ~/.pal or repo root)
@@ -1287,9 +1294,28 @@ async function install(targets: Targets) {
1287
1294
  `Shared: ${indexedSkills} skills indexed · ${palDocsCount} docs → ~/.pal/docs/ · AGENTS.md + context digests written`
1288
1295
  );
1289
1296
 
1297
+ await refreshControlRoom();
1298
+
1290
1299
  log.success("Done. Existing config was preserved — only new entries were added.");
1291
1300
  }
1292
1301
 
1302
+ /**
1303
+ * A published install carries the page in its tarball; a checkout does not —
1304
+ * ui/dist is gitignored, so a pull leaves whatever was built last. Rebuild
1305
+ * there, then replace the process, because the API is the running code.
1306
+ */
1307
+ async function refreshControlRoom(): Promise<void> {
1308
+ const { isRepoMode } = await import("../hooks/handlers/update-check");
1309
+ const { buildPage } = await import("../tools/control-room/static");
1310
+ if (isRepoMode() && !buildPage()) {
1311
+ log.warn("Control room page could not be rebuilt — run: bun run build:ui");
1312
+ return;
1313
+ }
1314
+
1315
+ const { restartIfRunning } = await import("./server");
1316
+ if (await restartIfRunning()) log.success("Control room restarted on the new build");
1317
+ }
1318
+
1293
1319
  async function uninstall(args: string[]) {
1294
1320
  const targets = parseTargets(args);
1295
1321
 
@@ -1598,21 +1624,29 @@ function cliDebug(args: string[]) {
1598
1624
  }
1599
1625
  }
1600
1626
 
1601
- async function status() {
1602
- const home = palHome();
1603
- const pkg = palPkg();
1604
-
1605
- let pkgJson: { version: string };
1627
+ function packageVersion(): string {
1606
1628
  try {
1607
- pkgJson = JSON.parse(readFileSync(resolve(pkg, "package.json"), "utf-8")) as {
1629
+ const pkgJson = JSON.parse(
1630
+ readFileSync(resolve(palPkg(), "package.json"), "utf-8")
1631
+ ) as {
1608
1632
  version: string;
1609
1633
  };
1634
+ return pkgJson.version;
1610
1635
  } catch (e) {
1611
1636
  throw new Error(`Failed to read package.json: ${e}`);
1612
1637
  }
1638
+ }
1639
+
1640
+ function showVersion() {
1641
+ console.log(packageVersion());
1642
+ }
1643
+
1644
+ async function status() {
1645
+ const home = palHome();
1646
+ const pkg = palPkg();
1613
1647
 
1614
1648
  console.log("");
1615
- log.info(`Version: ${pkgJson.version}`);
1649
+ log.info(`Version: ${packageVersion()}`);
1616
1650
  log.info(`Package: ${pkg}`);
1617
1651
  log.info(`Home: ${home}`);
1618
1652
  console.log("");
package/src/cli/server.ts CHANGED
@@ -37,6 +37,8 @@ export async function runServer(args: string[]): Promise<number> {
37
37
  return cmdStart(rest);
38
38
  case "stop":
39
39
  return cmdStop();
40
+ case "restart":
41
+ return cmdRestart();
40
42
  case "status":
41
43
  return cmdStatus();
42
44
  case undefined:
@@ -60,6 +62,7 @@ function showHelp(): void {
60
62
  Subcommands:
61
63
  start [--port <n>] Start the control room in the background (default port ${DEFAULT_PORT})
62
64
  stop Stop it
65
+ restart Replace a running one — the API only changes when the process does
63
66
  status Show whether it is running, and where
64
67
 
65
68
  The page listens on ${LOOPBACK} only.
@@ -156,6 +159,29 @@ async function cmdStart(args: string[]): Promise<number> {
156
159
  return 0;
157
160
  }
158
161
 
162
+ /**
163
+ * The page is read off disk per request, so a rebuilt dist reaches the browser
164
+ * on the next reload — but the API routes are the running process's own code,
165
+ * and those only change when the process does.
166
+ */
167
+ async function cmdRestart(): Promise<number> {
168
+ const running = await runningServer();
169
+ if (!running) {
170
+ console.log("Not running — nothing to restart. Use `start`.");
171
+ return 0;
172
+ }
173
+ await cmdStop();
174
+ return cmdStart(["--port", String(running.port)]);
175
+ }
176
+
177
+ /**
178
+ * Used by `pal cli install`: an install that leaves an old build answering on
179
+ * the port has not finished. A server nobody started stays unstarted.
180
+ */
181
+ export async function restartIfRunning(): Promise<boolean> {
182
+ return (await runningServer()) !== null && (await cmdRestart()) === 0;
183
+ }
184
+
159
185
  /** The state file is a claim; the process answering on that port is the fact. */
160
186
  async function runningServer(): Promise<ServerState | null> {
161
187
  const state = readState();
@@ -46,7 +46,7 @@ function writeCache(cache: UpdateCache): void {
46
46
  }
47
47
  }
48
48
 
49
- function isRepoMode(): boolean {
49
+ export function isRepoMode(): boolean {
50
50
  return existsSync(resolve(palPkg(), ".git"));
51
51
  }
52
52
 
@@ -18,15 +18,26 @@
18
18
  */
19
19
 
20
20
  import { spawn } from "node:child_process";
21
+ import { getActiveAgent } from "./agent";
21
22
  import { logDebug, logError } from "./log";
22
23
 
24
+ /**
25
+ * A hook run by Cursor, Copilot or Codex knows its agent only from the
26
+ * `--agent=` flag its own config put on the command line, and argv does not
27
+ * cross a spawn. Re-declaring it here is what stops the child from taking the
28
+ * "claude" default and spawning the wrong CLI for its inference.
29
+ */
30
+ function withAgentDeclaration(args: string[]): string[] {
31
+ return [...args, `--agent=${getActiveAgent()}`];
32
+ }
33
+
23
34
  export function spawnDetachedInference(
24
35
  scriptPath: string,
25
36
  args: string[],
26
37
  scope: string
27
38
  ): void {
28
39
  try {
29
- const child = spawn("bun", [scriptPath, ...args], {
40
+ const child = spawn("bun", [scriptPath, ...withAgentDeclaration(args)], {
30
41
  detached: true,
31
42
  stdio: "ignore",
32
43
  env: { ...process.env, CLAUDECODE: undefined },
@@ -24,6 +24,14 @@ export const WATCHED_PATHS = [
24
24
  "assets/agents",
25
25
  ];
26
26
 
27
+ /**
28
+ * A flag spelling of a command the README documents under its canonical name,
29
+ * or the `cli` prefix the dispatcher consumes before a command is ever named.
30
+ */
31
+ function isAliasOrInternalRoute(cmd: string): boolean {
32
+ return ["--help", "-h", "help", "-v", "--version", "cli"].includes(cmd);
33
+ }
34
+
27
35
  /** Extract CLI command names from the switch statement in index.ts */
28
36
  function extractCliCommands(): string[] {
29
37
  const pkg = palPkg();
@@ -36,8 +44,7 @@ function extractCliCommands(): string[] {
36
44
 
37
45
  for (const match of matches) {
38
46
  const cmd = match[1];
39
- // Skip help aliases and internal routing
40
- if (["--help", "-h", "help", "cli"].includes(cmd)) continue;
47
+ if (isAliasOrInternalRoute(cmd)) continue;
41
48
  commands.push(cmd);
42
49
  }
43
50