@4pm/cli 0.4.0 → 0.4.2

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 +401 -188
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1345,7 +1345,7 @@ var require_react_development = __commonJS({
1345
1345
  }
1346
1346
  return dispatcher.useContext(Context);
1347
1347
  }
1348
- function useState5(initialState) {
1348
+ function useState6(initialState) {
1349
1349
  var dispatcher = resolveDispatcher();
1350
1350
  return dispatcher.useState(initialState);
1351
1351
  }
@@ -1373,7 +1373,7 @@ var require_react_development = __commonJS({
1373
1373
  var dispatcher = resolveDispatcher();
1374
1374
  return dispatcher.useCallback(callback, deps);
1375
1375
  }
1376
- function useMemo3(create2, deps) {
1376
+ function useMemo4(create2, deps) {
1377
1377
  var dispatcher = resolveDispatcher();
1378
1378
  return dispatcher.useMemo(create2, deps);
1379
1379
  }
@@ -2145,10 +2145,10 @@ var require_react_development = __commonJS({
2145
2145
  exports.useImperativeHandle = useImperativeHandle;
2146
2146
  exports.useInsertionEffect = useInsertionEffect;
2147
2147
  exports.useLayoutEffect = useLayoutEffect2;
2148
- exports.useMemo = useMemo3;
2148
+ exports.useMemo = useMemo4;
2149
2149
  exports.useReducer = useReducer;
2150
2150
  exports.useRef = useRef;
2151
- exports.useState = useState5;
2151
+ exports.useState = useState6;
2152
2152
  exports.useSyncExternalStore = useSyncExternalStore;
2153
2153
  exports.useTransition = useTransition;
2154
2154
  exports.version = ReactVersion;
@@ -7843,9 +7843,9 @@ var require_react_reconciler_development = __commonJS({
7843
7843
  module.exports = function $$$reconciler($$$hostConfig) {
7844
7844
  var exports2 = {};
7845
7845
  "use strict";
7846
- var React16 = require_react();
7846
+ var React17 = require_react();
7847
7847
  var Scheduler = require_scheduler();
7848
- var ReactSharedInternals = React16.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
7848
+ var ReactSharedInternals = React17.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
7849
7849
  var suppressWarning = false;
7850
7850
  function setSuppressWarning(newSuppressWarning) {
7851
7851
  {
@@ -23490,7 +23490,7 @@ var require_react_jsx_runtime_development = __commonJS({
23490
23490
  if (process.env.NODE_ENV !== "production") {
23491
23491
  (function() {
23492
23492
  "use strict";
23493
- var React16 = require_react();
23493
+ var React17 = require_react();
23494
23494
  var REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("react.element");
23495
23495
  var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for("react.portal");
23496
23496
  var REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
@@ -23516,7 +23516,7 @@ var require_react_jsx_runtime_development = __commonJS({
23516
23516
  }
23517
23517
  return null;
23518
23518
  }
23519
- var ReactSharedInternals = React16.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
23519
+ var ReactSharedInternals = React17.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
23520
23520
  function error(format) {
23521
23521
  {
23522
23522
  {
@@ -24366,11 +24366,11 @@ var require_react_jsx_runtime_development = __commonJS({
24366
24366
  return jsxWithValidation(type, props, key, false);
24367
24367
  }
24368
24368
  }
24369
- var jsx7 = jsxWithValidationDynamic;
24370
- var jsxs5 = jsxWithValidationStatic;
24369
+ var jsx8 = jsxWithValidationDynamic;
24370
+ var jsxs6 = jsxWithValidationStatic;
24371
24371
  exports.Fragment = REACT_FRAGMENT_TYPE;
24372
- exports.jsx = jsx7;
24373
- exports.jsxs = jsxs5;
24372
+ exports.jsx = jsx8;
24373
+ exports.jsxs = jsxs6;
24374
24374
  })();
24375
24375
  }
24376
24376
  }
@@ -24404,7 +24404,14 @@ import { homedir as homedir2 } from "os";
24404
24404
  import { join as join2 } from "path";
24405
24405
 
24406
24406
  // src/config/profile.ts
24407
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
24407
+ import {
24408
+ existsSync,
24409
+ mkdirSync,
24410
+ readdirSync,
24411
+ readFileSync,
24412
+ rmSync,
24413
+ writeFileSync
24414
+ } from "fs";
24408
24415
  import { homedir } from "os";
24409
24416
  import { join } from "path";
24410
24417
  var DEFAULT_PROFILE = "default";
@@ -24443,6 +24450,18 @@ function resolveProfileArg(args) {
24443
24450
  }
24444
24451
  return [process.env.FOURPM_PROFILE ?? null, args];
24445
24452
  }
24453
+ function listProfiles() {
24454
+ const base = join(homedir(), ".4pm", "profiles");
24455
+ if (!existsSync(base)) return [];
24456
+ try {
24457
+ return readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => ({
24458
+ name: d.name,
24459
+ linked: existsSync(join(base, d.name, "credential.cre"))
24460
+ })).sort((a, b) => a.name.localeCompare(b.name));
24461
+ } catch {
24462
+ return [];
24463
+ }
24464
+ }
24446
24465
  function profileDir(name) {
24447
24466
  const dir = join(homedir(), ".4pm", "profiles", name);
24448
24467
  mkdirSync(dir, { recursive: true });
@@ -24590,15 +24609,10 @@ async function fetchCliVersion(serverUrl) {
24590
24609
  }
24591
24610
 
24592
24611
  // src/commands/link.ts
24612
+ function sanitizeProfileName(name) {
24613
+ return name.replace(/[^a-zA-Z0-9._-]/g, "_") || "profile";
24614
+ }
24593
24615
  async function runLink(serverUrl, explicitProfile) {
24594
- const currentName = explicitProfile ?? defaultProfileName();
24595
- if (readCredential(profileDir(currentName), currentName)) {
24596
- const flag = explicitProfile ? ` --profile ${explicitProfile}` : "";
24597
- console.log(
24598
- `Profile "${currentName}" is already linked (.cre). Run \`4pm unlink${flag}\` first if you want to pair again.`
24599
- );
24600
- return;
24601
- }
24602
24616
  const hashcode1 = randomBytes(32).toString("hex");
24603
24617
  console.log(`\u2500\u2500 4PM cli pairing (profile: ${explicitProfile ?? "default"}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
24604
24618
  console.log("1. Open the 4PM web \u2192 /machines, enter hashcode (1):");
@@ -24609,12 +24623,13 @@ async function runLink(serverUrl, explicitProfile) {
24609
24623
  const rl = createInterface({ input: process.stdin, output: process.stdout });
24610
24624
  const hashcode2 = (await rl.question("hashcode (2): ")).trim();
24611
24625
  rl.close();
24612
- const { hashcode3, userId, username, scope } = await confirmPairing(
24626
+ const { hashcode3, username, scope } = await confirmPairing(
24613
24627
  serverUrl,
24614
24628
  hashcode2,
24615
24629
  collectFingerprint()
24616
24630
  );
24617
- const profileName = explicitProfile ?? userId;
24631
+ const profileName = explicitProfile ?? sanitizeProfileName(username);
24632
+ const existed = readCredential(profileDir(profileName), profileName) != null;
24618
24633
  writeCredential(profileDir(profileName), {
24619
24634
  serverUrl,
24620
24635
  hashcode3,
@@ -24622,10 +24637,16 @@ async function runLink(serverUrl, explicitProfile) {
24622
24637
  // decided by the server from the pairing user (ADR-0010); shown in the TUI
24623
24638
  pairedAt: (/* @__PURE__ */ new Date()).toISOString()
24624
24639
  });
24625
- if (!explicitProfile) writeDefaultProfile(userId);
24640
+ writeDefaultProfile(profileName);
24641
+ const verb = existed ? "Renewed" : "Added";
24626
24642
  console.log(
24627
- `\u2714 Linked successfully as "${username}" \u2014 saved .cre. Run \`4pm start\` to connect.`
24643
+ `\u2714 ${verb} profile "${profileName}" as "${username}" \xB7 scope: ${scope} \u2014 saved .cre. Run \`4pm start\` to connect.`
24628
24644
  );
24645
+ if (scope === "orchestrator") {
24646
+ console.log(
24647
+ " (This is the orchestrator/admin link. To pair a worker instead, pick the MACHINE user in the web pairing wizard before entering hashcode (1).)"
24648
+ );
24649
+ }
24629
24650
  }
24630
24651
 
24631
24652
  // src/commands/start.ts
@@ -24638,6 +24659,7 @@ import { createHash as createHash2, createPublicKey, verify } from "crypto";
24638
24659
  import { mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
24639
24660
  import { tmpdir } from "os";
24640
24661
  import { dirname as dirname2, join as join4, sep } from "path";
24662
+ import { fileURLToPath as fileURLToPath2 } from "url";
24641
24663
 
24642
24664
  // src/config/signing.ts
24643
24665
  var CLI_SIGNING_PUBLIC_KEY = `
@@ -24671,12 +24693,15 @@ function compareSemver(a, b) {
24671
24693
  }
24672
24694
  return 0;
24673
24695
  }
24696
+ function runningInstallRoot() {
24697
+ return dirname2(dirname2(fileURLToPath2(import.meta.url)));
24698
+ }
24674
24699
  function installedViaNpm() {
24675
24700
  return (process.argv[1] ?? "").split(sep).includes("node_modules");
24676
24701
  }
24677
24702
  function readInstalledVersion() {
24678
24703
  try {
24679
- const pkgPath = join4(dirname2(dirname2(process.argv[1] ?? ".")), "package.json");
24704
+ const pkgPath = join4(runningInstallRoot(), "package.json");
24680
24705
  return JSON.parse(readFileSync5(pkgPath, "utf8")).version ?? "0.0.0";
24681
24706
  } catch {
24682
24707
  return "0.0.0";
@@ -24710,7 +24735,7 @@ async function updateViaDownload(tarballUrl, checksum, signature) {
24710
24735
  }
24711
24736
  const tmp = join4(mkdtempSync(join4(tmpdir(), "4pm-update-")), "cli.tgz");
24712
24737
  writeFileSync3(tmp, buf);
24713
- const installRoot = dirname2(dirname2(process.argv[1] ?? "."));
24738
+ const installRoot = runningInstallRoot();
24714
24739
  execSync2(`tar -xzf "${tmp}" -C "${installRoot}" --strip-components=1`, {
24715
24740
  stdio: "inherit"
24716
24741
  });
@@ -24731,10 +24756,17 @@ async function updateToLatest(serverUrl) {
24731
24756
  meta.source.signature
24732
24757
  );
24733
24758
  }
24734
- return { action: "updated", version: meta.latest };
24735
24759
  } catch (err) {
24736
24760
  return { action: "failed", error: String(err) };
24737
24761
  }
24762
+ const installed = readInstalledVersion();
24763
+ if (compareSemver(installed, meta.latest) < 0) {
24764
+ return {
24765
+ action: "failed",
24766
+ error: `update ran but the installed version is still ${installed} (expected ${meta.latest}) \u2014 it did not apply to the running install at ${runningInstallRoot()}.`
24767
+ };
24768
+ }
24769
+ return { action: "updated", version: meta.latest };
24738
24770
  }
24739
24771
  async function checkAndUpdate(serverUrl, autoUpdate) {
24740
24772
  if (CLI_VERSION.startsWith("0.0.0")) return { action: "none" };
@@ -24790,6 +24822,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
24790
24822
 
24791
24823
  // src/core/ws-client.ts
24792
24824
  import { randomUUID } from "crypto";
24825
+ import { basename as basename3 } from "path";
24793
24826
 
24794
24827
  // ../../22-packages/11-shared/51-utils/src/helpers.ts
24795
24828
  function sleep(ms) {
@@ -25364,6 +25397,12 @@ function profileEnvVar(cmd) {
25364
25397
  if (cmd.includes("codex")) return "CODEX_HOME";
25365
25398
  return null;
25366
25399
  }
25400
+ function profileLabels(config) {
25401
+ const cmd = config.aiCli ?? DEFAULT_AI_CLI;
25402
+ const envVar = profileEnvVar(cmd);
25403
+ const dirs = envVar === "CLAUDE_CONFIG_DIR" ? resolveDirs(config.claudeConfigDir) : envVar === "CODEX_HOME" ? resolveDirs(config.codexHome) : [];
25404
+ return dirs.map((dir) => basename(dir));
25405
+ }
25367
25406
  function planAiRun(prompt, config, workingDir) {
25368
25407
  const cmd = config.aiCli ?? DEFAULT_AI_CLI;
25369
25408
  const args = [...config.aiArgs ?? DEFAULT_AI_ARGS[cmd] ?? [], prompt];
@@ -25656,12 +25695,12 @@ ${stderr}`;
25656
25695
  import { cp, mkdir, writeFile } from "fs/promises";
25657
25696
  import { execFile as execFile4 } from "child_process";
25658
25697
  import { dirname as dirname4, join as join8, resolve as resolve4 } from "path";
25659
- import { fileURLToPath as fileURLToPath2 } from "url";
25698
+ import { fileURLToPath as fileURLToPath3 } from "url";
25660
25699
  import { promisify as promisify4 } from "util";
25661
25700
  var run4 = promisify4(execFile4);
25662
25701
  function sampleDir() {
25663
25702
  if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
25664
- const here = dirname4(fileURLToPath2(import.meta.url));
25703
+ const here = dirname4(fileURLToPath3(import.meta.url));
25665
25704
  return resolve4(here, "../../project-sample");
25666
25705
  }
25667
25706
  async function scaffoldProject(payload, onProgress) {
@@ -25847,6 +25886,13 @@ var WsClient = class {
25847
25886
  this.bus.setStatus("connecting");
25848
25887
  const token = await requestWsToken(this.credential.serverUrl, this.credential.hashcode3);
25849
25888
  this.wsToken = token.wsToken;
25889
+ if (token.scope && token.scope !== this.credential.scope) {
25890
+ this.credential.scope = token.scope;
25891
+ writeCredential(this.context.profileDir, this.credential);
25892
+ this.bus.setScope(token.scope);
25893
+ } else if (token.scope) {
25894
+ this.bus.setScope(token.scope);
25895
+ }
25850
25896
  const wsUrl = this.credential.serverUrl.replace(/^http/, "ws") + "/ws";
25851
25897
  const socket = new wrapper_default(wsUrl);
25852
25898
  this.socket = socket;
@@ -26077,7 +26123,10 @@ var WsClient = class {
26077
26123
  })
26078
26124
  });
26079
26125
  this.bus.endBusy(plan.cmd);
26080
- if (result.workedDir) setWorkingProfile(this.context.profileDir, plan.cmd, result.workedDir);
26126
+ if (result.workedDir) {
26127
+ setWorkingProfile(this.context.profileDir, plan.cmd, result.workedDir);
26128
+ this.bus.setActiveProfile(basename3(result.workedDir));
26129
+ }
26081
26130
  this.send(WsChannels.COMMAND_OUTPUT, {
26082
26131
  commandId,
26083
26132
  seq: seq++,
@@ -26254,6 +26303,9 @@ function releaseInstanceLock(profileDir2) {
26254
26303
  }
26255
26304
  }
26256
26305
 
26306
+ // src/commands/start.ts
26307
+ import { basename as basename4 } from "path";
26308
+
26257
26309
  // src/core/session-bus.ts
26258
26310
  import { EventEmitter } from "events";
26259
26311
  import { randomUUID as randomUUID2 } from "crypto";
@@ -26265,6 +26317,10 @@ var SessionBus = class {
26265
26317
  history = [];
26266
26318
  /** Labels of in-flight commands (tool names) — top = the one shown as "processing". */
26267
26319
  busyStack = [];
26320
+ /** The AI-CLI profile that last authenticated (shown in the header). */
26321
+ currentActiveProfile = null;
26322
+ /** Link scope learned from the server at connect time (heals the header/.cre). */
26323
+ currentScope = null;
26268
26324
  constructor() {
26269
26325
  this.emitter.setMaxListeners(50);
26270
26326
  }
@@ -26318,6 +26374,34 @@ var SessionBus = class {
26318
26374
  this.emitter.on("busy", fn);
26319
26375
  return () => this.emitter.off("busy", fn);
26320
26376
  }
26377
+ /** The AI-CLI profile currently in use (last authenticated), or null. */
26378
+ get activeProfile() {
26379
+ return this.currentActiveProfile;
26380
+ }
26381
+ /** Set the active AI-CLI profile (shown in the header). */
26382
+ setActiveProfile(label) {
26383
+ this.currentActiveProfile = label;
26384
+ this.emitter.emit("active-profile", label);
26385
+ }
26386
+ /** Subscribe to active-profile changes. */
26387
+ onActiveProfile(fn) {
26388
+ this.emitter.on("active-profile", fn);
26389
+ return () => this.emitter.off("active-profile", fn);
26390
+ }
26391
+ /** The link scope learned from the server, or null (fall back to the `.cre` value). */
26392
+ get scope() {
26393
+ return this.currentScope;
26394
+ }
26395
+ /** Set the link scope (learned at connect — heals the header). */
26396
+ setScope(scope) {
26397
+ this.currentScope = scope;
26398
+ this.emitter.emit("scope", scope);
26399
+ }
26400
+ /** Subscribe to scope changes. */
26401
+ onScope(fn) {
26402
+ this.emitter.on("scope", fn);
26403
+ return () => this.emitter.off("scope", fn);
26404
+ }
26321
26405
  /**
26322
26406
  * Subscribe to transcript entries. Any entries emitted before this subscription
26323
26407
  * are replayed first (so a consumer that mounts a tick late — e.g. React effects —
@@ -31680,25 +31764,6 @@ var getInstance = (stdout, createInstance) => {
31680
31764
 
31681
31765
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/components/Static.js
31682
31766
  var import_react11 = __toESM(require_react(), 1);
31683
- function Static(props) {
31684
- const { items, children: render2, style: customStyle } = props;
31685
- const [index, setIndex] = (0, import_react11.useState)(0);
31686
- const itemsToRender = (0, import_react11.useMemo)(() => {
31687
- return items.slice(index);
31688
- }, [items, index]);
31689
- (0, import_react11.useLayoutEffect)(() => {
31690
- setIndex(items.length);
31691
- }, [items.length]);
31692
- const children = itemsToRender.map((item, itemIndex) => {
31693
- return render2(item, index + itemIndex);
31694
- });
31695
- const style = (0, import_react11.useMemo)(() => ({
31696
- position: "absolute",
31697
- flexDirection: "column",
31698
- ...customStyle
31699
- }), [customStyle]);
31700
- return import_react11.default.createElement("ink-box", { internal_static: true, style }, children);
31701
- }
31702
31767
 
31703
31768
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/components/Transform.js
31704
31769
  var import_react12 = __toESM(require_react(), 1);
@@ -32024,12 +32089,22 @@ function appendInputHistory(profileDir2, line, current) {
32024
32089
  // src/ui/banner.tsx
32025
32090
  var import_react22 = __toESM(require_react(), 1);
32026
32091
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
32092
+ function dotColor(status) {
32093
+ if (status === "connected") return "green";
32094
+ if (status === "stopped") return "red";
32095
+ return "yellow";
32096
+ }
32027
32097
  function scopeLabel(scope) {
32028
32098
  if (scope === "project") return "project (MACHINE)";
32029
32099
  if (scope === "orchestrator") return "orchestrator (root)";
32030
32100
  return scope;
32031
32101
  }
32032
- function Banner({ info }) {
32102
+ function Banner({
32103
+ info,
32104
+ scope,
32105
+ status,
32106
+ activeProfile
32107
+ }) {
32033
32108
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "row", paddingX: 1, paddingTop: 1, children: [
32034
32109
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginRight: 2, flexDirection: "column", children: [
32035
32110
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", bold: true, children: "\u250C\u2500\u2510" }),
@@ -32046,20 +32121,32 @@ function Banner({ info }) {
32046
32121
  ] }),
32047
32122
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
32048
32123
  "scope: ",
32049
- scopeLabel(info.scope),
32124
+ scopeLabel(scope),
32050
32125
  " \xB7 profile: ",
32051
32126
  info.profile
32052
32127
  ] }),
32053
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
32128
+ scope === "orchestrator" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
32054
32129
  "server: ",
32055
- info.serverUrl
32056
- ] }),
32057
- info.scope === "orchestrator" ? (
32058
- // Orchestrator cli is not tied to a physic project (ADR-0010) — no serving line.
32059
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: "mode: orchestrator (short-lived AI requests)" })
32060
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
32061
- "serving: ",
32130
+ info.serverUrl,
32131
+ " \xB7 mode: orchestrator (short-lived AI)"
32132
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
32133
+ "server: ",
32134
+ info.serverUrl,
32135
+ " \xB7 serving: ",
32062
32136
  info.physicPath ?? "(no physic project)"
32137
+ ] }),
32138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
32139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: dotColor(status), children: "\u25CF" }),
32140
+ " ",
32141
+ status,
32142
+ " \xB7 using",
32143
+ " ",
32144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", children: info.aiCli }),
32145
+ activeProfile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
32146
+ " ",
32147
+ "\xB7 profile ",
32148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", children: activeProfile })
32149
+ ] }) : null
32063
32150
  ] })
32064
32151
  ] })
32065
32152
  ] });
@@ -32112,93 +32199,6 @@ function TranscriptLine({ line }) {
32112
32199
 
32113
32200
  // src/ui/command-input.tsx
32114
32201
  var import_react24 = __toESM(require_react(), 1);
32115
- var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
32116
- function dotColor(status) {
32117
- if (status === "connected") return "green";
32118
- if (status === "stopped") return "red";
32119
- return "yellow";
32120
- }
32121
- function StatusHint({
32122
- status,
32123
- aiCli: aiCli2
32124
- }) {
32125
- const connected = status === "connected";
32126
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, children: [
32127
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: dotColor(status), children: "\u25CF" }),
32128
- " ",
32129
- status,
32130
- connected ? ` \xB7 prompt \u2192 ${aiCli2} \xB7 /help \xB7 \u2191\u2193 history \xB7 Ctrl+C quit` : " \xB7 waiting for connection\u2026"
32131
- ] });
32132
- }
32133
- function CommandInput({
32134
- status,
32135
- history,
32136
- onSubmit
32137
- }) {
32138
- const { exit } = use_app_default();
32139
- const [value, setValue] = (0, import_react24.useState)("");
32140
- const [navIndex, setNavIndex] = (0, import_react24.useState)(null);
32141
- const connected = status === "connected";
32142
- use_input_default((input, key) => {
32143
- if (key.ctrl && (input === "c" || input === "d")) {
32144
- exit();
32145
- return;
32146
- }
32147
- if (key.upArrow) {
32148
- if (history.length === 0) return;
32149
- const next = navIndex === null ? history.length - 1 : Math.max(0, navIndex - 1);
32150
- setNavIndex(next);
32151
- setValue(history[next] ?? "");
32152
- return;
32153
- }
32154
- if (key.downArrow) {
32155
- if (navIndex === null) return;
32156
- const next = navIndex + 1;
32157
- if (next >= history.length) {
32158
- setNavIndex(null);
32159
- setValue("");
32160
- } else {
32161
- setNavIndex(next);
32162
- setValue(history[next] ?? "");
32163
- }
32164
- return;
32165
- }
32166
- if (key.return) {
32167
- const trimmed = value.trim();
32168
- if (trimmed.length > 0) onSubmit(trimmed);
32169
- setValue("");
32170
- setNavIndex(null);
32171
- return;
32172
- }
32173
- if (key.backspace || key.delete) {
32174
- setValue((v) => v.slice(0, -1));
32175
- setNavIndex(null);
32176
- return;
32177
- }
32178
- if (!key.ctrl && !key.meta && input) {
32179
- setValue((v) => v + input);
32180
- setNavIndex(null);
32181
- }
32182
- });
32183
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
32184
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: connected ? "cyan" : "gray", children: "\u203A " }),
32185
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: value }),
32186
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { inverse: true, children: " " })
32187
- ] });
32188
- }
32189
-
32190
- // src/ui/spinner.tsx
32191
- var import_react25 = __toESM(require_react(), 1);
32192
- var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
32193
- var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
32194
- function Spinner() {
32195
- const [index, setIndex] = (0, import_react25.useState)(0);
32196
- (0, import_react25.useEffect)(() => {
32197
- const timer = setInterval(() => setIndex((i) => (i + 1) % FRAMES.length), 90);
32198
- return () => clearInterval(timer);
32199
- }, []);
32200
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", children: FRAMES[index] });
32201
- }
32202
32202
 
32203
32203
  // src/ui/slash-commands.ts
32204
32204
  import { existsSync as existsSync9 } from "fs";
@@ -32343,6 +32343,19 @@ var COMMANDS = [
32343
32343
  description: "View / init / update / delete profile config",
32344
32344
  run: runConfig
32345
32345
  },
32346
+ {
32347
+ name: "claude-cmd",
32348
+ usage: "/claude-cmd /context",
32349
+ description: "Run an AI-CLI slash command (e.g. /context, /usage, /status)",
32350
+ run: (ctx) => {
32351
+ const rest = ctx.args.join(" ").trim();
32352
+ if (!rest) {
32353
+ ctx.print("usage: /claude-cmd /context (forwards to the AI CLI)", "error");
32354
+ return;
32355
+ }
32356
+ ctx.submitAi(rest);
32357
+ }
32358
+ },
32346
32359
  {
32347
32360
  name: "clear",
32348
32361
  usage: "/clear",
@@ -32357,6 +32370,7 @@ var COMMANDS = [
32357
32370
  }
32358
32371
  ];
32359
32372
  var ALIASES = { exit: "quit" };
32373
+ var SLASH_COMMANDS = COMMANDS.map((c) => ({ name: c.name, usage: c.usage, description: c.description }));
32360
32374
  function runSlashCommand(line, base) {
32361
32375
  const tokens = line.replace(/^\//, "").trim().split(/\s+/);
32362
32376
  const rawName = tokens[0] ?? "";
@@ -32370,10 +32384,121 @@ function runSlashCommand(line, base) {
32370
32384
  command.run(ctx);
32371
32385
  }
32372
32386
 
32387
+ // src/ui/command-input.tsx
32388
+ var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
32389
+ function Hint() {
32390
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: " /help \xB7 Tab complete \xB7 \u2191\u2193 history \xB7 PgUp/PgDn scroll \xB7 Ctrl+C quit" });
32391
+ }
32392
+ function CommandInput({
32393
+ status,
32394
+ history,
32395
+ onSubmit
32396
+ }) {
32397
+ const { exit } = use_app_default();
32398
+ const [value, setValue] = (0, import_react24.useState)("");
32399
+ const [navIndex, setNavIndex] = (0, import_react24.useState)(null);
32400
+ const [sugIndex, setSugIndex] = (0, import_react24.useState)(0);
32401
+ const connected = status === "connected";
32402
+ const matching = value.startsWith("/") && !value.includes(" ") ? SLASH_COMMANDS.filter((c) => c.name.startsWith(value.slice(1))) : [];
32403
+ const menuOpen = matching.length > 0;
32404
+ const selected = matching[Math.min(sugIndex, matching.length - 1)];
32405
+ use_input_default((input, key) => {
32406
+ if (key.ctrl && (input === "c" || input === "d")) {
32407
+ exit();
32408
+ return;
32409
+ }
32410
+ if (key.tab && selected) {
32411
+ setValue(`/${selected.name} `);
32412
+ setSugIndex(0);
32413
+ setNavIndex(null);
32414
+ return;
32415
+ }
32416
+ if (key.upArrow) {
32417
+ if (menuOpen) {
32418
+ setSugIndex((i) => (i - 1 + matching.length) % matching.length);
32419
+ return;
32420
+ }
32421
+ if (history.length === 0) return;
32422
+ const next = navIndex === null ? history.length - 1 : Math.max(0, navIndex - 1);
32423
+ setNavIndex(next);
32424
+ setValue(history[next] ?? "");
32425
+ return;
32426
+ }
32427
+ if (key.downArrow) {
32428
+ if (menuOpen) {
32429
+ setSugIndex((i) => (i + 1) % matching.length);
32430
+ return;
32431
+ }
32432
+ if (navIndex === null) return;
32433
+ const next = navIndex + 1;
32434
+ if (next >= history.length) {
32435
+ setNavIndex(null);
32436
+ setValue("");
32437
+ } else {
32438
+ setNavIndex(next);
32439
+ setValue(history[next] ?? "");
32440
+ }
32441
+ return;
32442
+ }
32443
+ if (key.return) {
32444
+ const trimmed = value.trim();
32445
+ if (trimmed.length > 0) onSubmit(trimmed);
32446
+ setValue("");
32447
+ setNavIndex(null);
32448
+ setSugIndex(0);
32449
+ return;
32450
+ }
32451
+ if (key.backspace || key.delete) {
32452
+ setValue((v) => v.slice(0, -1));
32453
+ setNavIndex(null);
32454
+ setSugIndex(0);
32455
+ return;
32456
+ }
32457
+ if (!key.ctrl && !key.meta && input) {
32458
+ setValue((v) => v + input);
32459
+ setNavIndex(null);
32460
+ setSugIndex(0);
32461
+ }
32462
+ });
32463
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
32464
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { children: menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { children: [
32465
+ matching.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: i === sugIndex ? "cyan" : "gray", inverse: i === sugIndex, children: [
32466
+ " ",
32467
+ "/",
32468
+ c.name,
32469
+ " "
32470
+ ] }, c.name)),
32471
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, children: [
32472
+ " \u2014 ",
32473
+ selected.description,
32474
+ " (Tab)"
32475
+ ] }) : null
32476
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: " " }) }),
32477
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
32478
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: connected ? "cyan" : "gray", children: "\u203A " }),
32479
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: value }),
32480
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { inverse: true, children: " " })
32481
+ ] })
32482
+ ] });
32483
+ }
32484
+
32485
+ // src/ui/spinner.tsx
32486
+ var import_react25 = __toESM(require_react(), 1);
32487
+ var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
32488
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
32489
+ function Spinner() {
32490
+ const [index, setIndex] = (0, import_react25.useState)(0);
32491
+ (0, import_react25.useEffect)(() => {
32492
+ const timer = setInterval(() => setIndex((i) => (i + 1) % FRAMES.length), 90);
32493
+ return () => clearInterval(timer);
32494
+ }, []);
32495
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", children: FRAMES[index] });
32496
+ }
32497
+
32373
32498
  // src/ui/app.tsx
32374
32499
  var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
32375
- var BANNER_ROWS = 5;
32376
- var COMMAND_BLOCK_ROWS = 6;
32500
+ var CHROME_ROWS = 12;
32501
+ var ENTRY_CAP = 3e3;
32377
32502
  function Rule({ width }) {
32378
32503
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "\u2500".repeat(Math.max(10, width)) });
32379
32504
  }
@@ -32399,24 +32524,30 @@ function App2({
32399
32524
  const [entries2, setEntries] = (0, import_react26.useState)([]);
32400
32525
  const [status, setStatus] = (0, import_react26.useState)(bus.status);
32401
32526
  const [busy, setBusy] = (0, import_react26.useState)(bus.busy);
32527
+ const [activeProfile, setActiveProfile] = (0, import_react26.useState)(bus.activeProfile);
32528
+ const [scope, setScope] = (0, import_react26.useState)(bus.scope ?? info.scope);
32529
+ const [scrollBack, setScrollBack] = (0, import_react26.useState)(0);
32402
32530
  const [history, setHistory] = (0, import_react26.useState)(() => loadInputHistory(info.profileDir));
32403
- const [staticKey, setStaticKey] = (0, import_react26.useState)(0);
32404
- const clearTranscript = () => {
32405
- setEntries([]);
32406
- setStaticKey((k) => k + 1);
32407
- stdout.write("\x1B[2J\x1B[3J\x1B[H");
32408
- };
32409
32531
  (0, import_react26.useEffect)(() => {
32410
- const offTranscript = bus.onTranscript((entry) => setEntries((prev) => [...prev, entry]));
32532
+ const offTranscript = bus.onTranscript(
32533
+ (entry) => setEntries(
32534
+ (prev) => prev.length >= ENTRY_CAP ? [...prev.slice(prev.length - ENTRY_CAP + 1), entry] : [...prev, entry]
32535
+ )
32536
+ );
32411
32537
  const offStatus = bus.onStatus(setStatus);
32412
32538
  const offBusy = bus.onBusy(setBusy);
32539
+ const offProfile = bus.onActiveProfile(setActiveProfile);
32540
+ const offScope = bus.onScope(setScope);
32413
32541
  return () => {
32414
32542
  offTranscript();
32415
32543
  offStatus();
32416
32544
  offBusy();
32545
+ offProfile();
32546
+ offScope();
32417
32547
  };
32418
32548
  }, [bus]);
32419
32549
  const handleSubmit = (input) => {
32550
+ setScrollBack(0);
32420
32551
  setHistory((prev) => appendInputHistory(info.profileDir, input, prev));
32421
32552
  const trimmed = input.trim();
32422
32553
  if (trimmed.startsWith("/")) {
@@ -32424,36 +32555,47 @@ function App2({
32424
32555
  runSlashCommand(trimmed, {
32425
32556
  info,
32426
32557
  print: (text, level) => bus.push({ source: "local", kind: "log", text, level }),
32427
- clear: clearTranscript,
32428
- quit: () => exit()
32558
+ clear: () => {
32559
+ setEntries([]);
32560
+ setScrollBack(0);
32561
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
32562
+ },
32563
+ quit: () => exit(),
32564
+ submitAi: (aiInput) => bus.submitLocal(aiInput)
32429
32565
  });
32430
32566
  return;
32431
32567
  }
32432
32568
  bus.submitLocal(input);
32433
32569
  };
32434
- const lines = flattenEntries(entries2);
32435
- const items = [
32436
- { type: "banner" },
32437
- ...lines.map((line) => ({ type: "line", line }))
32438
- ];
32570
+ const bodyHeight = Math.max(3, rows - CHROME_ROWS);
32571
+ const lines = (0, import_react26.useMemo)(() => flattenEntries(entries2), [entries2]);
32572
+ const maxScroll = Math.max(0, lines.length - bodyHeight);
32573
+ const clampedBack = Math.min(scrollBack, maxScroll);
32574
+ const lineRows = clampedBack > 0 ? bodyHeight - 1 : bodyHeight;
32575
+ const start = Math.max(0, lines.length - lineRows - clampedBack);
32576
+ const visible = lines.slice(start, start + lineRows);
32439
32577
  const width = cols - 1;
32440
- const spacer = Math.max(0, rows - (BANNER_ROWS + lines.length) - COMMAND_BLOCK_ROWS);
32441
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
32442
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Static, { items, children: (item, index) => item.type === "banner" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Banner, { info }, "banner") : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TranscriptLine, { line: item.line }, item.line.key || index) }, staticKey),
32443
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
32444
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { height: spacer }),
32445
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { justifyContent: "flex-end", children: busy ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "yellow", children: [
32446
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Spinner, {}),
32447
- " processing \xB7 ",
32448
- busy,
32449
- "\u2026"
32450
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }) }),
32451
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
32452
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CommandInput, { status, history, onSubmit: handleSubmit }),
32453
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
32454
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatusHint, { status, aiCli: info.aiCli }),
32455
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: " tip: mouse wheel = scroll \xB7 drag to select \u2192 right-click to copy" })
32456
- ] })
32578
+ use_input_default((_input, key) => {
32579
+ if (key.pageUp) setScrollBack((b) => Math.min(maxScroll, b + bodyHeight));
32580
+ else if (key.pageDown) setScrollBack((b) => Math.max(0, b - bodyHeight));
32581
+ });
32582
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", height: rows, children: [
32583
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Banner, { info, scope, status, activeProfile }),
32584
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
32585
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", height: bodyHeight, overflow: "hidden", children: [
32586
+ visible.map((line) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TranscriptLine, { line }, line.key)),
32587
+ clampedBack > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: ` \u2191 ${clampedBack} newer line(s) hidden \xB7 PageDown` }) : null
32588
+ ] }),
32589
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { justifyContent: "flex-end", children: busy ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "yellow", children: [
32590
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Spinner, {}),
32591
+ " processing \xB7 ",
32592
+ busy,
32593
+ "\u2026"
32594
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }) }),
32595
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
32596
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CommandInput, { status, history, onSubmit: handleSubmit }),
32597
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
32598
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Hint, {})
32457
32599
  ] });
32458
32600
  }
32459
32601
 
@@ -32539,6 +32681,10 @@ async function runStart(profileDir2, profileName) {
32539
32681
  physicPath: config.physicPath ?? null,
32540
32682
  aiCli: config.aiCli ?? "claude"
32541
32683
  };
32684
+ const aiCli2 = config.aiCli ?? "claude";
32685
+ const workingDir = getWorkingProfile(profileDir2, aiCli2);
32686
+ const labels = profileLabels(config);
32687
+ bus.setActiveProfile(workingDir ? basename4(workingDir) : labels[0] ?? null);
32542
32688
  const tui = runTui(bus, info);
32543
32689
  bus.log(`Connecting to ${credential.serverUrl} (profile: ${profileName})\u2026`);
32544
32690
  void client.run();
@@ -32605,11 +32751,68 @@ async function runVersion(profileDir2, profileName, serverUrlFlag) {
32605
32751
  }
32606
32752
  }
32607
32753
 
32754
+ // src/ui/select-profile.tsx
32755
+ var import_react28 = __toESM(require_react(), 1);
32756
+ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
32757
+ function Picker({
32758
+ items,
32759
+ title,
32760
+ onDone
32761
+ }) {
32762
+ const { exit } = use_app_default();
32763
+ const [index, setIndex] = (0, import_react28.useState)(0);
32764
+ use_input_default((input, key) => {
32765
+ if (key.upArrow) setIndex((i) => (i - 1 + items.length) % items.length);
32766
+ else if (key.downArrow) setIndex((i) => (i + 1) % items.length);
32767
+ else if (key.return) {
32768
+ onDone(items[index]?.value ?? null);
32769
+ exit();
32770
+ } else if (key.escape || key.ctrl && input === "c") {
32771
+ onDone(null);
32772
+ exit();
32773
+ }
32774
+ });
32775
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", paddingY: 1, children: [
32776
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, children: title }),
32777
+ items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: i === index ? "cyan" : void 0, children: [
32778
+ i === index ? "\u276F " : " ",
32779
+ item.label
32780
+ ] }, item.value)),
32781
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: "\u2191\u2193 move \xB7 Enter select \xB7 Esc cancel" })
32782
+ ] });
32783
+ }
32784
+ function selectFromList(items, title) {
32785
+ return new Promise((resolve5) => {
32786
+ let chosen = null;
32787
+ const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v) => chosen = v }));
32788
+ void app.waitUntilExit().then(() => resolve5(chosen));
32789
+ });
32790
+ }
32791
+
32608
32792
  // src/index.ts
32609
32793
  function flagValue(args, name) {
32610
32794
  const index = args.indexOf(`--${name}`);
32611
32795
  return index >= 0 ? args[index + 1] : void 0;
32612
32796
  }
32797
+ async function pickLinkedProfile(explicit, action) {
32798
+ if (explicit) return explicit;
32799
+ const linked = listProfiles().filter((p) => p.linked);
32800
+ if (linked.length === 0) {
32801
+ console.error("No linked profile \u2014 run `4pm link` first.");
32802
+ return null;
32803
+ }
32804
+ if (linked.length === 1) return linked[0].name;
32805
+ if (!process.stdout.isTTY) {
32806
+ console.error(
32807
+ `Multiple profiles \u2014 pass --profile <name> (or run \`4pm ${action}\` in a terminal to pick).`
32808
+ );
32809
+ return null;
32810
+ }
32811
+ return selectFromList(
32812
+ linked.map((p) => ({ label: p.name, value: p.name })),
32813
+ `Select a profile to ${action}:`
32814
+ );
32815
+ }
32613
32816
  async function main() {
32614
32817
  const [explicitProfile, args] = resolveProfileArg(process.argv.slice(2));
32615
32818
  const [command, ...rest] = args;
@@ -32620,12 +32823,20 @@ async function main() {
32620
32823
  break;
32621
32824
  }
32622
32825
  case "start": {
32623
- const name = explicitProfile ?? defaultProfileName();
32826
+ const name = await pickLinkedProfile(explicitProfile, "start");
32827
+ if (!name) {
32828
+ process.exitCode = 1;
32829
+ break;
32830
+ }
32624
32831
  await runStart(profileDir(name), name);
32625
32832
  break;
32626
32833
  }
32627
32834
  case "unlink": {
32628
- const name = explicitProfile ?? defaultProfileName();
32835
+ const name = await pickLinkedProfile(explicitProfile, "unlink");
32836
+ if (!name) {
32837
+ process.exitCode = 1;
32838
+ break;
32839
+ }
32629
32840
  runUnlink(profileDir(name), name);
32630
32841
  break;
32631
32842
  }
@@ -32648,13 +32859,15 @@ async function main() {
32648
32859
  "4pm \u2014 the 4PM worker-machine CLI",
32649
32860
  "",
32650
32861
  "Usage:",
32651
- " 4pm [--profile <name>] link [--server URL] Pair with the server (hashcode 1\u21922\u21923)",
32652
- " 4pm [--profile <name>] start Connect WS, receive commands from the server",
32653
- " 4pm [--profile <name>] unlink Delete the local credential (.cre)",
32654
- " 4pm version [--server URL] Show the installed version (+ latest from the server)",
32655
- " 4pm update [--server URL] Update to the latest version now",
32862
+ " 4pm link Pair with the server (hashcode 1\u21922\u21923)",
32863
+ " 4pm start Connect WS, receive commands from the server",
32864
+ " 4pm unlink Delete a link (pick a profile if several)",
32865
+ " 4pm version Show the installed version (+ latest from the server)",
32866
+ " 4pm update Update to the latest version now",
32656
32867
  "",
32657
- "Without --profile \u21D2 the `default` profile (ADR-0014)."
32868
+ "Profiles: named after the paired account (e.g. mcacc1); when several exist,",
32869
+ "start/unlink show a picker (\u2191\u2193 + Enter) \u2014 no --profile needed.",
32870
+ "Server URL: from FOURPM_SERVER (default http://localhost:3001)."
32658
32871
  ].join("\n")
32659
32872
  );
32660
32873
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "type": "module",