codeam-cli 2.16.0 → 2.16.1

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 +6 -0
  2. package/dist/index.js +326 -219
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ 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.16.0] — 2026-05-21
8
+
9
+ ### Added
10
+
11
+ - **cli,vsc-plugin,jetbrains-plugin:** Codeam link <agent> CLI handoff (#41)
12
+
7
13
  ## [2.15.8] — 2026-05-21
8
14
 
9
15
  ### Fixed
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
9
12
  var __commonJS = (cb, mod) => function __require() {
10
13
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
14
  };
@@ -30,6 +33,38 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
33
  mod
31
34
  ));
32
35
 
36
+ // src/services/pty/types.ts
37
+ var types_exports = {};
38
+ __export(types_exports, {
39
+ findInPath: () => findInPath
40
+ });
41
+ function findInPath(name) {
42
+ const isWin = process.platform === "win32";
43
+ const dirs = (process.env.PATH ?? "").split(path3.delimiter).filter(Boolean);
44
+ const hasExt = path3.extname(name).length > 0;
45
+ const candidates = isWin && !hasExt ? [`${name}.exe`, `${name}.cmd`, `${name}.bat`, `${name}.ps1`, name] : [name];
46
+ const accessFlag = isWin ? fs3.constants.F_OK : fs3.constants.X_OK;
47
+ for (const dir of dirs) {
48
+ for (const candidate of candidates) {
49
+ const full = path3.join(dir, candidate);
50
+ try {
51
+ fs3.accessSync(full, accessFlag);
52
+ return full;
53
+ } catch {
54
+ }
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ var fs3, path3;
60
+ var init_types = __esm({
61
+ "src/services/pty/types.ts"() {
62
+ "use strict";
63
+ fs3 = __toESM(require("fs"));
64
+ path3 = __toESM(require("path"));
65
+ }
66
+ });
67
+
33
68
  // ../../node_modules/sisteransi/src/index.js
34
69
  var require_src = __commonJS({
35
70
  "../../node_modules/sisteransi/src/index.js"(exports2, module2) {
@@ -389,7 +424,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
389
424
  // package.json
390
425
  var package_default = {
391
426
  name: "codeam-cli",
392
- version: "2.16.0",
427
+ version: "2.16.1",
393
428
  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.",
394
429
  type: "commonjs",
395
430
  main: "dist/index.js",
@@ -676,7 +711,7 @@ async function postLinkCredential(input) {
676
711
  }
677
712
  }
678
713
  async function _postJsonAuthed(url, body, pluginAuthToken) {
679
- return new Promise((resolve2, reject) => {
714
+ return new Promise((resolve3, reject) => {
680
715
  const data = JSON.stringify(body);
681
716
  const u2 = new URL(url);
682
717
  const transport = u2.protocol === "https:" ? https : http;
@@ -708,9 +743,9 @@ async function _postJsonAuthed(url, body, pluginAuthToken) {
708
743
  return;
709
744
  }
710
745
  try {
711
- resolve2(JSON.parse(responseBody));
746
+ resolve3(JSON.parse(responseBody));
712
747
  } catch {
713
- resolve2(null);
748
+ resolve3(null);
714
749
  }
715
750
  });
716
751
  }
@@ -725,7 +760,7 @@ async function _postJsonAuthed(url, body, pluginAuthToken) {
725
760
  });
726
761
  }
727
762
  async function _postJson(url, body) {
728
- return new Promise((resolve2, reject) => {
763
+ return new Promise((resolve3, reject) => {
729
764
  const data = JSON.stringify(body);
730
765
  const u2 = new URL(url);
731
766
  const transport = u2.protocol === "https:" ? https : http;
@@ -754,9 +789,9 @@ async function _postJson(url, body) {
754
789
  return;
755
790
  }
756
791
  try {
757
- resolve2(JSON.parse(body2));
792
+ resolve3(JSON.parse(body2));
758
793
  } catch {
759
- resolve2(null);
794
+ resolve3(null);
760
795
  }
761
796
  });
762
797
  }
@@ -771,7 +806,7 @@ async function _postJson(url, body) {
771
806
  });
772
807
  }
773
808
  async function _getJson(url) {
774
- return new Promise((resolve2, reject) => {
809
+ return new Promise((resolve3, reject) => {
775
810
  const u2 = new URL(url);
776
811
  const transport = u2.protocol === "https:" ? https : http;
777
812
  const req = transport.request(
@@ -795,9 +830,9 @@ async function _getJson(url) {
795
830
  return;
796
831
  }
797
832
  try {
798
- resolve2(JSON.parse(body));
833
+ resolve3(JSON.parse(body));
799
834
  } catch {
800
- resolve2(null);
835
+ resolve3(null);
801
836
  }
802
837
  });
803
838
  }
@@ -1105,30 +1140,7 @@ var import_child_process2 = require("child_process");
1105
1140
  var fs4 = __toESM(require("fs"));
1106
1141
  var os4 = __toESM(require("os"));
1107
1142
  var path4 = __toESM(require("path"));
1108
-
1109
- // src/services/pty/types.ts
1110
- var fs3 = __toESM(require("fs"));
1111
- var path3 = __toESM(require("path"));
1112
- function findInPath(name) {
1113
- const isWin = process.platform === "win32";
1114
- const dirs = (process.env.PATH ?? "").split(path3.delimiter).filter(Boolean);
1115
- const hasExt = path3.extname(name).length > 0;
1116
- const candidates = isWin && !hasExt ? [`${name}.exe`, `${name}.cmd`, `${name}.bat`, `${name}.ps1`, name] : [name];
1117
- const accessFlag = isWin ? fs3.constants.F_OK : fs3.constants.X_OK;
1118
- for (const dir of dirs) {
1119
- for (const candidate of candidates) {
1120
- const full = path3.join(dir, candidate);
1121
- try {
1122
- fs3.accessSync(full, accessFlag);
1123
- return full;
1124
- } catch {
1125
- }
1126
- }
1127
- }
1128
- return null;
1129
- }
1130
-
1131
- // src/services/pty/unix.strategy.ts
1143
+ init_types();
1132
1144
  var PYTHON_PTY_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
1133
1145
  m,s=pty.openpty()
1134
1146
  try:
@@ -3441,6 +3453,7 @@ ${c2}
3441
3453
  } }).prompt();
3442
3454
 
3443
3455
  // src/services/claude-installer.ts
3456
+ init_types();
3444
3457
  function probeInstallDirs() {
3445
3458
  const home = os5.homedir();
3446
3459
  if (process.platform === "win32") {
@@ -3478,15 +3491,15 @@ function runInstaller() {
3478
3491
  "-Command",
3479
3492
  "irm https://claude.ai/install.ps1 | iex"
3480
3493
  ] : ["-c", "curl -fsSL https://claude.ai/install.sh | bash"];
3481
- return new Promise((resolve2) => {
3494
+ return new Promise((resolve3) => {
3482
3495
  const proc = (0, import_child_process4.spawn)(cmd, args2, { stdio: "inherit" });
3483
3496
  proc.on("error", (err) => {
3484
3497
  console.error(`
3485
3498
  \u2717 Installer failed to launch: ${err.message}`);
3486
- resolve2(false);
3499
+ resolve3(false);
3487
3500
  });
3488
3501
  proc.on("exit", (code) => {
3489
- resolve2(code === 0);
3502
+ resolve3(code === 0);
3490
3503
  });
3491
3504
  });
3492
3505
  }
@@ -3520,6 +3533,7 @@ async function ensureClaudeInstalled() {
3520
3533
 
3521
3534
  // src/services/claude-resolver.ts
3522
3535
  var path7 = __toESM(require("path"));
3536
+ init_types();
3523
3537
  function buildClaudeLaunch(extraArgs = []) {
3524
3538
  const found = findInPath("claude") ?? findInPath("claude-code");
3525
3539
  if (!found) return null;
@@ -3779,6 +3793,7 @@ var fs5 = __toESM(require("fs"));
3779
3793
  var os6 = __toESM(require("os"));
3780
3794
  var path8 = __toESM(require("path"));
3781
3795
  var import_child_process5 = require("child_process");
3796
+ init_types();
3782
3797
  var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
3783
3798
  m,s=pty.openpty()
3784
3799
  try:
@@ -3834,17 +3849,17 @@ function parseUsageOutput(raw) {
3834
3849
  return { percent, resetAt };
3835
3850
  }
3836
3851
  async function fetchClaudeQuota() {
3837
- return new Promise((resolve2) => {
3852
+ return new Promise((resolve3) => {
3838
3853
  const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
3839
3854
  if (!claudeCmd) {
3840
- resolve2(null);
3855
+ resolve3(null);
3841
3856
  return;
3842
3857
  }
3843
3858
  const helperPath = path8.join(os6.tmpdir(), "codeam-quota-helper.py");
3844
3859
  fs5.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
3845
3860
  const python = findInPath("python3") ?? findInPath("python");
3846
3861
  if (!python) {
3847
- resolve2(null);
3862
+ resolve3(null);
3848
3863
  return;
3849
3864
  }
3850
3865
  const proc = (0, import_child_process5.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
@@ -3871,13 +3886,13 @@ async function fetchClaudeQuota() {
3871
3886
  fs5.unlinkSync(helperPath);
3872
3887
  } catch {
3873
3888
  }
3874
- resolve2(result);
3889
+ resolve3(result);
3875
3890
  }, 5e3);
3876
3891
  }, 8e3);
3877
3892
  setTimeout(() => {
3878
3893
  if (!resolved) {
3879
3894
  resolved = true;
3880
- resolve2(null);
3895
+ resolve3(null);
3881
3896
  }
3882
3897
  try {
3883
3898
  proc.kill();
@@ -4522,6 +4537,7 @@ var ClaudeDeployStrategy = class {
4522
4537
 
4523
4538
  // src/agents/codex/runtime.ts
4524
4539
  var import_node_child_process = require("child_process");
4540
+ init_types();
4525
4541
 
4526
4542
  // src/agents/codex/history.ts
4527
4543
  var import_node_fs2 = __toESM(require("fs"));
@@ -5116,13 +5132,13 @@ var CodexRuntimeStrategy = class {
5116
5132
  }
5117
5133
  };
5118
5134
  async function installCodexViaNpm() {
5119
- return new Promise((resolve2, reject) => {
5135
+ return new Promise((resolve3, reject) => {
5120
5136
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
5121
5137
  const proc = (0, import_node_child_process.spawn)(npm, ["install", "-g", "@openai/codex"], {
5122
5138
  stdio: "inherit"
5123
5139
  });
5124
5140
  proc.on("close", (code) => {
5125
- if (code === 0) resolve2();
5141
+ if (code === 0) resolve3();
5126
5142
  else reject(new Error(`npm install -g @openai/codex exited ${code}`));
5127
5143
  });
5128
5144
  proc.on("error", reject);
@@ -5321,14 +5337,14 @@ var ChunkEmitter = class {
5321
5337
  "chunkEmitter",
5322
5338
  `send type=${body.type ?? "(clear)"} bytes=${payload.length} done=${body.done === true}`
5323
5339
  );
5324
- return new Promise((resolve2) => {
5340
+ return new Promise((resolve3) => {
5325
5341
  const attempt = (attemptsLeft) => {
5326
5342
  _transport2.post(this.url, this.headers, payload).then(({ statusCode, body: resBody }) => {
5327
5343
  const tookMs = Date.now() - t0;
5328
5344
  if (statusCode === 410 || statusCode === 404 && /SESSION_NOT_FOUND|SESSION_GONE/.test(resBody)) {
5329
5345
  process.stderr.write("[codeam] session was deleted/disconnected \u2014 stopping output stream.\n");
5330
5346
  log.info("chunkEmitter", `dead status=${statusCode} took=${tookMs}ms`);
5331
- resolve2({ dead: true });
5347
+ resolve3({ dead: true });
5332
5348
  return;
5333
5349
  }
5334
5350
  if (statusCode >= 400) {
@@ -5338,7 +5354,7 @@ var ChunkEmitter = class {
5338
5354
  } else {
5339
5355
  log.info("chunkEmitter", `ok status=${statusCode} took=${tookMs}ms`);
5340
5356
  }
5341
- resolve2({ dead: false });
5357
+ resolve3({ dead: false });
5342
5358
  }).catch((err) => {
5343
5359
  log.warn(
5344
5360
  "chunkEmitter",
@@ -5349,7 +5365,7 @@ var ChunkEmitter = class {
5349
5365
  const delay = 200 * (maxRetries - attemptsLeft + 1);
5350
5366
  setTimeout(() => attempt(attemptsLeft - 1), delay);
5351
5367
  } else {
5352
- resolve2({ dead: false });
5368
+ resolve3({ dead: false });
5353
5369
  }
5354
5370
  });
5355
5371
  };
@@ -5361,7 +5377,7 @@ var _transport2 = {
5361
5377
  post: _post
5362
5378
  };
5363
5379
  function _post(url, headers, payload) {
5364
- return new Promise((resolve2, reject) => {
5380
+ return new Promise((resolve3, reject) => {
5365
5381
  let settled = false;
5366
5382
  const u2 = new URL(url);
5367
5383
  const transport = u2.protocol === "https:" ? https3 : http3;
@@ -5385,7 +5401,7 @@ function _post(url, headers, payload) {
5385
5401
  res.on("end", () => {
5386
5402
  if (settled) return;
5387
5403
  settled = true;
5388
- resolve2({ statusCode: res.statusCode ?? 0, body: resData });
5404
+ resolve3({ statusCode: res.statusCode ?? 0, body: resData });
5389
5405
  });
5390
5406
  }
5391
5407
  );
@@ -5821,7 +5837,7 @@ function parseJsonl(filePath) {
5821
5837
  return messages;
5822
5838
  }
5823
5839
  function post(endpoint, body) {
5824
- return new Promise((resolve2) => {
5840
+ return new Promise((resolve3) => {
5825
5841
  const payload = JSON.stringify(body);
5826
5842
  const u2 = new URL(`${API_BASE4}${endpoint}`);
5827
5843
  const transport = u2.protocol === "https:" ? https4 : http4;
@@ -5842,17 +5858,17 @@ function post(endpoint, body) {
5842
5858
  res.resume();
5843
5859
  const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300;
5844
5860
  if (!ok) log.warn("history:post", `${endpoint} \u2192 HTTP ${res.statusCode}`);
5845
- resolve2(ok);
5861
+ resolve3(ok);
5846
5862
  }
5847
5863
  );
5848
5864
  req.on("error", (err) => {
5849
5865
  log.warn("history:post", `${endpoint} network error`, err);
5850
- resolve2(false);
5866
+ resolve3(false);
5851
5867
  });
5852
5868
  req.on("timeout", () => {
5853
5869
  log.warn("history:post", `${endpoint} timeout after 15s`);
5854
5870
  req.destroy();
5855
- resolve2(false);
5871
+ resolve3(false);
5856
5872
  });
5857
5873
  req.write(payload);
5858
5874
  req.end();
@@ -6334,7 +6350,7 @@ var _transport3 = {
6334
6350
  post: _post2
6335
6351
  };
6336
6352
  function _post2(url, headers, payload) {
6337
- return new Promise((resolve2, reject) => {
6353
+ return new Promise((resolve3, reject) => {
6338
6354
  let settled = false;
6339
6355
  const u2 = new URL(url);
6340
6356
  const lib = u2.protocol === "https:" ? https5 : http5;
@@ -6359,7 +6375,7 @@ function _post2(url, headers, payload) {
6359
6375
  res.on("end", () => {
6360
6376
  if (settled) return;
6361
6377
  settled = true;
6362
- resolve2({ statusCode: res.statusCode ?? 0, body });
6378
+ resolve3({ statusCode: res.statusCode ?? 0, body });
6363
6379
  });
6364
6380
  }
6365
6381
  );
@@ -6403,9 +6419,9 @@ var FileWatcherService = class {
6403
6419
  if (this.stopped) {
6404
6420
  throw new Error("FileWatcherService has already been stopped \u2014 re-instantiate to restart.");
6405
6421
  }
6406
- let chokidar;
6422
+ let chokidar2;
6407
6423
  try {
6408
- chokidar = require("chokidar");
6424
+ chokidar2 = require("chokidar");
6409
6425
  } catch (err) {
6410
6426
  log.warn(
6411
6427
  "fileWatcher",
@@ -6414,7 +6430,7 @@ var FileWatcherService = class {
6414
6430
  );
6415
6431
  return;
6416
6432
  }
6417
- const watcher = chokidar.watch(this.opts.workingDir, {
6433
+ const watcher = chokidar2.watch(this.opts.workingDir, {
6418
6434
  ignored: [
6419
6435
  /(^|[\\/])\../,
6420
6436
  // dot-files & dot-dirs (.git, .next, .expo, .DS_Store, …)
@@ -6667,12 +6683,12 @@ var _gitSeam = {
6667
6683
  run: _runGitImpl
6668
6684
  };
6669
6685
  async function _runGitImpl(cwd, args2, opts = {}) {
6670
- return new Promise((resolve2) => {
6686
+ return new Promise((resolve3) => {
6671
6687
  let proc;
6672
6688
  try {
6673
6689
  proc = (0, import_child_process7.spawn)("git", args2, { cwd, env: process.env });
6674
6690
  } catch {
6675
- resolve2(null);
6691
+ resolve3(null);
6676
6692
  return;
6677
6693
  }
6678
6694
  let stdout = "";
@@ -6683,13 +6699,13 @@ async function _runGitImpl(cwd, args2, opts = {}) {
6683
6699
  proc.stderr?.on("data", (c2) => {
6684
6700
  stderr += c2.toString();
6685
6701
  });
6686
- proc.on("error", () => resolve2(null));
6702
+ proc.on("error", () => resolve3(null));
6687
6703
  proc.on("close", (code) => {
6688
6704
  if (code === 0 || opts.allowNonZeroExit) {
6689
- resolve2(stdout);
6705
+ resolve3(stdout);
6690
6706
  } else {
6691
6707
  log.trace("fileWatcher", `git ${args2.join(" ")} exited ${code} stderr=${stderr.slice(0, 200)}`);
6692
- resolve2(null);
6708
+ resolve3(null);
6693
6709
  }
6694
6710
  });
6695
6711
  });
@@ -6709,7 +6725,7 @@ var _transport4 = {
6709
6725
  get: _get
6710
6726
  };
6711
6727
  function _post3(url, headers, payload) {
6712
- return new Promise((resolve2, reject) => {
6728
+ return new Promise((resolve3, reject) => {
6713
6729
  let settled = false;
6714
6730
  const u2 = new URL(url);
6715
6731
  const lib = u2.protocol === "https:" ? https6 : http6;
@@ -6734,7 +6750,7 @@ function _post3(url, headers, payload) {
6734
6750
  res.on("end", () => {
6735
6751
  if (settled) return;
6736
6752
  settled = true;
6737
- resolve2({ statusCode: res.statusCode ?? 0, body });
6753
+ resolve3({ statusCode: res.statusCode ?? 0, body });
6738
6754
  });
6739
6755
  }
6740
6756
  );
@@ -6751,7 +6767,7 @@ function _post3(url, headers, payload) {
6751
6767
  });
6752
6768
  }
6753
6769
  function _get(url, headers) {
6754
- return new Promise((resolve2, reject) => {
6770
+ return new Promise((resolve3, reject) => {
6755
6771
  let settled = false;
6756
6772
  const u2 = new URL(url);
6757
6773
  const lib = u2.protocol === "https:" ? https6 : http6;
@@ -6775,7 +6791,7 @@ function _get(url, headers) {
6775
6791
  res.on("end", () => {
6776
6792
  if (settled) return;
6777
6793
  settled = true;
6778
- resolve2({ statusCode: res.statusCode ?? 0, body });
6794
+ resolve3({ statusCode: res.statusCode ?? 0, body });
6779
6795
  });
6780
6796
  }
6781
6797
  );
@@ -7144,7 +7160,7 @@ function buildKeepAlive(ctx) {
7144
7160
  let timer = null;
7145
7161
  async function setIdleTimeout(minutes) {
7146
7162
  if (!ctx.inCodespace || !ctx.codespaceName) return;
7147
- await new Promise((resolve2) => {
7163
+ await new Promise((resolve3) => {
7148
7164
  const proc = (0, import_child_process8.spawn)(
7149
7165
  "gh",
7150
7166
  [
@@ -7158,8 +7174,8 @@ function buildKeepAlive(ctx) {
7158
7174
  { stdio: "ignore", detached: true }
7159
7175
  );
7160
7176
  proc.unref();
7161
- proc.on("exit", () => resolve2());
7162
- proc.on("error", () => resolve2());
7177
+ proc.on("exit", () => resolve3());
7178
+ proc.on("error", () => resolve3());
7163
7179
  });
7164
7180
  }
7165
7181
  return {
@@ -8537,7 +8553,7 @@ async function pair(args2 = []) {
8537
8553
  waitSpin.message(waitMessage());
8538
8554
  }, 1e3);
8539
8555
  countdownInterval.unref?.();
8540
- await new Promise((resolve2) => {
8556
+ await new Promise((resolve3) => {
8541
8557
  let stopPolling = null;
8542
8558
  function sigintHandler() {
8543
8559
  clearInterval(countdownInterval);
@@ -8564,7 +8580,7 @@ async function pair(args2 = []) {
8564
8580
  saveCliConfig({ ...loadCliConfig(), preferredAgent: agentId });
8565
8581
  showSuccess(`Paired with ${info.userName} (${info.plan})`);
8566
8582
  console.log("");
8567
- resolve2();
8583
+ resolve3();
8568
8584
  },
8569
8585
  () => {
8570
8586
  clearInterval(countdownInterval);
@@ -8597,12 +8613,12 @@ function readTokenFromArgs(args2) {
8597
8613
  }
8598
8614
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
8599
8615
  if (fileFlag) {
8600
- const path27 = fileFlag.slice("--token-file=".length);
8616
+ const path28 = fileFlag.slice("--token-file=".length);
8601
8617
  try {
8602
- const content = fs15.readFileSync(path27, "utf8").trim();
8603
- if (content.length === 0) fail(`--token-file ${path27} is empty`);
8618
+ const content = fs15.readFileSync(path28, "utf8").trim();
8619
+ if (content.length === 0) fail(`--token-file ${path28} is empty`);
8604
8620
  try {
8605
- fs15.unlinkSync(path27);
8621
+ fs15.unlinkSync(path28);
8606
8622
  } catch {
8607
8623
  }
8608
8624
  return content;
@@ -8820,12 +8836,12 @@ var GitHubCodespacesProvider = class {
8820
8836
  }
8821
8837
  if (!isAuthed) {
8822
8838
  resetStdinForChild();
8823
- await new Promise((resolve2, reject) => {
8839
+ await new Promise((resolve3, reject) => {
8824
8840
  const proc = (0, import_child_process12.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
8825
8841
  stdio: "inherit"
8826
8842
  });
8827
8843
  proc.on("exit", (code) => {
8828
- if (code === 0) resolve2();
8844
+ if (code === 0) resolve3();
8829
8845
  else reject(new Error("gh auth login failed."));
8830
8846
  });
8831
8847
  proc.on("error", reject);
@@ -8854,13 +8870,13 @@ var GitHubCodespacesProvider = class {
8854
8870
  }
8855
8871
  wt(noteLines.join("\n"), "One more permission needed");
8856
8872
  resetStdinForChild();
8857
- const refreshCode = await new Promise((resolve2, reject) => {
8873
+ const refreshCode = await new Promise((resolve3, reject) => {
8858
8874
  const proc = (0, import_child_process12.spawn)(
8859
8875
  "gh",
8860
8876
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
8861
8877
  { stdio: "inherit" }
8862
8878
  );
8863
- proc.on("exit", (code) => resolve2(code ?? 1));
8879
+ proc.on("exit", (code) => resolve3(code ?? 1));
8864
8880
  proc.on("error", reject);
8865
8881
  });
8866
8882
  if (refreshCode !== 0) {
@@ -9004,10 +9020,10 @@ var GitHubCodespacesProvider = class {
9004
9020
  if (q(proceed) || !proceed) return;
9005
9021
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
9006
9022
  resetStdinForChild();
9007
- const ok = await new Promise((resolve2) => {
9023
+ const ok = await new Promise((resolve3) => {
9008
9024
  const proc = (0, import_child_process12.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
9009
- proc.on("exit", (code) => resolve2(code === 0));
9010
- proc.on("error", () => resolve2(false));
9025
+ proc.on("exit", (code) => resolve3(code === 0));
9026
+ proc.on("error", () => resolve3(false));
9011
9027
  });
9012
9028
  if (ok) O2.success("gh installed");
9013
9029
  else O2.error("gh install failed");
@@ -9031,14 +9047,14 @@ var GitHubCodespacesProvider = class {
9031
9047
  "Expanding GitHub scopes"
9032
9048
  );
9033
9049
  resetStdinForChild();
9034
- await new Promise((resolve2, reject) => {
9050
+ await new Promise((resolve3, reject) => {
9035
9051
  const proc = (0, import_child_process12.spawn)(
9036
9052
  "gh",
9037
9053
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
9038
9054
  { stdio: "inherit" }
9039
9055
  );
9040
9056
  proc.on("exit", (code) => {
9041
- if (code === 0) resolve2();
9057
+ if (code === 0) resolve3();
9042
9058
  else reject(new Error(
9043
9059
  "gh auth refresh failed. Re-run `gh auth refresh -h github.com -s repo,read:org` manually."
9044
9060
  ));
@@ -9209,13 +9225,13 @@ var GitHubCodespacesProvider = class {
9209
9225
  }
9210
9226
  async streamCommand(workspaceId, command2) {
9211
9227
  resetStdinForChild();
9212
- return new Promise((resolve2, reject) => {
9228
+ return new Promise((resolve3, reject) => {
9213
9229
  const proc = (0, import_child_process12.spawn)(
9214
9230
  "gh",
9215
9231
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
9216
9232
  { stdio: "inherit" }
9217
9233
  );
9218
- proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
9234
+ proc.on("exit", (code) => resolve3({ code: code ?? 0 }));
9219
9235
  proc.on("error", reject);
9220
9236
  });
9221
9237
  }
@@ -9236,7 +9252,7 @@ var GitHubCodespacesProvider = class {
9236
9252
  "--",
9237
9253
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
9238
9254
  ];
9239
- await new Promise((resolve2, reject) => {
9255
+ await new Promise((resolve3, reject) => {
9240
9256
  const tar = (0, import_child_process12.spawn)("tar", tarArgs, {
9241
9257
  stdio: ["ignore", "pipe", "pipe"],
9242
9258
  env: tarEnv
@@ -9256,7 +9272,7 @@ var GitHubCodespacesProvider = class {
9256
9272
  ssh.on("error", reject);
9257
9273
  ssh.on("exit", (code) => {
9258
9274
  if (code === 0) {
9259
- resolve2();
9275
+ resolve3();
9260
9276
  } else {
9261
9277
  const reason = (sshErr || tarErr || `exit ${code}`).trim().slice(0, 500);
9262
9278
  reject(new Error(`Remote tar failed: ${reason}`));
@@ -9274,7 +9290,7 @@ var GitHubCodespacesProvider = class {
9274
9290
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote(remotePath)}`);
9275
9291
  }
9276
9292
  const cmd = parts.join(" && ");
9277
- await new Promise((resolve2, reject) => {
9293
+ await new Promise((resolve3, reject) => {
9278
9294
  const proc = (0, import_child_process12.spawn)(
9279
9295
  "gh",
9280
9296
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
@@ -9286,7 +9302,7 @@ var GitHubCodespacesProvider = class {
9286
9302
  });
9287
9303
  proc.on("error", reject);
9288
9304
  proc.on("exit", (code) => {
9289
- if (code === 0) resolve2();
9305
+ if (code === 0) resolve3();
9290
9306
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
9291
9307
  });
9292
9308
  proc.stdin?.write(contents);
@@ -9376,10 +9392,10 @@ var GitpodProvider = class {
9376
9392
  "Authenticating Gitpod"
9377
9393
  );
9378
9394
  resetStdinForChild2();
9379
- await new Promise((resolve2, reject) => {
9395
+ await new Promise((resolve3, reject) => {
9380
9396
  const proc = (0, import_child_process13.spawn)("gitpod", ["login"], { stdio: "inherit" });
9381
9397
  proc.on("exit", (code) => {
9382
- if (code === 0) resolve2();
9398
+ if (code === 0) resolve3();
9383
9399
  else reject(new Error("gitpod login failed."));
9384
9400
  });
9385
9401
  proc.on("error", reject);
@@ -9528,13 +9544,13 @@ var GitpodProvider = class {
9528
9544
  }
9529
9545
  async streamCommand(workspaceId, command2) {
9530
9546
  resetStdinForChild2();
9531
- return new Promise((resolve2, reject) => {
9547
+ return new Promise((resolve3, reject) => {
9532
9548
  const proc = (0, import_child_process13.spawn)(
9533
9549
  "gitpod",
9534
9550
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
9535
9551
  { stdio: "inherit" }
9536
9552
  );
9537
- proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
9553
+ proc.on("exit", (code) => resolve3({ code: code ?? 0 }));
9538
9554
  proc.on("error", reject);
9539
9555
  });
9540
9556
  }
@@ -9548,7 +9564,7 @@ var GitpodProvider = class {
9548
9564
  tarArgs.push(".");
9549
9565
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
9550
9566
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
9551
- await new Promise((resolve2, reject) => {
9567
+ await new Promise((resolve3, reject) => {
9552
9568
  const tar = (0, import_child_process13.spawn)("tar", tarArgs, {
9553
9569
  stdio: ["ignore", "pipe", "pipe"],
9554
9570
  env: tarEnv
@@ -9569,7 +9585,7 @@ var GitpodProvider = class {
9569
9585
  tar.on("error", reject);
9570
9586
  ssh.on("error", reject);
9571
9587
  ssh.on("exit", (code) => {
9572
- if (code === 0) resolve2();
9588
+ if (code === 0) resolve3();
9573
9589
  else reject(new Error(`Remote tar failed: ${(sshErr || tarErr || `exit ${code}`).trim().slice(0, 500)}`));
9574
9590
  });
9575
9591
  });
@@ -9584,7 +9600,7 @@ var GitpodProvider = class {
9584
9600
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote2(remotePath)}`);
9585
9601
  }
9586
9602
  const cmd = parts.join(" && ");
9587
- await new Promise((resolve2, reject) => {
9603
+ await new Promise((resolve3, reject) => {
9588
9604
  const proc = (0, import_child_process13.spawn)(
9589
9605
  "gitpod",
9590
9606
  ["workspace", "ssh", workspaceId, "--", cmd],
@@ -9596,7 +9612,7 @@ var GitpodProvider = class {
9596
9612
  });
9597
9613
  proc.on("error", reject);
9598
9614
  proc.on("exit", (code) => {
9599
- if (code === 0) resolve2();
9615
+ if (code === 0) resolve3();
9600
9616
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
9601
9617
  });
9602
9618
  proc.stdin?.write(contents);
@@ -9653,14 +9669,14 @@ var GitLabWorkspacesProvider = class {
9653
9669
  "Authenticating GitLab"
9654
9670
  );
9655
9671
  resetStdinForChild3();
9656
- await new Promise((resolve2, reject) => {
9672
+ await new Promise((resolve3, reject) => {
9657
9673
  const proc = (0, import_child_process14.spawn)(
9658
9674
  "glab",
9659
9675
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
9660
9676
  { stdio: "inherit" }
9661
9677
  );
9662
9678
  proc.on("exit", (code) => {
9663
- if (code === 0) resolve2();
9679
+ if (code === 0) resolve3();
9664
9680
  else reject(new Error("glab auth login failed."));
9665
9681
  });
9666
9682
  proc.on("error", reject);
@@ -9825,13 +9841,13 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
9825
9841
  async streamCommand(workspaceId, command2) {
9826
9842
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
9827
9843
  resetStdinForChild3();
9828
- return new Promise((resolve2, reject) => {
9844
+ return new Promise((resolve3, reject) => {
9829
9845
  const proc = (0, import_child_process14.spawn)(
9830
9846
  "ssh",
9831
9847
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
9832
9848
  { stdio: "inherit" }
9833
9849
  );
9834
- proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
9850
+ proc.on("exit", (code) => resolve3({ code: code ?? 0 }));
9835
9851
  proc.on("error", reject);
9836
9852
  });
9837
9853
  }
@@ -9846,7 +9862,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
9846
9862
  tarArgs.push(".");
9847
9863
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
9848
9864
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
9849
- await new Promise((resolve2, reject) => {
9865
+ await new Promise((resolve3, reject) => {
9850
9866
  const tar = (0, import_child_process14.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
9851
9867
  const ssh = (0, import_child_process14.spawn)(
9852
9868
  "ssh",
@@ -9864,7 +9880,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
9864
9880
  tar.on("error", reject);
9865
9881
  ssh.on("error", reject);
9866
9882
  ssh.on("exit", (code) => {
9867
- if (code === 0) resolve2();
9883
+ if (code === 0) resolve3();
9868
9884
  else reject(new Error(`Remote tar failed: ${(sshErr || tarErr || `exit ${code}`).trim().slice(0, 500)}`));
9869
9885
  });
9870
9886
  });
@@ -9877,7 +9893,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
9877
9893
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
9878
9894
  }
9879
9895
  const cmd = parts.join(" && ");
9880
- await new Promise((resolve2, reject) => {
9896
+ await new Promise((resolve3, reject) => {
9881
9897
  const proc = (0, import_child_process14.spawn)(
9882
9898
  "ssh",
9883
9899
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
@@ -9889,7 +9905,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
9889
9905
  });
9890
9906
  proc.on("error", reject);
9891
9907
  proc.on("exit", (code) => {
9892
- if (code === 0) resolve2();
9908
+ if (code === 0) resolve3();
9893
9909
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
9894
9910
  });
9895
9911
  proc.stdin?.write(contents);
@@ -9980,10 +9996,10 @@ var RailwayProvider = class {
9980
9996
  "Authenticating Railway"
9981
9997
  );
9982
9998
  resetStdinForChild4();
9983
- await new Promise((resolve2, reject) => {
9999
+ await new Promise((resolve3, reject) => {
9984
10000
  const proc = (0, import_child_process15.spawn)("railway", ["login"], { stdio: "inherit" });
9985
10001
  proc.on("exit", (code) => {
9986
- if (code === 0) resolve2();
10002
+ if (code === 0) resolve3();
9987
10003
  else reject(new Error("railway login failed."));
9988
10004
  });
9989
10005
  proc.on("error", reject);
@@ -10123,13 +10139,13 @@ var RailwayProvider = class {
10123
10139
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
10124
10140
  }
10125
10141
  resetStdinForChild4();
10126
- return new Promise((resolve2, reject) => {
10142
+ return new Promise((resolve3, reject) => {
10127
10143
  const proc = (0, import_child_process15.spawn)(
10128
10144
  "railway",
10129
10145
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
10130
10146
  { stdio: "inherit" }
10131
10147
  );
10132
- proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
10148
+ proc.on("exit", (code) => resolve3({ code: code ?? 0 }));
10133
10149
  proc.on("error", reject);
10134
10150
  });
10135
10151
  }
@@ -10147,7 +10163,7 @@ var RailwayProvider = class {
10147
10163
  tarArgs.push(".");
10148
10164
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
10149
10165
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
10150
- await new Promise((resolve2, reject) => {
10166
+ await new Promise((resolve3, reject) => {
10151
10167
  const tar = (0, import_child_process15.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
10152
10168
  const sh = (0, import_child_process15.spawn)(
10153
10169
  "railway",
@@ -10165,7 +10181,7 @@ var RailwayProvider = class {
10165
10181
  tar.on("error", reject);
10166
10182
  sh.on("error", reject);
10167
10183
  sh.on("exit", (code) => {
10168
- if (code === 0) resolve2();
10184
+ if (code === 0) resolve3();
10169
10185
  else reject(new Error(`Remote tar failed: ${(shErr || tarErr || `exit ${code}`).trim().slice(0, 500)}`));
10170
10186
  });
10171
10187
  });
@@ -10181,7 +10197,7 @@ var RailwayProvider = class {
10181
10197
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
10182
10198
  }
10183
10199
  const cmd = parts.join(" && ");
10184
- await new Promise((resolve2, reject) => {
10200
+ await new Promise((resolve3, reject) => {
10185
10201
  const proc = (0, import_child_process15.spawn)(
10186
10202
  "railway",
10187
10203
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
@@ -10193,7 +10209,7 @@ var RailwayProvider = class {
10193
10209
  });
10194
10210
  proc.on("error", reject);
10195
10211
  proc.on("exit", (code) => {
10196
- if (code === 0) resolve2();
10212
+ if (code === 0) resolve3();
10197
10213
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
10198
10214
  });
10199
10215
  proc.stdin?.write(contents);
@@ -10717,6 +10733,9 @@ async function stopWorkspaceFromLocal(target) {
10717
10733
  // src/commands/link.ts
10718
10734
  var import_node_child_process3 = require("child_process");
10719
10735
  var import_node_crypto = require("crypto");
10736
+ var fs18 = __toESM(require("fs"));
10737
+ var path26 = __toESM(require("path"));
10738
+ var import_chokidar = __toESM(require("chokidar"));
10720
10739
  var import_picocolors11 = __toESM(require("picocolors"));
10721
10740
 
10722
10741
  // src/agents/claude/local-token.ts
@@ -10726,41 +10745,45 @@ var os15 = __toESM(require("os"));
10726
10745
  var path24 = __toESM(require("path"));
10727
10746
  var import_node_util3 = require("util");
10728
10747
  var execFileP7 = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
10729
- function claudeCredentialsPath() {
10730
- return path24.join(os15.homedir(), ".claude", ".credentials.json");
10748
+ var KEYCHAIN_SERVICE_NAMES = [
10749
+ "Claude Code-credentials",
10750
+ "claude-code-credentials",
10751
+ "Claude",
10752
+ "Anthropic Claude"
10753
+ ];
10754
+ function claudeCredentialsPaths() {
10755
+ const home = os15.homedir();
10756
+ return [
10757
+ path24.join(home, ".claude", ".credentials.json"),
10758
+ path24.join(home, ".config", "claude", ".credentials.json")
10759
+ ];
10731
10760
  }
10732
10761
  async function extractLocalClaudeToken() {
10733
- const flat = claudeCredentialsPath();
10734
- if (fs16.existsSync(flat)) {
10762
+ for (const flat of claudeCredentialsPaths()) {
10763
+ if (!fs16.existsSync(flat)) continue;
10735
10764
  const credential = fs16.readFileSync(flat, "utf8").trim();
10736
10765
  if (credential.length > 0) {
10737
10766
  return { method: "oauth", credential, source: "flat-file" };
10738
10767
  }
10739
10768
  }
10740
10769
  if (process.platform === "darwin") {
10741
- try {
10742
- const { stdout } = await execFileP7(
10743
- "security",
10744
- ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
10745
- { maxBuffer: 1024 * 1024 }
10746
- );
10747
- const credential = stdout.trim();
10748
- if (credential.length > 0) {
10749
- return { method: "oauth", credential, source: "macos-keychain" };
10770
+ for (const service of KEYCHAIN_SERVICE_NAMES) {
10771
+ try {
10772
+ const { stdout } = await execFileP7(
10773
+ "security",
10774
+ ["find-generic-password", "-s", service, "-w"],
10775
+ { maxBuffer: 1024 * 1024 }
10776
+ );
10777
+ const credential = stdout.trim();
10778
+ if (credential.length > 0) {
10779
+ return { method: "oauth", credential, source: "macos-keychain" };
10780
+ }
10781
+ } catch {
10750
10782
  }
10751
- } catch {
10752
10783
  }
10753
10784
  }
10754
10785
  return null;
10755
10786
  }
10756
- function claudeCredentialsMtime() {
10757
- const flat = claudeCredentialsPath();
10758
- try {
10759
- return fs16.statSync(flat).mtimeMs;
10760
- } catch {
10761
- return null;
10762
- }
10763
- }
10764
10787
 
10765
10788
  // src/agents/codex/local-token.ts
10766
10789
  var fs17 = __toESM(require("fs"));
@@ -10776,13 +10799,8 @@ async function extractLocalCodexToken() {
10776
10799
  if (credential.length === 0) return null;
10777
10800
  return { method: "oauth", credential, source: "flat-file" };
10778
10801
  }
10779
- function codexCredentialsMtime() {
10780
- const file = codexCredentialsPath();
10781
- try {
10782
- return fs17.statSync(file).mtimeMs;
10783
- } catch {
10784
- return null;
10785
- }
10802
+ function codexCredentialsPaths() {
10803
+ return [codexCredentialsPath()];
10786
10804
  }
10787
10805
 
10788
10806
  // src/commands/link.ts
@@ -10791,23 +10809,38 @@ var AGENT_META = {
10791
10809
  internalId: "claude",
10792
10810
  publicId: "claude_code",
10793
10811
  binary: "claude",
10794
- loginArgs: ["login"],
10795
10812
  displayName: "Claude Code",
10796
10813
  vendor: "Anthropic",
10797
- credentialsHint: "~/.claude/.credentials.json (or macOS Keychain)",
10814
+ credentialsHint: "~/.claude/.credentials.json or the macOS Keychain",
10815
+ watchPaths: claudeCredentialsPaths,
10798
10816
  extract: extractLocalClaudeToken,
10799
- mtime: claudeCredentialsMtime
10817
+ ensureInstalled: ensureClaudeInstalled,
10818
+ launchLogin: () => {
10819
+ const child = (0, import_node_child_process3.spawn)("claude", [], { stdio: ["pipe", "inherit", "inherit"] });
10820
+ child.stdin?.write("/login\n");
10821
+ return child;
10822
+ }
10800
10823
  },
10801
10824
  codex: {
10802
10825
  internalId: "codex",
10803
10826
  publicId: "codex",
10804
10827
  binary: "codex",
10805
- loginArgs: ["login"],
10806
10828
  displayName: "Codex",
10807
10829
  vendor: "OpenAI",
10808
10830
  credentialsHint: "~/.codex/auth.json",
10831
+ watchPaths: codexCredentialsPaths,
10809
10832
  extract: extractLocalCodexToken,
10810
- mtime: codexCredentialsMtime
10833
+ ensureInstalled: async () => {
10834
+ const { findInPath: findInPath2 } = await Promise.resolve().then(() => (init_types(), types_exports));
10835
+ if (findInPath2("codex")) return true;
10836
+ showError(
10837
+ "codex binary not found on PATH. Install it first (https://github.com/openai/codex-cli) then re-run `codeam link codex`."
10838
+ );
10839
+ return false;
10840
+ },
10841
+ launchLogin: () => {
10842
+ return (0, import_node_child_process3.spawn)("codex", ["login"], { stdio: "inherit" });
10843
+ }
10811
10844
  }
10812
10845
  };
10813
10846
  function parseLinkArgs(args2) {
@@ -10825,11 +10858,15 @@ function parseLinkArgs(args2) {
10825
10858
  );
10826
10859
  }
10827
10860
  const reuseExisting = args2.includes("--reuse-existing");
10828
- return { agent: normalised, reuseExisting };
10861
+ const apiKeyArg = args2.find((a) => a.startsWith("--api-key="));
10862
+ const apiKey = apiKeyArg ? apiKeyArg.slice("--api-key=".length) : null;
10863
+ const tokenFileArg = args2.find((a) => a.startsWith("--token-file="));
10864
+ const tokenFile = tokenFileArg ? tokenFileArg.slice("--token-file=".length) : null;
10865
+ return { agent: normalised, reuseExisting, apiKey, tokenFile };
10829
10866
  }
10830
10867
  async function link(args2 = []) {
10831
- const { agent, reuseExisting } = parseLinkArgs(args2);
10832
- const meta = AGENT_META[agent];
10868
+ const parsed = parseLinkArgs(args2);
10869
+ const meta = AGENT_META[parsed.agent];
10833
10870
  showIntro();
10834
10871
  console.log(
10835
10872
  import_picocolors11.default.bold(` Link ${meta.displayName}`) + import_picocolors11.default.dim(` \xB7 ${meta.vendor}`)
@@ -10853,7 +10890,7 @@ async function link(args2 = []) {
10853
10890
  waitSpin.start(waitMsg());
10854
10891
  const countdown = setInterval(() => waitSpin.message(waitMsg()), 1e3);
10855
10892
  countdown.unref?.();
10856
- const paired = await new Promise((resolve2, reject) => {
10893
+ const paired = await new Promise((resolve3, reject) => {
10857
10894
  let stopPoll = null;
10858
10895
  const sigint = () => {
10859
10896
  clearInterval(countdown);
@@ -10866,7 +10903,7 @@ async function link(args2 = []) {
10866
10903
  process.removeListener("SIGINT", sigint);
10867
10904
  clearInterval(countdown);
10868
10905
  waitSpin.stop("Paired");
10869
- resolve2(info);
10906
+ resolve3(info);
10870
10907
  },
10871
10908
  () => {
10872
10909
  clearInterval(countdown);
@@ -10893,36 +10930,125 @@ async function link(args2 = []) {
10893
10930
  agent: meta.internalId
10894
10931
  });
10895
10932
  saveCliConfig({ ...loadCliConfig(), preferredAgent: meta.internalId });
10896
- let token = await meta.extract();
10897
- if (token && reuseExisting) {
10898
- showInfo(`Reusing existing ${meta.displayName} token at ${import_picocolors11.default.bold(meta.credentialsHint)}.`);
10899
- } else {
10900
- const beforeMtime = meta.mtime();
10901
- showInfo(`Launching ${import_picocolors11.default.bold(`${meta.binary} ${meta.loginArgs.join(" ")}`)} \u2014 complete the sign-in in your browser, then return here.`);
10902
- console.log("");
10903
- const code = await runAgentLogin(meta);
10904
- console.log("");
10905
- if (code !== 0) {
10906
- showError(
10907
- `${meta.binary} ${meta.loginArgs.join(" ")} exited with code ${code}. Re-run when ready.`
10908
- );
10909
- process.exit(1);
10910
- }
10911
- const refreshed = await meta.extract();
10912
- const afterMtime = meta.mtime();
10913
- if (!refreshed) {
10914
- showError(
10915
- `${meta.displayName} login finished but no credential was found at ${meta.credentialsHint}. Re-run when ready.`
10916
- );
10933
+ if (parsed.apiKey) {
10934
+ await uploadAndSucceed(meta, paired, pluginId, {
10935
+ method: "api_key",
10936
+ credential: parsed.apiKey.trim(),
10937
+ source: "manual"
10938
+ });
10939
+ return;
10940
+ }
10941
+ if (parsed.tokenFile) {
10942
+ const credential = fs18.readFileSync(path26.resolve(parsed.tokenFile), "utf8").trim();
10943
+ if (!credential) {
10944
+ showError(`--token-file ${parsed.tokenFile} is empty.`);
10917
10945
  process.exit(1);
10918
10946
  }
10919
- if (token && refreshed.credential === token.credential && beforeMtime !== null && afterMtime !== null && afterMtime <= beforeMtime) {
10920
- showError(
10921
- `${meta.displayName} login didn't produce a fresh token. Re-run when ready, or pass --reuse-existing to keep the current one.`
10922
- );
10923
- process.exit(1);
10947
+ await uploadAndSucceed(meta, paired, pluginId, {
10948
+ method: "oauth",
10949
+ credential,
10950
+ source: "manual"
10951
+ });
10952
+ return;
10953
+ }
10954
+ const installSpin = dist_exports.spinner();
10955
+ installSpin.start(`Checking that ${meta.binary} is installed...`);
10956
+ const installed = await meta.ensureInstalled();
10957
+ if (!installed) {
10958
+ installSpin.stop("Failed");
10959
+ showError(`Could not install ${meta.displayName}. Install it manually then re-run.`);
10960
+ process.exit(1);
10961
+ }
10962
+ installSpin.stop(`${meta.displayName} is installed`);
10963
+ const existing = await meta.extract();
10964
+ if (existing) {
10965
+ showInfo(`Found existing ${meta.displayName} credentials at ${import_picocolors11.default.bold(existing.source)}.`);
10966
+ await uploadAndSucceed(meta, paired, pluginId, existing);
10967
+ return;
10968
+ }
10969
+ if (parsed.reuseExisting) {
10970
+ showError(
10971
+ `--reuse-existing set, but no local ${meta.displayName} credentials were found at ${meta.credentialsHint}.`
10972
+ );
10973
+ process.exit(1);
10974
+ }
10975
+ showInfo(
10976
+ `No local ${meta.displayName} credentials found. Launching the sign-in \u2014 complete it in your browser, the CLI will detect the new token and finish automatically.`
10977
+ );
10978
+ console.log("");
10979
+ const captured = await captureFreshCredentials(meta);
10980
+ console.log("");
10981
+ await uploadAndSucceed(meta, paired, pluginId, captured);
10982
+ }
10983
+ async function captureFreshCredentials(meta) {
10984
+ const watcher = import_chokidar.default.watch(meta.watchPaths(), {
10985
+ persistent: true,
10986
+ ignoreInitial: false,
10987
+ awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
10988
+ });
10989
+ let child = null;
10990
+ let keychainPoll = null;
10991
+ const cleanup = () => {
10992
+ void watcher.close();
10993
+ if (keychainPoll) clearInterval(keychainPoll);
10994
+ if (child && !child.killed) {
10995
+ try {
10996
+ child.kill("SIGTERM");
10997
+ } catch {
10998
+ }
10924
10999
  }
10925
- token = refreshed;
11000
+ };
11001
+ try {
11002
+ const token = await new Promise((resolve3, reject) => {
11003
+ let settled = false;
11004
+ const tryExtract = async () => {
11005
+ if (settled) return;
11006
+ const t2 = await meta.extract();
11007
+ if (t2 && !settled) {
11008
+ settled = true;
11009
+ resolve3(t2);
11010
+ }
11011
+ };
11012
+ watcher.on("add", () => void tryExtract());
11013
+ watcher.on("change", () => void tryExtract());
11014
+ keychainPoll = setInterval(() => void tryExtract(), 2e3);
11015
+ keychainPoll.unref?.();
11016
+ const sigint = () => {
11017
+ if (settled) return;
11018
+ settled = true;
11019
+ reject(new Error("cancelled"));
11020
+ };
11021
+ process.once("SIGINT", sigint);
11022
+ setTimeout(() => {
11023
+ if (settled) return;
11024
+ settled = true;
11025
+ reject(new Error(`Timed out waiting for ${meta.displayName} sign-in (5 minutes).`));
11026
+ }, 5 * 6e4);
11027
+ child = meta.launchLogin();
11028
+ child.on("exit", () => {
11029
+ void tryExtract().then(() => {
11030
+ if (!settled) {
11031
+ settled = true;
11032
+ reject(
11033
+ new Error(
11034
+ `${meta.binary} exited but no credentials were written at ${meta.credentialsHint}.`
11035
+ )
11036
+ );
11037
+ }
11038
+ });
11039
+ });
11040
+ });
11041
+ cleanup();
11042
+ return token;
11043
+ } catch (err) {
11044
+ cleanup();
11045
+ throw err;
11046
+ }
11047
+ }
11048
+ async function uploadAndSucceed(meta, paired, pluginId, token) {
11049
+ if (!paired.pluginAuthToken) {
11050
+ showError("Missing pluginAuthToken; re-run codeam link.");
11051
+ process.exit(1);
10926
11052
  }
10927
11053
  const uploadSpin = dist_exports.spinner();
10928
11054
  uploadSpin.start("Sealing credential in your vault...");
@@ -10937,12 +11063,10 @@ async function link(args2 = []) {
10937
11063
  if (!result.ok) {
10938
11064
  uploadSpin.stop("Failed");
10939
11065
  if (result.status === 401) {
10940
- showError(
10941
- "Pair token rejected by the backend (401). Re-run `codeam link` to start fresh."
10942
- );
11066
+ showError("Pair token rejected by the backend (401). Re-run `codeam link`.");
10943
11067
  } else if (result.status === 404) {
10944
11068
  showError(
10945
- `${meta.displayName} link endpoint not available on this backend (404). The api-v2 deployment may not yet include the /api/plugin/agents/:agentId/link route.`
11069
+ `${meta.displayName} link endpoint not available on this backend (404). The api-v2 deployment may not yet include the route.`
10946
11070
  );
10947
11071
  } else {
10948
11072
  showError(`Upload failed: ${result.message}`);
@@ -10957,28 +11081,11 @@ async function link(args2 = []) {
10957
11081
  );
10958
11082
  console.log("");
10959
11083
  }
10960
- function runAgentLogin(meta) {
10961
- return new Promise((resolve2) => {
10962
- const child = (0, import_node_child_process3.spawn)(meta.binary, meta.loginArgs, { stdio: "inherit" });
10963
- child.on("error", (err) => {
10964
- if (err.code === "ENOENT") {
10965
- showError(
10966
- `${meta.binary} binary not found on PATH. Install ${meta.displayName} first, then re-run \`codeam link ${meta.internalId}\`.`
10967
- );
10968
- resolve2(127);
10969
- return;
10970
- }
10971
- showError(`Failed to launch ${meta.binary}: ${err.message}`);
10972
- resolve2(1);
10973
- });
10974
- child.on("exit", (code) => resolve2(code ?? 1));
10975
- });
10976
- }
10977
11084
 
10978
11085
  // src/commands/version.ts
10979
11086
  var import_picocolors12 = __toESM(require("picocolors"));
10980
11087
  function version() {
10981
- const v = true ? "2.16.0" : "unknown";
11088
+ const v = true ? "2.16.1" : "unknown";
10982
11089
  console.log(`${import_picocolors12.default.bold("codeam-cli")} ${import_picocolors12.default.cyan(v)}`);
10983
11090
  }
10984
11091
 
@@ -11020,9 +11127,9 @@ function help() {
11020
11127
  }
11021
11128
 
11022
11129
  // src/lib/updateNotifier.ts
11023
- var fs18 = __toESM(require("fs"));
11130
+ var fs19 = __toESM(require("fs"));
11024
11131
  var os17 = __toESM(require("os"));
11025
- var path26 = __toESM(require("path"));
11132
+ var path27 = __toESM(require("path"));
11026
11133
  var https7 = __toESM(require("https"));
11027
11134
  var import_picocolors14 = __toESM(require("picocolors"));
11028
11135
  var PKG_NAME = "codeam-cli";
@@ -11030,12 +11137,12 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
11030
11137
  var TTL_MS = 24 * 60 * 60 * 1e3;
11031
11138
  var REQUEST_TIMEOUT_MS = 1500;
11032
11139
  function cachePath() {
11033
- const dir = path26.join(os17.homedir(), ".codeam");
11034
- return path26.join(dir, "update-check.json");
11140
+ const dir = path27.join(os17.homedir(), ".codeam");
11141
+ return path27.join(dir, "update-check.json");
11035
11142
  }
11036
11143
  function readCache() {
11037
11144
  try {
11038
- const raw = fs18.readFileSync(cachePath(), "utf8");
11145
+ const raw = fs19.readFileSync(cachePath(), "utf8");
11039
11146
  const parsed = JSON.parse(raw);
11040
11147
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
11041
11148
  return parsed;
@@ -11046,8 +11153,8 @@ function readCache() {
11046
11153
  function writeCache(cache) {
11047
11154
  try {
11048
11155
  const file = cachePath();
11049
- fs18.mkdirSync(path26.dirname(file), { recursive: true });
11050
- fs18.writeFileSync(file, JSON.stringify(cache));
11156
+ fs19.mkdirSync(path27.dirname(file), { recursive: true });
11157
+ fs19.writeFileSync(file, JSON.stringify(cache));
11051
11158
  } catch {
11052
11159
  }
11053
11160
  }
@@ -11065,14 +11172,14 @@ function compareSemver(a, b) {
11065
11172
  return 0;
11066
11173
  }
11067
11174
  function fetchLatest() {
11068
- return new Promise((resolve2) => {
11175
+ return new Promise((resolve3) => {
11069
11176
  const req = https7.get(
11070
11177
  REGISTRY_URL,
11071
11178
  { headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
11072
11179
  (res) => {
11073
11180
  if (res.statusCode !== 200) {
11074
11181
  res.resume();
11075
- resolve2(null);
11182
+ resolve3(null);
11076
11183
  return;
11077
11184
  }
11078
11185
  let buf = "";
@@ -11084,21 +11191,21 @@ function fetchLatest() {
11084
11191
  try {
11085
11192
  const json = JSON.parse(buf);
11086
11193
  if (typeof json.version === "string") {
11087
- resolve2(json.version);
11194
+ resolve3(json.version);
11088
11195
  } else {
11089
- resolve2(null);
11196
+ resolve3(null);
11090
11197
  }
11091
11198
  } catch {
11092
- resolve2(null);
11199
+ resolve3(null);
11093
11200
  }
11094
11201
  });
11095
11202
  }
11096
11203
  );
11097
11204
  req.on("timeout", () => {
11098
11205
  req.destroy();
11099
- resolve2(null);
11206
+ resolve3(null);
11100
11207
  });
11101
- req.on("error", () => resolve2(null));
11208
+ req.on("error", () => resolve3(null));
11102
11209
  });
11103
11210
  }
11104
11211
  function notifyIfStale(currentVersion, latest) {
@@ -11118,7 +11225,7 @@ function checkForUpdates() {
11118
11225
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
11119
11226
  if (process.env.CI) return;
11120
11227
  if (!process.stdout.isTTY) return;
11121
- const current = true ? "2.16.0" : null;
11228
+ const current = true ? "2.16.1" : null;
11122
11229
  if (!current) return;
11123
11230
  const cache = readCache();
11124
11231
  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.16.0",
3
+ "version": "2.16.1",
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",