@jentrix/cli 0.5.4 → 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 +453 -32
  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.4";
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";
@@ -23541,10 +23564,20 @@ var MAX_WORK_ITEMS = 15;
23541
23564
  async function projectBoards(caller, projectId) {
23542
23565
  const project = await callStructured(caller, "get_project", { projectId });
23543
23566
  const links = project.links ?? [];
23567
+ const workspaceId = String(project.workspaceId);
23568
+ const linkedBoardIds = links.filter((link) => link.targetType === "BOARD").map((link) => link.targetId);
23569
+ let liveBoardIds = new Set(linkedBoardIds);
23570
+ if (linkedBoardIds.length > 0) {
23571
+ const boards = (await callStructured(caller, "list_boards", { workspaceId })).boards ?? [];
23572
+ liveBoardIds = new Set(
23573
+ boards.filter((b) => !b.archivedAt).map((b) => String(b.id))
23574
+ );
23575
+ }
23544
23576
  return {
23545
- workspaceId: String(project.workspaceId),
23577
+ workspaceId,
23546
23578
  name: String(project.name),
23547
- boardIds: links.filter((link) => link.targetType === "BOARD").map((link) => link.targetId),
23579
+ boardIds: linkedBoardIds.filter((id) => liveBoardIds.has(id)),
23580
+ staleBoardIds: linkedBoardIds.filter((id) => !liveBoardIds.has(id)),
23548
23581
  repoOwners: links.filter((link) => link.targetType === "REPO").map((link) => link.targetId.trim().toLowerCase())
23549
23582
  };
23550
23583
  }
@@ -24025,7 +24058,7 @@ ${JSON.stringify(session.alignment, null, 2)}`
24025
24058
  targetId: boardId
24026
24059
  });
24027
24060
  deps2.writeOut(
24028
- `Project "${project.name}" linked no board \u2014 created board "${project.name}" and linked it.`
24061
+ project.staleBoardIds.length > 0 ? `Project "${project.name}" linked ${project.staleBoardIds.length === 1 ? "a board that no longer exists" : "only boards that no longer exist"} (${project.staleBoardIds.join(", ")}) \u2014 created board "${project.name}" and linked it.` : `Project "${project.name}" linked no board \u2014 created board "${project.name}" and linked it.`
24029
24062
  );
24030
24063
  }
24031
24064
  const columns = await callStructured(caller, "list_columns", { boardId });
@@ -27181,6 +27214,11 @@ function relayFailure(deps2, step, result) {
27181
27214
  );
27182
27215
  return EXIT_CODES.INTERNAL;
27183
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
+ }
27184
27222
  function isPluginMarketplaceDir(dir) {
27185
27223
  return existsSync4(join4(dir, ".claude-plugin", "marketplace.json"));
27186
27224
  }
@@ -27458,22 +27496,33 @@ function invokeRunnerProcess(file, args, stdin, timeoutMs = RUNNER_TIMEOUT_MS) {
27458
27496
  if (!isAbsolute(file)) {
27459
27497
  return Promise.reject(new Error("runner executable path must be absolute"));
27460
27498
  }
27499
+ const target = execTarget(file, args);
27461
27500
  return new Promise((resolveInvocation, reject) => {
27462
27501
  const child = execFile2(
27463
- file,
27464
- args,
27502
+ target.file,
27503
+ target.args,
27465
27504
  {
27466
27505
  timeout: timeoutMs,
27467
27506
  maxBuffer: RUNNER_OUTPUT_CAP,
27468
- windowsHide: true
27507
+ windowsHide: true,
27508
+ ...target.options
27469
27509
  },
27470
27510
  (error2, stdout, stderr) => {
27471
27511
  if (error2 && error2.killed) {
27472
27512
  reject(new Error("runner subprocess timed out"));
27473
27513
  return;
27474
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
+ }
27475
27524
  resolveInvocation({
27476
- code: typeof error2?.code === "number" ? Number(error2.code) : 0,
27525
+ code: typeof raw === "number" ? Number(raw) : 0,
27477
27526
  stdout: String(stdout),
27478
27527
  stderr: String(stderr)
27479
27528
  });
@@ -27794,8 +27843,12 @@ function runRunnerForeground(file, args) {
27794
27843
  if (!isAbsolute(file)) {
27795
27844
  return Promise.reject(new Error("runner executable path must be absolute"));
27796
27845
  }
27846
+ const target = execTarget(file, args);
27797
27847
  return new Promise((resolveRun, reject) => {
27798
- const child = spawn2(file, args, { stdio: "inherit" });
27848
+ const child = spawn2(target.file, target.args, {
27849
+ stdio: "inherit",
27850
+ ...target.options
27851
+ });
27799
27852
  child.once("error", reject);
27800
27853
  child.once("exit", (code, signal) => resolveRun(code ?? (signal ? 1 : 0)));
27801
27854
  });
@@ -27842,6 +27895,312 @@ function registerRunnerCommand(program3, deps2, onExit2) {
27842
27895
  );
27843
27896
  }
27844
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
+
27845
28204
  // src/commands/snapshot.ts
27846
28205
  import {
27847
28206
  closeSync as closeSync2,
@@ -28393,10 +28752,16 @@ var sessionDeps = {
28393
28752
  runSessionHost: (runnerBin, planPath) => runRunnerForeground(runnerBin, ["session-run", "--plan-file", planPath]),
28394
28753
  spawnSessionHostDetached: (runnerBin, planPath, logPath) => {
28395
28754
  const fd = openSync3(logPath, "a", 384);
28755
+ const target = execTarget(runnerBin, [
28756
+ "session-run",
28757
+ "--plan-file",
28758
+ planPath
28759
+ ]);
28396
28760
  try {
28397
- const child = spawn3(runnerBin, ["session-run", "--plan-file", planPath], {
28761
+ const child = spawn3(target.file, target.args, {
28398
28762
  detached: true,
28399
- stdio: ["ignore", fd, fd]
28763
+ stdio: ["ignore", fd, fd],
28764
+ ...target.options
28400
28765
  });
28401
28766
  child.unref();
28402
28767
  return child.pid ?? -1;
@@ -28410,36 +28775,92 @@ var sessionCommand = registerSessionCommand(program2, sessionDeps, onExit);
28410
28775
  registerSnapshotCommand(sessionCommand, sessionDeps, onExit);
28411
28776
  registerAlignCommand(program2, sessionDeps, onExit);
28412
28777
  registerPushCommand(program2, sessionDeps, onExit);
28778
+ var PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
28413
28779
  var PLUGIN_DIR = fileURLToPath(new URL("../claude-plugin", import.meta.url));
28414
28780
  var CODEX_PLUGIN_DIR = fileURLToPath(
28415
28781
  new URL("../codex-plugin", import.meta.url)
28416
28782
  );
28417
- registerPluginCommand(
28418
- program2,
28419
- {
28420
- resolvePluginDir: () => existsSync6(PLUGIN_DIR) && isPluginMarketplaceDir(PLUGIN_DIR) ? PLUGIN_DIR : null,
28421
- resolveCodexPluginDir: () => existsSync6(CODEX_PLUGIN_DIR) && isCodexPluginMarketplaceDir(CODEX_PLUGIN_DIR) ? CODEX_PLUGIN_DIR : null,
28422
- resolveClaude: () => resolveExecutableOnPath(platformExecutableNames("claude"), process.env),
28423
- resolveCodex: () => resolveExecutableOnPath(platformExecutableNames("codex"), process.env),
28424
- resolveRunner: () => resolveRunnerExecutable(process.env),
28425
- resolveNpm: () => resolveExecutableOnPath(platformExecutableNames("npm"), process.env),
28426
- invoke: invokeRunnerProcess,
28427
- 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}
28428
28792
  `),
28429
- writeErr: (text) => process.stderr.write(`${text}
28793
+ writeErr: (text) => process.stderr.write(`${text}
28430
28794
  `),
28431
- // AGE-952: the one-liner install ends connected — same in-process login
28432
- // reuse as runner setup (never a `jentrix` subprocess).
28433
- 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: () => {
28434
28845
  try {
28435
- resolveConfig({ env: process.env, file: configFile() });
28436
- return true;
28846
+ return resolveConfig({ env: process.env, file: configFile() }).url;
28437
28847
  } catch {
28438
- return false;
28848
+ return null;
28439
28849
  }
28440
28850
  },
28441
- login: () => runLoginCommand({}, loginDeps),
28442
- 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
28443
28864
  },
28444
28865
  onExit
28445
28866
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jentrix/cli",
3
- "version": "0.5.4",
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",