@jentrix/cli 0.5.17 → 0.5.19

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jentrix",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Connect Codex tasks to Jentrix projects with trusted lifecycle hooks, alignment, checkpoints, and honest session capture.",
5
5
  "author": {
6
6
  "name": "Jentrix",
@@ -22,8 +22,8 @@ description: End a connected Jentrix session after preserving approved outputs,
22
22
  Declining never blocks closure.
23
23
  5. Run `jentrix session end <session-id>` and relay all output, including the exact
24
24
  telemetry line and any capture gap.
25
- 6. Explain that Codex lifecycle hooks do not report token receipts, so null token
26
- fields are expected on hook-watched sessions. Do not report estimates.
25
+ 6. Report provider-reported Codex token receipts when the hook supplied a rollout
26
+ path. If none were observed, keep token fields null; never report estimates.
27
27
  7. A final-response artifact exists only when a prior `Stop` hook observed assistant
28
28
  text before closure. The assistant text produced by this closing turn occurs after
29
29
  the command and must not be claimed as captured.
package/dist/main.js CHANGED
@@ -20966,7 +20966,7 @@ function refreshAccessToken(input) {
20966
20966
 
20967
20967
  // src/client.ts
20968
20968
  var CLI_NAME = "stacks-cli";
20969
- var CLI_VERSION = "0.5.17";
20969
+ var CLI_VERSION = "0.5.19";
20970
20970
  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).";
20971
20971
  var RELOGIN_MESSAGE = "OAuth session expired and could not be refreshed \u2014 run `jentrix login` to sign in again.";
20972
20972
  function originOf2(url2) {
@@ -22620,6 +22620,7 @@ function readTranscriptHead(path) {
22620
22620
  function parseHookLedger(body) {
22621
22621
  const starts = [];
22622
22622
  const endedAt = /* @__PURE__ */ new Map();
22623
+ const transcriptBySession = /* @__PURE__ */ new Map();
22623
22624
  for (const line of body.split("\n").filter(Boolean)) {
22624
22625
  let parsed;
22625
22626
  try {
@@ -22629,6 +22630,10 @@ function parseHookLedger(body) {
22629
22630
  }
22630
22631
  const sessionId = parsed.payload?.session_id;
22631
22632
  if (typeof sessionId !== "string") continue;
22633
+ const transcriptPath = parsed.payload?.transcript_path?.trim();
22634
+ if (transcriptPath) {
22635
+ transcriptBySession.set(sessionId, transcriptPath);
22636
+ }
22632
22637
  const stamped = parsed.at ? Date.parse(parsed.at) : NaN;
22633
22638
  const ms = Number.isNaN(stamped) ? 0 : stamped;
22634
22639
  if (parsed.event === "SessionEnd") {
@@ -22643,16 +22648,10 @@ function parseHookLedger(body) {
22643
22648
  });
22644
22649
  }
22645
22650
  }
22646
- return { starts, endedAt };
22651
+ return { starts, endedAt, transcriptBySession };
22647
22652
  }
22648
22653
  function transcriptForSessionIn(ledger, sessionId) {
22649
- for (let i = ledger.starts.length - 1; i >= 0; i--) {
22650
- const start = ledger.starts[i];
22651
- if (start.sessionId === sessionId && start.transcriptPath) {
22652
- return start.transcriptPath;
22653
- }
22654
- }
22655
- return null;
22654
+ return ledger.transcriptBySession.get(sessionId) ?? null;
22656
22655
  }
22657
22656
  var CLAUDE_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
22658
22657
  var CODEX_THREAD_ENV = "CODEX_THREAD_ID";
@@ -22676,9 +22675,10 @@ function readClaudeHookContext(deps2, readFile = (path) => readFileSync2(path, "
22676
22675
  } catch {
22677
22676
  }
22678
22677
  }
22679
- const { starts, endedAt } = parseHookLedger(body);
22678
+ const ledger = parseHookLedger(body);
22679
+ const { starts, endedAt } = ledger;
22680
22680
  if (own) {
22681
- const transcriptPath = transcriptForSessionIn({ starts }, own);
22681
+ const transcriptPath = transcriptForSessionIn(ledger, own);
22682
22682
  return {
22683
22683
  sessionId: own,
22684
22684
  transcriptPath,
@@ -22740,13 +22740,15 @@ function readCodexHookContext(deps2, readFile = (path) => readFileSync2(path, "u
22740
22740
  } catch {
22741
22741
  }
22742
22742
  }
22743
- const { starts, endedAt } = parseHookLedger(body);
22743
+ const ledger = parseHookLedger(body);
22744
+ const { starts, endedAt } = ledger;
22744
22745
  if (own) {
22745
22746
  const source = threadId ? CODEX_THREAD_ENV : CODEX_SESSION_ENV;
22747
+ const transcriptPath = transcriptForSessionIn(ledger, own);
22746
22748
  return {
22747
22749
  sessionId: own,
22748
- transcriptPath: null,
22749
- basis: `${source}=${own} \u2014 this Codex task's own id`,
22750
+ transcriptPath,
22751
+ basis: `${source}=${own} \u2014 this Codex task's own id${transcriptPath ? " (rollout from its hook record)" : ""}`,
22750
22752
  newerElsewhere: null
22751
22753
  };
22752
22754
  }
@@ -22769,7 +22771,7 @@ function readCodexHookContext(deps2, readFile = (path) => readFileSync2(path, "u
22769
22771
  const match = matches[0];
22770
22772
  return match ? {
22771
22773
  sessionId: match.sessionId,
22772
- transcriptPath: null,
22774
+ transcriptPath: transcriptForSessionIn(ledger, match.sessionId),
22773
22775
  basis: `unambiguous SessionStart${match.at ? ` at ${match.at}` : ""} in ${match.cwd}`,
22774
22776
  newerElsewhere: null
22775
22777
  } : null;
@@ -22851,7 +22853,12 @@ async function runSessionAttach(flags, deps2) {
22851
22853
  }
22852
22854
  } else if (!flags.providerSession && flags.provider === "codex") {
22853
22855
  const hookContext = readCodexHookContext(deps2);
22854
- if (hookContext) flags.providerSession = hookContext.sessionId;
22856
+ if (hookContext) {
22857
+ flags.providerSession = hookContext.sessionId;
22858
+ if (!flags.transcriptPath && hookContext.transcriptPath) {
22859
+ flags.transcriptPath = hookContext.transcriptPath;
22860
+ }
22861
+ }
22855
22862
  } else if (flags.provider === "claude" && flags.providerSession && !flags.transcriptPath) {
22856
22863
  flags.transcriptPath = readClaudeHookTranscript(deps2, flags.providerSession) ?? void 0;
22857
22864
  }
@@ -23076,7 +23083,7 @@ function telemetryVerdict(sessionId, usage, hostRan, provider = "claude") {
23076
23083
  if (provider === "codex" && hostRan) {
23077
23084
  return {
23078
23085
  state: "unavailable",
23079
- line: `Telemetry: token usage unavailable \u2014 Codex lifecycle hooks report no token receipts${wallPart} (coverage ${coverage})`,
23086
+ line: `Telemetry: token usage unavailable \u2014 no Codex rollout receipt was observed${wallPart} (coverage ${coverage})`,
23080
23087
  warning: null
23081
23088
  };
23082
23089
  }
@@ -23858,7 +23865,7 @@ function sessionBindingCheck(detected) {
23858
23865
  return detected.hookDir ? {
23859
23866
  name: "session",
23860
23867
  status: "ok",
23861
- detail: `${detected.providerSessionId} \xB7 ${detected.basis ?? "resolved"} \xB7 host watches trusted Codex lifecycle hooks; token usage is unavailable by provider contract`
23868
+ detail: `${detected.providerSessionId} \xB7 ${detected.basis ?? "resolved"} \xB7 host watches trusted Codex lifecycle hooks and provider-reported rollout usage`
23862
23869
  } : {
23863
23870
  name: "session",
23864
23871
  status: "warn",
@@ -23886,7 +23893,7 @@ function detectProvider(flags, deps2) {
23886
23893
  return {
23887
23894
  provider: "codex",
23888
23895
  providerSessionId: flags.providerSession ?? hook2?.sessionId ?? null,
23889
- transcriptPath: null,
23896
+ transcriptPath: flags.transcriptPath ?? hook2?.transcriptPath ?? null,
23890
23897
  basis: flags.providerSession ? `codex thread ${flags.providerSession} (explicit)` : hook2?.basis ?? null,
23891
23898
  newerElsewhere: null,
23892
23899
  transcriptGap: null,
@@ -23898,7 +23905,7 @@ function detectProvider(flags, deps2) {
23898
23905
  return {
23899
23906
  provider: "codex",
23900
23907
  providerSessionId: current.sessionId,
23901
- transcriptPath: null,
23908
+ transcriptPath: flags.transcriptPath ?? current.transcriptPath,
23902
23909
  basis: current.basis,
23903
23910
  newerElsewhere: null,
23904
23911
  transcriptGap: null,
@@ -24332,7 +24339,7 @@ ${JSON.stringify(session.alignment, null, 2)}`
24332
24339
  });
24333
24340
  if (pid !== null) {
24334
24341
  deps2.writeOut(
24335
- captureMode === "on" ? `Capture host running (pid ${pid}) \u2014 TRACE capture ON for this session.` : detected.provider === "codex" ? `Session host running (pid ${pid}) \u2014 heartbeats + Codex lifecycle hooks; token usage is unavailable and TRACE capture is OFF.` : `Session host running (pid ${pid}) \u2014 heartbeats + telemetry only; TRACE capture is OFF (MVP default; a NEW session aligned with --capture records one \u2014 it cannot start mid-session).`
24342
+ captureMode === "on" ? `Capture host running (pid ${pid}) \u2014 TRACE capture ON for this session.` : detected.provider === "codex" ? `Session host running (pid ${pid}) \u2014 heartbeats + Codex rollout telemetry; TRACE capture is OFF.` : `Session host running (pid ${pid}) \u2014 heartbeats + telemetry only; TRACE capture is OFF (MVP default; a NEW session aligned with --capture records one \u2014 it cannot start mid-session).`
24336
24343
  );
24337
24344
  }
24338
24345
  } else if (!liveHost) {
@@ -27496,7 +27503,7 @@ function registerLogoutCommand(program3, deps2, onExit2) {
27496
27503
 
27497
27504
  // src/commands/plugin.ts
27498
27505
  import { existsSync as existsSync4 } from "node:fs";
27499
- import { join as join4, resolve as resolve2 } from "node:path";
27506
+ import { join as join4, resolve as resolve2, win32 } from "node:path";
27500
27507
  var MARKETPLACE_NAME = "jentrix";
27501
27508
  var PLUGIN_REF = "jentrix@jentrix";
27502
27509
  var LEGACY_MARKETPLACE = "stacks";
@@ -27536,6 +27543,14 @@ function persistentPluginRoot(globalNodeModules, ownRoot, exists) {
27536
27543
  function isPluginMarketplaceDir(dir) {
27537
27544
  return existsSync4(join4(dir, ".claude-plugin", "marketplace.json"));
27538
27545
  }
27546
+ function samePluginPath(a, b, win = process.platform === "win32") {
27547
+ const normalize = (raw) => {
27548
+ const bare = raw.replace(/^\\\\\?\\UNC\\/i, "\\\\").replace(/^\\\\\?\\/, "");
27549
+ const abs = win ? win32.resolve(bare) : resolve2(bare);
27550
+ return win ? abs.toLowerCase() : abs;
27551
+ };
27552
+ return normalize(a) === normalize(b);
27553
+ }
27539
27554
  function isCodexPluginMarketplaceDir(dir) {
27540
27555
  return existsSync4(join4(dir, ".agents", "plugins", "marketplace.json"));
27541
27556
  }
@@ -27582,7 +27597,7 @@ async function installCodexPlugin(deps2, codex, pluginDir) {
27582
27597
  const existing = marketplaces.find((row) => row.name === MARKETPLACE_NAME);
27583
27598
  if (existing) {
27584
27599
  const source = existing.marketplaceSource;
27585
- if (source?.sourceType !== "local" || typeof source.source !== "string" || resolve2(source.source) !== resolve2(pluginDir)) {
27600
+ if (source?.sourceType !== "local" || typeof source.source !== "string" || !samePluginPath(source.source, pluginDir)) {
27586
27601
  deps2.writeErr(
27587
27602
  `PLUGIN_MARKETPLACE_CONFLICT: Codex marketplace "${MARKETPLACE_NAME}" points somewhere else \u2014 inspect with \`codex plugin marketplace list\`; if intentional, run \`codex plugin marketplace remove ${MARKETPLACE_NAME}\` and retry`
27588
27603
  );
@@ -27627,7 +27642,7 @@ async function installCodexPlugin(deps2, codex, pluginDir) {
27627
27642
  }
27628
27643
  return finishInstall(
27629
27644
  deps2,
27630
- "Jentrix Codex plugin ready: $jentrix-connect, $jentrix-align, $jentrix-plan, $jentrix-checkpoint, $jentrix-status, and $jentrix-end are available in NEW Codex tasks. Open `/hooks` and trust the Jentrix hooks before use." + await removeLegacyPlugin(deps2, codex, "codex")
27645
+ "Jentrix Codex plugin ready: $jentrix-connect, $jentrix-align, $jentrix-plan, $jentrix-checkpoint, $jentrix-status, $jentrix-review, and $jentrix-end are available in NEW Codex tasks. Open `/hooks` and trust the Jentrix hooks before use." + await removeLegacyPlugin(deps2, codex, "codex")
27631
27646
  );
27632
27647
  }
27633
27648
  async function runPluginInstall(deps2, provider = "claude") {
@@ -27718,7 +27733,7 @@ ${update.stderr}`.match(
27718
27733
  );
27719
27734
  return finishInstall(
27720
27735
  deps2,
27721
- `Jentrix plugin ready: /jentrix-connect, /jentrix-align, /jentrix-plan, /jentrix-checkpoint, /jentrix-status, /jentrix-end are available in NEW Claude Code sessions (running sessions pick it up on restart).${await removeLegacyPlugin(deps2, claude, "claude")}`
27736
+ `Jentrix plugin ready: /jentrix-connect, /jentrix-align, /jentrix-plan, /jentrix-checkpoint, /jentrix-status, /jentrix-review, /jentrix-end are available in NEW Claude Code sessions (running sessions pick it up on restart).${await removeLegacyPlugin(deps2, claude, "claude")}`
27722
27737
  );
27723
27738
  }
27724
27739
  function registerPluginCommand(program3, deps2, onExit2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jentrix/cli",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "description": "Command-line client for the Jentrix MCP surface (jentrix tool <name>, generated noun-verb commands).",
5
5
  "keywords": [
6
6
  "jentrix",