codeam-cli 2.1.0 → 2.2.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 +38 -0
- package/dist/index.js +174 -76
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ 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.1.0] — 2026-04-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Exponential polling backoff with ±10% jitter
|
|
12
|
+
- **cli:** Forward X-Plugin-Auth-Token on /commands/output
|
|
13
|
+
|
|
14
|
+
### Build
|
|
15
|
+
|
|
16
|
+
- **deps:** Bump com.google.zxing:core in /apps/jetbrains-plugin (#5)
|
|
17
|
+
- **deps:** Bump org.jetbrains.intellij.platform (#6)
|
|
18
|
+
- **deps:** Bump gradle-wrapper in /apps/jetbrains-plugin (#14)
|
|
19
|
+
|
|
20
|
+
### CI
|
|
21
|
+
|
|
22
|
+
- **jetbrains:** Publish plugin to Marketplace stable channel on tag
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **cli:** Drop unused token field from WS auth payload
|
|
27
|
+
- **cli:** Zod-validate remote command payloads
|
|
28
|
+
|
|
29
|
+
### Chore
|
|
30
|
+
|
|
31
|
+
- Ignore .worktrees directory
|
|
32
|
+
- **cli:** Upgrade @clack/prompts to 1.2.0 (ESM bundled via tsup)
|
|
33
|
+
- **deps:** Bump vitest to clear esbuild CVE (GHSA-67mh-4wv8-2f99)
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
|
|
37
|
+
- Enforce correct-and-implicit TypeScript typing
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **cli:** Pass PTY args as argv array (no shell concatenation)
|
|
42
|
+
- **cli:** Clean up PTY child on SIGINT/SIGTERM
|
|
43
|
+
- **vsc-plugin:** Guard startMonitoring against re-entry; scaffold vitest
|
|
44
|
+
|
|
7
45
|
## [2.0.2] — 2026-04-23
|
|
8
46
|
|
|
9
47
|
### Build
|
package/dist/index.js
CHANGED
|
@@ -87,9 +87,9 @@ var require_src = __commonJS({
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// src/commands/start.ts
|
|
90
|
-
var
|
|
90
|
+
var fs6 = __toESM(require("fs"));
|
|
91
91
|
var os5 = __toESM(require("os"));
|
|
92
|
-
var
|
|
92
|
+
var path6 = __toESM(require("path"));
|
|
93
93
|
var import_crypto = require("crypto");
|
|
94
94
|
var import_child_process3 = require("child_process");
|
|
95
95
|
var import_picocolors2 = __toESM(require("picocolors"));
|
|
@@ -179,7 +179,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
179
179
|
// package.json
|
|
180
180
|
var package_default = {
|
|
181
181
|
name: "codeam-cli",
|
|
182
|
-
version: "2.
|
|
182
|
+
version: "2.2.0",
|
|
183
183
|
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.",
|
|
184
184
|
main: "dist/index.js",
|
|
185
185
|
bin: {
|
|
@@ -251,7 +251,7 @@ var package_default = {
|
|
|
251
251
|
picocolors: "^1.1.0",
|
|
252
252
|
"qrcode-terminal": "^0.12.0",
|
|
253
253
|
ws: "^8.18.0",
|
|
254
|
-
zod: "^3.
|
|
254
|
+
zod: "^4.3.6"
|
|
255
255
|
},
|
|
256
256
|
devDependencies: {
|
|
257
257
|
"@codeagent/shared": "*",
|
|
@@ -259,8 +259,8 @@ var package_default = {
|
|
|
259
259
|
"@types/qrcode-terminal": "^0.12.0",
|
|
260
260
|
"@types/ws": "^8.5.0",
|
|
261
261
|
tsup: "^8.0.0",
|
|
262
|
-
typescript: "^
|
|
263
|
-
vitest: "^
|
|
262
|
+
typescript: "^6.0.3",
|
|
263
|
+
vitest: "^4.1.5"
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
266
|
|
|
@@ -298,6 +298,16 @@ function showPairingCode(code, expiresAt) {
|
|
|
298
298
|
|
|
299
299
|
// src/services/websocket.service.ts
|
|
300
300
|
var import_ws = __toESM(require("ws"));
|
|
301
|
+
|
|
302
|
+
// src/lib/poll-delay.ts
|
|
303
|
+
var MAX_DELAY_MS = 3e4;
|
|
304
|
+
function computePollDelay({ baseMs, failures }) {
|
|
305
|
+
const exp = Math.min(MAX_DELAY_MS, baseMs * Math.pow(2, failures));
|
|
306
|
+
const jitter = exp * (0.9 + Math.random() * 0.2);
|
|
307
|
+
return Math.round(jitter);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/services/websocket.service.ts
|
|
301
311
|
var API_BASE = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
302
312
|
var WS_URL = API_BASE.replace("https://", "wss://").replace("http://", "ws://") + "/api/ws";
|
|
303
313
|
var HEARTBEAT_MS = 3e4;
|
|
@@ -350,7 +360,7 @@ var WebSocketService = class {
|
|
|
350
360
|
this.handlers.forEach((h) => h.onDisconnected());
|
|
351
361
|
if (this.reconnectAttempts < MAX_RECONNECT) {
|
|
352
362
|
this.reconnectAttempts++;
|
|
353
|
-
const delay =
|
|
363
|
+
const delay = computePollDelay({ baseMs: 1e3, failures: this.reconnectAttempts });
|
|
354
364
|
this.reconnectTimer = setTimeout(() => this.connect(), delay);
|
|
355
365
|
}
|
|
356
366
|
});
|
|
@@ -393,16 +403,6 @@ var WebSocketService = class {
|
|
|
393
403
|
var https = __toESM(require("https"));
|
|
394
404
|
var http = __toESM(require("http"));
|
|
395
405
|
var os2 = __toESM(require("os"));
|
|
396
|
-
|
|
397
|
-
// src/lib/poll-delay.ts
|
|
398
|
-
var MAX_DELAY_MS = 3e4;
|
|
399
|
-
function computePollDelay({ baseMs, failures }) {
|
|
400
|
-
const exp = Math.min(MAX_DELAY_MS, baseMs * Math.pow(2, failures));
|
|
401
|
-
const jitter = exp * (0.9 + Math.random() * 0.2);
|
|
402
|
-
return Math.round(jitter);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// src/services/pairing.service.ts
|
|
406
406
|
var API_BASE2 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
407
407
|
async function requestCode(pluginId) {
|
|
408
408
|
try {
|
|
@@ -476,7 +476,7 @@ var _transport = {
|
|
|
476
476
|
getJson: _getJson
|
|
477
477
|
};
|
|
478
478
|
async function _postJson(url, body) {
|
|
479
|
-
return new Promise((
|
|
479
|
+
return new Promise((resolve2, reject) => {
|
|
480
480
|
const data = JSON.stringify(body);
|
|
481
481
|
const u2 = new URL(url);
|
|
482
482
|
const transport = u2.protocol === "https:" ? https : http;
|
|
@@ -504,9 +504,9 @@ async function _postJson(url, body) {
|
|
|
504
504
|
return;
|
|
505
505
|
}
|
|
506
506
|
try {
|
|
507
|
-
|
|
507
|
+
resolve2(JSON.parse(body2));
|
|
508
508
|
} catch {
|
|
509
|
-
|
|
509
|
+
resolve2(null);
|
|
510
510
|
}
|
|
511
511
|
});
|
|
512
512
|
}
|
|
@@ -521,7 +521,7 @@ async function _postJson(url, body) {
|
|
|
521
521
|
});
|
|
522
522
|
}
|
|
523
523
|
async function _getJson(url) {
|
|
524
|
-
return new Promise((
|
|
524
|
+
return new Promise((resolve2, reject) => {
|
|
525
525
|
const u2 = new URL(url);
|
|
526
526
|
const transport = u2.protocol === "https:" ? https : http;
|
|
527
527
|
const req = transport.request(
|
|
@@ -544,9 +544,9 @@ async function _getJson(url) {
|
|
|
544
544
|
return;
|
|
545
545
|
}
|
|
546
546
|
try {
|
|
547
|
-
|
|
547
|
+
resolve2(JSON.parse(body));
|
|
548
548
|
} catch {
|
|
549
|
-
|
|
549
|
+
resolve2(null);
|
|
550
550
|
}
|
|
551
551
|
});
|
|
552
552
|
}
|
|
@@ -1661,20 +1661,20 @@ var OutputService = class _OutputService {
|
|
|
1661
1661
|
if (this.pluginAuthToken) {
|
|
1662
1662
|
headers["X-Plugin-Auth-Token"] = this.pluginAuthToken;
|
|
1663
1663
|
}
|
|
1664
|
-
return new Promise((
|
|
1664
|
+
return new Promise((resolve2) => {
|
|
1665
1665
|
const attempt = (attemptsLeft) => {
|
|
1666
1666
|
_transport2.sendOutputChunk(`${API_BASE4}/api/commands/output`, headers, payload).then(({ statusCode, body: resBody }) => {
|
|
1667
1667
|
if (statusCode >= 400) {
|
|
1668
1668
|
process.stderr.write(`[codeam] output API error ${statusCode}: ${resBody}
|
|
1669
1669
|
`);
|
|
1670
1670
|
}
|
|
1671
|
-
|
|
1671
|
+
resolve2();
|
|
1672
1672
|
}).catch(() => {
|
|
1673
1673
|
if (attemptsLeft > 0) {
|
|
1674
1674
|
const delay = 200 * (maxRetries - attemptsLeft + 1);
|
|
1675
1675
|
setTimeout(() => attempt(attemptsLeft - 1), delay);
|
|
1676
1676
|
} else {
|
|
1677
|
-
|
|
1677
|
+
resolve2();
|
|
1678
1678
|
}
|
|
1679
1679
|
});
|
|
1680
1680
|
};
|
|
@@ -1686,7 +1686,7 @@ var _transport2 = {
|
|
|
1686
1686
|
sendOutputChunk: _sendOutputChunk
|
|
1687
1687
|
};
|
|
1688
1688
|
function _sendOutputChunk(url, headers, payload) {
|
|
1689
|
-
return new Promise((
|
|
1689
|
+
return new Promise((resolve2, reject) => {
|
|
1690
1690
|
let settled = false;
|
|
1691
1691
|
const u2 = new URL(url);
|
|
1692
1692
|
const transport = u2.protocol === "https:" ? https2 : http2;
|
|
@@ -1710,7 +1710,7 @@ function _sendOutputChunk(url, headers, payload) {
|
|
|
1710
1710
|
res.on("end", () => {
|
|
1711
1711
|
if (settled) return;
|
|
1712
1712
|
settled = true;
|
|
1713
|
-
|
|
1713
|
+
resolve2({ statusCode: res.statusCode ?? 0, body: resData });
|
|
1714
1714
|
});
|
|
1715
1715
|
}
|
|
1716
1716
|
);
|
|
@@ -1733,6 +1733,7 @@ var path4 = __toESM(require("path"));
|
|
|
1733
1733
|
var os4 = __toESM(require("os"));
|
|
1734
1734
|
var https3 = __toESM(require("https"));
|
|
1735
1735
|
var http3 = __toESM(require("http"));
|
|
1736
|
+
var import_zod = require("zod");
|
|
1736
1737
|
|
|
1737
1738
|
// src/services/logger.ts
|
|
1738
1739
|
var LEVELS = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
@@ -1754,6 +1755,16 @@ var log = {
|
|
|
1754
1755
|
};
|
|
1755
1756
|
|
|
1756
1757
|
// src/services/history.service.ts
|
|
1758
|
+
var historyRecordSchema = import_zod.z.object({
|
|
1759
|
+
type: import_zod.z.string().optional(),
|
|
1760
|
+
timestamp: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
|
|
1761
|
+
uuid: import_zod.z.string().optional(),
|
|
1762
|
+
isMeta: import_zod.z.boolean().optional(),
|
|
1763
|
+
message: import_zod.z.object({
|
|
1764
|
+
// Claude content is either a string or an array of typed blocks.
|
|
1765
|
+
content: import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.unknown())]).optional()
|
|
1766
|
+
}).passthrough().optional()
|
|
1767
|
+
}).passthrough();
|
|
1757
1768
|
var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
1758
1769
|
function encodeCwd(cwd) {
|
|
1759
1770
|
return cwd.replace(/\//g, "-");
|
|
@@ -1779,28 +1790,35 @@ function parseJsonl(filePath) {
|
|
|
1779
1790
|
}
|
|
1780
1791
|
const lines = raw.split("\n").filter(Boolean);
|
|
1781
1792
|
for (const line of lines) {
|
|
1793
|
+
let parsedJson;
|
|
1782
1794
|
try {
|
|
1783
|
-
|
|
1784
|
-
const type = record["type"];
|
|
1785
|
-
const msg = record["message"];
|
|
1786
|
-
const ts = record["timestamp"];
|
|
1787
|
-
const timestamp = typeof ts === "string" ? new Date(ts).getTime() : typeof ts === "number" ? ts : Date.now();
|
|
1788
|
-
const uuid = record["uuid"] ?? `${Date.now()}-${Math.random()}`;
|
|
1789
|
-
if (record["isMeta"]) continue;
|
|
1790
|
-
if (type === "user" && msg) {
|
|
1791
|
-
const text = extractText(msg["content"]).trim();
|
|
1792
|
-
if (text) messages.push({ id: uuid, role: "user", text, timestamp });
|
|
1793
|
-
} else if (type === "assistant" && msg) {
|
|
1794
|
-
const text = extractText(msg["content"]).trim();
|
|
1795
|
-
if (text) messages.push({ id: uuid, role: "agent", text, timestamp });
|
|
1796
|
-
}
|
|
1795
|
+
parsedJson = JSON.parse(line);
|
|
1797
1796
|
} catch {
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
const result = historyRecordSchema.safeParse(parsedJson);
|
|
1800
|
+
if (!result.success) {
|
|
1801
|
+
log.warn("history:parseJsonl", `record failed schema validation in ${filePath}`, result.error.issues);
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
const record = result.data;
|
|
1805
|
+
if (record.isMeta) continue;
|
|
1806
|
+
const ts = record.timestamp;
|
|
1807
|
+
const timestamp = typeof ts === "string" ? new Date(ts).getTime() : typeof ts === "number" ? ts : Date.now();
|
|
1808
|
+
const uuid = record.uuid ?? `${Date.now()}-${Math.random()}`;
|
|
1809
|
+
const msg = record.message;
|
|
1810
|
+
if (record.type === "user" && msg) {
|
|
1811
|
+
const text = extractText(msg.content).trim();
|
|
1812
|
+
if (text) messages.push({ id: uuid, role: "user", text, timestamp });
|
|
1813
|
+
} else if (record.type === "assistant" && msg) {
|
|
1814
|
+
const text = extractText(msg.content).trim();
|
|
1815
|
+
if (text) messages.push({ id: uuid, role: "agent", text, timestamp });
|
|
1798
1816
|
}
|
|
1799
1817
|
}
|
|
1800
1818
|
return messages;
|
|
1801
1819
|
}
|
|
1802
1820
|
function post(endpoint, body) {
|
|
1803
|
-
return new Promise((
|
|
1821
|
+
return new Promise((resolve2) => {
|
|
1804
1822
|
const payload = JSON.stringify(body);
|
|
1805
1823
|
const u2 = new URL(`${API_BASE5}${endpoint}`);
|
|
1806
1824
|
const transport = u2.protocol === "https:" ? https3 : http3;
|
|
@@ -1820,17 +1838,17 @@ function post(endpoint, body) {
|
|
|
1820
1838
|
res.resume();
|
|
1821
1839
|
const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300;
|
|
1822
1840
|
if (!ok) log.warn("history:post", `${endpoint} \u2192 HTTP ${res.statusCode}`);
|
|
1823
|
-
|
|
1841
|
+
resolve2(ok);
|
|
1824
1842
|
}
|
|
1825
1843
|
);
|
|
1826
1844
|
req.on("error", (err) => {
|
|
1827
1845
|
log.warn("history:post", `${endpoint} network error`, err);
|
|
1828
|
-
|
|
1846
|
+
resolve2(false);
|
|
1829
1847
|
});
|
|
1830
1848
|
req.on("timeout", () => {
|
|
1831
1849
|
log.warn("history:post", `${endpoint} timeout after 15s`);
|
|
1832
1850
|
req.destroy();
|
|
1833
|
-
|
|
1851
|
+
resolve2(false);
|
|
1834
1852
|
});
|
|
1835
1853
|
req.write(payload);
|
|
1836
1854
|
req.end();
|
|
@@ -2127,32 +2145,92 @@ var HistoryService = class {
|
|
|
2127
2145
|
};
|
|
2128
2146
|
|
|
2129
2147
|
// src/lib/payload.ts
|
|
2130
|
-
var
|
|
2131
|
-
var fileEntrySchema =
|
|
2132
|
-
filename:
|
|
2133
|
-
mimeType:
|
|
2134
|
-
base64:
|
|
2148
|
+
var import_zod2 = require("zod");
|
|
2149
|
+
var fileEntrySchema = import_zod2.z.object({
|
|
2150
|
+
filename: import_zod2.z.string().min(1).max(256),
|
|
2151
|
+
mimeType: import_zod2.z.string(),
|
|
2152
|
+
base64: import_zod2.z.string()
|
|
2135
2153
|
});
|
|
2136
|
-
var startCommandSchema =
|
|
2137
|
-
prompt:
|
|
2138
|
-
files:
|
|
2139
|
-
input:
|
|
2140
|
-
index:
|
|
2141
|
-
from:
|
|
2142
|
-
id:
|
|
2143
|
-
auto:
|
|
2154
|
+
var startCommandSchema = import_zod2.z.object({
|
|
2155
|
+
prompt: import_zod2.z.string().optional(),
|
|
2156
|
+
files: import_zod2.z.array(fileEntrySchema).optional(),
|
|
2157
|
+
input: import_zod2.z.string().optional(),
|
|
2158
|
+
index: import_zod2.z.number().optional(),
|
|
2159
|
+
from: import_zod2.z.number().optional(),
|
|
2160
|
+
id: import_zod2.z.string().optional(),
|
|
2161
|
+
auto: import_zod2.z.boolean().optional(),
|
|
2162
|
+
// `read_file` / `write_file` for the mobile + landing mini-IDE modal.
|
|
2163
|
+
// `path` is bounded to 4096 chars (a comfortable POSIX path max) so a
|
|
2164
|
+
// malformed payload can't blow up the disk-side validator.
|
|
2165
|
+
path: import_zod2.z.string().min(1).max(4096).optional(),
|
|
2166
|
+
content: import_zod2.z.string().optional()
|
|
2144
2167
|
});
|
|
2145
2168
|
function parsePayload(schema, raw) {
|
|
2146
2169
|
const result = schema.safeParse(raw);
|
|
2147
2170
|
return result.success ? result.data : null;
|
|
2148
2171
|
}
|
|
2149
2172
|
|
|
2173
|
+
// src/services/file-ops.service.ts
|
|
2174
|
+
var fs5 = __toESM(require("fs/promises"));
|
|
2175
|
+
var path5 = __toESM(require("path"));
|
|
2176
|
+
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
2177
|
+
function resolveSafe(rawPath) {
|
|
2178
|
+
const cwd = process.cwd();
|
|
2179
|
+
const absolute = path5.isAbsolute(rawPath) ? path5.normalize(rawPath) : path5.resolve(cwd, rawPath);
|
|
2180
|
+
const relativeFromCwd = path5.relative(cwd, absolute);
|
|
2181
|
+
if (relativeFromCwd.startsWith("..") || path5.isAbsolute(relativeFromCwd)) {
|
|
2182
|
+
throw new Error(`Path escapes the project root: ${rawPath}`);
|
|
2183
|
+
}
|
|
2184
|
+
return absolute;
|
|
2185
|
+
}
|
|
2186
|
+
function looksBinary(buf) {
|
|
2187
|
+
const sample = buf.subarray(0, Math.min(8192, buf.length));
|
|
2188
|
+
for (let i = 0; i < sample.length; i++) {
|
|
2189
|
+
if (sample[i] === 0) return true;
|
|
2190
|
+
}
|
|
2191
|
+
return false;
|
|
2192
|
+
}
|
|
2193
|
+
async function readProjectFile(rawPath) {
|
|
2194
|
+
try {
|
|
2195
|
+
const abs = resolveSafe(rawPath);
|
|
2196
|
+
const stat2 = await fs5.stat(abs);
|
|
2197
|
+
if (!stat2.isFile()) {
|
|
2198
|
+
return { error: "Not a regular file." };
|
|
2199
|
+
}
|
|
2200
|
+
if (stat2.size > MAX_FILE_BYTES) {
|
|
2201
|
+
return { error: `File too large (${(stat2.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
|
|
2202
|
+
}
|
|
2203
|
+
const buf = await fs5.readFile(abs);
|
|
2204
|
+
if (looksBinary(buf)) {
|
|
2205
|
+
return { error: "Binary file \u2014 refusing to open in a code editor." };
|
|
2206
|
+
}
|
|
2207
|
+
return { content: buf.toString("utf-8") };
|
|
2208
|
+
} catch (e) {
|
|
2209
|
+
const msg = e instanceof Error ? e.message : "Read failed";
|
|
2210
|
+
return { error: msg };
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
async function writeProjectFile(rawPath, content) {
|
|
2214
|
+
try {
|
|
2215
|
+
const abs = resolveSafe(rawPath);
|
|
2216
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
2217
|
+
return { error: "Content too large." };
|
|
2218
|
+
}
|
|
2219
|
+
await fs5.mkdir(path5.dirname(abs), { recursive: true });
|
|
2220
|
+
await fs5.writeFile(abs, content, "utf-8");
|
|
2221
|
+
return { ok: true };
|
|
2222
|
+
} catch (e) {
|
|
2223
|
+
const msg = e instanceof Error ? e.message : "Write failed";
|
|
2224
|
+
return { error: msg };
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2150
2228
|
// src/commands/start.ts
|
|
2151
2229
|
function saveFilesTemp(files) {
|
|
2152
2230
|
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
2153
2231
|
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
2154
|
-
const tmpPath =
|
|
2155
|
-
|
|
2232
|
+
const tmpPath = path6.join(os5.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
|
|
2233
|
+
fs6.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
2156
2234
|
return tmpPath;
|
|
2157
2235
|
});
|
|
2158
2236
|
}
|
|
@@ -2225,8 +2303,8 @@ try:
|
|
|
2225
2303
|
sys.exit((st>>8)&0xFF)
|
|
2226
2304
|
except Exception:sys.exit(0)
|
|
2227
2305
|
`;
|
|
2228
|
-
const helperPath =
|
|
2229
|
-
|
|
2306
|
+
const helperPath = path6.join(os5.tmpdir(), "codeam-quota-helper.py");
|
|
2307
|
+
fs6.writeFileSync(helperPath, helperScript, { mode: 420 });
|
|
2230
2308
|
const python = findInPath("python3") ?? findInPath("python");
|
|
2231
2309
|
if (!python) {
|
|
2232
2310
|
quotaFetchInProgress = false;
|
|
@@ -2258,7 +2336,7 @@ except Exception:sys.exit(0)
|
|
|
2258
2336
|
} catch {
|
|
2259
2337
|
}
|
|
2260
2338
|
try {
|
|
2261
|
-
|
|
2339
|
+
fs6.unlinkSync(helperPath);
|
|
2262
2340
|
} catch {
|
|
2263
2341
|
}
|
|
2264
2342
|
quotaFetchInProgress = false;
|
|
@@ -2308,7 +2386,7 @@ except Exception:sys.exit(0)
|
|
|
2308
2386
|
setTimeout(() => {
|
|
2309
2387
|
for (const p2 of paths) {
|
|
2310
2388
|
try {
|
|
2311
|
-
|
|
2389
|
+
fs6.unlinkSync(p2);
|
|
2312
2390
|
} catch {
|
|
2313
2391
|
}
|
|
2314
2392
|
}
|
|
@@ -2380,6 +2458,26 @@ except Exception:sys.exit(0)
|
|
|
2380
2458
|
await relay.sendResult(cmd.id, "completed", { models });
|
|
2381
2459
|
break;
|
|
2382
2460
|
}
|
|
2461
|
+
case "read_file": {
|
|
2462
|
+
const { path: filePath } = parsed;
|
|
2463
|
+
if (!filePath) {
|
|
2464
|
+
await relay.sendResult(cmd.id, "failed", { error: "Missing path" });
|
|
2465
|
+
break;
|
|
2466
|
+
}
|
|
2467
|
+
const result = await readProjectFile(filePath);
|
|
2468
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2469
|
+
break;
|
|
2470
|
+
}
|
|
2471
|
+
case "write_file": {
|
|
2472
|
+
const { path: filePath, content } = parsed;
|
|
2473
|
+
if (!filePath || typeof content !== "string") {
|
|
2474
|
+
await relay.sendResult(cmd.id, "failed", { error: "Missing path or content" });
|
|
2475
|
+
break;
|
|
2476
|
+
}
|
|
2477
|
+
const result = await writeProjectFile(filePath, content);
|
|
2478
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2479
|
+
break;
|
|
2480
|
+
}
|
|
2383
2481
|
}
|
|
2384
2482
|
});
|
|
2385
2483
|
ws.addHandler({
|
|
@@ -2407,7 +2505,7 @@ except Exception:sys.exit(0)
|
|
|
2407
2505
|
setTimeout(() => {
|
|
2408
2506
|
for (const p2 of paths) {
|
|
2409
2507
|
try {
|
|
2410
|
-
|
|
2508
|
+
fs6.unlinkSync(p2);
|
|
2411
2509
|
} catch {
|
|
2412
2510
|
}
|
|
2413
2511
|
}
|
|
@@ -2509,7 +2607,7 @@ __export(dist_exports, {
|
|
|
2509
2607
|
S_ERROR: () => ge,
|
|
2510
2608
|
S_INFO: () => he,
|
|
2511
2609
|
S_PASSWORD_MASK: () => xe,
|
|
2512
|
-
S_RADIO_ACTIVE: () =>
|
|
2610
|
+
S_RADIO_ACTIVE: () => z4,
|
|
2513
2611
|
S_RADIO_INACTIVE: () => H2,
|
|
2514
2612
|
S_STEP_ACTIVE: () => _e,
|
|
2515
2613
|
S_STEP_CANCEL: () => oe,
|
|
@@ -2988,7 +3086,7 @@ function w(r, t2) {
|
|
|
2988
3086
|
const e = r;
|
|
2989
3087
|
e.isTTY && e.setRawMode(t2);
|
|
2990
3088
|
}
|
|
2991
|
-
function
|
|
3089
|
+
function z3({ input: r = import_node_process.stdin, output: t2 = import_node_process.stdout, overwrite: e = true, hideCursor: s = true } = {}) {
|
|
2992
3090
|
const i = _.createInterface({ input: r, output: t2, prompt: "", tabSize: 1 });
|
|
2993
3091
|
_.emitKeypressEvents(r, i), r instanceof import_node_tty.ReadStream && r.isTTY && r.setRawMode(true);
|
|
2994
3092
|
const n = (o, { name: a, sequence: h }) => {
|
|
@@ -3600,7 +3698,7 @@ var d2 = w2("\u2502", "|");
|
|
|
3600
3698
|
var E2 = w2("\u2514", "\u2014");
|
|
3601
3699
|
var Ie = w2("\u2510", "T");
|
|
3602
3700
|
var Ee = w2("\u2518", "\u2014");
|
|
3603
|
-
var
|
|
3701
|
+
var z4 = w2("\u25CF", ">");
|
|
3604
3702
|
var H2 = w2("\u25CB", " ");
|
|
3605
3703
|
var te = w2("\u25FB", "[\u2022]");
|
|
3606
3704
|
var U = w2("\u25FC", "[+]");
|
|
@@ -3692,7 +3790,7 @@ var Ae = (e) => new H({ options: e.options, initialValue: e.initialValue ? [e.in
|
|
|
3692
3790
|
const $2 = Me(a), y2 = a.hint && a.value === this.focusedValue ? (0, import_node_util2.styleText)("dim", ` (${a.hint})`) : "";
|
|
3693
3791
|
switch (l) {
|
|
3694
3792
|
case "active":
|
|
3695
|
-
return `${(0, import_node_util2.styleText)("green",
|
|
3793
|
+
return `${(0, import_node_util2.styleText)("green", z4)} ${$2}${y2}`;
|
|
3696
3794
|
case "inactive":
|
|
3697
3795
|
return `${(0, import_node_util2.styleText)("dim", H2)} ${(0, import_node_util2.styleText)("dim", $2)}`;
|
|
3698
3796
|
case "disabled":
|
|
@@ -3805,9 +3903,9 @@ ${(0, import_node_util2.styleText)("gray", d2)}` : ""}`;
|
|
|
3805
3903
|
}
|
|
3806
3904
|
default: {
|
|
3807
3905
|
const l = r ? `${(0, import_node_util2.styleText)("cyan", d2)} ` : "", $2 = r ? (0, import_node_util2.styleText)("cyan", E2) : "";
|
|
3808
|
-
return `${c2}${l}${this.value ? `${(0, import_node_util2.styleText)("green",
|
|
3906
|
+
return `${c2}${l}${this.value ? `${(0, import_node_util2.styleText)("green", z4)} ${i}` : `${(0, import_node_util2.styleText)("dim", H2)} ${(0, import_node_util2.styleText)("dim", i)}`}${e.vertical ? r ? `
|
|
3809
3907
|
${(0, import_node_util2.styleText)("cyan", d2)} ` : `
|
|
3810
|
-
` : ` ${(0, import_node_util2.styleText)("dim", "/")} `}${this.value ? `${(0, import_node_util2.styleText)("dim", H2)} ${(0, import_node_util2.styleText)("dim", s)}` : `${(0, import_node_util2.styleText)("green",
|
|
3908
|
+
` : ` ${(0, import_node_util2.styleText)("dim", "/")} `}${this.value ? `${(0, import_node_util2.styleText)("dim", H2)} ${(0, import_node_util2.styleText)("dim", s)}` : `${(0, import_node_util2.styleText)("green", z4)} ${s}`}
|
|
3811
3909
|
${$2}
|
|
3812
3910
|
`;
|
|
3813
3911
|
}
|
|
@@ -4138,7 +4236,7 @@ var fe = ({ indicator: e = "dots", onCancel: i, output: s = process.stdout, canc
|
|
|
4138
4236
|
const A2 = (performance.now() - _2) / 1e3, k2 = Math.floor(A2 / 60), L2 = Math.floor(A2 % 60);
|
|
4139
4237
|
return k2 > 0 ? `[${k2}m ${L2}s]` : `[${L2}s]`;
|
|
4140
4238
|
}, D2 = a.withGuide ?? u.withGuide, ie = (_2 = "") => {
|
|
4141
|
-
p2 = true, $2 =
|
|
4239
|
+
p2 = true, $2 = z3({ output: s }), g = R2(_2), h = performance.now(), D2 && s.write(`${(0, import_node_util2.styleText)("gray", d2)}
|
|
4142
4240
|
`);
|
|
4143
4241
|
let A2 = 0, k2 = 0;
|
|
4144
4242
|
x(), y2 = setInterval(() => {
|
|
@@ -4209,7 +4307,7 @@ var _t = (e) => {
|
|
|
4209
4307
|
case "selected":
|
|
4210
4308
|
return `${re(u2, (n) => (0, import_node_util2.styleText)("dim", n))}`;
|
|
4211
4309
|
case "active":
|
|
4212
|
-
return `${(0, import_node_util2.styleText)("green",
|
|
4310
|
+
return `${(0, import_node_util2.styleText)("green", z4)} ${u2}${s.hint ? ` ${(0, import_node_util2.styleText)("dim", `(${s.hint})`)}` : ""}`;
|
|
4213
4311
|
case "cancelled":
|
|
4214
4312
|
return `${re(u2, (n) => (0, import_node_util2.styleText)(["strikethrough", "dim"], n))}`;
|
|
4215
4313
|
default:
|
|
@@ -4437,7 +4535,7 @@ async function pair() {
|
|
|
4437
4535
|
console.log("");
|
|
4438
4536
|
const waitSpin = dist_exports.spinner();
|
|
4439
4537
|
waitSpin.start("Waiting for mobile app...");
|
|
4440
|
-
await new Promise((
|
|
4538
|
+
await new Promise((resolve2) => {
|
|
4441
4539
|
let stopPolling = null;
|
|
4442
4540
|
function sigintHandler() {
|
|
4443
4541
|
stopPolling?.();
|
|
@@ -4460,7 +4558,7 @@ async function pair() {
|
|
|
4460
4558
|
});
|
|
4461
4559
|
showSuccess(`Paired with ${info.userName} (${info.plan})`);
|
|
4462
4560
|
console.log("");
|
|
4463
|
-
|
|
4561
|
+
resolve2();
|
|
4464
4562
|
},
|
|
4465
4563
|
() => {
|
|
4466
4564
|
waitSpin.stop("Timed out");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"picocolors": "^1.1.0",
|
|
73
73
|
"qrcode-terminal": "^0.12.0",
|
|
74
74
|
"ws": "^8.18.0",
|
|
75
|
-
"zod": "^3.
|
|
75
|
+
"zod": "^4.3.6"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@codeagent/shared": "*",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@types/qrcode-terminal": "^0.12.0",
|
|
81
81
|
"@types/ws": "^8.5.0",
|
|
82
82
|
"tsup": "^8.0.0",
|
|
83
|
-
"typescript": "^
|
|
84
|
-
"vitest": "^
|
|
83
|
+
"typescript": "^6.0.3",
|
|
84
|
+
"vitest": "^4.1.5"
|
|
85
85
|
}
|
|
86
86
|
}
|