@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.
- package/dist/index.js +401 -188
- 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
|
|
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
|
|
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 =
|
|
2148
|
+
exports.useMemo = useMemo4;
|
|
2149
2149
|
exports.useReducer = useReducer;
|
|
2150
2150
|
exports.useRef = useRef;
|
|
2151
|
-
exports.useState =
|
|
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
|
|
7846
|
+
var React17 = require_react();
|
|
7847
7847
|
var Scheduler = require_scheduler();
|
|
7848
|
-
var ReactSharedInternals =
|
|
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
|
|
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 =
|
|
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
|
|
24370
|
-
var
|
|
24369
|
+
var jsx8 = jsxWithValidationDynamic;
|
|
24370
|
+
var jsxs6 = jsxWithValidationStatic;
|
|
24371
24371
|
exports.Fragment = REACT_FRAGMENT_TYPE;
|
|
24372
|
-
exports.jsx =
|
|
24373
|
-
exports.jsxs =
|
|
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 {
|
|
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,
|
|
24626
|
+
const { hashcode3, username, scope } = await confirmPairing(
|
|
24613
24627
|
serverUrl,
|
|
24614
24628
|
hashcode2,
|
|
24615
24629
|
collectFingerprint()
|
|
24616
24630
|
);
|
|
24617
|
-
const profileName = explicitProfile ??
|
|
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
|
-
|
|
24640
|
+
writeDefaultProfile(profileName);
|
|
24641
|
+
const verb = existed ? "Renewed" : "Added";
|
|
24626
24642
|
console.log(
|
|
24627
|
-
`\u2714
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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)
|
|
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({
|
|
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(
|
|
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
|
-
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
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
|
|
32376
|
-
var
|
|
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(
|
|
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:
|
|
32428
|
-
|
|
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
|
|
32435
|
-
const
|
|
32436
|
-
|
|
32437
|
-
|
|
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
|
-
|
|
32441
|
-
|
|
32442
|
-
|
|
32443
|
-
|
|
32444
|
-
|
|
32445
|
-
|
|
32446
|
-
|
|
32447
|
-
|
|
32448
|
-
|
|
32449
|
-
|
|
32450
|
-
|
|
32451
|
-
|
|
32452
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
32453
|
-
|
|
32454
|
-
|
|
32455
|
-
|
|
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
|
|
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
|
|
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
|
|
32652
|
-
" 4pm
|
|
32653
|
-
" 4pm
|
|
32654
|
-
" 4pm version
|
|
32655
|
-
" 4pm update
|
|
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
|
-
"
|
|
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
|
}
|