codeam-cli 2.41.1 → 2.42.0
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 +10 -0
- package/dist/index.js +110 -61
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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.41.0] — 2026-06-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Surface friendly provider-outage message + status-page link instead of silent hang
|
|
12
|
+
|
|
13
|
+
### Merge
|
|
14
|
+
|
|
15
|
+
- Friendly provider-outage message + status-page link (no silent hang on provider 529/5xx)
|
|
16
|
+
|
|
7
17
|
## [2.40.2] — 2026-06-24
|
|
8
18
|
|
|
9
19
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -5390,7 +5390,7 @@ function readAnonId() {
|
|
|
5390
5390
|
}
|
|
5391
5391
|
function superProperties() {
|
|
5392
5392
|
return {
|
|
5393
|
-
cliVersion: true ? "2.
|
|
5393
|
+
cliVersion: true ? "2.42.0" : "0.0.0-dev",
|
|
5394
5394
|
nodeVersion: process.version,
|
|
5395
5395
|
platform: process.platform,
|
|
5396
5396
|
arch: process.arch,
|
|
@@ -5536,6 +5536,28 @@ var _execSeam = {
|
|
|
5536
5536
|
return typeof out2 === "string" ? out2 : out2.toString("utf8");
|
|
5537
5537
|
}
|
|
5538
5538
|
};
|
|
5539
|
+
var _execSeamAsync = {
|
|
5540
|
+
exec: (file, args2, opts) => new Promise((resolve7, reject) => {
|
|
5541
|
+
(0, import_child_process.execFile)(file, args2, opts, (err, stdout) => {
|
|
5542
|
+
if (err) reject(err);
|
|
5543
|
+
else resolve7(typeof stdout === "string" ? stdout : stdout.toString("utf8"));
|
|
5544
|
+
});
|
|
5545
|
+
})
|
|
5546
|
+
};
|
|
5547
|
+
async function detectCurrentBranchAsync(cwd = process.cwd()) {
|
|
5548
|
+
try {
|
|
5549
|
+
const raw = await _execSeamAsync.exec("git", ["branch", "--show-current"], {
|
|
5550
|
+
cwd,
|
|
5551
|
+
timeout: 1e3,
|
|
5552
|
+
encoding: "utf8",
|
|
5553
|
+
windowsHide: true
|
|
5554
|
+
});
|
|
5555
|
+
const trimmed = raw.trim();
|
|
5556
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
5557
|
+
} catch {
|
|
5558
|
+
return null;
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5539
5561
|
|
|
5540
5562
|
// src/services/command-relay.service.ts
|
|
5541
5563
|
var https2 = __toESM(require("https"));
|
|
@@ -5549,7 +5571,7 @@ var os4 = __toESM(require("os"));
|
|
|
5549
5571
|
// package.json
|
|
5550
5572
|
var package_default = {
|
|
5551
5573
|
name: "codeam-cli",
|
|
5552
|
-
version: "2.
|
|
5574
|
+
version: "2.42.0",
|
|
5553
5575
|
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.",
|
|
5554
5576
|
type: "commonjs",
|
|
5555
5577
|
main: "dist/index.js",
|
|
@@ -6044,13 +6066,31 @@ var CommandRelayService = class {
|
|
|
6044
6066
|
*/
|
|
6045
6067
|
sseWatchdog = null;
|
|
6046
6068
|
sseLastByteAt = 0;
|
|
6069
|
+
/**
|
|
6070
|
+
* Last-known git branch, shipped on every heartbeat. Seeded with a
|
|
6071
|
+
* single SYNCHRONOUS read at `start()` (before any turn is running)
|
|
6072
|
+
* for an accurate first beat, then refreshed ASYNCHRONOUSLY off the
|
|
6073
|
+
* recurring heartbeat tick. The heartbeat POST reads this cached
|
|
6074
|
+
* value and never spawns `git` synchronously — so the 20 s beat
|
|
6075
|
+
* stays punctual even while a long agent tool call is hammering the
|
|
6076
|
+
* event loop. Trade-off: because the async refresh fires alongside
|
|
6077
|
+
* the POST (not before it), a `git checkout` propagates on the NEXT
|
|
6078
|
+
* beat — up to ~40 s worst case vs the old at-POST-time read. The
|
|
6079
|
+
* backend dedupes a stable branch, so this lag costs nothing but
|
|
6080
|
+
* freshness, and a punctual heartbeat matters far more.
|
|
6081
|
+
*/
|
|
6082
|
+
cachedBranch = null;
|
|
6047
6083
|
start() {
|
|
6048
6084
|
this.cleanup();
|
|
6049
6085
|
this._running = true;
|
|
6050
6086
|
this.agentsRegistered = false;
|
|
6051
6087
|
log.info("relay", `start pluginId=${this.pluginId.slice(0, 8)} agent=${this.agentMeta.id}`);
|
|
6088
|
+
this.cachedBranch = detectCurrentBranch();
|
|
6052
6089
|
this.sendHeartbeat(true);
|
|
6053
|
-
this.heartbeatTimer = setInterval(() =>
|
|
6090
|
+
this.heartbeatTimer = setInterval(() => {
|
|
6091
|
+
void this.refreshBranch();
|
|
6092
|
+
this.sendHeartbeat(true);
|
|
6093
|
+
}, 2e4);
|
|
6054
6094
|
this.agentsTimer = setInterval(() => {
|
|
6055
6095
|
if (this._running && !this.agentsRegistered) this.reportAgents();
|
|
6056
6096
|
}, 5e3);
|
|
@@ -6294,9 +6334,20 @@ var CommandRelayService = class {
|
|
|
6294
6334
|
pluginId: this.pluginId,
|
|
6295
6335
|
online,
|
|
6296
6336
|
agentId: this.agentMeta.id,
|
|
6297
|
-
branch:
|
|
6337
|
+
branch: this.cachedBranch
|
|
6298
6338
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
6299
6339
|
}
|
|
6340
|
+
/**
|
|
6341
|
+
* Refresh {@link cachedBranch} without blocking the event loop.
|
|
6342
|
+
* Fire-and-forget from the heartbeat tick; failures leave the last
|
|
6343
|
+
* known value in place (the backend dedupes a stable branch).
|
|
6344
|
+
*/
|
|
6345
|
+
async refreshBranch() {
|
|
6346
|
+
try {
|
|
6347
|
+
this.cachedBranch = await detectCurrentBranchAsync();
|
|
6348
|
+
} catch {
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6300
6351
|
reportAgents() {
|
|
6301
6352
|
const agents = this.agentsOverride ? [...this.agentsOverride] : [
|
|
6302
6353
|
{
|
|
@@ -17753,7 +17804,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
17753
17804
|
if (process.env.NODE_ENV === "test") return;
|
|
17754
17805
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17755
17806
|
if (process.env.CI) return;
|
|
17756
|
-
const current = true ? "2.
|
|
17807
|
+
const current = true ? "2.42.0" : null;
|
|
17757
17808
|
if (!current) return;
|
|
17758
17809
|
const cache = readCache();
|
|
17759
17810
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17770,7 +17821,7 @@ function checkForUpdates() {
|
|
|
17770
17821
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17771
17822
|
if (process.env.CI) return;
|
|
17772
17823
|
if (!process.stdout.isTTY) return;
|
|
17773
|
-
const current = true ? "2.
|
|
17824
|
+
const current = true ? "2.42.0" : null;
|
|
17774
17825
|
if (!current) return;
|
|
17775
17826
|
const cache = readCache();
|
|
17776
17827
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -18208,7 +18259,7 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process14.s
|
|
|
18208
18259
|
detached: false
|
|
18209
18260
|
});
|
|
18210
18261
|
function currentCliVersion() {
|
|
18211
|
-
return true ? "2.
|
|
18262
|
+
return true ? "2.42.0" : null;
|
|
18212
18263
|
}
|
|
18213
18264
|
function runCmd(cmd, args2, timeoutMs) {
|
|
18214
18265
|
return new Promise((resolve7) => {
|
|
@@ -24450,6 +24501,22 @@ var StreamingState = class {
|
|
|
24450
24501
|
this.flushStreamingChunks()
|
|
24451
24502
|
]);
|
|
24452
24503
|
}
|
|
24504
|
+
/**
|
|
24505
|
+
* Close the turn but REPLACE the streamed reply with `bubble` as the
|
|
24506
|
+
* terminal chat frame. The chat pipe treats each `text` chunk's content as
|
|
24507
|
+
* the full (replacing) bubble body, so a final `done:true` carrying the
|
|
24508
|
+
* bubble overwrites whatever raw text streamed. Used when the agent's
|
|
24509
|
+
* completed-turn reply was itself an auth-failure notice
|
|
24510
|
+
* ({@link replyIsAuthFailure}) → swap the raw "Please run /login" for the
|
|
24511
|
+
* actionable re-auth bubble.
|
|
24512
|
+
*/
|
|
24513
|
+
async closeWithBubble(bubble) {
|
|
24514
|
+
this.text = "";
|
|
24515
|
+
await Promise.all([
|
|
24516
|
+
this.publisher.publishOutput({ type: "text", content: bubble, done: true }),
|
|
24517
|
+
this.flushStreamingChunks()
|
|
24518
|
+
]);
|
|
24519
|
+
}
|
|
24453
24520
|
/**
|
|
24454
24521
|
* Re-emit every open streaming-chunk buffer with `isFinal: true`
|
|
24455
24522
|
* so SessionDetailScreen's bubbles flip out of "still streaming"
|
|
@@ -24617,6 +24684,10 @@ var AUTH_FAILURE_RE = /invalid authentication credentials|authentication[_ ]erro
|
|
|
24617
24684
|
function looksLikeAuthFailure(text) {
|
|
24618
24685
|
return AUTH_FAILURE_RE.test(text);
|
|
24619
24686
|
}
|
|
24687
|
+
function replyIsAuthFailure(finalText) {
|
|
24688
|
+
const t2 = finalText.trim();
|
|
24689
|
+
return t2.length > 0 && t2.length <= 200 && looksLikeAuthFailure(t2);
|
|
24690
|
+
}
|
|
24620
24691
|
var AUTH_FAILURE_MESSAGE = "\u{1F512} **Authentication failed \u2014 your agent credentials are invalid or expired (API 401).**\n\nTap [Re-authenticate this agent](codeam://reauth) to renew your credentials in Profile \u203A Agents, then send your message again.";
|
|
24621
24692
|
var TURN_FAILURE_MESSAGE = "\u26A0\uFE0F **The agent hit an error and couldn\u2019t finish this turn.** Please send your message again.";
|
|
24622
24693
|
var PROVIDER_OUTAGE_RE = /overloaded_error|\boverloaded\b|service[ _]unavailable|temporarily[ _]unavailable|(?:api error|http|status)[:\s]+(?:529|503|502|504)\b|\b(?:529|503|502|504)\b[^\n]{0,40}(?:overload|unavailable|gateway|upstream|server error)|bad gateway|gateway time-?out|upstream (?:error|connect|timeout)/i;
|
|
@@ -24626,46 +24697,17 @@ function looksLikeProviderOutage(text) {
|
|
|
24626
24697
|
function agentStatusPage(agent) {
|
|
24627
24698
|
const a = (agent ?? "").toLowerCase();
|
|
24628
24699
|
if (a.includes("claude") || a.includes("anthropic"))
|
|
24629
|
-
return {
|
|
24630
|
-
vendor: "Anthropic",
|
|
24631
|
-
url: "https://status.anthropic.com",
|
|
24632
|
-
statusApi: "https://status.anthropic.com/api/v2/status.json"
|
|
24633
|
-
};
|
|
24700
|
+
return { vendor: "Anthropic", url: "https://status.anthropic.com" };
|
|
24634
24701
|
if (a.includes("codex") || a.includes("openai"))
|
|
24635
|
-
return {
|
|
24636
|
-
vendor: "OpenAI",
|
|
24637
|
-
url: "https://status.openai.com",
|
|
24638
|
-
statusApi: "https://status.openai.com/api/v2/status.json"
|
|
24639
|
-
};
|
|
24702
|
+
return { vendor: "OpenAI", url: "https://status.openai.com" };
|
|
24640
24703
|
if (a.includes("gemini") || a.includes("google"))
|
|
24641
24704
|
return { vendor: "Google", url: "https://status.cloud.google.com" };
|
|
24642
24705
|
if (a.includes("copilot"))
|
|
24643
|
-
return {
|
|
24644
|
-
vendor: "GitHub",
|
|
24645
|
-
url: "https://www.githubstatus.com",
|
|
24646
|
-
statusApi: "https://www.githubstatus.com/api/v2/status.json"
|
|
24647
|
-
};
|
|
24706
|
+
return { vendor: "GitHub", url: "https://www.githubstatus.com" };
|
|
24648
24707
|
if (a.includes("cursor"))
|
|
24649
|
-
return {
|
|
24650
|
-
vendor: "Cursor",
|
|
24651
|
-
url: "https://status.cursor.com",
|
|
24652
|
-
statusApi: "https://status.cursor.com/api/v2/status.json"
|
|
24653
|
-
};
|
|
24708
|
+
return { vendor: "Cursor", url: "https://status.cursor.com" };
|
|
24654
24709
|
return null;
|
|
24655
24710
|
}
|
|
24656
|
-
async function checkProviderStatus(agent, fetchImpl = fetch) {
|
|
24657
|
-
const info = agentStatusPage(agent);
|
|
24658
|
-
if (!info?.statusApi) return false;
|
|
24659
|
-
try {
|
|
24660
|
-
const res = await withTimeout(fetchImpl(info.statusApi), 4e3);
|
|
24661
|
-
if (!res || !res.ok) return false;
|
|
24662
|
-
const body = await res.json();
|
|
24663
|
-
const indicator = body?.status?.indicator;
|
|
24664
|
-
return typeof indicator === "string" && indicator.toLowerCase() !== "none";
|
|
24665
|
-
} catch {
|
|
24666
|
-
return false;
|
|
24667
|
-
}
|
|
24668
|
-
}
|
|
24669
24711
|
function providerOutageMessage(agent) {
|
|
24670
24712
|
const info = agentStatusPage(agent);
|
|
24671
24713
|
const who = info ? info.vendor : "The agent provider";
|
|
@@ -24977,34 +25019,41 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
24977
25019
|
try {
|
|
24978
25020
|
const reply = await client2.prompt(blocks);
|
|
24979
25021
|
const finalText = streaming.getCurrentText();
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
|
|
25022
|
+
if (replyIsAuthFailure(finalText)) {
|
|
25023
|
+
await streaming.closeWithBubble(AUTH_FAILURE_MESSAGE);
|
|
25024
|
+
history.appendAgentReply(AUTH_FAILURE_MESSAGE);
|
|
25025
|
+
void history.flush();
|
|
25026
|
+
turnFiles.flushTurn().catch((err) => {
|
|
25027
|
+
log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
|
|
25028
|
+
});
|
|
25029
|
+
void reportCredentialInvalid(opts);
|
|
25030
|
+
log.info("acpRunner", `start_task \u2190 auth-failure-in-reply id=${cmd.id.slice(0, 8)}`);
|
|
25031
|
+
await relay.sendResult(cmd.id, "failed", { error: "agent reply reported auth failure" });
|
|
25032
|
+
} else {
|
|
25033
|
+
await streaming.closeTurnWithInteractiveDetection();
|
|
25034
|
+
const replyLine = formatAgentReplyLine(finalText);
|
|
25035
|
+
if (replyLine.length > 0) {
|
|
25036
|
+
showInfo(replyLine);
|
|
25037
|
+
}
|
|
25038
|
+
history.appendAgentReply(finalText);
|
|
25039
|
+
void history.flush();
|
|
25040
|
+
turnFiles.flushTurn().catch((err) => {
|
|
25041
|
+
log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
|
|
25042
|
+
});
|
|
25043
|
+
log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
|
|
25044
|
+
await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
|
|
24984
25045
|
}
|
|
24985
|
-
history.appendAgentReply(finalText);
|
|
24986
|
-
void history.flush();
|
|
24987
|
-
turnFiles.flushTurn().catch((err) => {
|
|
24988
|
-
log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
|
|
24989
|
-
});
|
|
24990
|
-
log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
|
|
24991
|
-
await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
|
|
24992
25046
|
} catch (err) {
|
|
24993
25047
|
const hadText = streaming.getCurrentText().trim().length > 0;
|
|
24994
25048
|
await recoverFromFailedTurn(client2, streaming);
|
|
24995
25049
|
const detail = describeError(err);
|
|
24996
25050
|
log.warn("acpRunner", `prompt failed: ${detail}`);
|
|
24997
|
-
|
|
25051
|
+
const bubble = failureBubble({
|
|
24998
25052
|
detail,
|
|
24999
25053
|
recentStderr: recentStderr.join("\n"),
|
|
25000
25054
|
hadText,
|
|
25001
25055
|
agent: opts.agent
|
|
25002
25056
|
});
|
|
25003
|
-
if (bubble !== AUTH_FAILURE_MESSAGE && bubble !== providerOutageMessage(opts.agent)) {
|
|
25004
|
-
if (await checkProviderStatus(opts.agent)) {
|
|
25005
|
-
bubble = providerOutageMessage(opts.agent);
|
|
25006
|
-
}
|
|
25007
|
-
}
|
|
25008
25057
|
if (bubble) {
|
|
25009
25058
|
await publisher.publishOutput({ type: "text", content: bubble, done: true });
|
|
25010
25059
|
history.appendAgentReply(bubble);
|
|
@@ -28936,9 +28985,9 @@ async function probeCodeamPair(provider, workspace) {
|
|
|
28936
28985
|
}
|
|
28937
28986
|
async function stopWorkspaceFromLocal(target) {
|
|
28938
28987
|
if (target.provider.id === "github-codespaces") {
|
|
28939
|
-
const { execFile:
|
|
28988
|
+
const { execFile: execFile13 } = await import("child_process");
|
|
28940
28989
|
const { promisify: promisify11 } = await import("util");
|
|
28941
|
-
const execFileP10 = promisify11(
|
|
28990
|
+
const execFileP10 = promisify11(execFile13);
|
|
28942
28991
|
await execFileP10("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
|
|
28943
28992
|
return;
|
|
28944
28993
|
}
|
|
@@ -29162,7 +29211,7 @@ function checkChokidar() {
|
|
|
29162
29211
|
}
|
|
29163
29212
|
async function doctor(args2 = []) {
|
|
29164
29213
|
const json = args2.includes("--json");
|
|
29165
|
-
const cliVersion = true ? "2.
|
|
29214
|
+
const cliVersion = true ? "2.42.0" : "0.0.0-dev";
|
|
29166
29215
|
const apiBase2 = resolveApiBaseUrl();
|
|
29167
29216
|
const diagnosticId = (0, import_node_crypto8.randomUUID)();
|
|
29168
29217
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -29361,7 +29410,7 @@ async function completion(args2) {
|
|
|
29361
29410
|
// src/commands/version.ts
|
|
29362
29411
|
var import_picocolors14 = __toESM(require("picocolors"));
|
|
29363
29412
|
function version2() {
|
|
29364
|
-
const v = true ? "2.
|
|
29413
|
+
const v = true ? "2.42.0" : "unknown";
|
|
29365
29414
|
console.log(`${import_picocolors14.default.bold("codeam-cli")} ${import_picocolors14.default.cyan(v)}`);
|
|
29366
29415
|
}
|
|
29367
29416
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.42.0",
|
|
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",
|