@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.
Files changed (2) hide show
  1. package/dist/main.js +440 -29
  2. 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.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 error2?.code === "number" ? Number(error2.code) : 0,
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, { stdio: "inherit" });
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(runnerBin, ["session-run", "--plan-file", planPath], {
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
- registerPluginCommand(
28428
- program2,
28429
- {
28430
- resolvePluginDir: () => existsSync6(PLUGIN_DIR) && isPluginMarketplaceDir(PLUGIN_DIR) ? PLUGIN_DIR : null,
28431
- resolveCodexPluginDir: () => existsSync6(CODEX_PLUGIN_DIR) && isCodexPluginMarketplaceDir(CODEX_PLUGIN_DIR) ? CODEX_PLUGIN_DIR : null,
28432
- resolveClaude: () => resolveExecutableOnPath(platformExecutableNames("claude"), process.env),
28433
- resolveCodex: () => resolveExecutableOnPath(platformExecutableNames("codex"), process.env),
28434
- resolveRunner: () => resolveRunnerExecutable(process.env),
28435
- resolveNpm: () => resolveExecutableOnPath(platformExecutableNames("npm"), process.env),
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
- writeErr: (text) => process.stderr.write(`${text}
28793
+ writeErr: (text) => process.stderr.write(`${text}
28440
28794
  `),
28441
- // AGE-952: the one-liner install ends connected — same in-process login
28442
- // reuse as runner setup (never a `jentrix` subprocess).
28443
- hasCredential: () => {
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 false;
28848
+ return null;
28449
28849
  }
28450
28850
  },
28451
- login: () => runLoginCommand({}, loginDeps),
28452
- isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
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
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jentrix/cli",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Command-line client for the Jentrix MCP surface (jentrix tool <name>, generated noun-verb commands).",
5
5
  "keywords": [
6
6
  "jentrix",