codeam-cli 2.23.6 → 2.23.8

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 +109 -10
  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.23.7] — 2026-05-26
8
+
9
+ ### Added
10
+
11
+ - **cli:** Emit typed agent_banner chunk for Claude startup splash (#195)
12
+
13
+ ## [2.23.6] — 2026-05-26
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Raise listProjectFiles cap from 5000 to 50000 (#194)
18
+
7
19
  ## [2.23.5] — 2026-05-25
8
20
 
9
21
  ### Added
package/dist/index.js CHANGED
@@ -441,7 +441,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
441
441
  // package.json
442
442
  var package_default = {
443
443
  name: "codeam-cli",
444
- version: "2.23.6",
444
+ version: "2.23.8",
445
445
  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.",
446
446
  type: "commonjs",
447
447
  main: "dist/index.js",
@@ -718,6 +718,9 @@ async function postLinkCredential(input) {
718
718
  if (input.modelPreference) {
719
719
  body.modelPreference = input.modelPreference;
720
720
  }
721
+ if (input.agentState) {
722
+ body.agentState = input.agentState;
723
+ }
721
724
  try {
722
725
  await _transport.postJsonAuthed(
723
726
  `${API_BASE}/api/plugin/agents/${input.agentId}/link`,
@@ -5768,7 +5771,7 @@ function readAnonId() {
5768
5771
  }
5769
5772
  function superProperties() {
5770
5773
  return {
5771
- cliVersion: true ? "2.23.6" : "0.0.0-dev",
5774
+ cliVersion: true ? "2.23.8" : "0.0.0-dev",
5772
5775
  nodeVersion: process.version,
5773
5776
  platform: process.platform,
5774
5777
  arch: process.arch,
@@ -9036,11 +9039,12 @@ function claudeCredentialsPaths() {
9036
9039
  ];
9037
9040
  }
9038
9041
  async function extractLocalClaudeToken() {
9042
+ const agentState = readClaudeAgentState();
9039
9043
  for (const flat of claudeCredentialsPaths()) {
9040
9044
  if (!fs7.existsSync(flat)) continue;
9041
9045
  const credential = fs7.readFileSync(flat, "utf8").trim();
9042
9046
  if (credential.length > 0) {
9043
- return { method: "oauth", credential, source: "flat-file" };
9047
+ return { method: "oauth", credential, source: "flat-file", agentState };
9044
9048
  }
9045
9049
  }
9046
9050
  if (process.platform === "darwin") {
@@ -9053,7 +9057,7 @@ async function extractLocalClaudeToken() {
9053
9057
  );
9054
9058
  const credential = stdout.trim();
9055
9059
  if (credential.length > 0) {
9056
- return { method: "oauth", credential, source: "macos-keychain" };
9060
+ return { method: "oauth", credential, source: "macos-keychain", agentState };
9057
9061
  }
9058
9062
  } catch {
9059
9063
  }
@@ -9061,6 +9065,19 @@ async function extractLocalClaudeToken() {
9061
9065
  }
9062
9066
  return null;
9063
9067
  }
9068
+ function readClaudeAgentState() {
9069
+ const STATE_MAX_BYTES = 256 * 1024;
9070
+ const candidate = path10.join(os9.homedir(), ".claude.json");
9071
+ try {
9072
+ if (!fs7.existsSync(candidate)) return void 0;
9073
+ const buf = fs7.readFileSync(candidate);
9074
+ if (buf.length === 0 || buf.length > STATE_MAX_BYTES) return void 0;
9075
+ const text = buf.toString("utf8").trim();
9076
+ return text.length > 0 ? text : void 0;
9077
+ } catch {
9078
+ return void 0;
9079
+ }
9080
+ }
9064
9081
 
9065
9082
  // src/agents/claude/link.ts
9066
9083
  function claudeCredentialLocator() {
@@ -9633,6 +9650,38 @@ function detectListSelector(lines) {
9633
9650
  currentIndex
9634
9651
  };
9635
9652
  }
9653
+ var BANNER_ART_RE = /[█▀▄▌▐▝▘▛▜▙▟▖▗▔▕▮▯▰▱▓▒░◆◇]/;
9654
+ var BANNER_META_RE = /(?:Sonnet|Opus|Haiku|Claude)(?:\s|·|-|\(|$)/i;
9655
+ function detectStartupBanner(lines) {
9656
+ for (let i = 0; i + 2 < lines.length; i++) {
9657
+ if (!/^▐▛[█]+▜▌/.test(lines[i])) continue;
9658
+ if (!/^▝▜[█]+▛▘/.test(lines[i + 1])) continue;
9659
+ if (!lines[i + 2].includes("\u2598\u2598")) continue;
9660
+ return {
9661
+ title: lines[i].replace(/^▐▛[█]+▜▌\s*/, "").trim(),
9662
+ subtitle: lines[i + 1].replace(/^▝▜[█]+▛▘\s*/, "").trim(),
9663
+ path: lines[i + 2].replace(/.*▝▝\s*/, "").trim(),
9664
+ startIdx: i,
9665
+ endIdx: i + 2
9666
+ };
9667
+ }
9668
+ const metaIdx = lines.findIndex(
9669
+ (l) => BANNER_META_RE.test(l) && /(?:Claude|API|Console)/i.test(l) && !BANNER_ART_RE.test(l)
9670
+ );
9671
+ if (metaIdx === -1) return null;
9672
+ let artStart = metaIdx;
9673
+ while (artStart > 0 && BANNER_ART_RE.test(lines[artStart - 1])) artStart--;
9674
+ if (metaIdx - artStart < 2) return null;
9675
+ const pathLine = (lines[metaIdx + 1] ?? "").trim();
9676
+ const path40 = pathLine && !BANNER_ART_RE.test(pathLine) ? pathLine : "";
9677
+ return {
9678
+ title: "",
9679
+ subtitle: lines[metaIdx].trim(),
9680
+ path: path40,
9681
+ startIdx: artStart,
9682
+ endIdx: metaIdx + (path40 ? 1 : 0)
9683
+ };
9684
+ }
9636
9685
 
9637
9686
  // src/agents/claude/runtime.ts
9638
9687
  var ClaudeRuntimeStrategy = class {
@@ -9705,6 +9754,9 @@ var ClaudeRuntimeStrategy = class {
9705
9754
  detectReadyPrompt(lines) {
9706
9755
  return lines.some((l) => /^\?\s.*shortcut/i.test(l.trim()));
9707
9756
  }
9757
+ detectStartupBanner(lines) {
9758
+ return detectStartupBanner(lines);
9759
+ }
9708
9760
  credentialLocator() {
9709
9761
  return claudeCredentialLocator();
9710
9762
  }
@@ -11565,6 +11617,16 @@ var OutputService = class _OutputService {
11565
11617
  emitter;
11566
11618
  runtime;
11567
11619
  lastSentContent = "";
11620
+ /**
11621
+ * Per-session latch — emits the agent's startup banner as a typed
11622
+ * `agent_banner` chunk exactly once, then strips the banner lines
11623
+ * from every subsequent rendered frame so the ASCII art never
11624
+ * reaches the `text` chunk. Lives at the OutputService level (not
11625
+ * per-turn) because the banner is a session-boot artifact —
11626
+ * pair/re-pair spawns a fresh service so the latch naturally
11627
+ * resets.
11628
+ */
11629
+ bannerEmitted = false;
11568
11630
  /**
11569
11631
  * Wall-clock of the most recent tick where the rendered + filtered
11570
11632
  * content actually changed. Most agent TUIs keep redrawing a
@@ -11733,6 +11795,40 @@ var OutputService = class _OutputService {
11733
11795
  this.startTime = Date.now();
11734
11796
  this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
11735
11797
  }
11798
+ /**
11799
+ * One-shot banner emit + per-tick strip. Runs the per-agent
11800
+ * `detectStartupBanner` (when the strategy exposes one) and, on
11801
+ * first match, fires a typed `agent_banner` OutputChunk to the
11802
+ * backend. On every tick from then on it slices the banner range
11803
+ * out of the rendered line array so the ASCII / box-art body
11804
+ * doesn't leak into a downstream `text` chunk. Agents that don't
11805
+ * implement the detector pass through unchanged.
11806
+ */
11807
+ handleStartupBanner(lines) {
11808
+ const detect = this.runtime.detectStartupBanner?.bind(this.runtime);
11809
+ if (!detect) return lines;
11810
+ const banner = detect(lines);
11811
+ if (!banner) return lines;
11812
+ if (!this.bannerEmitted) {
11813
+ this.bannerEmitted = true;
11814
+ this.send(
11815
+ {
11816
+ type: "agent_banner",
11817
+ agentId: this.runtime.id,
11818
+ title: banner.title,
11819
+ subtitle: banner.subtitle,
11820
+ path: banner.path,
11821
+ done: true
11822
+ },
11823
+ { critical: true }
11824
+ ).catch(() => {
11825
+ });
11826
+ }
11827
+ return [
11828
+ ...lines.slice(0, banner.startIdx),
11829
+ ...lines.slice(banner.endIdx + 1)
11830
+ ];
11831
+ }
11736
11832
  async send(body, opts = {}) {
11737
11833
  const outcome = await this.emitter.send(body, opts);
11738
11834
  if (outcome.dead && this.pty.isActive) {
@@ -11779,7 +11875,8 @@ var OutputService = class _OutputService {
11779
11875
  return;
11780
11876
  }
11781
11877
  if (elapsed < _OutputService.WARMUP_MS) return;
11782
- const lines = this.runtime.renderToLines?.(this.pty.content) ?? renderLines(this.pty.content);
11878
+ const rendered = this.runtime.renderToLines?.(this.pty.content) ?? renderLines(this.pty.content);
11879
+ const lines = this.handleStartupBanner(rendered);
11783
11880
  const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
11784
11881
  this.steps.ingest(lines, parseLine2);
11785
11882
  const stepsDelta = this.steps.consumeDelta();
@@ -11851,7 +11948,8 @@ var OutputService = class _OutputService {
11851
11948
  }
11852
11949
  }
11853
11950
  finalize() {
11854
- const lines = this.runtime.renderToLines?.(this.pty.content) ?? renderLines(this.pty.content);
11951
+ const rendered = this.runtime.renderToLines?.(this.pty.content) ?? renderLines(this.pty.content);
11952
+ const lines = this.handleStartupBanner(rendered);
11855
11953
  const parseLine2 = this.runtime.parseTuiChrome?.bind(this.runtime) ?? (() => null);
11856
11954
  this.steps.ingest(lines, parseLine2);
11857
11955
  const stepsDelta = this.steps.consumeDelta();
@@ -15170,7 +15268,8 @@ async function uploadAndSucceed(ctx, paired, pluginId, token) {
15170
15268
  pluginId,
15171
15269
  pluginAuthToken: paired.pluginAuthToken,
15172
15270
  method: token.method,
15173
- credential: token.credential
15271
+ credential: token.credential,
15272
+ agentState: token.agentState
15174
15273
  });
15175
15274
  if (!result.ok) {
15176
15275
  uploadSpin.stop("Failed");
@@ -18401,7 +18500,7 @@ function checkChokidar() {
18401
18500
  }
18402
18501
  async function doctor(args2 = []) {
18403
18502
  const json = args2.includes("--json");
18404
- const cliVersion = true ? "2.23.6" : "0.0.0-dev";
18503
+ const cliVersion = true ? "2.23.8" : "0.0.0-dev";
18405
18504
  const apiBase = resolveApiBaseUrl();
18406
18505
  const diagnosticId = (0, import_node_crypto5.randomUUID)();
18407
18506
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -18600,7 +18699,7 @@ async function completion(args2) {
18600
18699
  // src/commands/version.ts
18601
18700
  var import_picocolors13 = __toESM(require("picocolors"));
18602
18701
  function version2() {
18603
- const v = true ? "2.23.6" : "unknown";
18702
+ const v = true ? "2.23.8" : "unknown";
18604
18703
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
18605
18704
  }
18606
18705
 
@@ -18828,7 +18927,7 @@ function checkForUpdates() {
18828
18927
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
18829
18928
  if (process.env.CI) return;
18830
18929
  if (!process.stdout.isTTY) return;
18831
- const current = true ? "2.23.6" : null;
18930
+ const current = true ? "2.23.8" : null;
18832
18931
  if (!current) return;
18833
18932
  const cache = readCache();
18834
18933
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.23.6",
3
+ "version": "2.23.8",
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",