@jentrix/cli 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +440 -29
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -10327,14 +10327,17 @@ var require_dist = __commonJS({
|
|
|
10327
10327
|
import { spawn as spawn3 } from "node:child_process";
|
|
10328
10328
|
import { createInterface } from "node:readline";
|
|
10329
10329
|
import {
|
|
10330
|
+
appendFileSync,
|
|
10330
10331
|
closeSync as closeSync3,
|
|
10332
|
+
copyFileSync,
|
|
10331
10333
|
existsSync as existsSync6,
|
|
10334
|
+
mkdirSync as mkdirSync4,
|
|
10332
10335
|
openSync as openSync3,
|
|
10333
10336
|
readFileSync as readFileSync7,
|
|
10334
10337
|
statSync as statSync3
|
|
10335
10338
|
} from "node:fs";
|
|
10336
10339
|
import { homedir } from "node:os";
|
|
10337
|
-
import { basename as basename2, join as join6 } from "node:path";
|
|
10340
|
+
import { basename as basename2, dirname as dirname4, join as join6 } from "node:path";
|
|
10338
10341
|
import { fileURLToPath } from "node:url";
|
|
10339
10342
|
|
|
10340
10343
|
// node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
|
|
@@ -20959,7 +20962,7 @@ function refreshAccessToken(input) {
|
|
|
20959
20962
|
|
|
20960
20963
|
// src/client.ts
|
|
20961
20964
|
var CLI_NAME = "stacks-cli";
|
|
20962
|
-
var CLI_VERSION = "0.5.
|
|
20965
|
+
var CLI_VERSION = "0.5.6";
|
|
20963
20966
|
var DEAD_TOKEN_MESSAGE = "authentication failed (HTTP 401): the token is dead or expired \u2014 mint a new PAT at /account/tokens on your Jentrix server and update STACKS_TOKEN (or --token / the config file).";
|
|
20964
20967
|
var RELOGIN_MESSAGE = "OAuth session expired and could not be refreshed \u2014 run `jentrix login` to sign in again.";
|
|
20965
20968
|
function originOf2(url2) {
|
|
@@ -21095,6 +21098,26 @@ function readOAuthRecord(configPath, reader) {
|
|
|
21095
21098
|
}
|
|
21096
21099
|
}
|
|
21097
21100
|
|
|
21101
|
+
// src/exec-target.ts
|
|
21102
|
+
var INTERPRETED = /\.(cmd|bat)$/i;
|
|
21103
|
+
function quoteForCmd(token) {
|
|
21104
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
21105
|
+
}
|
|
21106
|
+
function execTarget(file, args, platform2 = process.platform, comSpec = process.env.ComSpec) {
|
|
21107
|
+
if (platform2 !== "win32" || !INTERPRETED.test(file)) {
|
|
21108
|
+
return { file, args: [...args], options: {} };
|
|
21109
|
+
}
|
|
21110
|
+
const line = [file, ...args].map(quoteForCmd).join(" ");
|
|
21111
|
+
return {
|
|
21112
|
+
file: comSpec || "cmd.exe",
|
|
21113
|
+
// `/d` skips any AutoRun command the machine has registered; `/s` makes cmd
|
|
21114
|
+
// strip exactly the outer quote pair and take the rest verbatim, which is
|
|
21115
|
+
// the only form that survives a quoted program path.
|
|
21116
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
21117
|
+
options: { windowsVerbatimArguments: true }
|
|
21118
|
+
};
|
|
21119
|
+
}
|
|
21120
|
+
|
|
21098
21121
|
// src/loopback.ts
|
|
21099
21122
|
import { createServer } from "node:http";
|
|
21100
21123
|
import { spawn } from "node:child_process";
|
|
@@ -27191,6 +27214,11 @@ function relayFailure(deps2, step, result) {
|
|
|
27191
27214
|
);
|
|
27192
27215
|
return EXIT_CODES.INTERNAL;
|
|
27193
27216
|
}
|
|
27217
|
+
function persistentPluginRoot(globalNodeModules, ownRoot, exists) {
|
|
27218
|
+
if (!globalNodeModules) return ownRoot;
|
|
27219
|
+
const global = join4(globalNodeModules, "@jentrix", "cli");
|
|
27220
|
+
return exists(global) ? global : ownRoot;
|
|
27221
|
+
}
|
|
27194
27222
|
function isPluginMarketplaceDir(dir) {
|
|
27195
27223
|
return existsSync4(join4(dir, ".claude-plugin", "marketplace.json"));
|
|
27196
27224
|
}
|
|
@@ -27468,22 +27496,33 @@ function invokeRunnerProcess(file, args, stdin, timeoutMs = RUNNER_TIMEOUT_MS) {
|
|
|
27468
27496
|
if (!isAbsolute(file)) {
|
|
27469
27497
|
return Promise.reject(new Error("runner executable path must be absolute"));
|
|
27470
27498
|
}
|
|
27499
|
+
const target = execTarget(file, args);
|
|
27471
27500
|
return new Promise((resolveInvocation, reject) => {
|
|
27472
27501
|
const child = execFile2(
|
|
27473
|
-
file,
|
|
27474
|
-
args,
|
|
27502
|
+
target.file,
|
|
27503
|
+
target.args,
|
|
27475
27504
|
{
|
|
27476
27505
|
timeout: timeoutMs,
|
|
27477
27506
|
maxBuffer: RUNNER_OUTPUT_CAP,
|
|
27478
|
-
windowsHide: true
|
|
27507
|
+
windowsHide: true,
|
|
27508
|
+
...target.options
|
|
27479
27509
|
},
|
|
27480
27510
|
(error2, stdout, stderr) => {
|
|
27481
27511
|
if (error2 && error2.killed) {
|
|
27482
27512
|
reject(new Error("runner subprocess timed out"));
|
|
27483
27513
|
return;
|
|
27484
27514
|
}
|
|
27515
|
+
const raw = error2?.code;
|
|
27516
|
+
if (error2 && typeof raw === "string") {
|
|
27517
|
+
resolveInvocation({
|
|
27518
|
+
code: 1,
|
|
27519
|
+
stdout: String(stdout),
|
|
27520
|
+
stderr: `${String(stderr)}${raw}: ${error2.message}`
|
|
27521
|
+
});
|
|
27522
|
+
return;
|
|
27523
|
+
}
|
|
27485
27524
|
resolveInvocation({
|
|
27486
|
-
code: typeof
|
|
27525
|
+
code: typeof raw === "number" ? Number(raw) : 0,
|
|
27487
27526
|
stdout: String(stdout),
|
|
27488
27527
|
stderr: String(stderr)
|
|
27489
27528
|
});
|
|
@@ -27804,8 +27843,12 @@ function runRunnerForeground(file, args) {
|
|
|
27804
27843
|
if (!isAbsolute(file)) {
|
|
27805
27844
|
return Promise.reject(new Error("runner executable path must be absolute"));
|
|
27806
27845
|
}
|
|
27846
|
+
const target = execTarget(file, args);
|
|
27807
27847
|
return new Promise((resolveRun, reject) => {
|
|
27808
|
-
const child = spawn2(file, args, {
|
|
27848
|
+
const child = spawn2(target.file, target.args, {
|
|
27849
|
+
stdio: "inherit",
|
|
27850
|
+
...target.options
|
|
27851
|
+
});
|
|
27809
27852
|
child.once("error", reject);
|
|
27810
27853
|
child.once("exit", (code, signal) => resolveRun(code ?? (signal ? 1 : 0)));
|
|
27811
27854
|
});
|
|
@@ -27852,6 +27895,312 @@ function registerRunnerCommand(program3, deps2, onExit2) {
|
|
|
27852
27895
|
);
|
|
27853
27896
|
}
|
|
27854
27897
|
|
|
27898
|
+
// src/commands/setup.ts
|
|
27899
|
+
var NPM_INSTALL_TIMEOUT_MS2 = 3e5;
|
|
27900
|
+
var SUPERSEDED = ["@jentrix/stacks-cli", "@jentrix/stacks-runner"];
|
|
27901
|
+
var CLI_SPEC = `@jentrix/cli@${CLI_VERSION}`;
|
|
27902
|
+
var RUNNER_SPEC2 = `@jentrix/runner@${CLI_VERSION}`;
|
|
27903
|
+
function say(deps2, text = "") {
|
|
27904
|
+
deps2.writeOut(text);
|
|
27905
|
+
}
|
|
27906
|
+
async function installToolchain(deps2) {
|
|
27907
|
+
const npm = await deps2.resolveNpm();
|
|
27908
|
+
const existing = await deps2.resolveJentrix();
|
|
27909
|
+
if (existing) {
|
|
27910
|
+
say(deps2, `jentrix ${CLI_VERSION} is already installed (${existing}).`);
|
|
27911
|
+
say(deps2, " Upgrade it any time with: npm update -g @jentrix/cli");
|
|
27912
|
+
} else {
|
|
27913
|
+
if (!npm) {
|
|
27914
|
+
deps2.writeErr(
|
|
27915
|
+
"NPM_NOT_FOUND: the toolchain installs from npm and `npm` is not on PATH \u2014 install Node.js >= 20 (https://nodejs.org), then retry."
|
|
27916
|
+
);
|
|
27917
|
+
return EXIT_CODES.INVALID_INPUT;
|
|
27918
|
+
}
|
|
27919
|
+
await deps2.invoke(npm, ["rm", "-g", ...SUPERSEDED], void 0, 6e4);
|
|
27920
|
+
say(deps2, `Installing ${CLI_SPEC} + ${RUNNER_SPEC2} (npm -g)\u2026`);
|
|
27921
|
+
const installed = await deps2.invoke(
|
|
27922
|
+
npm,
|
|
27923
|
+
["install", "-g", CLI_SPEC, RUNNER_SPEC2],
|
|
27924
|
+
void 0,
|
|
27925
|
+
NPM_INSTALL_TIMEOUT_MS2
|
|
27926
|
+
);
|
|
27927
|
+
if (installed.code !== 0) {
|
|
27928
|
+
const detail = (installed.stderr || installed.stdout).trim();
|
|
27929
|
+
deps2.writeErr(
|
|
27930
|
+
`TOOLCHAIN_INSTALL_FAILED: \`npm install -g\` exited ${installed.code}${detail ? `: ${detail}` : ""}`
|
|
27931
|
+
);
|
|
27932
|
+
deps2.writeErr(
|
|
27933
|
+
" On EACCES, fix npm's global prefix (https://docs.npmjs.com/resolving-eacces-permissions-errors); on EEXIST, delete the bin npm names and retry."
|
|
27934
|
+
);
|
|
27935
|
+
return EXIT_CODES.INTERNAL;
|
|
27936
|
+
}
|
|
27937
|
+
if (!await deps2.resolveJentrix()) {
|
|
27938
|
+
deps2.writeErr(
|
|
27939
|
+
"TOOLCHAIN_NOT_ON_PATH: the packages installed but `jentrix` is not resolvable \u2014 add npm's global bin directory (`npm prefix -g`) to PATH, then retry."
|
|
27940
|
+
);
|
|
27941
|
+
return EXIT_CODES.INTERNAL;
|
|
27942
|
+
}
|
|
27943
|
+
say(deps2, "Installed the CLI and the session runner.");
|
|
27944
|
+
}
|
|
27945
|
+
if (!await deps2.resolveRunner()) {
|
|
27946
|
+
if (!npm) {
|
|
27947
|
+
deps2.writeErr(
|
|
27948
|
+
`RUNNER_NOT_INSTALLED: connected sessions need the runner and npm was not found to install it \u2014 npm install -g ${RUNNER_SPEC2}, then retry.`
|
|
27949
|
+
);
|
|
27950
|
+
return EXIT_CODES.INVALID_INPUT;
|
|
27951
|
+
}
|
|
27952
|
+
say(deps2, `Installing ${RUNNER_SPEC2} (connected sessions need it)\u2026`);
|
|
27953
|
+
const runner = await deps2.invoke(
|
|
27954
|
+
npm,
|
|
27955
|
+
["install", "-g", RUNNER_SPEC2],
|
|
27956
|
+
void 0,
|
|
27957
|
+
NPM_INSTALL_TIMEOUT_MS2
|
|
27958
|
+
);
|
|
27959
|
+
if (runner.code !== 0 || !await deps2.resolveRunner()) {
|
|
27960
|
+
deps2.writeErr(
|
|
27961
|
+
`RUNNER_NOT_INSTALLED: \`npm install -g ${RUNNER_SPEC2}\` did not leave a resolvable runner \u2014 install it by hand, then retry.`
|
|
27962
|
+
);
|
|
27963
|
+
return EXIT_CODES.INTERNAL;
|
|
27964
|
+
}
|
|
27965
|
+
}
|
|
27966
|
+
return EXIT_CODES.OK;
|
|
27967
|
+
}
|
|
27968
|
+
async function installRuntimes(deps2) {
|
|
27969
|
+
if (await deps2.resolveClaude()) {
|
|
27970
|
+
await deps2.installPlugin("claude");
|
|
27971
|
+
} else {
|
|
27972
|
+
say(deps2, "note: the `claude` CLI is not installed \u2014 skipped its plugin.");
|
|
27973
|
+
say(
|
|
27974
|
+
deps2,
|
|
27975
|
+
" Install Claude Code (https://claude.com/claude-code), then run: jentrix plugin install"
|
|
27976
|
+
);
|
|
27977
|
+
}
|
|
27978
|
+
if (await deps2.resolveCodex()) {
|
|
27979
|
+
if (await deps2.installPlugin("codex") !== EXIT_CODES.OK) {
|
|
27980
|
+
say(
|
|
27981
|
+
deps2,
|
|
27982
|
+
"note: `jentrix plugin install codex` did not complete \u2014 retry it after `jentrix login`."
|
|
27983
|
+
);
|
|
27984
|
+
}
|
|
27985
|
+
} else {
|
|
27986
|
+
say(
|
|
27987
|
+
deps2,
|
|
27988
|
+
"note: the `codex` CLI is not installed \u2014 skipped its plugin and MCP server."
|
|
27989
|
+
);
|
|
27990
|
+
say(
|
|
27991
|
+
deps2,
|
|
27992
|
+
" Install Codex (https://developers.openai.com/codex/cli), then re-run this command."
|
|
27993
|
+
);
|
|
27994
|
+
}
|
|
27995
|
+
}
|
|
27996
|
+
async function registerCodexMcp(deps2, codex, pending) {
|
|
27997
|
+
const configPath = deps2.codexConfigPath();
|
|
27998
|
+
const config2 = deps2.readTextFile(configPath);
|
|
27999
|
+
const existingKey = config2 ? /^\[mcp_servers\.jentrix\]/m.test(config2) ? "jentrix" : /^\[mcp_servers\.stacks\]/m.test(config2) ? "stacks" : null : null;
|
|
28000
|
+
if (existingKey) {
|
|
28001
|
+
say(
|
|
28002
|
+
deps2,
|
|
28003
|
+
`note: Codex already has an [mcp_servers.${existingKey}] entry \u2014 left untouched.`
|
|
28004
|
+
);
|
|
28005
|
+
say(
|
|
28006
|
+
deps2,
|
|
28007
|
+
` It may point at a different deployment; check ${configPath}.`
|
|
28008
|
+
);
|
|
28009
|
+
say(
|
|
28010
|
+
deps2,
|
|
28011
|
+
` To move it off an exported STACKS_CODEX_TOKEN and onto sign-in:`
|
|
28012
|
+
);
|
|
28013
|
+
say(
|
|
28014
|
+
deps2,
|
|
28015
|
+
` \`codex mcp remove ${existingKey}\`, then re-run this command.`
|
|
28016
|
+
);
|
|
28017
|
+
return;
|
|
28018
|
+
}
|
|
28019
|
+
const url2 = deps2.signedInUrl();
|
|
28020
|
+
if (!url2) {
|
|
28021
|
+
say(
|
|
28022
|
+
deps2,
|
|
28023
|
+
"note: skipped the Codex MCP entry \u2014 this machine is not signed in yet, so"
|
|
28024
|
+
);
|
|
28025
|
+
say(
|
|
28026
|
+
deps2,
|
|
28027
|
+
" there is no endpoint to register. Run `jentrix login`, then re-run this command."
|
|
28028
|
+
);
|
|
28029
|
+
return;
|
|
28030
|
+
}
|
|
28031
|
+
const oauth = (await deps2.invoke(codex, ["mcp", "login", "--help"])).code === 0;
|
|
28032
|
+
if (config2 !== null) {
|
|
28033
|
+
const stamp = deps2.now().toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
28034
|
+
deps2.copyFile(configPath, `${configPath}.bak-jentrix-${stamp}`);
|
|
28035
|
+
}
|
|
28036
|
+
const lines = ["", "# Added by the Jentrix installer."];
|
|
28037
|
+
lines.push(
|
|
28038
|
+
oauth ? "# Authenticated by `codex mcp login jentrix` \u2014 no token to export." : "# Needs STACKS_CODEX_TOKEN in the environment (a personal access token, scopes read + write)."
|
|
28039
|
+
);
|
|
28040
|
+
lines.push("[mcp_servers.jentrix]", `url = "${url2}"`);
|
|
28041
|
+
if (!oauth) lines.push('bearer_token_env_var = "STACKS_CODEX_TOKEN"');
|
|
28042
|
+
deps2.appendTextFile(configPath, `${lines.join("\n")}
|
|
28043
|
+
`);
|
|
28044
|
+
say(deps2, `Codex: registered [mcp_servers.jentrix] \u2192 ${url2}`);
|
|
28045
|
+
if (!oauth) {
|
|
28046
|
+
say(
|
|
28047
|
+
deps2,
|
|
28048
|
+
" (this Codex build has no `codex mcp login`, so the entry reads a token"
|
|
28049
|
+
);
|
|
28050
|
+
say(deps2, " from the environment)");
|
|
28051
|
+
pending.codexTokenUrl = `${url2.replace(/\/api\/mcp$/, "")}/account/tokens`;
|
|
28052
|
+
return;
|
|
28053
|
+
}
|
|
28054
|
+
if (!deps2.isInteractive) {
|
|
28055
|
+
pending.codexLoginUrl = url2;
|
|
28056
|
+
return;
|
|
28057
|
+
}
|
|
28058
|
+
say(
|
|
28059
|
+
deps2,
|
|
28060
|
+
"Signing Codex in \u2014 approve the Jentrix consent screen in your browser\u2026"
|
|
28061
|
+
);
|
|
28062
|
+
if ((await deps2.invoke(codex, ["mcp", "login", "jentrix"])).code !== 0) {
|
|
28063
|
+
pending.codexLoginUrl = url2;
|
|
28064
|
+
}
|
|
28065
|
+
}
|
|
28066
|
+
async function bootstrapRepository(deps2) {
|
|
28067
|
+
const here = deps2.cwd();
|
|
28068
|
+
if (!await deps2.resolveGit()) {
|
|
28069
|
+
say(deps2, "note: `git` is not installed \u2014 skipped the repository step.");
|
|
28070
|
+
say(
|
|
28071
|
+
deps2,
|
|
28072
|
+
" Jentrix aligns sessions by repository identity, so install git (https://git-scm.com/downloads) and re-run."
|
|
28073
|
+
);
|
|
28074
|
+
return;
|
|
28075
|
+
}
|
|
28076
|
+
const inRepo = async () => (await deps2.git(["rev-parse", "--is-inside-work-tree"], here)).code === 0;
|
|
28077
|
+
if (!await inRepo()) {
|
|
28078
|
+
const reply = await deps2.readLine(
|
|
28079
|
+
`Initialize a git repository in ${here} so agent sessions can align here? [y/N] `
|
|
28080
|
+
);
|
|
28081
|
+
if (!/^y/i.test(reply.trim())) return;
|
|
28082
|
+
if ((await deps2.git(["init"], here)).code !== 0) {
|
|
28083
|
+
say(deps2, "note: `git init` failed \u2014 skipped the repository step.");
|
|
28084
|
+
return;
|
|
28085
|
+
}
|
|
28086
|
+
}
|
|
28087
|
+
if ((await deps2.git(["remote", "get-url", "origin"], here)).code === 0)
|
|
28088
|
+
return;
|
|
28089
|
+
const fallback = here.split(/[/\\]/).filter(Boolean).pop().toLowerCase().replace(/ /g, "-");
|
|
28090
|
+
say(
|
|
28091
|
+
deps2,
|
|
28092
|
+
"Jentrix aligns sessions by repository identity (owner/name); the remote doesn't have to exist yet."
|
|
28093
|
+
);
|
|
28094
|
+
say(
|
|
28095
|
+
deps2,
|
|
28096
|
+
`Optional \u2014 leave it blank and this checkout aligns as local/${fallback}.`
|
|
28097
|
+
);
|
|
28098
|
+
const answer = (await deps2.readLine(
|
|
28099
|
+
"GitHub repo for origin (owner/name or full URL, blank to skip): "
|
|
28100
|
+
)).trim();
|
|
28101
|
+
if (!answer) return;
|
|
28102
|
+
const url2 = /:\/\/|^[^/]+@[^:]+:/.test(answer) ? answer : /^[^/]+\/[^/]+$/.test(answer) ? `https://github.com/${answer}.git` : null;
|
|
28103
|
+
if (!url2) {
|
|
28104
|
+
say(deps2, `skipped \u2014 "${answer}" is neither owner/name nor a URL`);
|
|
28105
|
+
return;
|
|
28106
|
+
}
|
|
28107
|
+
if ((await deps2.git(["remote", "add", "origin", url2], here)).code === 0) {
|
|
28108
|
+
say(deps2, `origin \u2192 ${url2}`);
|
|
28109
|
+
}
|
|
28110
|
+
}
|
|
28111
|
+
async function runSetupCommand(flags, deps2) {
|
|
28112
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
28113
|
+
if (Number.isFinite(nodeMajor) && nodeMajor < 20) {
|
|
28114
|
+
deps2.writeErr(
|
|
28115
|
+
`NODE_TOO_OLD: Jentrix requires Node.js >= 20 (running v${process.versions.node}) \u2014 upgrade from https://nodejs.org, then retry.`
|
|
28116
|
+
);
|
|
28117
|
+
return EXIT_CODES.INVALID_INPUT;
|
|
28118
|
+
}
|
|
28119
|
+
const toolchain = await installToolchain(deps2);
|
|
28120
|
+
if (toolchain !== EXIT_CODES.OK) return toolchain;
|
|
28121
|
+
await installRuntimes(deps2);
|
|
28122
|
+
if (!deps2.hasCredential()) {
|
|
28123
|
+
if (deps2.isInteractive) {
|
|
28124
|
+
say(deps2, "");
|
|
28125
|
+
say(deps2, "Connecting this machine to a Jentrix server\u2026");
|
|
28126
|
+
if (await deps2.login({ url: flags.url, local: flags.local }) !== 0) {
|
|
28127
|
+
say(
|
|
28128
|
+
deps2,
|
|
28129
|
+
"note: sign-in did not complete \u2014 run `jentrix login` when you're ready."
|
|
28130
|
+
);
|
|
28131
|
+
}
|
|
28132
|
+
} else {
|
|
28133
|
+
say(
|
|
28134
|
+
deps2,
|
|
28135
|
+
"note: no credential is configured \u2014 run `jentrix login` (interactive) to connect."
|
|
28136
|
+
);
|
|
28137
|
+
}
|
|
28138
|
+
}
|
|
28139
|
+
const pending = {};
|
|
28140
|
+
const codex = await deps2.resolveCodex();
|
|
28141
|
+
if (codex) await registerCodexMcp(deps2, codex, pending);
|
|
28142
|
+
if (flags.git !== false && deps2.isInteractive && deps2.cwd() !== deps2.homeDir()) {
|
|
28143
|
+
await bootstrapRepository(deps2);
|
|
28144
|
+
}
|
|
28145
|
+
say(deps2, "");
|
|
28146
|
+
say(deps2, "Done. Verify with: jentrix whoami");
|
|
28147
|
+
if (pending.codexLoginUrl) {
|
|
28148
|
+
say(deps2, "");
|
|
28149
|
+
say(
|
|
28150
|
+
deps2,
|
|
28151
|
+
`One step left for Codex \u2014 approve its access to ${pending.codexLoginUrl}:`
|
|
28152
|
+
);
|
|
28153
|
+
say(
|
|
28154
|
+
deps2,
|
|
28155
|
+
" codex mcp login jentrix (opens the browser; no token to copy)"
|
|
28156
|
+
);
|
|
28157
|
+
}
|
|
28158
|
+
if (pending.codexTokenUrl) {
|
|
28159
|
+
say(deps2, "");
|
|
28160
|
+
say(
|
|
28161
|
+
deps2,
|
|
28162
|
+
"One step left for Codex \u2014 its MCP client carries its own token:"
|
|
28163
|
+
);
|
|
28164
|
+
say(
|
|
28165
|
+
deps2,
|
|
28166
|
+
` 1. mint a personal access token (scopes read + write) at ${pending.codexTokenUrl}`
|
|
28167
|
+
);
|
|
28168
|
+
say(
|
|
28169
|
+
deps2,
|
|
28170
|
+
" 2. export STACKS_CODEX_TOKEN in the shell you start Codex from"
|
|
28171
|
+
);
|
|
28172
|
+
say(deps2, " 3. restart Codex");
|
|
28173
|
+
say(
|
|
28174
|
+
deps2,
|
|
28175
|
+
" (deliberately not STACKS_TOKEN \u2014 that would also override the CLI's own"
|
|
28176
|
+
);
|
|
28177
|
+
say(deps2, " rotating login, which refreshes itself and needs no export)");
|
|
28178
|
+
}
|
|
28179
|
+
say(deps2, "");
|
|
28180
|
+
say(deps2, "Next: open your agent in the project folder and align \u2014");
|
|
28181
|
+
say(deps2, " Claude Code: /jentrix-align");
|
|
28182
|
+
say(deps2, " Codex: $jentrix-align");
|
|
28183
|
+
say(deps2, " any terminal: jentrix align");
|
|
28184
|
+
say(
|
|
28185
|
+
deps2,
|
|
28186
|
+
"(Bind a folder to a different server any time with: jentrix login --local)"
|
|
28187
|
+
);
|
|
28188
|
+
return EXIT_CODES.OK;
|
|
28189
|
+
}
|
|
28190
|
+
function registerSetupCommand(program3, deps2, onExit2) {
|
|
28191
|
+
return program3.command("setup").description(
|
|
28192
|
+
"Install and connect the whole local toolchain \u2014 CLI, session runner, the plugins for whichever agent runtimes are present, browser sign-in, the Codex MCP server, and this folder's repository. The same command on Windows, macOS and Linux. Safe to re-run."
|
|
28193
|
+
).option(
|
|
28194
|
+
"--url <url>",
|
|
28195
|
+
"MCP endpoint to sign in against (default: the interactive server picker)"
|
|
28196
|
+
).option(
|
|
28197
|
+
"--local",
|
|
28198
|
+
"bind THIS folder to that server (./.stacks/config.json) instead of the machine-wide config"
|
|
28199
|
+
).option("--no-git", "never touch the current folder's git repository").action(async (options) => {
|
|
28200
|
+
onExit2(await runSetupCommand(options, deps2));
|
|
28201
|
+
});
|
|
28202
|
+
}
|
|
28203
|
+
|
|
27855
28204
|
// src/commands/snapshot.ts
|
|
27856
28205
|
import {
|
|
27857
28206
|
closeSync as closeSync2,
|
|
@@ -28403,10 +28752,16 @@ var sessionDeps = {
|
|
|
28403
28752
|
runSessionHost: (runnerBin, planPath) => runRunnerForeground(runnerBin, ["session-run", "--plan-file", planPath]),
|
|
28404
28753
|
spawnSessionHostDetached: (runnerBin, planPath, logPath) => {
|
|
28405
28754
|
const fd = openSync3(logPath, "a", 384);
|
|
28755
|
+
const target = execTarget(runnerBin, [
|
|
28756
|
+
"session-run",
|
|
28757
|
+
"--plan-file",
|
|
28758
|
+
planPath
|
|
28759
|
+
]);
|
|
28406
28760
|
try {
|
|
28407
|
-
const child = spawn3(
|
|
28761
|
+
const child = spawn3(target.file, target.args, {
|
|
28408
28762
|
detached: true,
|
|
28409
|
-
stdio: ["ignore", fd, fd]
|
|
28763
|
+
stdio: ["ignore", fd, fd],
|
|
28764
|
+
...target.options
|
|
28410
28765
|
});
|
|
28411
28766
|
child.unref();
|
|
28412
28767
|
return child.pid ?? -1;
|
|
@@ -28420,36 +28775,92 @@ var sessionCommand = registerSessionCommand(program2, sessionDeps, onExit);
|
|
|
28420
28775
|
registerSnapshotCommand(sessionCommand, sessionDeps, onExit);
|
|
28421
28776
|
registerAlignCommand(program2, sessionDeps, onExit);
|
|
28422
28777
|
registerPushCommand(program2, sessionDeps, onExit);
|
|
28778
|
+
var PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
28423
28779
|
var PLUGIN_DIR = fileURLToPath(new URL("../claude-plugin", import.meta.url));
|
|
28424
28780
|
var CODEX_PLUGIN_DIR = fileURLToPath(
|
|
28425
28781
|
new URL("../codex-plugin", import.meta.url)
|
|
28426
28782
|
);
|
|
28427
|
-
|
|
28428
|
-
|
|
28429
|
-
|
|
28430
|
-
|
|
28431
|
-
|
|
28432
|
-
|
|
28433
|
-
|
|
28434
|
-
|
|
28435
|
-
|
|
28436
|
-
invoke: invokeRunnerProcess,
|
|
28437
|
-
writeOut: (text) => process.stdout.write(`${text}
|
|
28783
|
+
var pluginDeps = {
|
|
28784
|
+
resolvePluginDir: () => existsSync6(PLUGIN_DIR) && isPluginMarketplaceDir(PLUGIN_DIR) ? PLUGIN_DIR : null,
|
|
28785
|
+
resolveCodexPluginDir: () => existsSync6(CODEX_PLUGIN_DIR) && isCodexPluginMarketplaceDir(CODEX_PLUGIN_DIR) ? CODEX_PLUGIN_DIR : null,
|
|
28786
|
+
resolveClaude: () => resolveExecutableOnPath(platformExecutableNames("claude"), process.env),
|
|
28787
|
+
resolveCodex: () => resolveExecutableOnPath(platformExecutableNames("codex"), process.env),
|
|
28788
|
+
resolveRunner: () => resolveRunnerExecutable(process.env),
|
|
28789
|
+
resolveNpm: () => resolveExecutableOnPath(platformExecutableNames("npm"), process.env),
|
|
28790
|
+
invoke: invokeRunnerProcess,
|
|
28791
|
+
writeOut: (text) => process.stdout.write(`${text}
|
|
28438
28792
|
`),
|
|
28439
|
-
|
|
28793
|
+
writeErr: (text) => process.stderr.write(`${text}
|
|
28440
28794
|
`),
|
|
28441
|
-
|
|
28442
|
-
|
|
28443
|
-
|
|
28795
|
+
// AGE-952: the one-liner install ends connected — same in-process login
|
|
28796
|
+
// reuse as runner setup (never a `jentrix` subprocess).
|
|
28797
|
+
hasCredential: () => {
|
|
28798
|
+
try {
|
|
28799
|
+
resolveConfig({ env: process.env, file: configFile() });
|
|
28800
|
+
return true;
|
|
28801
|
+
} catch {
|
|
28802
|
+
return false;
|
|
28803
|
+
}
|
|
28804
|
+
},
|
|
28805
|
+
login: () => runLoginCommand({}, loginDeps),
|
|
28806
|
+
isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
28807
|
+
};
|
|
28808
|
+
registerPluginCommand(program2, pluginDeps, onExit);
|
|
28809
|
+
registerSetupCommand(
|
|
28810
|
+
program2,
|
|
28811
|
+
{
|
|
28812
|
+
...pluginDeps,
|
|
28813
|
+
resolveJentrix: () => resolveExecutableOnPath(platformExecutableNames("jentrix"), process.env),
|
|
28814
|
+
resolveGit: () => resolveExecutableOnPath(platformExecutableNames("git"), process.env),
|
|
28815
|
+
// Register the plugin from whichever copy of the package PERSISTS. Under
|
|
28816
|
+
// `npx @jentrix/cli setup` the running module lives in npm's _npx cache,
|
|
28817
|
+
// and a marketplace registered from there dangles the moment npm prunes
|
|
28818
|
+
// it. `npm root -g` names the copy that stays.
|
|
28819
|
+
installPlugin: async (provider) => {
|
|
28820
|
+
const npm = await resolveExecutableOnPath(
|
|
28821
|
+
platformExecutableNames("npm"),
|
|
28822
|
+
process.env
|
|
28823
|
+
);
|
|
28824
|
+
const root = npm ? (await invokeRunnerProcess(npm, ["root", "-g"])).stdout.trim() || null : null;
|
|
28825
|
+
const packageRoot = persistentPluginRoot(
|
|
28826
|
+
root,
|
|
28827
|
+
PACKAGE_ROOT,
|
|
28828
|
+
(dir) => isPluginMarketplaceDir(join6(dir, "claude-plugin"))
|
|
28829
|
+
);
|
|
28830
|
+
return runPluginInstall(
|
|
28831
|
+
packageRoot === PACKAGE_ROOT ? pluginDeps : {
|
|
28832
|
+
...pluginDeps,
|
|
28833
|
+
resolvePluginDir: () => join6(packageRoot, "claude-plugin"),
|
|
28834
|
+
resolveCodexPluginDir: () => {
|
|
28835
|
+
const dir = join6(packageRoot, "codex-plugin");
|
|
28836
|
+
return isCodexPluginMarketplaceDir(dir) ? dir : null;
|
|
28837
|
+
}
|
|
28838
|
+
},
|
|
28839
|
+
provider
|
|
28840
|
+
);
|
|
28841
|
+
},
|
|
28842
|
+
// The endpoint a token actually resolves against — the in-process answer
|
|
28843
|
+
// to what install.sh had to ask for with `jentrix whoami --json`.
|
|
28844
|
+
signedInUrl: () => {
|
|
28444
28845
|
try {
|
|
28445
|
-
resolveConfig({ env: process.env, file: configFile() });
|
|
28446
|
-
return true;
|
|
28846
|
+
return resolveConfig({ env: process.env, file: configFile() }).url;
|
|
28447
28847
|
} catch {
|
|
28448
|
-
return
|
|
28848
|
+
return null;
|
|
28449
28849
|
}
|
|
28450
28850
|
},
|
|
28451
|
-
login: () => runLoginCommand(
|
|
28452
|
-
|
|
28851
|
+
login: (flags) => runLoginCommand(flags, loginDeps),
|
|
28852
|
+
codexConfigPath: () => join6(process.env.CODEX_HOME ?? join6(homedir(), ".codex"), "config.toml"),
|
|
28853
|
+
readTextFile: (path) => existsSync6(path) ? readFileSync7(path, "utf8") : null,
|
|
28854
|
+
appendTextFile: (path, text) => {
|
|
28855
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
28856
|
+
appendFileSync(path, text);
|
|
28857
|
+
},
|
|
28858
|
+
copyFile: (from, to) => copyFileSync(from, to),
|
|
28859
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
28860
|
+
cwd: () => process.cwd(),
|
|
28861
|
+
homeDir: () => homedir(),
|
|
28862
|
+
git: defaultGitRunner,
|
|
28863
|
+
readLine
|
|
28453
28864
|
},
|
|
28454
28865
|
onExit
|
|
28455
28866
|
);
|