@4pm/cli 1.2.6 → 1.2.7
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 +99 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28310,6 +28310,8 @@ function defaultProfileConfig() {
|
|
|
28310
28310
|
{ provider: "codex", profile: ".codex", args: [], model: "" }
|
|
28311
28311
|
],
|
|
28312
28312
|
aiFailoverMode: "remember",
|
|
28313
|
+
// Default to the fully-isolated posture (ADR-0199) — auth lives inside the sandbox.
|
|
28314
|
+
aiCredentialMode: "in-container",
|
|
28313
28315
|
autoUpdate: true,
|
|
28314
28316
|
commandHistoryUploadMinutes: 10,
|
|
28315
28317
|
autoClearIdleMinutes: 10
|
|
@@ -34012,6 +34014,29 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
34012
34014
|
};
|
|
34013
34015
|
}
|
|
34014
34016
|
|
|
34017
|
+
// src/core/idle-auto-clear.ts
|
|
34018
|
+
function startIdleAutoClear(bus, idleMinutes) {
|
|
34019
|
+
const idleMs = Math.max(0, idleMinutes) * 6e4;
|
|
34020
|
+
if (idleMs <= 0) return () => {
|
|
34021
|
+
};
|
|
34022
|
+
let lastActivity = Date.now();
|
|
34023
|
+
const off = bus.onTranscript(() => {
|
|
34024
|
+
lastActivity = Date.now();
|
|
34025
|
+
});
|
|
34026
|
+
const timer = setInterval(() => {
|
|
34027
|
+
if (bus.busy !== null || bus.snapshot().length === 0) return;
|
|
34028
|
+
if (Date.now() - lastActivity < idleMs) return;
|
|
34029
|
+
bus.clear();
|
|
34030
|
+
bus.log(`Transcript auto-cleared after ${Math.round(idleMs / 6e4)} min idle.`);
|
|
34031
|
+
lastActivity = Date.now();
|
|
34032
|
+
}, 6e4);
|
|
34033
|
+
timer.unref?.();
|
|
34034
|
+
return () => {
|
|
34035
|
+
off();
|
|
34036
|
+
clearInterval(timer);
|
|
34037
|
+
};
|
|
34038
|
+
}
|
|
34039
|
+
|
|
34015
34040
|
// src/ui/console-sink.ts
|
|
34016
34041
|
function attachConsoleSink(bus) {
|
|
34017
34042
|
bus.onTranscript((entry) => {
|
|
@@ -39927,8 +39952,10 @@ var CONFIG_KEYS = [
|
|
|
39927
39952
|
"physicPath",
|
|
39928
39953
|
"commandHistoryUploadMinutes",
|
|
39929
39954
|
"autoUpdate",
|
|
39930
|
-
"aiEnv"
|
|
39955
|
+
"aiEnv",
|
|
39956
|
+
"aiCredentialMode"
|
|
39931
39957
|
];
|
|
39958
|
+
var AI_CREDENTIAL_MODES = ["in-container", "host-mounted", "host-cli"];
|
|
39932
39959
|
function configPath(profileDir2) {
|
|
39933
39960
|
return join29(profileDir2, "config.json");
|
|
39934
39961
|
}
|
|
@@ -39960,6 +39987,11 @@ function parseConfigAssignment(key, valueTokens) {
|
|
|
39960
39987
|
} catch {
|
|
39961
39988
|
return { error: `aiEnv must be JSON, e.g. {"KEY":"val"}.` };
|
|
39962
39989
|
}
|
|
39990
|
+
case "aiCredentialMode":
|
|
39991
|
+
if (!AI_CREDENTIAL_MODES.includes(value)) {
|
|
39992
|
+
return { error: `Use ${AI_CREDENTIAL_MODES.join("|")}, not "${value}".` };
|
|
39993
|
+
}
|
|
39994
|
+
return { patch: { aiCredentialMode: value } };
|
|
39963
39995
|
default:
|
|
39964
39996
|
return { error: `Unknown key "${key}". Allowed: ${CONFIG_KEYS.join(", ")}.` };
|
|
39965
39997
|
}
|
|
@@ -40622,6 +40654,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
40622
40654
|
}
|
|
40623
40655
|
const release = () => releaseInstanceLock(profileDir2);
|
|
40624
40656
|
let stopControl = null;
|
|
40657
|
+
let stopIdleClear = null;
|
|
40625
40658
|
process.once("exit", release);
|
|
40626
40659
|
process.once("SIGINT", () => process.exit(0));
|
|
40627
40660
|
process.once("SIGTERM", () => process.exit(0));
|
|
@@ -40679,10 +40712,12 @@ async function runStart(profileDir2, profileName) {
|
|
|
40679
40712
|
await tui;
|
|
40680
40713
|
} else {
|
|
40681
40714
|
attachConsoleSink(bus);
|
|
40715
|
+
stopIdleClear = startIdleAutoClear(bus, config.autoClearIdleMinutes ?? 10);
|
|
40682
40716
|
bus.log(`Connecting to ${credential.serverUrl} (profile: ${profileName})\u2026`);
|
|
40683
40717
|
await runResilient();
|
|
40684
40718
|
}
|
|
40685
40719
|
} finally {
|
|
40720
|
+
stopIdleClear?.();
|
|
40686
40721
|
stopControl?.();
|
|
40687
40722
|
release();
|
|
40688
40723
|
}
|
|
@@ -40819,6 +40854,59 @@ async function runAttach(profileDir2, profileName) {
|
|
|
40819
40854
|
}
|
|
40820
40855
|
}
|
|
40821
40856
|
|
|
40857
|
+
// src/commands/ai-login.ts
|
|
40858
|
+
import { spawn as spawn9 } from "child_process";
|
|
40859
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
40860
|
+
import { basename as basename5 } from "path";
|
|
40861
|
+
function runProviderLogin(cmd, envVar, dir2) {
|
|
40862
|
+
return new Promise((resolve8) => {
|
|
40863
|
+
const child = spawn9(cmd, [], {
|
|
40864
|
+
stdio: "inherit",
|
|
40865
|
+
env: { ...process.env, [envVar]: dir2 }
|
|
40866
|
+
});
|
|
40867
|
+
child.on("error", (err) => {
|
|
40868
|
+
console.error(` \u2716 Could not launch \`${cmd}\` \u2014 is it installed? (${err.message})`);
|
|
40869
|
+
resolve8(1);
|
|
40870
|
+
});
|
|
40871
|
+
child.on("exit", (code) => resolve8(code ?? 0));
|
|
40872
|
+
});
|
|
40873
|
+
}
|
|
40874
|
+
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
40875
|
+
const config = readProfileConfig(profileDir2);
|
|
40876
|
+
const credentials = (config.aiProfiles ?? []).filter(isUsableCredential);
|
|
40877
|
+
if (credentials.length === 0) {
|
|
40878
|
+
console.log(
|
|
40879
|
+
`Profile "${profileName}" has no usable AI profiles configured \u2014 add one in the web Worker configs or config.json.`
|
|
40880
|
+
);
|
|
40881
|
+
return;
|
|
40882
|
+
}
|
|
40883
|
+
const selected = aiFilter ? credentials.filter(
|
|
40884
|
+
(c, i) => c.profile === aiFilter || basename5(resolveHomePath(c.profile)) === aiFilter || String(i + 1) === aiFilter
|
|
40885
|
+
) : credentials;
|
|
40886
|
+
if (selected.length === 0) {
|
|
40887
|
+
console.log(`No AI profile matches --ai "${aiFilter}". Available: ${credentials.map((c) => c.profile).join(", ")}`);
|
|
40888
|
+
return;
|
|
40889
|
+
}
|
|
40890
|
+
for (const cred of selected) {
|
|
40891
|
+
const envVar = profileEnvVar(cred.provider);
|
|
40892
|
+
if (!envVar) {
|
|
40893
|
+
console.log(`- Skipping ${cred.provider} "${cred.profile}" \u2014 no known credential dir for this provider.`);
|
|
40894
|
+
continue;
|
|
40895
|
+
}
|
|
40896
|
+
const dir2 = resolveHomePath(cred.profile);
|
|
40897
|
+
mkdirSync6(dir2, { recursive: true });
|
|
40898
|
+
console.log(`
|
|
40899
|
+
\u25B6 Logging in ${cred.provider} \xB7 ${cred.profile} (${envVar}=${dir2})`);
|
|
40900
|
+
console.log(` Complete the provider's own sign-in (claude: type \`/login\`; codex: follow its prompt), then exit.`);
|
|
40901
|
+
const code = await runProviderLogin(cred.provider, envVar, dir2);
|
|
40902
|
+
console.log(code === 0 ? ` \u2714 ${cred.provider} "${cred.profile}" session updated.` : ` \u26A0 ${cred.provider} exited with code ${code}.`);
|
|
40903
|
+
}
|
|
40904
|
+
console.log(
|
|
40905
|
+
`
|
|
40906
|
+
Tip: for the credentials to survive \`docker rm\`/recreate, keep each profile path under the mounted volume (e.g. \`~/.4pm/ai/<name>\`) \u2014 ADR-0199.`
|
|
40907
|
+
);
|
|
40908
|
+
}
|
|
40909
|
+
|
|
40822
40910
|
// src/commands/unlink.ts
|
|
40823
40911
|
import { rmSync as rmSync8 } from "fs";
|
|
40824
40912
|
async function runUnlink(profileDir2, profileName) {
|
|
@@ -40997,6 +41085,15 @@ async function main() {
|
|
|
40997
41085
|
await runAttach(profileDir(name), name);
|
|
40998
41086
|
break;
|
|
40999
41087
|
}
|
|
41088
|
+
case "ai-login": {
|
|
41089
|
+
const name = await pickLinkedProfile(explicitProfile, "ai-login");
|
|
41090
|
+
if (!name) {
|
|
41091
|
+
process.exitCode = 1;
|
|
41092
|
+
break;
|
|
41093
|
+
}
|
|
41094
|
+
await runAiLogin(profileDir(name), name, flagValue(rest, "ai") ?? null);
|
|
41095
|
+
break;
|
|
41096
|
+
}
|
|
41000
41097
|
case "unlink": {
|
|
41001
41098
|
const name = await pickLinkedProfile(explicitProfile, "unlink");
|
|
41002
41099
|
if (!name) {
|
|
@@ -41028,6 +41125,7 @@ async function main() {
|
|
|
41028
41125
|
" 4pm link Pair with the server (pairing \u2192 confirmation code)",
|
|
41029
41126
|
" 4pm start Connect WS, receive commands from the server",
|
|
41030
41127
|
" 4pm attach Open the TUI against a running headless daemon (ADR-0192)",
|
|
41128
|
+
" 4pm ai-login Log into a profile's AI CLI (claude/codex) in place (ADR-0199)",
|
|
41031
41129
|
" 4pm unlink Delete a link (pick a profile if several)",
|
|
41032
41130
|
" 4pm version Show the installed version (+ latest from the server)",
|
|
41033
41131
|
" 4pm update Update to the latest version now",
|