codeam-cli 2.10.6 → 2.10.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.
- package/CHANGELOG.md +12 -0
- package/dist/index.js +77 -46
- 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.10.7] — 2026-05-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Don't auto-detect + upload stale JSONL on boot
|
|
12
|
+
|
|
13
|
+
## [2.10.6] — 2026-05-11
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **jetbrains-plugin,vsc-plugin:** Install_cli_and_pair command
|
|
18
|
+
|
|
7
19
|
## [2.10.5] — 2026-05-11
|
|
8
20
|
|
|
9
21
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -1477,7 +1477,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
1477
1477
|
// package.json
|
|
1478
1478
|
var package_default = {
|
|
1479
1479
|
name: "codeam-cli",
|
|
1480
|
-
version: "2.10.
|
|
1480
|
+
version: "2.10.8",
|
|
1481
1481
|
description: "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands \u2014 from anywhere.",
|
|
1482
1482
|
type: "commonjs",
|
|
1483
1483
|
main: "dist/index.js",
|
|
@@ -1848,6 +1848,13 @@ var CommandRelayService = class {
|
|
|
1848
1848
|
/** Polling backoff state (only used on the fallback). */
|
|
1849
1849
|
pollTimer = null;
|
|
1850
1850
|
pollFailures = 0;
|
|
1851
|
+
// Successive polls that returned no commands. Drives an idle
|
|
1852
|
+
// backoff so a CLI sitting on the polling fallback (SSE was
|
|
1853
|
+
// unavailable) doesn't keep hammering the API every 2 s when
|
|
1854
|
+
// there's nothing to do. Reset to 0 whenever a poll DELIVERS a
|
|
1855
|
+
// command, so the first real work after a quiet period still
|
|
1856
|
+
// reaches the CLI quickly.
|
|
1857
|
+
pollEmptyStreak = 0;
|
|
1851
1858
|
/** Reconnect backoff state for the SSE stream. */
|
|
1852
1859
|
sseFailures = 0;
|
|
1853
1860
|
sseReconnectTimer = null;
|
|
@@ -1976,7 +1983,9 @@ var CommandRelayService = class {
|
|
|
1976
1983
|
if (!this._running) return;
|
|
1977
1984
|
await this.pollOnce();
|
|
1978
1985
|
if (this._running) {
|
|
1979
|
-
const
|
|
1986
|
+
const idleExp = Math.min(this.pollEmptyStreak, 4);
|
|
1987
|
+
const effectiveFailures = Math.max(this.pollFailures, idleExp);
|
|
1988
|
+
const delay = computePollDelay({ baseMs: 2e3, failures: effectiveFailures });
|
|
1980
1989
|
this.pollTimer = setTimeout(() => this.pollLoop(), delay);
|
|
1981
1990
|
}
|
|
1982
1991
|
}
|
|
@@ -1985,7 +1994,11 @@ var CommandRelayService = class {
|
|
|
1985
1994
|
const data = await _getJson(`${API_BASE2}/api/commands/pending?pluginId=${this.pluginId}`);
|
|
1986
1995
|
const commands = data?.data;
|
|
1987
1996
|
this.pollFailures = 0;
|
|
1988
|
-
if (!Array.isArray(commands) || commands.length === 0)
|
|
1997
|
+
if (!Array.isArray(commands) || commands.length === 0) {
|
|
1998
|
+
this.pollEmptyStreak += 1;
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
this.pollEmptyStreak = 0;
|
|
1989
2002
|
log.trace("relay", `poll received ${commands.length} command(s)`);
|
|
1990
2003
|
await this.dispatchCommands(commands);
|
|
1991
2004
|
} catch (err) {
|
|
@@ -5504,19 +5517,8 @@ var OutputService = class _OutputService {
|
|
|
5504
5517
|
tryExtractSessionId(text) {
|
|
5505
5518
|
if (!this.onSessionIdDetected) return;
|
|
5506
5519
|
const printable = text.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5507
|
-
const
|
|
5508
|
-
|
|
5509
|
-
/Session[:\s]+([a-f0-9-]{36})/i,
|
|
5510
|
-
/Conversation[:\s]+([a-f0-9-]{36})/i,
|
|
5511
|
-
/Session\s+ID[:\s]+([a-f0-9-]{36})/i
|
|
5512
|
-
];
|
|
5513
|
-
for (const pattern of patterns) {
|
|
5514
|
-
const match = printable.match(pattern);
|
|
5515
|
-
if (match) {
|
|
5516
|
-
this.onSessionIdDetected(match[1]);
|
|
5517
|
-
return;
|
|
5518
|
-
}
|
|
5519
|
-
}
|
|
5520
|
+
const match = printable.match(/Resuming session[:\s]+([a-f0-9-]{36})/i);
|
|
5521
|
+
if (match) this.onSessionIdDetected(match[1]);
|
|
5520
5522
|
}
|
|
5521
5523
|
tryDetectRateLimit(text) {
|
|
5522
5524
|
if (!this.onRateLimitDetected) return;
|
|
@@ -5652,10 +5654,11 @@ function post(endpoint, body) {
|
|
|
5652
5654
|
req.end();
|
|
5653
5655
|
});
|
|
5654
5656
|
}
|
|
5655
|
-
var HistoryService = class {
|
|
5656
|
-
constructor(pluginId, cwd) {
|
|
5657
|
+
var HistoryService = class _HistoryService {
|
|
5658
|
+
constructor(pluginId, cwd, options) {
|
|
5657
5659
|
this.pluginId = pluginId;
|
|
5658
5660
|
this.cwd = cwd;
|
|
5661
|
+
this.bootTimeMs = options?.bootTimeMs ?? Date.now();
|
|
5659
5662
|
}
|
|
5660
5663
|
pluginId;
|
|
5661
5664
|
cwd;
|
|
@@ -5671,6 +5674,25 @@ var HistoryService = class {
|
|
|
5671
5674
|
* resume re-uploads the full transcript on first call.
|
|
5672
5675
|
*/
|
|
5673
5676
|
lastUploadedUuid = /* @__PURE__ */ new Map();
|
|
5677
|
+
/**
|
|
5678
|
+
* Captured at construction time so every JSONL discovery (detect,
|
|
5679
|
+
* usage stats) can ignore files that already existed in the
|
|
5680
|
+
* project's `~/.claude/projects/<cwd>/` dir *before* this CLI
|
|
5681
|
+
* run started. Without this filter, an old conversation — or
|
|
5682
|
+
* a *parallel* Claude session actively writing to the same
|
|
5683
|
+
* project — wins the mtime sort and we publish its content
|
|
5684
|
+
* to the mobile app as if it were the fresh pair's chat.
|
|
5685
|
+
*/
|
|
5686
|
+
bootTimeMs;
|
|
5687
|
+
/**
|
|
5688
|
+
* Small grace window subtracted from `bootTimeMs` when filtering
|
|
5689
|
+
* by `birthtime`. Covers clock skew + filesystem timestamp
|
|
5690
|
+
* rounding (HFS+ floors to 1 s; some Linux filesystems round to
|
|
5691
|
+
* the nearest second). 5 s is comfortably wider than any
|
|
5692
|
+
* filesystem rounding while still excluding everything from a
|
|
5693
|
+
* previous pair / previous Claude run.
|
|
5694
|
+
*/
|
|
5695
|
+
static BIRTHTIME_GRACE_MS = 5e3;
|
|
5674
5696
|
/** Store rate limit reset info detected from Claude Code output */
|
|
5675
5697
|
setRateLimitReset(reset) {
|
|
5676
5698
|
this._rateLimitReset = reset;
|
|
@@ -5725,37 +5747,50 @@ var HistoryService = class {
|
|
|
5725
5747
|
}
|
|
5726
5748
|
return null;
|
|
5727
5749
|
}
|
|
5728
|
-
/**
|
|
5750
|
+
/**
|
|
5751
|
+
* Detect the active conversation by finding the most recently
|
|
5752
|
+
* modified JSONL file that was **created during this CLI run**.
|
|
5753
|
+
* The birthtime filter is critical: without it, an old
|
|
5754
|
+
* conversation in the same project dir — or a parallel Claude
|
|
5755
|
+
* session actively writing to a sibling JSONL — wins the mtime
|
|
5756
|
+
* sort, and we publish that other run's content to mobile as if
|
|
5757
|
+
* it were the fresh pair's chat. With the filter, only files
|
|
5758
|
+
* Claude created on or after `bootTimeMs` are eligible, so a
|
|
5759
|
+
* fresh pair stays empty until the user actually types a turn.
|
|
5760
|
+
*/
|
|
5729
5761
|
detectCurrentConversation() {
|
|
5730
5762
|
const dir = this.projectDir;
|
|
5763
|
+
const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
|
|
5731
5764
|
try {
|
|
5732
5765
|
const files = fs5.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
|
|
5733
5766
|
try {
|
|
5734
|
-
|
|
5767
|
+
const stat3 = fs5.statSync(path8.join(dir, e.name));
|
|
5768
|
+
return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
|
|
5735
5769
|
} catch {
|
|
5736
|
-
return { name: e.name, mtime: 0 };
|
|
5770
|
+
return { name: e.name, mtime: 0, birthtime: 0 };
|
|
5737
5771
|
}
|
|
5738
|
-
}).sort((a, b) => b.mtime - a.mtime);
|
|
5772
|
+
}).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
|
|
5739
5773
|
if (files.length > 0) {
|
|
5740
5774
|
this.currentConversationId = path8.basename(files[0].name, ".jsonl");
|
|
5741
5775
|
}
|
|
5742
5776
|
} catch {
|
|
5743
5777
|
}
|
|
5744
5778
|
}
|
|
5745
|
-
/**
|
|
5779
|
+
/**
|
|
5780
|
+
* Extract conversation ID from Claude output. Limited to the
|
|
5781
|
+
* unambiguous "Resuming session: <uuid>" pattern — the older
|
|
5782
|
+
* generic `Session: <uuid>` / `Conversation: <uuid>` patterns
|
|
5783
|
+
* were too greedy and matched any incidental UUID-bearing line
|
|
5784
|
+
* Claude printed (debug logs, status info, etc.), causing the
|
|
5785
|
+
* CLI to "detect" the wrong conversation on a fresh pair.
|
|
5786
|
+
* Resume is the only flow that legitimately needs to bind via
|
|
5787
|
+
* output text; everything else sets `currentConversationId`
|
|
5788
|
+
* via `setCurrentConversationId()` or the birthtime-filtered
|
|
5789
|
+
* `detectCurrentConversation()`.
|
|
5790
|
+
*/
|
|
5746
5791
|
tryExtractConversationIdFromOutput(output) {
|
|
5747
|
-
const
|
|
5748
|
-
|
|
5749
|
-
/session[:\s]+([a-f0-9-]{36})/i,
|
|
5750
|
-
/conversation[:\s]+([a-f0-9-]{36})/i
|
|
5751
|
-
];
|
|
5752
|
-
for (const pattern of patterns) {
|
|
5753
|
-
const match = output.match(pattern);
|
|
5754
|
-
if (match) {
|
|
5755
|
-
this.currentConversationId = match[1];
|
|
5756
|
-
return;
|
|
5757
|
-
}
|
|
5758
|
-
}
|
|
5792
|
+
const match = output.match(/Resuming session[:\s]+([a-f0-9-]{36})/i);
|
|
5793
|
+
if (match) this.currentConversationId = match[1];
|
|
5759
5794
|
}
|
|
5760
5795
|
/**
|
|
5761
5796
|
* Read the most recently modified JSONL session file and extract the
|
|
@@ -5767,6 +5802,7 @@ var HistoryService = class {
|
|
|
5767
5802
|
*/
|
|
5768
5803
|
getCurrentUsage() {
|
|
5769
5804
|
const dir = this.projectDir;
|
|
5805
|
+
const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
|
|
5770
5806
|
let entries;
|
|
5771
5807
|
try {
|
|
5772
5808
|
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
@@ -5775,11 +5811,12 @@ var HistoryService = class {
|
|
|
5775
5811
|
}
|
|
5776
5812
|
const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
|
|
5777
5813
|
try {
|
|
5778
|
-
|
|
5814
|
+
const stat3 = fs5.statSync(path8.join(dir, e.name));
|
|
5815
|
+
return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
|
|
5779
5816
|
} catch {
|
|
5780
|
-
return { name: e.name, mtime: 0 };
|
|
5817
|
+
return { name: e.name, mtime: 0, birthtime: 0 };
|
|
5781
5818
|
}
|
|
5782
|
-
}).sort((a, b) => b.mtime - a.mtime);
|
|
5819
|
+
}).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
|
|
5783
5820
|
if (files.length === 0) return null;
|
|
5784
5821
|
const targetFile = this.currentConversationId ? `${this.currentConversationId}.jsonl` : files[0].name;
|
|
5785
5822
|
if (!files.some((f) => f.name === targetFile)) return null;
|
|
@@ -6914,14 +6951,8 @@ async function start() {
|
|
|
6914
6951
|
await outputSvc.startTerminalTurn();
|
|
6915
6952
|
relay.start();
|
|
6916
6953
|
setTimeout(() => {
|
|
6917
|
-
historySvc.detectCurrentConversation();
|
|
6918
6954
|
historySvc.load().catch(() => {
|
|
6919
6955
|
});
|
|
6920
|
-
const currentId = historySvc.getCurrentConversationId();
|
|
6921
|
-
if (currentId) {
|
|
6922
|
-
historySvc.loadConversation(currentId).catch(() => {
|
|
6923
|
-
});
|
|
6924
|
-
}
|
|
6925
6956
|
}, 2e3);
|
|
6926
6957
|
setTimeout(() => fetchQuotaUsage(historySvc), 5e3);
|
|
6927
6958
|
}
|
|
@@ -9321,7 +9352,7 @@ async function stopWorkspaceFromLocal(target) {
|
|
|
9321
9352
|
// src/commands/version.ts
|
|
9322
9353
|
var import_picocolors11 = __toESM(require("picocolors"));
|
|
9323
9354
|
function version() {
|
|
9324
|
-
const v = true ? "2.10.
|
|
9355
|
+
const v = true ? "2.10.8" : "unknown";
|
|
9325
9356
|
console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
|
|
9326
9357
|
}
|
|
9327
9358
|
|
|
@@ -9456,7 +9487,7 @@ function checkForUpdates() {
|
|
|
9456
9487
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
9457
9488
|
if (process.env.CI) return;
|
|
9458
9489
|
if (!process.stdout.isTTY) return;
|
|
9459
|
-
const current = true ? "2.10.
|
|
9490
|
+
const current = true ? "2.10.8" : null;
|
|
9460
9491
|
if (!current) return;
|
|
9461
9492
|
const cache = readCache();
|
|
9462
9493
|
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.10.
|
|
3
|
+
"version": "2.10.8",
|
|
4
4
|
"description": "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands — from anywhere.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|