@4pm/cli 1.2.4 → 1.2.6

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.
Files changed (2) hide show
  1. package/dist/index.js +47 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28211,7 +28211,6 @@ import { join as join2 } from "path";
28211
28211
  // src/config/profile.ts
28212
28212
  import {
28213
28213
  existsSync,
28214
- mkdirSync,
28215
28214
  readdirSync,
28216
28215
  readFileSync,
28217
28216
  rmSync,
@@ -28219,6 +28218,30 @@ import {
28219
28218
  } from "fs";
28220
28219
  import { homedir } from "os";
28221
28220
  import { join } from "path";
28221
+
28222
+ // src/common/io/ensure-dir.ts
28223
+ import { mkdirSync } from "fs";
28224
+ var CLI_IMAGE = "ghcr.io/4azuo/4pm-cli:full";
28225
+ function ensureDir(dir2) {
28226
+ try {
28227
+ mkdirSync(dir2, { recursive: true });
28228
+ } catch (err) {
28229
+ const code = err.code;
28230
+ if (code === "EACCES" || code === "EPERM") {
28231
+ throw new Error(
28232
+ `Cannot write "${dir2}" \u2014 the mounted home is owned by root. A reused Docker volume or a host bind-mount shadows the image's node-owned home, and the cli runs as the unprivileged "node" user (uid 1000), so it cannot chown it.
28233
+ Fix the mount ONCE, then retry:
28234
+ docker run --rm --user 0 --cap-drop=ALL --cap-add=CHOWN --cap-add=DAC_OVERRIDE \\
28235
+ --security-opt=no-new-privileges --entrypoint chown \\
28236
+ -v <volume>:/home/node/.4pm ${CLI_IMAGE} -R node:node /home/node/.4pm
28237
+ (host bind-mount: run \`sudo chown -R 1000:1000 <host-path>\` on the host instead; a FRESH named volume needs none of this.)`
28238
+ );
28239
+ }
28240
+ throw err;
28241
+ }
28242
+ }
28243
+
28244
+ // src/config/profile.ts
28222
28245
  var DEFAULT_PROFILE = "default";
28223
28246
  function defaultPointerPath() {
28224
28247
  return join(homedir(), ".4pm", "default");
@@ -28232,7 +28255,7 @@ function defaultProfileName() {
28232
28255
  return DEFAULT_PROFILE;
28233
28256
  }
28234
28257
  function writeDefaultProfile(userId) {
28235
- mkdirSync(join(homedir(), ".4pm"), { recursive: true });
28258
+ ensureDir(join(homedir(), ".4pm"));
28236
28259
  writeFileSync(defaultPointerPath(), userId, "utf8");
28237
28260
  }
28238
28261
  function clearDefaultProfileIf(name) {
@@ -28269,7 +28292,7 @@ function listProfiles() {
28269
28292
  }
28270
28293
  function profileDir(name) {
28271
28294
  const dir2 = join(homedir(), ".4pm", "profiles", name);
28272
- mkdirSync(dir2, { recursive: true });
28295
+ ensureDir(dir2);
28273
28296
  return dir2;
28274
28297
  }
28275
28298
  function defaultProfileConfig() {
@@ -28492,7 +28515,7 @@ ${INSECURE_URL_WARNING}
28492
28515
  `);
28493
28516
  console.log("2. The web will show a confirmation code \u2014 paste it here.");
28494
28517
  const rl = createInterface({ input: process.stdin, output: process.stdout });
28495
- const hashcode2 = (await rl.question("confirmation code: ")).trim();
28518
+ const hashcode2 = (await rl.question("Confirmation code: ")).trim();
28496
28519
  rl.close();
28497
28520
  result = await confirmPairing(serverUrl, hashcode2, collectFingerprint());
28498
28521
  }
@@ -29803,7 +29826,7 @@ async function runAiFailover(plan, commandId, cwd2, handlers) {
29803
29826
  let finalExit = -1;
29804
29827
  for (let i = 0; i < total; i++) {
29805
29828
  const attempt = plan.attempts[i];
29806
- if (total > 1) handlers.onAttemptStart(attempt.label, i, total);
29829
+ if (total > 1) handlers.onAttemptStart(attempt.label, i, total, attempt.cmd);
29807
29830
  const parser = createAiStreamParser(attempt.cmd);
29808
29831
  let captured = "";
29809
29832
  const emit = (raw) => {
@@ -29911,6 +29934,10 @@ function resolveClaudeAuthMode(config) {
29911
29934
  function isUnifiedConfig(config) {
29912
29935
  return Array.isArray(config.aiProfiles) && config.aiProfiles.some(isUsableCredential);
29913
29936
  }
29937
+ function activeProvider(config) {
29938
+ const v = config.aiCli?.trim();
29939
+ return v && AI_PROVIDERS.includes(v) ? v : null;
29940
+ }
29914
29941
  function claudeProfilesOf(config) {
29915
29942
  if (isUnifiedConfig(config)) return config.aiProfiles.filter((c) => c.provider === "claude");
29916
29943
  return config.claudeHome ?? [];
@@ -29984,7 +30011,18 @@ function planAiRun(prompt, config, hint = {}) {
29984
30011
  }
29985
30012
  function planUnifiedRun(prompt, config, workingCredential) {
29986
30013
  const baseEnv = config.aiEnv ?? {};
29987
- const resolved = config.aiProfiles.filter(isUsableCredential).map((cred) => {
30014
+ const active = activeProvider(config);
30015
+ const usable = config.aiProfiles.filter(isUsableCredential);
30016
+ const scoped = active ? usable.filter((c) => c.provider === active) : usable;
30017
+ if (active && scoped.length === 0) {
30018
+ return {
30019
+ cmd: active,
30020
+ attempts: [
30021
+ { cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, prompt), env: { ...baseEnv } }
30022
+ ]
30023
+ };
30024
+ }
30025
+ const resolved = scoped.map((cred) => {
29988
30026
  const cmd = cred.provider;
29989
30027
  const dir2 = resolveHomePath(cred.profile);
29990
30028
  return { cred, cmd, dir: dir2, key: credentialKey(cmd, dir2), envVar: profileEnvVar(cmd) };
@@ -33080,8 +33118,8 @@ var WsClient = class {
33080
33118
  this.bus.push({ source: origin, kind: "out", text: resultBuf });
33081
33119
  }
33082
33120
  },
33083
- onAttemptStart: (label, index, total) => {
33084
- const text = `\u2192 trying ${plan.cmd} profile "${label}" (${index + 1}/${total})\u2026`;
33121
+ onAttemptStart: (label, index, total, cmd2) => {
33122
+ const text = `\u2192 trying ${cmd2} profile "${label}" (${index + 1}/${total})\u2026`;
33085
33123
  this.bus.push({ source: origin, kind: "log", text });
33086
33124
  this.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
33087
33125
  `, log: true });
@@ -33596,7 +33634,7 @@ function isAlive(pid) {
33596
33634
  function lockHolder(profileDir2) {
33597
33635
  try {
33598
33636
  const pid = Number.parseInt(readFileSync15(lockPath(profileDir2), "utf8").trim(), 10);
33599
- if (Number.isInteger(pid) && pid > 0 && isAlive(pid)) return pid;
33637
+ if (Number.isInteger(pid) && pid > 0 && pid !== process.pid && isAlive(pid)) return pid;
33600
33638
  } catch {
33601
33639
  }
33602
33640
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",