codeam-cli 2.26.6 → 2.26.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 +201 -49
- 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.26.7] — 2026-06-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Preview no longer pollutes the host terminal + waits for tunnel DNS (#240)
|
|
12
|
+
|
|
13
|
+
## [2.26.6] — 2026-06-03
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** Preview shutdown is graceful + parser tolerates prose-wrapped JSON (#239)
|
|
18
|
+
|
|
7
19
|
## [2.26.5] — 2026-06-03
|
|
8
20
|
|
|
9
21
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -472,7 +472,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
472
472
|
// package.json
|
|
473
473
|
var package_default = {
|
|
474
474
|
name: "codeam-cli",
|
|
475
|
-
version: "2.26.
|
|
475
|
+
version: "2.26.8",
|
|
476
476
|
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.",
|
|
477
477
|
type: "commonjs",
|
|
478
478
|
main: "dist/index.js",
|
|
@@ -5829,7 +5829,7 @@ function readAnonId() {
|
|
|
5829
5829
|
}
|
|
5830
5830
|
function superProperties() {
|
|
5831
5831
|
return {
|
|
5832
|
-
cliVersion: true ? "2.26.
|
|
5832
|
+
cliVersion: true ? "2.26.8" : "0.0.0-dev",
|
|
5833
5833
|
nodeVersion: process.version,
|
|
5834
5834
|
platform: process.platform,
|
|
5835
5835
|
arch: process.arch,
|
|
@@ -9572,6 +9572,50 @@ function parseHistoryFile(filePath) {
|
|
|
9572
9572
|
}
|
|
9573
9573
|
return out2;
|
|
9574
9574
|
}
|
|
9575
|
+
function listResumableSessions(cwd) {
|
|
9576
|
+
const dir = resolveHistoryDir(cwd);
|
|
9577
|
+
if (!dir) return [];
|
|
9578
|
+
let entries;
|
|
9579
|
+
try {
|
|
9580
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
9581
|
+
} catch {
|
|
9582
|
+
return [];
|
|
9583
|
+
}
|
|
9584
|
+
const out2 = [];
|
|
9585
|
+
for (const entry of entries) {
|
|
9586
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
9587
|
+
const id = entry.name.slice(0, -".jsonl".length);
|
|
9588
|
+
const filePath = path12.join(dir, entry.name);
|
|
9589
|
+
let timestamp = Date.now();
|
|
9590
|
+
try {
|
|
9591
|
+
timestamp = fs9.statSync(filePath).mtimeMs;
|
|
9592
|
+
} catch {
|
|
9593
|
+
}
|
|
9594
|
+
let summary = "";
|
|
9595
|
+
try {
|
|
9596
|
+
const raw = fs9.readFileSync(filePath, "utf8");
|
|
9597
|
+
for (const line of raw.split("\n")) {
|
|
9598
|
+
if (!line.trim()) continue;
|
|
9599
|
+
try {
|
|
9600
|
+
const record = JSON.parse(line);
|
|
9601
|
+
if (record["type"] === "user") {
|
|
9602
|
+
const msg = record["message"];
|
|
9603
|
+
const text = extractText(msg?.["content"]).trim();
|
|
9604
|
+
if (text) {
|
|
9605
|
+
summary = text.slice(0, 120);
|
|
9606
|
+
break;
|
|
9607
|
+
}
|
|
9608
|
+
}
|
|
9609
|
+
} catch {
|
|
9610
|
+
}
|
|
9611
|
+
}
|
|
9612
|
+
} catch {
|
|
9613
|
+
}
|
|
9614
|
+
if (summary) out2.push({ id, summary, timestamp });
|
|
9615
|
+
}
|
|
9616
|
+
out2.sort((a, b) => b.timestamp - a.timestamp);
|
|
9617
|
+
return out2;
|
|
9618
|
+
}
|
|
9575
9619
|
|
|
9576
9620
|
// src/agents/claude/parsing.ts
|
|
9577
9621
|
function filterChrome(lines) {
|
|
@@ -9933,6 +9977,9 @@ var ClaudeRuntimeStrategy = class {
|
|
|
9933
9977
|
getCurrentUsage(historyDir) {
|
|
9934
9978
|
return getCurrentUsage(historyDir);
|
|
9935
9979
|
}
|
|
9980
|
+
listResumableSessions(cwd) {
|
|
9981
|
+
return listResumableSessions(cwd);
|
|
9982
|
+
}
|
|
9936
9983
|
async fetchWeeklyUsage() {
|
|
9937
9984
|
return fetchClaudeQuota();
|
|
9938
9985
|
}
|
|
@@ -10297,6 +10344,84 @@ function parseHistoryFile2(filePath) {
|
|
|
10297
10344
|
}
|
|
10298
10345
|
return out2;
|
|
10299
10346
|
}
|
|
10347
|
+
function listResumableSessions2(cwd, homeOverride) {
|
|
10348
|
+
const home = homeOverride ?? import_node_os.default.homedir();
|
|
10349
|
+
const sessionsRoot = import_node_path2.default.join(home, ".codex", "sessions");
|
|
10350
|
+
if (!import_node_fs3.default.existsSync(sessionsRoot)) return [];
|
|
10351
|
+
let resolvedCurrent;
|
|
10352
|
+
try {
|
|
10353
|
+
resolvedCurrent = import_node_fs3.default.realpathSync(cwd);
|
|
10354
|
+
} catch {
|
|
10355
|
+
resolvedCurrent = import_node_path2.default.resolve(cwd);
|
|
10356
|
+
}
|
|
10357
|
+
const out2 = [];
|
|
10358
|
+
const now = /* @__PURE__ */ new Date();
|
|
10359
|
+
for (let dayOffset = 0; dayOffset < 7; dayOffset += 1) {
|
|
10360
|
+
const d3 = new Date(now.getTime() - dayOffset * 24 * 60 * 60 * 1e3);
|
|
10361
|
+
const yyyy = String(d3.getUTCFullYear());
|
|
10362
|
+
const mm = String(d3.getUTCMonth() + 1).padStart(2, "0");
|
|
10363
|
+
const dd = String(d3.getUTCDate()).padStart(2, "0");
|
|
10364
|
+
const dayDir = import_node_path2.default.join(sessionsRoot, yyyy, mm, dd);
|
|
10365
|
+
if (!import_node_fs3.default.existsSync(dayDir)) continue;
|
|
10366
|
+
let dayFiles;
|
|
10367
|
+
try {
|
|
10368
|
+
dayFiles = import_node_fs3.default.readdirSync(dayDir, { withFileTypes: true });
|
|
10369
|
+
} catch {
|
|
10370
|
+
continue;
|
|
10371
|
+
}
|
|
10372
|
+
for (const entry of dayFiles) {
|
|
10373
|
+
if (!entry.isFile()) continue;
|
|
10374
|
+
if (!entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl")) {
|
|
10375
|
+
continue;
|
|
10376
|
+
}
|
|
10377
|
+
const filePath = import_node_path2.default.join(dayDir, entry.name);
|
|
10378
|
+
let timestamp = Date.now();
|
|
10379
|
+
try {
|
|
10380
|
+
timestamp = import_node_fs3.default.statSync(filePath).mtimeMs;
|
|
10381
|
+
} catch {
|
|
10382
|
+
}
|
|
10383
|
+
let metaCwd;
|
|
10384
|
+
let metaId;
|
|
10385
|
+
let summary = "";
|
|
10386
|
+
try {
|
|
10387
|
+
const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
|
|
10388
|
+
for (const line of raw.split("\n")) {
|
|
10389
|
+
if (!line.trim()) continue;
|
|
10390
|
+
const rec = parseLine(line);
|
|
10391
|
+
if (!rec) continue;
|
|
10392
|
+
if (rec.type === "session_meta") {
|
|
10393
|
+
const meta = rec.payload;
|
|
10394
|
+
metaCwd = typeof meta?.cwd === "string" ? meta.cwd : void 0;
|
|
10395
|
+
metaId = typeof meta?.id === "string" ? meta.id : void 0;
|
|
10396
|
+
continue;
|
|
10397
|
+
}
|
|
10398
|
+
if (!summary && rec.type === "response_item") {
|
|
10399
|
+
const payload = rec.payload;
|
|
10400
|
+
const msg = payload?.Message;
|
|
10401
|
+
if (msg && msg.role === "user") {
|
|
10402
|
+
const text = extractMessageText(msg.content).trim();
|
|
10403
|
+
if (text) summary = text.slice(0, 120);
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
if (metaCwd !== void 0 && summary) break;
|
|
10407
|
+
}
|
|
10408
|
+
} catch {
|
|
10409
|
+
continue;
|
|
10410
|
+
}
|
|
10411
|
+
if (!metaCwd || !metaId || !summary) continue;
|
|
10412
|
+
let resolvedMeta;
|
|
10413
|
+
try {
|
|
10414
|
+
resolvedMeta = import_node_fs3.default.realpathSync(metaCwd);
|
|
10415
|
+
} catch {
|
|
10416
|
+
resolvedMeta = import_node_path2.default.resolve(metaCwd);
|
|
10417
|
+
}
|
|
10418
|
+
if (resolvedMeta !== resolvedCurrent) continue;
|
|
10419
|
+
out2.push({ id: metaId, summary, timestamp });
|
|
10420
|
+
}
|
|
10421
|
+
}
|
|
10422
|
+
out2.sort((a, b) => b.timestamp - a.timestamp);
|
|
10423
|
+
return out2;
|
|
10424
|
+
}
|
|
10300
10425
|
function getCurrentUsage2(historyDir) {
|
|
10301
10426
|
if (!import_node_fs3.default.existsSync(historyDir)) return null;
|
|
10302
10427
|
const files = import_node_fs3.default.readdirSync(historyDir).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl")).map((f) => ({ name: f, full: import_node_path2.default.join(historyDir, f) })).map((e) => ({ ...e, mtime: import_node_fs3.default.statSync(e.full).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
@@ -10830,6 +10955,9 @@ var CodexRuntimeStrategy = class {
|
|
|
10830
10955
|
getCurrentUsage(historyDir) {
|
|
10831
10956
|
return getCurrentUsage2(historyDir);
|
|
10832
10957
|
}
|
|
10958
|
+
listResumableSessions(cwd) {
|
|
10959
|
+
return listResumableSessions2(cwd);
|
|
10960
|
+
}
|
|
10833
10961
|
/**
|
|
10834
10962
|
* Codex's quota lives behind the `account/get_account_rate_limits` RPC,
|
|
10835
10963
|
* not a TUI slash command. Phase 2 ships with this stubbed to null so the
|
|
@@ -12731,52 +12859,32 @@ var HistoryService = class _HistoryService {
|
|
|
12731
12859
|
return Math.round(totalCost * 100) / 100;
|
|
12732
12860
|
}
|
|
12733
12861
|
/**
|
|
12734
|
-
*
|
|
12735
|
-
*
|
|
12862
|
+
* Push the active agent's resumable-sessions list to the backend.
|
|
12863
|
+
* Delegates the per-agent JSONL/rollout walk to
|
|
12864
|
+
* `runtime.listResumableSessions(cwd)` so each agent reads its own
|
|
12865
|
+
* on-disk format (Claude's JSONL files vs Codex's date-bucketed
|
|
12866
|
+
* rollouts). Strategies that don't yet expose the helper (Cursor,
|
|
12867
|
+
* Aider) cause this to no-op — the Conversations sheet on mobile
|
|
12868
|
+
* just shows the empty state for those agents until each one's
|
|
12869
|
+
* `listResumableSessions` lands.
|
|
12870
|
+
*
|
|
12871
|
+
* The push body now includes `agentId` so the backend keys by
|
|
12872
|
+
* (pluginId, agentId). Old CLI clients that omit `agentId` continue
|
|
12873
|
+
* to land in the `claude-code` slot via the backend's default.
|
|
12874
|
+
*
|
|
12875
|
+
* Called once ~2 s after the agent spawns (non-blocking).
|
|
12736
12876
|
*/
|
|
12737
12877
|
async load() {
|
|
12738
|
-
|
|
12739
|
-
let entries;
|
|
12740
|
-
try {
|
|
12741
|
-
entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
12742
|
-
} catch {
|
|
12878
|
+
if (!this.runtime.listResumableSessions) {
|
|
12743
12879
|
return;
|
|
12744
12880
|
}
|
|
12745
|
-
const sessions3 =
|
|
12746
|
-
for (const entry of entries) {
|
|
12747
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
12748
|
-
const id = path24.basename(entry.name, ".jsonl");
|
|
12749
|
-
const filePath = path24.join(dir, entry.name);
|
|
12750
|
-
let mtime = Date.now();
|
|
12751
|
-
try {
|
|
12752
|
-
mtime = fs20.statSync(filePath).mtimeMs;
|
|
12753
|
-
} catch {
|
|
12754
|
-
}
|
|
12755
|
-
let summary = "";
|
|
12756
|
-
try {
|
|
12757
|
-
const raw = fs20.readFileSync(filePath, "utf8");
|
|
12758
|
-
for (const line of raw.split("\n")) {
|
|
12759
|
-
if (!line.trim()) continue;
|
|
12760
|
-
try {
|
|
12761
|
-
const record = JSON.parse(line);
|
|
12762
|
-
if (record["type"] === "user") {
|
|
12763
|
-
const msg = record["message"];
|
|
12764
|
-
const text = extractText2(msg?.["content"]).trim();
|
|
12765
|
-
if (text) {
|
|
12766
|
-
summary = text.slice(0, 120);
|
|
12767
|
-
break;
|
|
12768
|
-
}
|
|
12769
|
-
}
|
|
12770
|
-
} catch {
|
|
12771
|
-
}
|
|
12772
|
-
}
|
|
12773
|
-
} catch {
|
|
12774
|
-
}
|
|
12775
|
-
if (summary) sessions3.push({ id, summary, timestamp: mtime });
|
|
12776
|
-
}
|
|
12881
|
+
const sessions3 = this.runtime.listResumableSessions(this.cwd);
|
|
12777
12882
|
if (sessions3.length === 0) return;
|
|
12778
|
-
|
|
12779
|
-
|
|
12883
|
+
await post("/api/sessions/list", {
|
|
12884
|
+
pluginId: this.pluginId,
|
|
12885
|
+
agentId: this.runtime.id,
|
|
12886
|
+
sessions: sessions3
|
|
12887
|
+
});
|
|
12780
12888
|
}
|
|
12781
12889
|
/**
|
|
12782
12890
|
* Read a specific session's full conversation and POST it to the API in batches.
|
|
@@ -15944,6 +16052,20 @@ var import_path4 = __toESM(require("path"));
|
|
|
15944
16052
|
var import_promises2 = require("stream/promises");
|
|
15945
16053
|
var import_which = __toESM(require("which"));
|
|
15946
16054
|
var CACHED_BINARY = import_path4.default.join(import_os7.default.homedir(), ".codeam", "bin", "cloudflared");
|
|
16055
|
+
async function waitForCloudflaredReady(url, timeoutMs = 3e4) {
|
|
16056
|
+
const start2 = Date.now();
|
|
16057
|
+
while (Date.now() - start2 < timeoutMs) {
|
|
16058
|
+
try {
|
|
16059
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
16060
|
+
if (res.status < 500) return;
|
|
16061
|
+
} catch {
|
|
16062
|
+
}
|
|
16063
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
16064
|
+
}
|
|
16065
|
+
throw new Error(
|
|
16066
|
+
`Tunnel URL ${url} not reachable after ${timeoutMs}ms (DNS may still be propagating).`
|
|
16067
|
+
);
|
|
16068
|
+
}
|
|
15947
16069
|
async function resolveCloudflared(opts = {}) {
|
|
15948
16070
|
try {
|
|
15949
16071
|
return await (0, import_which.default)("cloudflared");
|
|
@@ -16919,10 +17041,12 @@ var previewStartH = (ctx, _cmd, parsed) => {
|
|
|
16919
17041
|
const onTunnelChunk = (chunk) => {
|
|
16920
17042
|
const s = chunk.toString();
|
|
16921
17043
|
if (!parsedUrl) parsedUrl = parseCloudflaredUrl(s);
|
|
17044
|
+
const trimmed = s.replace(/\n+$/g, "");
|
|
17045
|
+
if (trimmed.length > 0) log.info("preview", `cloudflared: ${trimmed}`);
|
|
16922
17046
|
};
|
|
16923
17047
|
tunnel.stderr.on("data", onTunnelChunk);
|
|
16924
17048
|
tunnel.stdout.on("data", onTunnelChunk);
|
|
16925
|
-
const tunnelDeadline = Date.now() +
|
|
17049
|
+
const tunnelDeadline = Date.now() + 45e3;
|
|
16926
17050
|
while (!parsedUrl && Date.now() < tunnelDeadline) {
|
|
16927
17051
|
await new Promise((r) => setTimeout(r, 250));
|
|
16928
17052
|
}
|
|
@@ -16940,7 +17064,27 @@ var previewStartH = (ctx, _cmd, parsed) => {
|
|
|
16940
17064
|
pluginId: ctx.pluginId,
|
|
16941
17065
|
pluginAuthToken,
|
|
16942
17066
|
type: "preview_error",
|
|
16943
|
-
payload: { stage: "tunnel", message: "cloudflared did not emit a URL within
|
|
17067
|
+
payload: { stage: "tunnel", message: "cloudflared did not emit a URL within 45s." }
|
|
17068
|
+
});
|
|
17069
|
+
return;
|
|
17070
|
+
}
|
|
17071
|
+
try {
|
|
17072
|
+
await waitForCloudflaredReady(parsedUrl, 3e4);
|
|
17073
|
+
} catch (e) {
|
|
17074
|
+
try {
|
|
17075
|
+
tunnel.kill("SIGTERM");
|
|
17076
|
+
} catch {
|
|
17077
|
+
}
|
|
17078
|
+
try {
|
|
17079
|
+
devServer.kill("SIGTERM");
|
|
17080
|
+
} catch {
|
|
17081
|
+
}
|
|
17082
|
+
void postPreviewEvent({
|
|
17083
|
+
sessionId: ctx.sessionId,
|
|
17084
|
+
pluginId: ctx.pluginId,
|
|
17085
|
+
pluginAuthToken,
|
|
17086
|
+
type: "preview_error",
|
|
17087
|
+
payload: { stage: "tunnel", message: e.message }
|
|
16944
17088
|
});
|
|
16945
17089
|
return;
|
|
16946
17090
|
}
|
|
@@ -16986,8 +17130,16 @@ function runOnce(cmd, args2, cwd, env) {
|
|
|
16986
17130
|
const child = (0, import_child_process15.spawn)(cmd, args2, {
|
|
16987
17131
|
cwd,
|
|
16988
17132
|
env: { ...process.env, ...env ?? {} },
|
|
16989
|
-
stdio: "
|
|
17133
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
16990
17134
|
});
|
|
17135
|
+
const tag = `setup:${cmd}`;
|
|
17136
|
+
const onChunk = (chunk) => {
|
|
17137
|
+
const text = chunk.toString().replace(/\n+$/g, "");
|
|
17138
|
+
if (text.length === 0) return;
|
|
17139
|
+
log.info("preview", `${tag}: ${text}`);
|
|
17140
|
+
};
|
|
17141
|
+
child.stdout?.on("data", onChunk);
|
|
17142
|
+
child.stderr?.on("data", onChunk);
|
|
16991
17143
|
child.once("exit", (code) => resolve5(code));
|
|
16992
17144
|
child.once("error", () => resolve5(-1));
|
|
16993
17145
|
});
|
|
@@ -19992,7 +20144,7 @@ function checkChokidar() {
|
|
|
19992
20144
|
}
|
|
19993
20145
|
async function doctor(args2 = []) {
|
|
19994
20146
|
const json = args2.includes("--json");
|
|
19995
|
-
const cliVersion = true ? "2.26.
|
|
20147
|
+
const cliVersion = true ? "2.26.8" : "0.0.0-dev";
|
|
19996
20148
|
const apiBase = resolveApiBaseUrl();
|
|
19997
20149
|
const diagnosticId = (0, import_node_crypto6.randomUUID)();
|
|
19998
20150
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -20191,7 +20343,7 @@ async function completion(args2) {
|
|
|
20191
20343
|
// src/commands/version.ts
|
|
20192
20344
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
20193
20345
|
function version2() {
|
|
20194
|
-
const v = true ? "2.26.
|
|
20346
|
+
const v = true ? "2.26.8" : "unknown";
|
|
20195
20347
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
20196
20348
|
}
|
|
20197
20349
|
|
|
@@ -20419,7 +20571,7 @@ function checkForUpdates() {
|
|
|
20419
20571
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
20420
20572
|
if (process.env.CI) return;
|
|
20421
20573
|
if (!process.stdout.isTTY) return;
|
|
20422
|
-
const current = true ? "2.26.
|
|
20574
|
+
const current = true ? "2.26.8" : null;
|
|
20423
20575
|
if (!current) return;
|
|
20424
20576
|
const cache = readCache();
|
|
20425
20577
|
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.26.
|
|
3
|
+
"version": "2.26.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",
|