codeam-cli 2.39.31 → 2.39.33
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 +194 -55
- 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.39.32] — 2026-06-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Host-agent deploy clones private repos + reports progress, no silent hang
|
|
12
|
+
|
|
13
|
+
## [2.39.31] — 2026-06-18
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** Host-agent re-enrollment + self-heal on deleted host
|
|
18
|
+
|
|
7
19
|
## [2.39.30] — 2026-06-18
|
|
8
20
|
|
|
9
21
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -498,7 +498,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
498
498
|
// package.json
|
|
499
499
|
var package_default = {
|
|
500
500
|
name: "codeam-cli",
|
|
501
|
-
version: "2.39.
|
|
501
|
+
version: "2.39.33",
|
|
502
502
|
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.",
|
|
503
503
|
type: "commonjs",
|
|
504
504
|
main: "dist/index.js",
|
|
@@ -5908,7 +5908,7 @@ function readAnonId() {
|
|
|
5908
5908
|
}
|
|
5909
5909
|
function superProperties() {
|
|
5910
5910
|
return {
|
|
5911
|
-
cliVersion: true ? "2.39.
|
|
5911
|
+
cliVersion: true ? "2.39.33" : "0.0.0-dev",
|
|
5912
5912
|
nodeVersion: process.version,
|
|
5913
5913
|
platform: process.platform,
|
|
5914
5914
|
arch: process.arch,
|
|
@@ -14705,6 +14705,14 @@ function buildAcpPromptBlocks(payload) {
|
|
|
14705
14705
|
return blocks;
|
|
14706
14706
|
}
|
|
14707
14707
|
|
|
14708
|
+
// src/agents/acp/reconcileDelta.ts
|
|
14709
|
+
function reconcileCumulative(existing, incoming) {
|
|
14710
|
+
if (incoming.length === 0) return existing;
|
|
14711
|
+
if (incoming.startsWith(existing)) return incoming;
|
|
14712
|
+
if (existing.startsWith(incoming)) return existing;
|
|
14713
|
+
return existing + incoming;
|
|
14714
|
+
}
|
|
14715
|
+
|
|
14708
14716
|
// src/agents/acp/onboarding.ts
|
|
14709
14717
|
var fs22 = __toESM(require("fs"));
|
|
14710
14718
|
var os23 = __toESM(require("os"));
|
|
@@ -20639,6 +20647,18 @@ var StreamingState = class {
|
|
|
20639
20647
|
this.publisher = publisher;
|
|
20640
20648
|
}
|
|
20641
20649
|
publisher;
|
|
20650
|
+
/**
|
|
20651
|
+
* Cumulative agent reply for the in-progress turn — the body of the
|
|
20652
|
+
* chat bubble (`/api/commands/output` text events). DERIVED from the
|
|
20653
|
+
* per-chunkId text buffers in {@link streamingChunks} via
|
|
20654
|
+
* {@link recomputeText} on every `append`, NOT accumulated with a
|
|
20655
|
+
* blind `+=`. That derivation is what makes the chat pipe correct for
|
|
20656
|
+
* adapters that send cumulative snapshots (a self-hosted MiniMax proxy
|
|
20657
|
+
* behind claude-agent-acp) as well as true-delta adapters (Anthropic
|
|
20658
|
+
* Claude): `reconcileCumulative` collapses a re-sent snapshot instead
|
|
20659
|
+
* of concatenating the reply with itself (the "…hoy?¡Hola!…hoy?"
|
|
20660
|
+
* intra-reply duplication bug).
|
|
20661
|
+
*/
|
|
20642
20662
|
text = "";
|
|
20643
20663
|
pending = null;
|
|
20644
20664
|
/**
|
|
@@ -20754,19 +20774,19 @@ var StreamingState = class {
|
|
|
20754
20774
|
await this.publisher.publishOutput({ type: "new_turn", done: false });
|
|
20755
20775
|
}
|
|
20756
20776
|
append(delta) {
|
|
20757
|
-
if (delta.kind === "text") {
|
|
20758
|
-
this.text += delta.delta;
|
|
20759
|
-
void this.publisher.publishOutput({ type: "text", content: this.text, done: false });
|
|
20760
|
-
}
|
|
20761
20777
|
const existing = this.streamingChunks.get(delta.chunkId);
|
|
20762
|
-
const cumulativeContent = (existing?.content ?? "") + delta.delta;
|
|
20763
20778
|
if (existing && existing.kind !== delta.kind) {
|
|
20764
20779
|
log.warn(
|
|
20765
20780
|
"acpRunner",
|
|
20766
20781
|
`streaming-chunk kind flip chunkId=${delta.chunkId.slice(0, 8)} from=${existing.kind} to=${delta.kind}`
|
|
20767
20782
|
);
|
|
20768
20783
|
}
|
|
20784
|
+
const cumulativeContent = reconcileCumulative(existing?.content ?? "", delta.delta);
|
|
20769
20785
|
this.streamingChunks.set(delta.chunkId, { kind: delta.kind, content: cumulativeContent });
|
|
20786
|
+
if (delta.kind === "text") {
|
|
20787
|
+
this.recomputeText();
|
|
20788
|
+
void this.publisher.publishOutput({ type: "text", content: this.text, done: false });
|
|
20789
|
+
}
|
|
20770
20790
|
void this.publisher.publishStreamingChunk({
|
|
20771
20791
|
chunkId: delta.chunkId,
|
|
20772
20792
|
kind: delta.kind,
|
|
@@ -20774,6 +20794,20 @@ var StreamingState = class {
|
|
|
20774
20794
|
isFinal: false
|
|
20775
20795
|
});
|
|
20776
20796
|
}
|
|
20797
|
+
/**
|
|
20798
|
+
* Rebuild the cumulative chat-bubble text from the per-chunkId text
|
|
20799
|
+
* buffers, in arrival order (Map preserves insertion order). Source
|
|
20800
|
+
* of truth for the `text` field — never accumulated incrementally, so
|
|
20801
|
+
* a re-sent snapshot updates its own chunk in place rather than
|
|
20802
|
+
* lengthening the reply.
|
|
20803
|
+
*/
|
|
20804
|
+
recomputeText() {
|
|
20805
|
+
let next = "";
|
|
20806
|
+
for (const { kind, content } of this.streamingChunks.values()) {
|
|
20807
|
+
if (kind === "text") next += content;
|
|
20808
|
+
}
|
|
20809
|
+
this.text = next;
|
|
20810
|
+
}
|
|
20777
20811
|
/**
|
|
20778
20812
|
* Flip the chat out of "Thinking…" with one final cumulative
|
|
20779
20813
|
* `done: true`. Idempotent — safe to call from happy + error +
|
|
@@ -26393,6 +26427,30 @@ async function reportProgress(auth, step, message) {
|
|
|
26393
26427
|
} catch {
|
|
26394
26428
|
}
|
|
26395
26429
|
}
|
|
26430
|
+
async function reportDeployProgress(auth, deployId, step, message, sessionId) {
|
|
26431
|
+
try {
|
|
26432
|
+
const controller = new AbortController();
|
|
26433
|
+
const timer = setTimeout(() => controller.abort(), PROGRESS_TIMEOUT_MS);
|
|
26434
|
+
timer.unref?.();
|
|
26435
|
+
try {
|
|
26436
|
+
await fetch(`${apiBase()}/api/self-hosted/deploy-progress`, {
|
|
26437
|
+
method: "POST",
|
|
26438
|
+
headers: { "Content-Type": "application/json", ...vercelBypassHeader() },
|
|
26439
|
+
body: JSON.stringify({
|
|
26440
|
+
...auth,
|
|
26441
|
+
deployId,
|
|
26442
|
+
step,
|
|
26443
|
+
message,
|
|
26444
|
+
...sessionId ? { sessionId } : {}
|
|
26445
|
+
}),
|
|
26446
|
+
signal: controller.signal
|
|
26447
|
+
});
|
|
26448
|
+
} finally {
|
|
26449
|
+
clearTimeout(timer);
|
|
26450
|
+
}
|
|
26451
|
+
} catch {
|
|
26452
|
+
}
|
|
26453
|
+
}
|
|
26396
26454
|
|
|
26397
26455
|
// src/commands/host.ts
|
|
26398
26456
|
function readTokenFlag(args2) {
|
|
@@ -26455,12 +26513,46 @@ function isAbsolutePathTarget(target) {
|
|
|
26455
26513
|
function selfHostedWorkspaceRoot() {
|
|
26456
26514
|
return path54.join(os33.homedir(), ".codeam", "self-hosted");
|
|
26457
26515
|
}
|
|
26458
|
-
function
|
|
26516
|
+
function nonInteractiveGitEnv() {
|
|
26517
|
+
return {
|
|
26518
|
+
...process.env,
|
|
26519
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
26520
|
+
GIT_ASKPASS: "",
|
|
26521
|
+
GCM_INTERACTIVE: "never"
|
|
26522
|
+
};
|
|
26523
|
+
}
|
|
26524
|
+
function githubOwnerRepo(repoRef) {
|
|
26459
26525
|
const trimmed = repoRef.trim();
|
|
26526
|
+
const shorthand = /^([^/\s]+)\/([^/\s]+?)(?:\.git)?$/.exec(trimmed);
|
|
26527
|
+
if (shorthand && !/^https?:\/\//.test(trimmed) && !trimmed.startsWith("git@")) {
|
|
26528
|
+
return { owner: shorthand[1], repo: shorthand[2] };
|
|
26529
|
+
}
|
|
26530
|
+
const httpsMatch = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/.exec(trimmed);
|
|
26531
|
+
if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
26532
|
+
return null;
|
|
26533
|
+
}
|
|
26534
|
+
function repoCloneUrl(repoRef, cloneToken) {
|
|
26535
|
+
const trimmed = repoRef.trim();
|
|
26536
|
+
if (cloneToken) {
|
|
26537
|
+
const gh = githubOwnerRepo(trimmed);
|
|
26538
|
+
if (gh) {
|
|
26539
|
+
return `https://x-access-token:${cloneToken}@github.com/${gh.owner}/${gh.repo}.git`;
|
|
26540
|
+
}
|
|
26541
|
+
}
|
|
26460
26542
|
if (/^https?:\/\//.test(trimmed) || trimmed.startsWith("git@")) return trimmed;
|
|
26461
26543
|
return `https://github.com/${trimmed.replace(/\.git$/, "")}.git`;
|
|
26462
26544
|
}
|
|
26463
|
-
|
|
26545
|
+
function maskCloneUrl(url) {
|
|
26546
|
+
return url.replace(/(https?:\/\/)[^@/]+@/, "$1***@");
|
|
26547
|
+
}
|
|
26548
|
+
function maskToken(text, cloneToken) {
|
|
26549
|
+
const masked = maskCloneUrl(text);
|
|
26550
|
+
if (cloneToken && cloneToken.length > 0) {
|
|
26551
|
+
return masked.split(cloneToken).join("***");
|
|
26552
|
+
}
|
|
26553
|
+
return masked;
|
|
26554
|
+
}
|
|
26555
|
+
async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
|
|
26464
26556
|
if (isAbsolutePathTarget(repoOrPath)) {
|
|
26465
26557
|
if (!fs42.existsSync(repoOrPath)) {
|
|
26466
26558
|
throw new Error(`deploy target path does not exist: ${repoOrPath}`);
|
|
@@ -26472,10 +26564,17 @@ async function prepareWorkspace(repoOrPath, deployId) {
|
|
|
26472
26564
|
return dest;
|
|
26473
26565
|
}
|
|
26474
26566
|
fs42.mkdirSync(selfHostedWorkspaceRoot(), { recursive: true, mode: 448 });
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26478
|
-
|
|
26567
|
+
const cloneUrl = repoCloneUrl(repoOrPath, cloneToken);
|
|
26568
|
+
try {
|
|
26569
|
+
await execFileP9("git", ["clone", "--depth", "1", cloneUrl, dest], {
|
|
26570
|
+
timeout: 12e4,
|
|
26571
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
26572
|
+
env: nonInteractiveGitEnv()
|
|
26573
|
+
});
|
|
26574
|
+
} catch (err) {
|
|
26575
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
26576
|
+
throw new Error(`git clone failed for ${maskCloneUrl(cloneUrl)}: ${maskToken(reason, cloneToken)}`);
|
|
26577
|
+
}
|
|
26479
26578
|
return dest;
|
|
26480
26579
|
}
|
|
26481
26580
|
|
|
@@ -26560,6 +26659,9 @@ function isDeployPayload(p2) {
|
|
|
26560
26659
|
if (typeof p2.deployId !== "string" || typeof p2.repoOrPath !== "string" || typeof p2.agentId !== "string" || typeof p2.autoPairToken !== "string") {
|
|
26561
26660
|
return false;
|
|
26562
26661
|
}
|
|
26662
|
+
if (p2.cloneToken !== void 0 && typeof p2.cloneToken !== "string") {
|
|
26663
|
+
return false;
|
|
26664
|
+
}
|
|
26563
26665
|
const hasHouse = isHouseProxy(p2.houseProxy);
|
|
26564
26666
|
const hasSealed = typeof p2.sealedAgentAuth === "string";
|
|
26565
26667
|
return hasHouse || hasSealed;
|
|
@@ -26578,7 +26680,7 @@ var CONTROL_AGENT_META = {
|
|
|
26578
26680
|
var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process13.spawn)(process.execPath, [process.argv[1], "pair-auto", ...args2], {
|
|
26579
26681
|
cwd,
|
|
26580
26682
|
env: { ...process.env, ...env },
|
|
26581
|
-
stdio: "ignore",
|
|
26683
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26582
26684
|
detached: false
|
|
26583
26685
|
});
|
|
26584
26686
|
var defaultOnIdentityRejected = () => {
|
|
@@ -26731,43 +26833,86 @@ var HostAgentSupervisor = class {
|
|
|
26731
26833
|
"host-agent",
|
|
26732
26834
|
`deploy id=${payload.deployId.slice(0, 8)} agent=${payload.agentId} target=${payload.repoOrPath}`
|
|
26733
26835
|
);
|
|
26734
|
-
const
|
|
26735
|
-
|
|
26736
|
-
|
|
26737
|
-
|
|
26738
|
-
|
|
26739
|
-
|
|
26740
|
-
|
|
26741
|
-
|
|
26742
|
-
|
|
26743
|
-
|
|
26744
|
-
|
|
26745
|
-
|
|
26746
|
-
|
|
26747
|
-
|
|
26748
|
-
|
|
26749
|
-
|
|
26750
|
-
|
|
26751
|
-
|
|
26752
|
-
|
|
26753
|
-
|
|
26754
|
-
|
|
26755
|
-
|
|
26756
|
-
|
|
26836
|
+
const report = (step, message) => {
|
|
26837
|
+
void reportDeployProgress(
|
|
26838
|
+
{ hostId: this.identity.hostId, hostToken: this.identity.hostToken },
|
|
26839
|
+
payload.deployId,
|
|
26840
|
+
step,
|
|
26841
|
+
message
|
|
26842
|
+
);
|
|
26843
|
+
};
|
|
26844
|
+
try {
|
|
26845
|
+
report("preparing", "preparing workspace");
|
|
26846
|
+
if (!isAbsolutePathTarget(payload.repoOrPath)) {
|
|
26847
|
+
report("cloning", "cloning repository");
|
|
26848
|
+
}
|
|
26849
|
+
const cwd = await prepareWorkspace(payload.repoOrPath, payload.deployId, payload.cloneToken);
|
|
26850
|
+
let childEnv;
|
|
26851
|
+
let extraArgs = [];
|
|
26852
|
+
if (payload.houseProxy) {
|
|
26853
|
+
const { baseUrl, token, agentKind } = payload.houseProxy;
|
|
26854
|
+
childEnv = {
|
|
26855
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
26856
|
+
ANTHROPIC_AUTH_TOKEN: token,
|
|
26857
|
+
ANTHROPIC_MODEL: "MiniMax-M3",
|
|
26858
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M3",
|
|
26859
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M3",
|
|
26860
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M3",
|
|
26861
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "512000",
|
|
26862
|
+
API_TIMEOUT_MS: "3000000",
|
|
26863
|
+
CODEAM_AUTO_TOKEN: payload.autoPairToken
|
|
26864
|
+
};
|
|
26865
|
+
extraArgs = [`--agent=${agentKind || "claude"}`];
|
|
26866
|
+
} else {
|
|
26867
|
+
const auth = await this.resolveAgentAuth(this.identity, payload.sealedAgentAuth);
|
|
26868
|
+
const credEnv = provisionAgentCredentials(payload.agentId, auth, void 0);
|
|
26869
|
+
childEnv = {
|
|
26870
|
+
...credEnv,
|
|
26871
|
+
CODEAM_AUTO_TOKEN: payload.autoPairToken
|
|
26872
|
+
};
|
|
26873
|
+
}
|
|
26874
|
+
report("spawning", "starting agent");
|
|
26875
|
+
const proc = this.spawnChild(childEnv, cwd, extraArgs);
|
|
26876
|
+
const child = { deployId: payload.deployId, proc };
|
|
26877
|
+
this.children.set(payload.deployId, child);
|
|
26878
|
+
let tail = "";
|
|
26879
|
+
const appendTail = (buf) => {
|
|
26880
|
+
tail = (tail + buf.toString("utf8")).slice(-2e3);
|
|
26757
26881
|
};
|
|
26758
|
-
|
|
26759
|
-
|
|
26760
|
-
|
|
26761
|
-
|
|
26762
|
-
|
|
26763
|
-
|
|
26882
|
+
proc.stdout?.on("data", appendTail);
|
|
26883
|
+
proc.stderr?.on("data", appendTail);
|
|
26884
|
+
report("agent_starting", "agent process started");
|
|
26885
|
+
proc.once("exit", (code) => {
|
|
26886
|
+
const tracked = this.children.get(payload.deployId)?.proc === proc;
|
|
26887
|
+
if (tracked) {
|
|
26888
|
+
this.children.delete(payload.deployId);
|
|
26889
|
+
}
|
|
26890
|
+
if (tracked && typeof code === "number" && code !== 0) {
|
|
26891
|
+
const detail = tail.trim().slice(-500);
|
|
26892
|
+
report("failed", detail ? `agent exited (${code}): ${detail}` : `agent exited (${code})`);
|
|
26893
|
+
}
|
|
26894
|
+
});
|
|
26895
|
+
} catch (err) {
|
|
26896
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26897
|
+
log.warn("host-agent", `deploy ${payload.deployId.slice(0, 8)} failed: ${message}`);
|
|
26898
|
+
const existing = this.children.get(payload.deployId);
|
|
26899
|
+
if (existing) {
|
|
26900
|
+
try {
|
|
26901
|
+
existing.proc.kill("SIGTERM");
|
|
26902
|
+
} catch {
|
|
26903
|
+
}
|
|
26764
26904
|
this.children.delete(payload.deployId);
|
|
26765
26905
|
}
|
|
26766
|
-
|
|
26906
|
+
report("failed", message);
|
|
26907
|
+
}
|
|
26767
26908
|
}
|
|
26768
|
-
/**
|
|
26909
|
+
/**
|
|
26910
|
+
* Kill the child for the given id. The backend correlates the session it
|
|
26911
|
+
* sends to this deploy, so the id matches the deployId we keyed on. No-op
|
|
26912
|
+
* if absent.
|
|
26913
|
+
*/
|
|
26769
26914
|
stopChild(sessionId) {
|
|
26770
|
-
const child = this.children.get(sessionId)
|
|
26915
|
+
const child = this.children.get(sessionId);
|
|
26771
26916
|
if (!child) {
|
|
26772
26917
|
log.trace("host-agent", `stop: no child for sessionId=${sessionId}`);
|
|
26773
26918
|
return;
|
|
@@ -26779,12 +26924,6 @@ var HostAgentSupervisor = class {
|
|
|
26779
26924
|
}
|
|
26780
26925
|
this.children.delete(child.deployId);
|
|
26781
26926
|
}
|
|
26782
|
-
findBySessionId(sessionId) {
|
|
26783
|
-
for (const child of this.children.values()) {
|
|
26784
|
-
if (child.sessionId === sessionId) return child;
|
|
26785
|
-
}
|
|
26786
|
-
return void 0;
|
|
26787
|
-
}
|
|
26788
26927
|
};
|
|
26789
26928
|
async function resolveHostIdentity(enrollToken) {
|
|
26790
26929
|
const existing = loadHostIdentity();
|
|
@@ -27008,7 +27147,7 @@ function checkChokidar() {
|
|
|
27008
27147
|
}
|
|
27009
27148
|
async function doctor(args2 = []) {
|
|
27010
27149
|
const json = args2.includes("--json");
|
|
27011
|
-
const cliVersion = true ? "2.39.
|
|
27150
|
+
const cliVersion = true ? "2.39.33" : "0.0.0-dev";
|
|
27012
27151
|
const apiBase2 = resolveApiBaseUrl();
|
|
27013
27152
|
const diagnosticId = (0, import_node_crypto8.randomUUID)();
|
|
27014
27153
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -27207,7 +27346,7 @@ async function completion(args2) {
|
|
|
27207
27346
|
// src/commands/version.ts
|
|
27208
27347
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
27209
27348
|
function version2() {
|
|
27210
|
-
const v = true ? "2.39.
|
|
27349
|
+
const v = true ? "2.39.33" : "unknown";
|
|
27211
27350
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
27212
27351
|
}
|
|
27213
27352
|
|
|
@@ -27493,7 +27632,7 @@ function checkForUpdates() {
|
|
|
27493
27632
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
27494
27633
|
if (process.env.CI) return;
|
|
27495
27634
|
if (!process.stdout.isTTY) return;
|
|
27496
|
-
const current = true ? "2.39.
|
|
27635
|
+
const current = true ? "2.39.33" : null;
|
|
27497
27636
|
if (!current) return;
|
|
27498
27637
|
const cache = readCache();
|
|
27499
27638
|
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.39.
|
|
3
|
+
"version": "2.39.33",
|
|
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",
|