codeam-cli 2.75.9 → 2.75.11

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 (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +139 -39
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.75.10] — 2026-09-23
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Guardrails judge a write by its target; pack stage shows a mid-turn permission as awaiting
12
+
13
+ ## [2.75.9] — 2026-09-23
14
+
15
+ ### Added
16
+
17
+ - **cli:** Agent packs — structured Reviewer findings, in-place resume, rehydration after restart
18
+
7
19
  ## [2.75.8] — 2026-09-23
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -10509,7 +10509,7 @@ function readAnonId() {
10509
10509
  }
10510
10510
  function superProperties() {
10511
10511
  return {
10512
- cliVersion: true ? "2.75.9" : "0.0.0-dev",
10512
+ cliVersion: true ? "2.75.11" : "0.0.0-dev",
10513
10513
  nodeVersion: process.version,
10514
10514
  platform: process.platform,
10515
10515
  arch: process.arch,
@@ -10751,7 +10751,7 @@ var http = __toESM(require("http"));
10751
10751
  // package.json
10752
10752
  var package_default = {
10753
10753
  name: "codeam-cli",
10754
- version: "2.75.9",
10754
+ version: "2.75.11",
10755
10755
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
10756
10756
  type: "commonjs",
10757
10757
  main: "dist/index.js",
@@ -12081,7 +12081,7 @@ var CommandRelayService = class _CommandRelayService {
12081
12081
  // fresh + clear the "CLI update available" banner after a self-update
12082
12082
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
12083
12083
  // pair/reconnect). Older backends ignore the extra field.
12084
- ..."2.75.9" ? { ideVersion: "2.75.9" } : {}
12084
+ ..."2.75.11" ? { ideVersion: "2.75.11" } : {}
12085
12085
  };
12086
12086
  try {
12087
12087
  const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body);
@@ -33795,7 +33795,7 @@ function internalPathPermissionOutcome(request2, homeDir2) {
33795
33795
  var INTERNAL_BLOCK_REASON = "This path is a CodeAgent platform internal (CodeAgent's own runtime plumbing, not part of the user's project) and is off-limits. Continue with the user's own project files.";
33796
33796
 
33797
33797
  // src/agents/acp/guardrails.ts
33798
- var SECRET_RE = /(^|[\s"'`=(/\\])(\.env(\.[\w.-]+)?|[\w.-]+\.(pem|key|pfx|p12|jks|keystore)|id_rsa|id_ed25519|\.npmrc|\.pgpass|\.netrc|\.git-credentials|kubeconfig|credentials(\.[\w-]+)?)\b/i;
33798
+ var SECRET_RE = /(^|[\s"'`=(/\\])(\.env(\.[\w.-]+)?|[\w.-]+\.(pem|key|pfx|p12|jks|keystore)|id_rsa|id_ed25519|\.npmrc|\.pgpass|\.netrc|\.git-credentials|kubeconfig|\.credentials(\.[\w-]+)?)\b|[/\\]credentials(\.[\w-]+)?\b/i;
33799
33799
  var DESTRUCTIVE_RES = [
33800
33800
  /\brm\s+-[a-z]*f/i,
33801
33801
  // rm -rf / -fr / -f
@@ -33821,6 +33821,15 @@ var OUTWARD_RES = [
33821
33821
  /\bnetlify\s+deploy\b/i,
33822
33822
  /\beas\s+(submit|build)\b/i
33823
33823
  ];
33824
+ function editTargetHaystack(call) {
33825
+ const parts = [typeof call.title === "string" ? call.title : ""];
33826
+ if (call.rawInput && typeof call.rawInput === "object" && !Array.isArray(call.rawInput)) {
33827
+ for (const [key, value] of Object.entries(call.rawInput)) {
33828
+ if (typeof value === "string" && /path|file|dest|target/i.test(key)) parts.push(value);
33829
+ }
33830
+ }
33831
+ return parts.join("\n");
33832
+ }
33824
33833
  function haystack(call) {
33825
33834
  let s = typeof call.title === "string" ? call.title : "";
33826
33835
  if (call.rawInput != null) {
@@ -33849,7 +33858,7 @@ function toolPathIsSecret(p2) {
33849
33858
  var GUARDRAIL_SECRET_READ_BLOCK_REASON = "Reading this secret file is blocked by this session's guardrails (Reading secrets = Deny). Adjust it in the session Guardrails settings if intended.";
33850
33859
  function guardrailDecision(request2, policy) {
33851
33860
  if (isProseOnlyToolKind(request2.toolCall.kind)) return null;
33852
- const hay = haystack(request2.toolCall);
33861
+ const hay = request2.toolCall.kind === "edit" ? editTargetHaystack(request2.toolCall) : haystack(request2.toolCall);
33853
33862
  if (!hay) return null;
33854
33863
  const matched = matchedCategories(hay);
33855
33864
  if (matched.size === 0) return null;
@@ -33873,7 +33882,11 @@ function guardrailDecision(request2, policy) {
33873
33882
  };
33874
33883
  }
33875
33884
  if (confirmCat) {
33876
- return { kind: "confirm", category: confirmCat, reason: GUARDRAIL_CATEGORY_META[confirmCat].description };
33885
+ return {
33886
+ kind: "confirm",
33887
+ category: confirmCat,
33888
+ reason: GUARDRAIL_CATEGORY_META[confirmCat].description
33889
+ };
33877
33890
  }
33878
33891
  return null;
33879
33892
  }
@@ -35563,6 +35576,18 @@ function _get(url2, headers) {
35563
35576
  }
35564
35577
 
35565
35578
  // src/agents/acp/publisher.ts
35579
+ var STREAMING_CHUNK_SNAPSHOT_MAX_CHARS = 56 * 1024;
35580
+ var STREAMING_CHUNK_COALESCE_MIN_CHARS = 8 * 1024;
35581
+ var STREAMING_CHUNK_COALESCE_WINDOW_MS = 300;
35582
+ function boundStreamingChunkContent(content, max = STREAMING_CHUNK_SNAPSHOT_MAX_CHARS) {
35583
+ if (content.length <= max) return { content, truncated: false };
35584
+ const dropped = content.length - max;
35585
+ return {
35586
+ content: `${content.slice(0, max)}
35587
+ \u2026 [output truncated: ${dropped} more characters \u2014 the full text is in the conversation transcript]`,
35588
+ truncated: true
35589
+ };
35590
+ }
35566
35591
  var AcpPublisher = class {
35567
35592
  constructor(opts) {
35568
35593
  this.opts = opts;
@@ -35572,6 +35597,11 @@ var AcpPublisher = class {
35572
35597
  opts;
35573
35598
  apiBase;
35574
35599
  token;
35600
+ /** chunkIds whose truncated snapshot already went out — nothing new to show
35601
+ * until the terminal frame. */
35602
+ truncatedChunks = /* @__PURE__ */ new Set();
35603
+ lastChunkPostAt = /* @__PURE__ */ new Map();
35604
+ pendingChunkPosts = /* @__PURE__ */ new Map();
35575
35605
  /** Latched on an unrecoverable 401/403 — every surface stops posting
35576
35606
  * (2026-06-28 incident: a dead-token publisher spammed 401 ×34 while
35577
35607
  * the agent's replies silently never reached the phone). */
@@ -35600,7 +35630,10 @@ var AcpPublisher = class {
35600
35630
  const fresh = await this.opts.refreshAuthToken();
35601
35631
  if (fresh) {
35602
35632
  this.token = fresh;
35603
- log.info("acpPublisher", `plugin-auth token refreshed after ${first.statusCode}; retrying POST`);
35633
+ log.info(
35634
+ "acpPublisher",
35635
+ `plugin-auth token refreshed after ${first.statusCode}; retrying POST`
35636
+ );
35604
35637
  const second = await _transport4.post(url2, this.authHeaders(), payload);
35605
35638
  if (second.statusCode !== 401 && second.statusCode !== 403) return second;
35606
35639
  this.markPairingInvalid(second.statusCode);
@@ -35709,17 +35742,51 @@ var AcpPublisher = class {
35709
35742
  * richer SessionDetail surface.
35710
35743
  */
35711
35744
  async publishStreamingChunk(event) {
35745
+ const { chunkId, isFinal } = event;
35746
+ const pending = this.pendingChunkPosts.get(chunkId);
35747
+ if (pending) {
35748
+ clearTimeout(pending.timer);
35749
+ this.pendingChunkPosts.delete(chunkId);
35750
+ }
35751
+ const bounded = boundStreamingChunkContent(event.content);
35752
+ if (bounded.truncated) {
35753
+ if (!isFinal && this.truncatedChunks.has(chunkId)) return;
35754
+ if (!this.truncatedChunks.has(chunkId)) {
35755
+ log.warn(
35756
+ "acpPublisher",
35757
+ `streaming-chunk ${chunkId.slice(0, 12)} (${event.kind}) exceeds ${STREAMING_CHUNK_SNAPSHOT_MAX_CHARS} chars \u2014 sending a truncated snapshot once`
35758
+ );
35759
+ }
35760
+ this.truncatedChunks.add(chunkId);
35761
+ }
35762
+ if (isFinal) {
35763
+ this.truncatedChunks.delete(chunkId);
35764
+ this.lastChunkPostAt.delete(chunkId);
35765
+ } else if (!bounded.truncated && bounded.content.length >= STREAMING_CHUNK_COALESCE_MIN_CHARS) {
35766
+ const last = this.lastChunkPostAt.get(chunkId) ?? 0;
35767
+ const wait = STREAMING_CHUNK_COALESCE_WINDOW_MS - (Date.now() - last);
35768
+ if (wait > 0) {
35769
+ const timer = setTimeout(() => {
35770
+ const latest = this.pendingChunkPosts.get(chunkId);
35771
+ this.pendingChunkPosts.delete(chunkId);
35772
+ if (latest) void this.postStreamingChunk(latest.event);
35773
+ }, wait);
35774
+ this.pendingChunkPosts.set(chunkId, { timer, event });
35775
+ return;
35776
+ }
35777
+ }
35778
+ await this.postStreamingChunk({ ...event, content: bounded.content });
35779
+ }
35780
+ async postStreamingChunk(event) {
35712
35781
  const url2 = `${this.apiBase}/api/sessions/${encodeURIComponent(this.opts.sessionId)}/streaming-chunk`;
35782
+ if (!event.isFinal) this.lastChunkPostAt.set(event.chunkId, Date.now());
35713
35783
  try {
35714
35784
  const { statusCode, body } = await this.postWithReauth(
35715
35785
  url2,
35716
35786
  this.envelope(event)
35717
35787
  );
35718
35788
  if (statusCode < 200 || statusCode >= 300) {
35719
- log.warn(
35720
- "acpPublisher",
35721
- `streaming-chunk status=${statusCode} body=${body.slice(0, 200)}`
35722
- );
35789
+ log.warn("acpPublisher", `streaming-chunk status=${statusCode} body=${body.slice(0, 200)}`);
35723
35790
  }
35724
35791
  } catch (err) {
35725
35792
  log.trace("acpPublisher", "streaming-chunk post failed", err);
@@ -38054,7 +38121,7 @@ var PackRunner = class _PackRunner {
38054
38121
  previousAttemptError
38055
38122
  });
38056
38123
  const displayLine = `\u25B6 ${this.pack.name} \u2014 stage ${index + 1}/${this.pack.stages.length}: ${stageDef.name}`;
38057
- turn = await this.drive(prompt, displayLine);
38124
+ turn = await this.drive(prompt, displayLine, index);
38058
38125
  }
38059
38126
  if (this.aborted()) {
38060
38127
  await this.settle("aborted");
@@ -38069,7 +38136,7 @@ var PackRunner = class _PackRunner {
38069
38136
  return this.recordHandoff(
38070
38137
  index,
38071
38138
  stageDef,
38072
- await this.noChangeHandoff(startSha, text, startedMs)
38139
+ await this.noChangeHandoff(stageDef, startSha, text, startedMs)
38073
38140
  );
38074
38141
  }
38075
38142
  if (!this.state.stages[index].conversationId) {
@@ -38078,7 +38145,7 @@ var PackRunner = class _PackRunner {
38078
38145
  "stage produced no commit \u2014 Retry stage to run it again in a fresh conversation"
38079
38146
  );
38080
38147
  }
38081
- const nudged = await this.drive(NUDGE_PROMPT, "\u25B6 Waiting for the stage commit\u2026");
38148
+ const nudged = await this.drive(NUDGE_PROMPT, "\u25B6 Waiting for the stage commit\u2026", index);
38082
38149
  if (this.aborted()) {
38083
38150
  await this.settle("aborted");
38084
38151
  return false;
@@ -38091,7 +38158,7 @@ var PackRunner = class _PackRunner {
38091
38158
  return this.recordHandoff(
38092
38159
  index,
38093
38160
  stageDef,
38094
- await this.noChangeHandoff(startSha, text, startedMs)
38161
+ await this.noChangeHandoff(stageDef, startSha, text, startedMs)
38095
38162
  );
38096
38163
  }
38097
38164
  return this.stall(index, "stage produced no commit", text);
@@ -38123,10 +38190,13 @@ var PackRunner = class _PackRunner {
38123
38190
  aborted() {
38124
38191
  return this.control === "abort";
38125
38192
  }
38126
- async drive(prompt, displayLine) {
38193
+ async drive(prompt, displayLine, index) {
38127
38194
  this.inTurn = true;
38128
38195
  try {
38129
- return await this.deps.driver.runTurn(prompt, displayLine);
38196
+ return await this.deps.driver.runTurn(prompt, displayLine, (pending) => {
38197
+ this.patchStage(index, { awaitingUser: pending ? true : void 0 });
38198
+ void this.publish();
38199
+ });
38130
38200
  } finally {
38131
38201
  this.inTurn = false;
38132
38202
  }
@@ -38138,13 +38208,19 @@ var PackRunner = class _PackRunner {
38138
38208
  if (fresh.length > 0) this.deps.driver.mountSkills(fresh);
38139
38209
  this.mountedSkills = next.slice();
38140
38210
  }
38141
- async noChangeHandoff(startSha, text, startedMs) {
38211
+ async noChangeHandoff(stageDef, startSha, text, startedMs) {
38142
38212
  const reviewedCommit = startSha ? await this.deps.gates.canonicalCommit(startSha) : null;
38143
38213
  return {
38144
38214
  commit: reviewedCommit ?? "(no changes)",
38145
38215
  summary: summarizeReply(text),
38146
38216
  diffStat: "reviewed \u2014 no changes needed",
38147
- durationMs: Date.now() - startedMs
38217
+ durationMs: Date.now() - startedMs,
38218
+ // A findings-producing stage that hands off without a commit never
38219
+ // wrote its findings file (its contract says: commit it even when
38220
+ // empty). Say so — the next stage must not assume a clean bill.
38221
+ ...stageDef.producesFindings ? {
38222
+ findingsNote: `no ${PACK_FINDINGS_FILE} committed \u2014 the stage handed off without structured findings`
38223
+ } : {}
38148
38224
  };
38149
38225
  }
38150
38226
  /**
@@ -38253,15 +38329,20 @@ function buildPackRunnerDeps(ctx) {
38253
38329
  ctx.onActiveSessionChanged?.(id);
38254
38330
  return id;
38255
38331
  },
38256
- runTurn: async (prompt, displayLine) => {
38332
+ runTurn: async (prompt, displayLine, onAwaiting) => {
38257
38333
  await ctx.streaming.beginTurn();
38258
38334
  ctx.history.appendUserPrompt(displayLine);
38259
- await ctx.client.prompt(prompt);
38260
- const text = ctx.streaming.getCurrentText();
38261
- const awaitingUser = await ctx.streaming.closeTurnWithInteractiveDetection();
38262
- ctx.history.appendAgentReply(text);
38263
- await ctx.history.flush();
38264
- return { text, awaitingUser };
38335
+ ctx.streaming.setPendingListener(onAwaiting ?? null);
38336
+ try {
38337
+ await ctx.client.prompt(prompt);
38338
+ const text = ctx.streaming.getCurrentText();
38339
+ const awaitingUser = await ctx.streaming.closeTurnWithInteractiveDetection();
38340
+ ctx.history.appendAgentReply(text);
38341
+ await ctx.history.flush();
38342
+ return { text, awaitingUser };
38343
+ } finally {
38344
+ ctx.streaming.setPendingListener(null);
38345
+ }
38265
38346
  },
38266
38347
  cancel: () => ctx.client.cancel(),
38267
38348
  mountSkills: (skillIds) => {
@@ -40064,6 +40145,25 @@ var StreamingState = class {
40064
40145
  */
40065
40146
  text = "";
40066
40147
  pending = null;
40148
+ /** Observer for "is a question pending?" flips. The Agent Packs runner marks
40149
+ * its stage `awaitingUser` while a permission prompt waits on the user
40150
+ * MID-turn, instead of a stage that looks silently "working" for the 5-min
40151
+ * permission TTL (live run 2026-09-23). Never throws into the turn. */
40152
+ pendingListener = null;
40153
+ setPendingListener(listener) {
40154
+ this.pendingListener = listener;
40155
+ }
40156
+ setPending(next) {
40157
+ const was = this.pending !== null;
40158
+ this.pending = next;
40159
+ const is = next !== null;
40160
+ if (was !== is) {
40161
+ try {
40162
+ this.pendingListener?.(is);
40163
+ } catch {
40164
+ }
40165
+ }
40166
+ }
40067
40167
  /**
40068
40168
  * While true, {@link append} drops incoming session/update deltas instead of
40069
40169
  * publishing them. Used by the baton {@link AcpDriver} to swallow the
@@ -40166,7 +40266,7 @@ var StreamingState = class {
40166
40266
  "acpRunner",
40167
40267
  `permission ${args2.questionId.slice(0, 8)} TTL expired \u2014 auto-cancel`
40168
40268
  );
40169
- this.pending = null;
40269
+ this.setPending(null);
40170
40270
  void this.publisher.publishOutput({
40171
40271
  type: "text",
40172
40272
  content: permissionExpiredNotice(),
@@ -40175,13 +40275,13 @@ var StreamingState = class {
40175
40275
  resolve11({ outcome: { outcome: "cancelled" } });
40176
40276
  }
40177
40277
  }, PERMISSION_TIMEOUT_MS);
40178
- this.pending = {
40278
+ this.setPending({
40179
40279
  kind: "permission",
40180
40280
  questionId: args2.questionId,
40181
40281
  options: args2.options,
40182
40282
  resolve: resolve11,
40183
40283
  timeoutTimer
40184
- };
40284
+ });
40185
40285
  });
40186
40286
  }
40187
40287
  /**
@@ -40190,7 +40290,7 @@ var StreamingState = class {
40190
40290
  * `resolveSelection()` (user picked).
40191
40291
  */
40192
40292
  registerFreeformOptions(options) {
40193
- this.pending = { kind: "free-form", options };
40293
+ this.setPending({ kind: "free-form", options });
40194
40294
  }
40195
40295
  /**
40196
40296
  * Apply a `select_option` command (delivered via the CLI's SSE
@@ -40217,7 +40317,7 @@ var StreamingState = class {
40217
40317
  const picked = byId ?? this.pending.options[index];
40218
40318
  clearTimeout(this.pending.timeoutTimer);
40219
40319
  const resolve11 = this.pending.resolve;
40220
- this.pending = null;
40320
+ this.setPending(null);
40221
40321
  if (!picked) {
40222
40322
  log.warn("acpRunner", `select_option index=${index} out of bounds \u2014 cancel`);
40223
40323
  resolve11({ outcome: { outcome: "cancelled" } });
@@ -40227,7 +40327,7 @@ var StreamingState = class {
40227
40327
  return { kind: "resolved", optionId: picked.optionId };
40228
40328
  }
40229
40329
  const text = this.pending.options[index];
40230
- this.pending = null;
40330
+ this.setPending(null);
40231
40331
  if (!text) {
40232
40332
  log.warn("acpRunner", `select_option index=${index} out of bounds (free-form) \u2014 drop`);
40233
40333
  return { kind: "none" };
@@ -40283,7 +40383,7 @@ var StreamingState = class {
40283
40383
  if (this.pending?.kind === "permission") {
40284
40384
  clearTimeout(this.pending.timeoutTimer);
40285
40385
  }
40286
- this.pending = null;
40386
+ this.setPending(null);
40287
40387
  if (opts?.clear !== false) {
40288
40388
  await this.publisher.publishOutput({ type: "clear" });
40289
40389
  }
@@ -40470,7 +40570,7 @@ var StreamingState = class {
40470
40570
  done: true
40471
40571
  });
40472
40572
  }
40473
- this.pending = { kind: "free-form", options: extracted.options };
40573
+ this.setPending({ kind: "free-form", options: extracted.options });
40474
40574
  await this.publisher.publishOutput({
40475
40575
  type: "select_prompt",
40476
40576
  content: extracted.question ?? "Pick an option",
@@ -47014,7 +47114,7 @@ async function autoUpgradeBeforeCriticalCommand() {
47014
47114
  if (process.env.NODE_ENV === "test") return;
47015
47115
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
47016
47116
  if (process.env.CI) return;
47017
- const current2 = true ? "2.75.9" : null;
47117
+ const current2 = true ? "2.75.11" : null;
47018
47118
  if (!current2) return;
47019
47119
  const cache = readCache();
47020
47120
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -47031,7 +47131,7 @@ function checkForUpdates() {
47031
47131
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
47032
47132
  if (process.env.CI) return;
47033
47133
  if (!process.stdout.isTTY) return;
47034
- const current2 = true ? "2.75.9" : null;
47134
+ const current2 = true ? "2.75.11" : null;
47035
47135
  if (!current2) return;
47036
47136
  const cache = readCache();
47037
47137
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -47054,7 +47154,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
47054
47154
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
47055
47155
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
47056
47156
  function currentCliVersion() {
47057
- return true ? "2.75.9" : null;
47157
+ return true ? "2.75.11" : null;
47058
47158
  }
47059
47159
  async function installedVersion(deps) {
47060
47160
  const ls = await deps.run(
@@ -48794,7 +48894,7 @@ function checkChokidar() {
48794
48894
  }
48795
48895
  async function doctor(args2 = []) {
48796
48896
  const json = args2.includes("--json");
48797
- const cliVersion = true ? "2.75.9" : "0.0.0-dev";
48897
+ const cliVersion = true ? "2.75.11" : "0.0.0-dev";
48798
48898
  const apiBase2 = resolveApiBaseUrl();
48799
48899
  const diagnosticId = (0, import_node_crypto12.randomUUID)();
48800
48900
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -49318,7 +49418,7 @@ async function mcpWarm(argv = [], opts = {}) {
49318
49418
  // src/commands/version.ts
49319
49419
  var import_picocolors15 = __toESM(require("picocolors"));
49320
49420
  function version2() {
49321
- const v = true ? "2.75.9" : "unknown";
49421
+ const v = true ? "2.75.11" : "unknown";
49322
49422
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
49323
49423
  }
49324
49424
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.75.9",
3
+ "version": "2.75.11",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",