@4pm/cli 0.4.1 → 0.4.3
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 +408 -184
- 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
|
|
@@ -24801,6 +24822,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
|
|
|
24801
24822
|
|
|
24802
24823
|
// src/core/ws-client.ts
|
|
24803
24824
|
import { randomUUID } from "crypto";
|
|
24825
|
+
import { basename as basename3 } from "path";
|
|
24804
24826
|
|
|
24805
24827
|
// ../../22-packages/11-shared/51-utils/src/helpers.ts
|
|
24806
24828
|
function sleep(ms) {
|
|
@@ -25375,6 +25397,12 @@ function profileEnvVar(cmd) {
|
|
|
25375
25397
|
if (cmd.includes("codex")) return "CODEX_HOME";
|
|
25376
25398
|
return null;
|
|
25377
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
|
+
}
|
|
25378
25406
|
function planAiRun(prompt, config, workingDir) {
|
|
25379
25407
|
const cmd = config.aiCli ?? DEFAULT_AI_CLI;
|
|
25380
25408
|
const args = [...config.aiArgs ?? DEFAULT_AI_ARGS[cmd] ?? [], prompt];
|
|
@@ -25858,6 +25886,13 @@ var WsClient = class {
|
|
|
25858
25886
|
this.bus.setStatus("connecting");
|
|
25859
25887
|
const token = await requestWsToken(this.credential.serverUrl, this.credential.hashcode3);
|
|
25860
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
|
+
}
|
|
25861
25896
|
const wsUrl = this.credential.serverUrl.replace(/^http/, "ws") + "/ws";
|
|
25862
25897
|
const socket = new wrapper_default(wsUrl);
|
|
25863
25898
|
this.socket = socket;
|
|
@@ -25904,6 +25939,7 @@ var WsClient = class {
|
|
|
25904
25939
|
if ("type" in message) {
|
|
25905
25940
|
if (message.type === "hello_ack" && this.ecdh) {
|
|
25906
25941
|
this.sessionKey = deriveSessionKey(this.ecdh, message.pubkey);
|
|
25942
|
+
if (message.workerName) this.bus.setWorker(message.workerName);
|
|
25907
25943
|
logger.info("ws.connected", { version: CLI_VERSION });
|
|
25908
25944
|
this.bus.setStatus("connected");
|
|
25909
25945
|
this.bus.log("\u2714 Connected to the server (session encryption ready).");
|
|
@@ -26088,7 +26124,10 @@ var WsClient = class {
|
|
|
26088
26124
|
})
|
|
26089
26125
|
});
|
|
26090
26126
|
this.bus.endBusy(plan.cmd);
|
|
26091
|
-
if (result.workedDir)
|
|
26127
|
+
if (result.workedDir) {
|
|
26128
|
+
setWorkingProfile(this.context.profileDir, plan.cmd, result.workedDir);
|
|
26129
|
+
this.bus.setActiveProfile(basename3(result.workedDir));
|
|
26130
|
+
}
|
|
26092
26131
|
this.send(WsChannels.COMMAND_OUTPUT, {
|
|
26093
26132
|
commandId,
|
|
26094
26133
|
seq: seq++,
|
|
@@ -26265,6 +26304,9 @@ function releaseInstanceLock(profileDir2) {
|
|
|
26265
26304
|
}
|
|
26266
26305
|
}
|
|
26267
26306
|
|
|
26307
|
+
// src/commands/start.ts
|
|
26308
|
+
import { basename as basename4 } from "path";
|
|
26309
|
+
|
|
26268
26310
|
// src/core/session-bus.ts
|
|
26269
26311
|
import { EventEmitter } from "events";
|
|
26270
26312
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -26276,6 +26318,12 @@ var SessionBus = class {
|
|
|
26276
26318
|
history = [];
|
|
26277
26319
|
/** Labels of in-flight commands (tool names) — top = the one shown as "processing". */
|
|
26278
26320
|
busyStack = [];
|
|
26321
|
+
/** The AI-CLI profile that last authenticated (shown in the header). */
|
|
26322
|
+
currentActiveProfile = null;
|
|
26323
|
+
/** Link scope learned from the server at connect time (heals the header/.cre). */
|
|
26324
|
+
currentScope = null;
|
|
26325
|
+
/** Worker (physical machine) name learned from the server at connect time. */
|
|
26326
|
+
currentWorker = null;
|
|
26279
26327
|
constructor() {
|
|
26280
26328
|
this.emitter.setMaxListeners(50);
|
|
26281
26329
|
}
|
|
@@ -26329,6 +26377,48 @@ var SessionBus = class {
|
|
|
26329
26377
|
this.emitter.on("busy", fn);
|
|
26330
26378
|
return () => this.emitter.off("busy", fn);
|
|
26331
26379
|
}
|
|
26380
|
+
/** The AI-CLI profile currently in use (last authenticated), or null. */
|
|
26381
|
+
get activeProfile() {
|
|
26382
|
+
return this.currentActiveProfile;
|
|
26383
|
+
}
|
|
26384
|
+
/** Set the active AI-CLI profile (shown in the header). */
|
|
26385
|
+
setActiveProfile(label) {
|
|
26386
|
+
this.currentActiveProfile = label;
|
|
26387
|
+
this.emitter.emit("active-profile", label);
|
|
26388
|
+
}
|
|
26389
|
+
/** Subscribe to active-profile changes. */
|
|
26390
|
+
onActiveProfile(fn) {
|
|
26391
|
+
this.emitter.on("active-profile", fn);
|
|
26392
|
+
return () => this.emitter.off("active-profile", fn);
|
|
26393
|
+
}
|
|
26394
|
+
/** The link scope learned from the server, or null (fall back to the `.cre` value). */
|
|
26395
|
+
get scope() {
|
|
26396
|
+
return this.currentScope;
|
|
26397
|
+
}
|
|
26398
|
+
/** Set the link scope (learned at connect — heals the header). */
|
|
26399
|
+
setScope(scope) {
|
|
26400
|
+
this.currentScope = scope;
|
|
26401
|
+
this.emitter.emit("scope", scope);
|
|
26402
|
+
}
|
|
26403
|
+
/** Subscribe to scope changes. */
|
|
26404
|
+
onScope(fn) {
|
|
26405
|
+
this.emitter.on("scope", fn);
|
|
26406
|
+
return () => this.emitter.off("scope", fn);
|
|
26407
|
+
}
|
|
26408
|
+
/** The worker (physical machine) name learned from the server at connect, or null. */
|
|
26409
|
+
get worker() {
|
|
26410
|
+
return this.currentWorker;
|
|
26411
|
+
}
|
|
26412
|
+
/** Set the worker name (learned from the hello_ack — heals the header). */
|
|
26413
|
+
setWorker(worker) {
|
|
26414
|
+
this.currentWorker = worker;
|
|
26415
|
+
this.emitter.emit("worker", worker);
|
|
26416
|
+
}
|
|
26417
|
+
/** Subscribe to worker-name changes. */
|
|
26418
|
+
onWorker(fn) {
|
|
26419
|
+
this.emitter.on("worker", fn);
|
|
26420
|
+
return () => this.emitter.off("worker", fn);
|
|
26421
|
+
}
|
|
26332
26422
|
/**
|
|
26333
26423
|
* Subscribe to transcript entries. Any entries emitted before this subscription
|
|
26334
26424
|
* are replayed first (so a consumer that mounts a tick late — e.g. React effects —
|
|
@@ -31691,25 +31781,6 @@ var getInstance = (stdout, createInstance) => {
|
|
|
31691
31781
|
|
|
31692
31782
|
// ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/components/Static.js
|
|
31693
31783
|
var import_react11 = __toESM(require_react(), 1);
|
|
31694
|
-
function Static(props) {
|
|
31695
|
-
const { items, children: render2, style: customStyle } = props;
|
|
31696
|
-
const [index, setIndex] = (0, import_react11.useState)(0);
|
|
31697
|
-
const itemsToRender = (0, import_react11.useMemo)(() => {
|
|
31698
|
-
return items.slice(index);
|
|
31699
|
-
}, [items, index]);
|
|
31700
|
-
(0, import_react11.useLayoutEffect)(() => {
|
|
31701
|
-
setIndex(items.length);
|
|
31702
|
-
}, [items.length]);
|
|
31703
|
-
const children = itemsToRender.map((item, itemIndex) => {
|
|
31704
|
-
return render2(item, index + itemIndex);
|
|
31705
|
-
});
|
|
31706
|
-
const style = (0, import_react11.useMemo)(() => ({
|
|
31707
|
-
position: "absolute",
|
|
31708
|
-
flexDirection: "column",
|
|
31709
|
-
...customStyle
|
|
31710
|
-
}), [customStyle]);
|
|
31711
|
-
return import_react11.default.createElement("ink-box", { internal_static: true, style }, children);
|
|
31712
|
-
}
|
|
31713
31784
|
|
|
31714
31785
|
// ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/components/Transform.js
|
|
31715
31786
|
var import_react12 = __toESM(require_react(), 1);
|
|
@@ -32035,12 +32106,23 @@ function appendInputHistory(profileDir2, line, current) {
|
|
|
32035
32106
|
// src/ui/banner.tsx
|
|
32036
32107
|
var import_react22 = __toESM(require_react(), 1);
|
|
32037
32108
|
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
|
|
32109
|
+
function dotColor(status) {
|
|
32110
|
+
if (status === "connected") return "green";
|
|
32111
|
+
if (status === "stopped") return "red";
|
|
32112
|
+
return "yellow";
|
|
32113
|
+
}
|
|
32038
32114
|
function scopeLabel(scope) {
|
|
32039
32115
|
if (scope === "project") return "project (MACHINE)";
|
|
32040
32116
|
if (scope === "orchestrator") return "orchestrator (root)";
|
|
32041
32117
|
return scope;
|
|
32042
32118
|
}
|
|
32043
|
-
function Banner({
|
|
32119
|
+
function Banner({
|
|
32120
|
+
info,
|
|
32121
|
+
scope,
|
|
32122
|
+
worker,
|
|
32123
|
+
status,
|
|
32124
|
+
activeProfile
|
|
32125
|
+
}) {
|
|
32044
32126
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "row", paddingX: 1, paddingTop: 1, children: [
|
|
32045
32127
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginRight: 2, flexDirection: "column", children: [
|
|
32046
32128
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", bold: true, children: "\u250C\u2500\u2510" }),
|
|
@@ -32057,20 +32139,33 @@ function Banner({ info }) {
|
|
|
32057
32139
|
] }),
|
|
32058
32140
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
|
|
32059
32141
|
"scope: ",
|
|
32060
|
-
scopeLabel(
|
|
32142
|
+
scopeLabel(scope),
|
|
32061
32143
|
" \xB7 profile: ",
|
|
32062
|
-
info.profile
|
|
32144
|
+
info.profile,
|
|
32145
|
+
worker ? ` \xB7 worker: ${worker}` : ""
|
|
32063
32146
|
] }),
|
|
32064
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
|
|
32147
|
+
scope === "orchestrator" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
|
|
32065
32148
|
"server: ",
|
|
32066
|
-
info.serverUrl
|
|
32067
|
-
|
|
32068
|
-
|
|
32069
|
-
|
|
32070
|
-
|
|
32071
|
-
|
|
32072
|
-
"serving: ",
|
|
32149
|
+
info.serverUrl,
|
|
32150
|
+
" \xB7 mode: orchestrator (short-lived AI)"
|
|
32151
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
|
|
32152
|
+
"server: ",
|
|
32153
|
+
info.serverUrl,
|
|
32154
|
+
" \xB7 serving: ",
|
|
32073
32155
|
info.physicPath ?? "(no physic project)"
|
|
32156
|
+
] }),
|
|
32157
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
|
|
32158
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: dotColor(status), children: "\u25CF" }),
|
|
32159
|
+
" ",
|
|
32160
|
+
status,
|
|
32161
|
+
" \xB7 using",
|
|
32162
|
+
" ",
|
|
32163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", children: info.aiCli }),
|
|
32164
|
+
activeProfile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
|
|
32165
|
+
" ",
|
|
32166
|
+
"\xB7 profile ",
|
|
32167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", children: activeProfile })
|
|
32168
|
+
] }) : null
|
|
32074
32169
|
] })
|
|
32075
32170
|
] })
|
|
32076
32171
|
] });
|
|
@@ -32123,93 +32218,6 @@ function TranscriptLine({ line }) {
|
|
|
32123
32218
|
|
|
32124
32219
|
// src/ui/command-input.tsx
|
|
32125
32220
|
var import_react24 = __toESM(require_react(), 1);
|
|
32126
|
-
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
|
32127
|
-
function dotColor(status) {
|
|
32128
|
-
if (status === "connected") return "green";
|
|
32129
|
-
if (status === "stopped") return "red";
|
|
32130
|
-
return "yellow";
|
|
32131
|
-
}
|
|
32132
|
-
function StatusHint({
|
|
32133
|
-
status,
|
|
32134
|
-
aiCli: aiCli2
|
|
32135
|
-
}) {
|
|
32136
|
-
const connected = status === "connected";
|
|
32137
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, children: [
|
|
32138
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: dotColor(status), children: "\u25CF" }),
|
|
32139
|
-
" ",
|
|
32140
|
-
status,
|
|
32141
|
-
connected ? ` \xB7 prompt \u2192 ${aiCli2} \xB7 /help \xB7 \u2191\u2193 history \xB7 Ctrl+C quit` : " \xB7 waiting for connection\u2026"
|
|
32142
|
-
] });
|
|
32143
|
-
}
|
|
32144
|
-
function CommandInput({
|
|
32145
|
-
status,
|
|
32146
|
-
history,
|
|
32147
|
-
onSubmit
|
|
32148
|
-
}) {
|
|
32149
|
-
const { exit } = use_app_default();
|
|
32150
|
-
const [value, setValue] = (0, import_react24.useState)("");
|
|
32151
|
-
const [navIndex, setNavIndex] = (0, import_react24.useState)(null);
|
|
32152
|
-
const connected = status === "connected";
|
|
32153
|
-
use_input_default((input, key) => {
|
|
32154
|
-
if (key.ctrl && (input === "c" || input === "d")) {
|
|
32155
|
-
exit();
|
|
32156
|
-
return;
|
|
32157
|
-
}
|
|
32158
|
-
if (key.upArrow) {
|
|
32159
|
-
if (history.length === 0) return;
|
|
32160
|
-
const next = navIndex === null ? history.length - 1 : Math.max(0, navIndex - 1);
|
|
32161
|
-
setNavIndex(next);
|
|
32162
|
-
setValue(history[next] ?? "");
|
|
32163
|
-
return;
|
|
32164
|
-
}
|
|
32165
|
-
if (key.downArrow) {
|
|
32166
|
-
if (navIndex === null) return;
|
|
32167
|
-
const next = navIndex + 1;
|
|
32168
|
-
if (next >= history.length) {
|
|
32169
|
-
setNavIndex(null);
|
|
32170
|
-
setValue("");
|
|
32171
|
-
} else {
|
|
32172
|
-
setNavIndex(next);
|
|
32173
|
-
setValue(history[next] ?? "");
|
|
32174
|
-
}
|
|
32175
|
-
return;
|
|
32176
|
-
}
|
|
32177
|
-
if (key.return) {
|
|
32178
|
-
const trimmed = value.trim();
|
|
32179
|
-
if (trimmed.length > 0) onSubmit(trimmed);
|
|
32180
|
-
setValue("");
|
|
32181
|
-
setNavIndex(null);
|
|
32182
|
-
return;
|
|
32183
|
-
}
|
|
32184
|
-
if (key.backspace || key.delete) {
|
|
32185
|
-
setValue((v) => v.slice(0, -1));
|
|
32186
|
-
setNavIndex(null);
|
|
32187
|
-
return;
|
|
32188
|
-
}
|
|
32189
|
-
if (!key.ctrl && !key.meta && input) {
|
|
32190
|
-
setValue((v) => v + input);
|
|
32191
|
-
setNavIndex(null);
|
|
32192
|
-
}
|
|
32193
|
-
});
|
|
32194
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
|
|
32195
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: connected ? "cyan" : "gray", children: "\u203A " }),
|
|
32196
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: value }),
|
|
32197
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { inverse: true, children: " " })
|
|
32198
|
-
] });
|
|
32199
|
-
}
|
|
32200
|
-
|
|
32201
|
-
// src/ui/spinner.tsx
|
|
32202
|
-
var import_react25 = __toESM(require_react(), 1);
|
|
32203
|
-
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
|
|
32204
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
32205
|
-
function Spinner() {
|
|
32206
|
-
const [index, setIndex] = (0, import_react25.useState)(0);
|
|
32207
|
-
(0, import_react25.useEffect)(() => {
|
|
32208
|
-
const timer = setInterval(() => setIndex((i) => (i + 1) % FRAMES.length), 90);
|
|
32209
|
-
return () => clearInterval(timer);
|
|
32210
|
-
}, []);
|
|
32211
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", children: FRAMES[index] });
|
|
32212
|
-
}
|
|
32213
32221
|
|
|
32214
32222
|
// src/ui/slash-commands.ts
|
|
32215
32223
|
import { existsSync as existsSync9 } from "fs";
|
|
@@ -32354,6 +32362,19 @@ var COMMANDS = [
|
|
|
32354
32362
|
description: "View / init / update / delete profile config",
|
|
32355
32363
|
run: runConfig
|
|
32356
32364
|
},
|
|
32365
|
+
{
|
|
32366
|
+
name: "claude-cmd",
|
|
32367
|
+
usage: "/claude-cmd /context",
|
|
32368
|
+
description: "Run an AI-CLI slash command (e.g. /context, /usage, /status)",
|
|
32369
|
+
run: (ctx) => {
|
|
32370
|
+
const rest = ctx.args.join(" ").trim();
|
|
32371
|
+
if (!rest) {
|
|
32372
|
+
ctx.print("usage: /claude-cmd /context (forwards to the AI CLI)", "error");
|
|
32373
|
+
return;
|
|
32374
|
+
}
|
|
32375
|
+
ctx.submitAi(rest);
|
|
32376
|
+
}
|
|
32377
|
+
},
|
|
32357
32378
|
{
|
|
32358
32379
|
name: "clear",
|
|
32359
32380
|
usage: "/clear",
|
|
@@ -32368,6 +32389,7 @@ var COMMANDS = [
|
|
|
32368
32389
|
}
|
|
32369
32390
|
];
|
|
32370
32391
|
var ALIASES = { exit: "quit" };
|
|
32392
|
+
var SLASH_COMMANDS = COMMANDS.map((c) => ({ name: c.name, usage: c.usage, description: c.description }));
|
|
32371
32393
|
function runSlashCommand(line, base) {
|
|
32372
32394
|
const tokens = line.replace(/^\//, "").trim().split(/\s+/);
|
|
32373
32395
|
const rawName = tokens[0] ?? "";
|
|
@@ -32381,10 +32403,121 @@ function runSlashCommand(line, base) {
|
|
|
32381
32403
|
command.run(ctx);
|
|
32382
32404
|
}
|
|
32383
32405
|
|
|
32406
|
+
// src/ui/command-input.tsx
|
|
32407
|
+
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
|
32408
|
+
function Hint() {
|
|
32409
|
+
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" });
|
|
32410
|
+
}
|
|
32411
|
+
function CommandInput({
|
|
32412
|
+
status,
|
|
32413
|
+
history,
|
|
32414
|
+
onSubmit
|
|
32415
|
+
}) {
|
|
32416
|
+
const { exit } = use_app_default();
|
|
32417
|
+
const [value, setValue] = (0, import_react24.useState)("");
|
|
32418
|
+
const [navIndex, setNavIndex] = (0, import_react24.useState)(null);
|
|
32419
|
+
const [sugIndex, setSugIndex] = (0, import_react24.useState)(0);
|
|
32420
|
+
const connected = status === "connected";
|
|
32421
|
+
const matching = value.startsWith("/") && !value.includes(" ") ? SLASH_COMMANDS.filter((c) => c.name.startsWith(value.slice(1))) : [];
|
|
32422
|
+
const menuOpen = matching.length > 0;
|
|
32423
|
+
const selected = matching[Math.min(sugIndex, matching.length - 1)];
|
|
32424
|
+
use_input_default((input, key) => {
|
|
32425
|
+
if (key.ctrl && (input === "c" || input === "d")) {
|
|
32426
|
+
exit();
|
|
32427
|
+
return;
|
|
32428
|
+
}
|
|
32429
|
+
if (key.tab && selected) {
|
|
32430
|
+
setValue(`/${selected.name} `);
|
|
32431
|
+
setSugIndex(0);
|
|
32432
|
+
setNavIndex(null);
|
|
32433
|
+
return;
|
|
32434
|
+
}
|
|
32435
|
+
if (key.upArrow) {
|
|
32436
|
+
if (menuOpen) {
|
|
32437
|
+
setSugIndex((i) => (i - 1 + matching.length) % matching.length);
|
|
32438
|
+
return;
|
|
32439
|
+
}
|
|
32440
|
+
if (history.length === 0) return;
|
|
32441
|
+
const next = navIndex === null ? history.length - 1 : Math.max(0, navIndex - 1);
|
|
32442
|
+
setNavIndex(next);
|
|
32443
|
+
setValue(history[next] ?? "");
|
|
32444
|
+
return;
|
|
32445
|
+
}
|
|
32446
|
+
if (key.downArrow) {
|
|
32447
|
+
if (menuOpen) {
|
|
32448
|
+
setSugIndex((i) => (i + 1) % matching.length);
|
|
32449
|
+
return;
|
|
32450
|
+
}
|
|
32451
|
+
if (navIndex === null) return;
|
|
32452
|
+
const next = navIndex + 1;
|
|
32453
|
+
if (next >= history.length) {
|
|
32454
|
+
setNavIndex(null);
|
|
32455
|
+
setValue("");
|
|
32456
|
+
} else {
|
|
32457
|
+
setNavIndex(next);
|
|
32458
|
+
setValue(history[next] ?? "");
|
|
32459
|
+
}
|
|
32460
|
+
return;
|
|
32461
|
+
}
|
|
32462
|
+
if (key.return) {
|
|
32463
|
+
const trimmed = value.trim();
|
|
32464
|
+
if (trimmed.length > 0) onSubmit(trimmed);
|
|
32465
|
+
setValue("");
|
|
32466
|
+
setNavIndex(null);
|
|
32467
|
+
setSugIndex(0);
|
|
32468
|
+
return;
|
|
32469
|
+
}
|
|
32470
|
+
if (key.backspace || key.delete) {
|
|
32471
|
+
setValue((v) => v.slice(0, -1));
|
|
32472
|
+
setNavIndex(null);
|
|
32473
|
+
setSugIndex(0);
|
|
32474
|
+
return;
|
|
32475
|
+
}
|
|
32476
|
+
if (!key.ctrl && !key.meta && input) {
|
|
32477
|
+
setValue((v) => v + input);
|
|
32478
|
+
setNavIndex(null);
|
|
32479
|
+
setSugIndex(0);
|
|
32480
|
+
}
|
|
32481
|
+
});
|
|
32482
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
32483
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { children: menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { children: [
|
|
32484
|
+
matching.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: i === sugIndex ? "cyan" : "gray", inverse: i === sugIndex, children: [
|
|
32485
|
+
" ",
|
|
32486
|
+
"/",
|
|
32487
|
+
c.name,
|
|
32488
|
+
" "
|
|
32489
|
+
] }, c.name)),
|
|
32490
|
+
selected ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, children: [
|
|
32491
|
+
" \u2014 ",
|
|
32492
|
+
selected.description,
|
|
32493
|
+
" (Tab)"
|
|
32494
|
+
] }) : null
|
|
32495
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: " " }) }),
|
|
32496
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
|
|
32497
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: connected ? "cyan" : "gray", children: "\u203A " }),
|
|
32498
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: value }),
|
|
32499
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { inverse: true, children: " " })
|
|
32500
|
+
] })
|
|
32501
|
+
] });
|
|
32502
|
+
}
|
|
32503
|
+
|
|
32504
|
+
// src/ui/spinner.tsx
|
|
32505
|
+
var import_react25 = __toESM(require_react(), 1);
|
|
32506
|
+
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
|
|
32507
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
32508
|
+
function Spinner() {
|
|
32509
|
+
const [index, setIndex] = (0, import_react25.useState)(0);
|
|
32510
|
+
(0, import_react25.useEffect)(() => {
|
|
32511
|
+
const timer = setInterval(() => setIndex((i) => (i + 1) % FRAMES.length), 90);
|
|
32512
|
+
return () => clearInterval(timer);
|
|
32513
|
+
}, []);
|
|
32514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", children: FRAMES[index] });
|
|
32515
|
+
}
|
|
32516
|
+
|
|
32384
32517
|
// src/ui/app.tsx
|
|
32385
32518
|
var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
|
|
32386
|
-
var
|
|
32387
|
-
var
|
|
32519
|
+
var CHROME_ROWS = 12;
|
|
32520
|
+
var ENTRY_CAP = 3e3;
|
|
32388
32521
|
function Rule({ width }) {
|
|
32389
32522
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "\u2500".repeat(Math.max(10, width)) });
|
|
32390
32523
|
}
|
|
@@ -32410,24 +32543,33 @@ function App2({
|
|
|
32410
32543
|
const [entries2, setEntries] = (0, import_react26.useState)([]);
|
|
32411
32544
|
const [status, setStatus] = (0, import_react26.useState)(bus.status);
|
|
32412
32545
|
const [busy, setBusy] = (0, import_react26.useState)(bus.busy);
|
|
32546
|
+
const [activeProfile, setActiveProfile] = (0, import_react26.useState)(bus.activeProfile);
|
|
32547
|
+
const [scope, setScope] = (0, import_react26.useState)(bus.scope ?? info.scope);
|
|
32548
|
+
const [worker, setWorker] = (0, import_react26.useState)(bus.worker);
|
|
32549
|
+
const [scrollBack, setScrollBack] = (0, import_react26.useState)(0);
|
|
32413
32550
|
const [history, setHistory] = (0, import_react26.useState)(() => loadInputHistory(info.profileDir));
|
|
32414
|
-
const [staticKey, setStaticKey] = (0, import_react26.useState)(0);
|
|
32415
|
-
const clearTranscript = () => {
|
|
32416
|
-
setEntries([]);
|
|
32417
|
-
setStaticKey((k) => k + 1);
|
|
32418
|
-
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
32419
|
-
};
|
|
32420
32551
|
(0, import_react26.useEffect)(() => {
|
|
32421
|
-
const offTranscript = bus.onTranscript(
|
|
32552
|
+
const offTranscript = bus.onTranscript(
|
|
32553
|
+
(entry) => setEntries(
|
|
32554
|
+
(prev) => prev.length >= ENTRY_CAP ? [...prev.slice(prev.length - ENTRY_CAP + 1), entry] : [...prev, entry]
|
|
32555
|
+
)
|
|
32556
|
+
);
|
|
32422
32557
|
const offStatus = bus.onStatus(setStatus);
|
|
32423
32558
|
const offBusy = bus.onBusy(setBusy);
|
|
32559
|
+
const offProfile = bus.onActiveProfile(setActiveProfile);
|
|
32560
|
+
const offScope = bus.onScope(setScope);
|
|
32561
|
+
const offWorker = bus.onWorker(setWorker);
|
|
32424
32562
|
return () => {
|
|
32425
32563
|
offTranscript();
|
|
32426
32564
|
offStatus();
|
|
32427
32565
|
offBusy();
|
|
32566
|
+
offProfile();
|
|
32567
|
+
offScope();
|
|
32568
|
+
offWorker();
|
|
32428
32569
|
};
|
|
32429
32570
|
}, [bus]);
|
|
32430
32571
|
const handleSubmit = (input) => {
|
|
32572
|
+
setScrollBack(0);
|
|
32431
32573
|
setHistory((prev) => appendInputHistory(info.profileDir, input, prev));
|
|
32432
32574
|
const trimmed = input.trim();
|
|
32433
32575
|
if (trimmed.startsWith("/")) {
|
|
@@ -32435,36 +32577,47 @@ function App2({
|
|
|
32435
32577
|
runSlashCommand(trimmed, {
|
|
32436
32578
|
info,
|
|
32437
32579
|
print: (text, level) => bus.push({ source: "local", kind: "log", text, level }),
|
|
32438
|
-
clear:
|
|
32439
|
-
|
|
32580
|
+
clear: () => {
|
|
32581
|
+
setEntries([]);
|
|
32582
|
+
setScrollBack(0);
|
|
32583
|
+
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
32584
|
+
},
|
|
32585
|
+
quit: () => exit(),
|
|
32586
|
+
submitAi: (aiInput) => bus.submitLocal(aiInput)
|
|
32440
32587
|
});
|
|
32441
32588
|
return;
|
|
32442
32589
|
}
|
|
32443
32590
|
bus.submitLocal(input);
|
|
32444
32591
|
};
|
|
32445
|
-
const
|
|
32446
|
-
const
|
|
32447
|
-
|
|
32448
|
-
|
|
32449
|
-
|
|
32592
|
+
const bodyHeight = Math.max(3, rows - CHROME_ROWS);
|
|
32593
|
+
const lines = (0, import_react26.useMemo)(() => flattenEntries(entries2), [entries2]);
|
|
32594
|
+
const maxScroll = Math.max(0, lines.length - bodyHeight);
|
|
32595
|
+
const clampedBack = Math.min(scrollBack, maxScroll);
|
|
32596
|
+
const lineRows = clampedBack > 0 ? bodyHeight - 1 : bodyHeight;
|
|
32597
|
+
const start = Math.max(0, lines.length - lineRows - clampedBack);
|
|
32598
|
+
const visible = lines.slice(start, start + lineRows);
|
|
32450
32599
|
const width = cols - 1;
|
|
32451
|
-
|
|
32452
|
-
|
|
32453
|
-
|
|
32454
|
-
|
|
32455
|
-
|
|
32456
|
-
|
|
32457
|
-
|
|
32458
|
-
|
|
32459
|
-
|
|
32460
|
-
|
|
32461
|
-
|
|
32462
|
-
|
|
32463
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
32464
|
-
|
|
32465
|
-
|
|
32466
|
-
|
|
32467
|
-
] })
|
|
32600
|
+
use_input_default((_input, key) => {
|
|
32601
|
+
if (key.pageUp) setScrollBack((b) => Math.min(maxScroll, b + bodyHeight));
|
|
32602
|
+
else if (key.pageDown) setScrollBack((b) => Math.max(0, b - bodyHeight));
|
|
32603
|
+
});
|
|
32604
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", height: rows, children: [
|
|
32605
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Banner, { info, scope, worker, status, activeProfile }),
|
|
32606
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
|
|
32607
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", height: bodyHeight, overflow: "hidden", children: [
|
|
32608
|
+
visible.map((line) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TranscriptLine, { line }, line.key)),
|
|
32609
|
+
clampedBack > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: ` \u2191 ${clampedBack} newer line(s) hidden \xB7 PageDown` }) : null
|
|
32610
|
+
] }),
|
|
32611
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { justifyContent: "flex-end", children: busy ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "yellow", children: [
|
|
32612
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Spinner, {}),
|
|
32613
|
+
" processing \xB7 ",
|
|
32614
|
+
busy,
|
|
32615
|
+
"\u2026"
|
|
32616
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }) }),
|
|
32617
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
|
|
32618
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CommandInput, { status, history, onSubmit: handleSubmit }),
|
|
32619
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rule, { width }),
|
|
32620
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Hint, {})
|
|
32468
32621
|
] });
|
|
32469
32622
|
}
|
|
32470
32623
|
|
|
@@ -32550,6 +32703,10 @@ async function runStart(profileDir2, profileName) {
|
|
|
32550
32703
|
physicPath: config.physicPath ?? null,
|
|
32551
32704
|
aiCli: config.aiCli ?? "claude"
|
|
32552
32705
|
};
|
|
32706
|
+
const aiCli2 = config.aiCli ?? "claude";
|
|
32707
|
+
const workingDir = getWorkingProfile(profileDir2, aiCli2);
|
|
32708
|
+
const labels = profileLabels(config);
|
|
32709
|
+
bus.setActiveProfile(workingDir ? basename4(workingDir) : labels[0] ?? null);
|
|
32553
32710
|
const tui = runTui(bus, info);
|
|
32554
32711
|
bus.log(`Connecting to ${credential.serverUrl} (profile: ${profileName})\u2026`);
|
|
32555
32712
|
void client.run();
|
|
@@ -32616,11 +32773,68 @@ async function runVersion(profileDir2, profileName, serverUrlFlag) {
|
|
|
32616
32773
|
}
|
|
32617
32774
|
}
|
|
32618
32775
|
|
|
32776
|
+
// src/ui/select-profile.tsx
|
|
32777
|
+
var import_react28 = __toESM(require_react(), 1);
|
|
32778
|
+
var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
|
|
32779
|
+
function Picker({
|
|
32780
|
+
items,
|
|
32781
|
+
title,
|
|
32782
|
+
onDone
|
|
32783
|
+
}) {
|
|
32784
|
+
const { exit } = use_app_default();
|
|
32785
|
+
const [index, setIndex] = (0, import_react28.useState)(0);
|
|
32786
|
+
use_input_default((input, key) => {
|
|
32787
|
+
if (key.upArrow) setIndex((i) => (i - 1 + items.length) % items.length);
|
|
32788
|
+
else if (key.downArrow) setIndex((i) => (i + 1) % items.length);
|
|
32789
|
+
else if (key.return) {
|
|
32790
|
+
onDone(items[index]?.value ?? null);
|
|
32791
|
+
exit();
|
|
32792
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
32793
|
+
onDone(null);
|
|
32794
|
+
exit();
|
|
32795
|
+
}
|
|
32796
|
+
});
|
|
32797
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", paddingY: 1, children: [
|
|
32798
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, children: title }),
|
|
32799
|
+
items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: i === index ? "cyan" : void 0, children: [
|
|
32800
|
+
i === index ? "\u276F " : " ",
|
|
32801
|
+
item.label
|
|
32802
|
+
] }, item.value)),
|
|
32803
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: "\u2191\u2193 move \xB7 Enter select \xB7 Esc cancel" })
|
|
32804
|
+
] });
|
|
32805
|
+
}
|
|
32806
|
+
function selectFromList(items, title) {
|
|
32807
|
+
return new Promise((resolve5) => {
|
|
32808
|
+
let chosen = null;
|
|
32809
|
+
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v) => chosen = v }));
|
|
32810
|
+
void app.waitUntilExit().then(() => resolve5(chosen));
|
|
32811
|
+
});
|
|
32812
|
+
}
|
|
32813
|
+
|
|
32619
32814
|
// src/index.ts
|
|
32620
32815
|
function flagValue(args, name) {
|
|
32621
32816
|
const index = args.indexOf(`--${name}`);
|
|
32622
32817
|
return index >= 0 ? args[index + 1] : void 0;
|
|
32623
32818
|
}
|
|
32819
|
+
async function pickLinkedProfile(explicit, action) {
|
|
32820
|
+
if (explicit) return explicit;
|
|
32821
|
+
const linked = listProfiles().filter((p) => p.linked);
|
|
32822
|
+
if (linked.length === 0) {
|
|
32823
|
+
console.error("No linked profile \u2014 run `4pm link` first.");
|
|
32824
|
+
return null;
|
|
32825
|
+
}
|
|
32826
|
+
if (linked.length === 1) return linked[0].name;
|
|
32827
|
+
if (!process.stdout.isTTY) {
|
|
32828
|
+
console.error(
|
|
32829
|
+
`Multiple profiles \u2014 pass --profile <name> (or run \`4pm ${action}\` in a terminal to pick).`
|
|
32830
|
+
);
|
|
32831
|
+
return null;
|
|
32832
|
+
}
|
|
32833
|
+
return selectFromList(
|
|
32834
|
+
linked.map((p) => ({ label: p.name, value: p.name })),
|
|
32835
|
+
`Select a profile to ${action}:`
|
|
32836
|
+
);
|
|
32837
|
+
}
|
|
32624
32838
|
async function main() {
|
|
32625
32839
|
const [explicitProfile, args] = resolveProfileArg(process.argv.slice(2));
|
|
32626
32840
|
const [command, ...rest] = args;
|
|
@@ -32631,12 +32845,20 @@ async function main() {
|
|
|
32631
32845
|
break;
|
|
32632
32846
|
}
|
|
32633
32847
|
case "start": {
|
|
32634
|
-
const name = explicitProfile
|
|
32848
|
+
const name = await pickLinkedProfile(explicitProfile, "start");
|
|
32849
|
+
if (!name) {
|
|
32850
|
+
process.exitCode = 1;
|
|
32851
|
+
break;
|
|
32852
|
+
}
|
|
32635
32853
|
await runStart(profileDir(name), name);
|
|
32636
32854
|
break;
|
|
32637
32855
|
}
|
|
32638
32856
|
case "unlink": {
|
|
32639
|
-
const name = explicitProfile
|
|
32857
|
+
const name = await pickLinkedProfile(explicitProfile, "unlink");
|
|
32858
|
+
if (!name) {
|
|
32859
|
+
process.exitCode = 1;
|
|
32860
|
+
break;
|
|
32861
|
+
}
|
|
32640
32862
|
runUnlink(profileDir(name), name);
|
|
32641
32863
|
break;
|
|
32642
32864
|
}
|
|
@@ -32659,13 +32881,15 @@ async function main() {
|
|
|
32659
32881
|
"4pm \u2014 the 4PM worker-machine CLI",
|
|
32660
32882
|
"",
|
|
32661
32883
|
"Usage:",
|
|
32662
|
-
" 4pm
|
|
32663
|
-
" 4pm
|
|
32664
|
-
" 4pm
|
|
32665
|
-
" 4pm version
|
|
32666
|
-
" 4pm update
|
|
32884
|
+
" 4pm link Pair with the server (hashcode 1\u21922\u21923)",
|
|
32885
|
+
" 4pm start Connect WS, receive commands from the server",
|
|
32886
|
+
" 4pm unlink Delete a link (pick a profile if several)",
|
|
32887
|
+
" 4pm version Show the installed version (+ latest from the server)",
|
|
32888
|
+
" 4pm update Update to the latest version now",
|
|
32667
32889
|
"",
|
|
32668
|
-
"
|
|
32890
|
+
"Profiles: named after the paired account (e.g. mcacc1); when several exist,",
|
|
32891
|
+
"start/unlink show a picker (\u2191\u2193 + Enter) \u2014 no --profile needed.",
|
|
32892
|
+
"Server URL: from FOURPM_SERVER (default http://localhost:3001)."
|
|
32669
32893
|
].join("\n")
|
|
32670
32894
|
);
|
|
32671
32895
|
}
|