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.
Files changed (3) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/index.js +174 -76
  3. 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 fs5 = __toESM(require("fs"));
90
+ var fs6 = __toESM(require("fs"));
91
91
  var os5 = __toESM(require("os"));
92
- var path5 = __toESM(require("path"));
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.1.0",
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.22.4"
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: "^5.0.0",
263
- vitest: "^3.2.4"
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 = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
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((resolve, reject) => {
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
- resolve(JSON.parse(body2));
507
+ resolve2(JSON.parse(body2));
508
508
  } catch {
509
- resolve(null);
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((resolve, reject) => {
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
- resolve(JSON.parse(body));
547
+ resolve2(JSON.parse(body));
548
548
  } catch {
549
- resolve(null);
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((resolve) => {
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
- resolve();
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
- resolve();
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((resolve, reject) => {
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
- resolve({ statusCode: res.statusCode ?? 0, body: resData });
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
- const record = JSON.parse(line);
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((resolve) => {
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
- resolve(ok);
1841
+ resolve2(ok);
1824
1842
  }
1825
1843
  );
1826
1844
  req.on("error", (err) => {
1827
1845
  log.warn("history:post", `${endpoint} network error`, err);
1828
- resolve(false);
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
- resolve(false);
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 import_zod = require("zod");
2131
- var fileEntrySchema = import_zod.z.object({
2132
- filename: import_zod.z.string().min(1).max(256),
2133
- mimeType: import_zod.z.string(),
2134
- base64: import_zod.z.string()
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 = import_zod.z.object({
2137
- prompt: import_zod.z.string().optional(),
2138
- files: import_zod.z.array(fileEntrySchema).optional(),
2139
- input: import_zod.z.string().optional(),
2140
- index: import_zod.z.number().optional(),
2141
- from: import_zod.z.number().optional(),
2142
- id: import_zod.z.string().optional(),
2143
- auto: import_zod.z.boolean().optional()
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 = path5.join(os5.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
2155
- fs5.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
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 = path5.join(os5.tmpdir(), "codeam-quota-helper.py");
2229
- fs5.writeFileSync(helperPath, helperScript, { mode: 420 });
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
- fs5.unlinkSync(helperPath);
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
- fs5.unlinkSync(p2);
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
- fs5.unlinkSync(p2);
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: () => z3,
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 z2({ input: r = import_node_process.stdin, output: t2 = import_node_process.stdout, overwrite: e = true, hideCursor: s = true } = {}) {
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 z3 = w2("\u25CF", ">");
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", z3)} ${$2}${y2}`;
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", z3)} ${i}` : `${(0, import_node_util2.styleText)("dim", H2)} ${(0, import_node_util2.styleText)("dim", i)}`}${e.vertical ? r ? `
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", z3)} ${s}`}
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 = z2({ output: s }), g = R2(_2), h = performance.now(), D2 && s.write(`${(0, import_node_util2.styleText)("gray", d2)}
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", z3)} ${u2}${s.hint ? ` ${(0, import_node_util2.styleText)("dim", `(${s.hint})`)}` : ""}`;
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((resolve) => {
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
- resolve();
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.1.0",
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.22.4"
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": "^5.0.0",
84
- "vitest": "^3.2.4"
83
+ "typescript": "^6.0.3",
84
+ "vitest": "^4.1.5"
85
85
  }
86
86
  }