portable-agent-layer 0.85.16 → 0.85.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.85.16",
3
+ "version": "0.85.18",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,11 @@
1
+ import { unresolvedDependencies } from "./doctor/state";
2
+
3
+ /**
4
+ * The `bun install` arguments that make PAL's dependencies resolve, or null when
5
+ * they already do. A published package ships no lockfile, and `bun add -g` has
6
+ * already installed its dependencies next to it.
7
+ */
8
+ export function dependencyInstall(pkg: string, repoMode: boolean): string[] | null {
9
+ if (repoMode) return ["install", "--frozen-lockfile"];
10
+ return unresolvedDependencies(pkg).length > 0 ? ["install", "--production"] : null;
11
+ }
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { palPkg, platform } from "../../hooks/lib/paths";
5
5
  import { NO_SESSION_AGENT_MESSAGE } from "../session-agent";
6
- import { type Finding, failing, optional, passed, warning } from "./finding";
6
+ import { type Finding, type Fix, failing, optional, passed, warning } from "./finding";
7
7
 
8
8
  export type AgentName = "claude" | "opencode" | "cursor" | "copilot" | "codex";
9
9
 
@@ -139,9 +139,10 @@ const LAYOUT: Record<AgentName, AgentLayout> = {
139
139
  },
140
140
  };
141
141
 
142
- const reinstall = (agent: AgentName) => ({
142
+ const reinstall = (agent: AgentName): Fix => ({
143
143
  say: `Reinstall PAL for ${LAYOUT[agent].label}`,
144
144
  command: `pal cli install --${agent}`,
145
+ external: false,
145
146
  });
146
147
 
147
148
  /** Hook-config keys that carry a shell command: cross-platform and per-shell variants. */
@@ -215,6 +216,7 @@ function hookFindings(agent: AgentName, file: string): Finding[] {
215
216
  {
216
217
  say: `Fix the JSON in ${file}, then reinstall`,
217
218
  command: `pal cli install --${agent}`,
219
+ external: false,
218
220
  }
219
221
  ),
220
222
  ];
@@ -310,6 +312,7 @@ export function agentFindings(agents: AgentName[]): Finding[] {
310
312
  : failing("agents-md", "AGENTS.md is missing", {
311
313
  say: "Reinstall PAL",
312
314
  command: "pal cli install",
315
+ external: false,
313
316
  }),
314
317
  ...agents.flatMap(oneAgentFindings),
315
318
  ];
@@ -83,6 +83,7 @@ export function playwrightFinding(host: BrowserHost): Finding {
83
83
  ? "Install it as Ubuntu 24.04, which Playwright supports"
84
84
  : "Install it",
85
85
  command: chromiumInstallCommand(host),
86
+ external: false,
86
87
  }
87
88
  );
88
89
  }
@@ -113,15 +114,34 @@ interface PalInstall {
113
114
  pal: string | null;
114
115
  pkg: string;
115
116
  repoMode: boolean;
117
+ bunBin: string;
118
+ inBunBin: boolean;
116
119
  }
117
120
 
118
121
  function putPalOnPath(install: PalInstall): Fix {
122
+ if (install.inBunBin)
123
+ return {
124
+ say: `Add ${install.bunBin} to PATH in your shell profile — Bun put pal there`,
125
+ };
119
126
  return install.repoMode
120
127
  ? {
121
128
  say: "Link the checkout — a shell alias is invisible to agents",
122
129
  command: `cd ${install.pkg} && bun link`,
130
+ external: false,
123
131
  }
124
- : { say: "Install PAL globally", command: "bun add -g portable-agent-layer" };
132
+ : {
133
+ say: "Install PAL globally",
134
+ command: "bun add -g portable-agent-layer",
135
+ external: true,
136
+ };
137
+ }
138
+
139
+ function bunBin(): string {
140
+ return resolve(process.env.BUN_INSTALL ?? resolve(homedir(), ".bun"), "bin");
141
+ }
142
+
143
+ function isInBunBin(): boolean {
144
+ return ["pal", "pal.exe"].some((name) => existsSync(resolve(bunBin(), name)));
125
145
  }
126
146
 
127
147
  export function palOnPathFinding(install: PalInstall): Finding {
@@ -140,11 +160,12 @@ function rtkInstall(): Fix {
140
160
  say: "Download rtk.exe from https://github.com/rtk-ai/rtk/releases and add it to PATH",
141
161
  };
142
162
  if (process.platform === "darwin")
143
- return { say: "Install rtk", command: "brew install rtk" };
163
+ return { say: "Install rtk", command: "brew install rtk", external: true };
144
164
  return {
145
165
  say: "Install rtk",
146
166
  command:
147
167
  "curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh",
168
+ external: true,
148
169
  };
149
170
  }
150
171
 
@@ -161,6 +182,8 @@ export function environmentFindings(rtk: ToolCheck): Finding[] {
161
182
  pal: findBinaryOnPath("pal"),
162
183
  pkg: palPkg(),
163
184
  repoMode: isRepoMode(),
185
+ bunBin: bunBin(),
186
+ inBunBin: isInBunBin(),
164
187
  }),
165
188
  playwrightFinding(thisBrowserHost()),
166
189
  rtkFinding(rtk),
@@ -6,10 +6,10 @@
6
6
 
7
7
  export type Severity = "fail" | "warn" | "optional" | "ok";
8
8
 
9
- export interface Fix {
10
- say: string;
11
- command?: string;
12
- }
9
+ /** `external`: the command belongs to another tool, so PAL leaves running it to you. */
10
+ export type Fix =
11
+ | { say: string; command?: undefined }
12
+ | { say: string; command: string; external: boolean };
13
13
 
14
14
  export interface Finding {
15
15
  id: string;
@@ -33,10 +33,12 @@ function claudeLoginExpiredFinding(
33
33
  return warning(`hook-errors.${group.source}`, title, {
34
34
  say: "CLAUDE_CODE_OAUTH_TOKEN is set — if these failures predate it, they clear 24h after the last one; if not, the token is invalid, so create a new one",
35
35
  command: "claude setup-token",
36
+ external: true,
36
37
  });
37
38
  return failing(`hook-errors.${group.source}`, title, {
38
39
  say: "Create a year-long token, then export it as CLAUDE_CODE_OAUTH_TOKEN in your shell profile",
39
40
  command: "claude setup-token",
41
+ external: true,
40
42
  });
41
43
  }
42
44
 
@@ -66,7 +68,7 @@ export function migrationFindings(pending: PendingMigration[]): Finding[] {
66
68
  return warning(
67
69
  `migration.${migration.id}`,
68
70
  `Migration pending: ${migration.description}${detail}`,
69
- { say: "Run it", command: "pal cli migrate" }
71
+ { say: "Run it", command: "pal cli migrate", external: false }
70
72
  );
71
73
  });
72
74
  }
@@ -76,6 +78,7 @@ export function updateFinding(cache: UpdateCache | null): Finding {
76
78
  return warning("update", `PAL ${cache.latest} is out (${cache.current} installed)`, {
77
79
  say: "Update",
78
80
  command: "pal cli update",
81
+ external: false,
79
82
  });
80
83
  }
81
84
 
@@ -34,6 +34,7 @@ export function leakedEnvFindings(env: Env, os: NodeJS.Platform): Finding[] {
34
34
  {
35
35
  say: "Clear it, and remove it from your shell profile if it is set there",
36
36
  command: unsetCommand(name, os),
37
+ external: true,
37
38
  }
38
39
  )
39
40
  : passed(`env.${name}`, `${name} not set`)
@@ -95,6 +95,7 @@ function identityFinding(settings: Record<string, unknown>): Finding {
95
95
  return warning("identity", "Identity is incomplete — PAL does not know both names", {
96
96
  say: "Answer the identity questions",
97
97
  command: "pal cli install",
98
+ external: false,
98
99
  });
99
100
  }
100
101
 
@@ -120,6 +121,7 @@ function settingsFindings(): Finding[] {
120
121
  warning("settings.missing", "pal-settings.json is missing", {
121
122
  say: "Recreate it",
122
123
  command: "pal cli install",
124
+ external: false,
123
125
  }),
124
126
  ];
125
127
  let settings: unknown;
@@ -165,6 +167,7 @@ function telosFindings(home: string): Finding[] {
165
167
  failing("telos.missing", "TELOS is not scaffolded", {
166
168
  say: "Scaffold it",
167
169
  command: "pal cli init",
170
+ external: false,
168
171
  }),
169
172
  ];
170
173
  const unanswered = telosStatus(home)
@@ -205,6 +208,7 @@ export function bindingFinding(issue: BindingIssue): Finding {
205
208
  return warning(`binding.${project}`, title, {
206
209
  say: "Point it at its checkout",
207
210
  command: `pal cli project set-path ${project} <path>`,
211
+ external: false,
208
212
  });
209
213
  }
210
214
 
@@ -250,6 +254,7 @@ function dependencyFinding(): Finding {
250
254
  {
251
255
  say: "Install them",
252
256
  command: "pal cli install",
257
+ external: false,
253
258
  }
254
259
  );
255
260
  }
@@ -30,22 +30,24 @@ const GH_ON_LINUX: Partial<Record<LinuxFamily, string>> = {
30
30
 
31
31
  export function gitInstallHint(platform: NodeJS.Platform, osRelease: string): Fix {
32
32
  if (platform === "win32")
33
- return { say: "Install git", command: "winget install Git.Git" };
34
- if (platform === "darwin") return { say: "Install git", command: "brew install git" };
33
+ return { say: "Install git", command: "winget install Git.Git", external: true };
34
+ if (platform === "darwin")
35
+ return { say: "Install git", command: "brew install git", external: true };
35
36
  const family = linuxFamily(osRelease);
36
37
  return family
37
- ? { say: "Install git", command: GIT_ON_LINUX[family] }
38
+ ? { say: "Install git", command: GIT_ON_LINUX[family], external: true }
38
39
  : { say: "Install git with your package manager" };
39
40
  }
40
41
 
41
42
  export function ghInstallHint(platform: NodeJS.Platform, osRelease: string): Fix {
42
43
  if (platform === "win32")
43
- return { say: "Install gh", command: "winget install GitHub.cli" };
44
- if (platform === "darwin") return { say: "Install gh", command: "brew install gh" };
44
+ return { say: "Install gh", command: "winget install GitHub.cli", external: true };
45
+ if (platform === "darwin")
46
+ return { say: "Install gh", command: "brew install gh", external: true };
45
47
  const family = linuxFamily(osRelease);
46
48
  const command = family ? GH_ON_LINUX[family] : undefined;
47
49
  return command
48
- ? { say: "Install gh", command }
50
+ ? { say: "Install gh", command, external: true }
49
51
  : { say: `Install gh as described at ${GH_LINUX_DOCS}` };
50
52
  }
51
53
 
@@ -94,6 +96,7 @@ function ghFinding(host: Host): Finding {
94
96
  return warning("gh.logged-out", `${gh.version} is not logged in`, {
95
97
  say: "Log in",
96
98
  command: "gh auth login",
99
+ external: true,
97
100
  });
98
101
  return passed("gh", `${gh.version}, logged in`);
99
102
  }
package/src/cli/index.ts CHANGED
@@ -520,7 +520,10 @@ function targetInstallers(): [keyof Targets, string, () => Promise<unknown>][] {
520
520
  async function install(targets: Targets, args: string[]): Promise<number> {
521
521
  narrateSteps(args.includes("--verbose"));
522
522
  const pkg = palPkg();
523
- if (runQuietly("bun", ["install", "--frozen-lockfile"], pkg) !== 0) {
523
+ const { dependencyInstall } = await import("./dependencies");
524
+ const { isRepoMode } = await import("../hooks/handlers/update-check");
525
+ const bunInstall = dependencyInstall(pkg, isRepoMode());
526
+ if (bunInstall && runQuietly("bun", bunInstall, pkg) !== 0) {
524
527
  log.warn("bun install failed — continuing anyway, but hooks may not work");
525
528
  }
526
529