codeam-cli 2.15.7 → 2.16.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 +12 -0
  2. package/dist/index.js +417 -60
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.15.8] — 2026-05-21
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Pairing-box alignment + countdown actually ticks (#39)
12
+
13
+ ## [2.15.7] — 2026-05-21
14
+
15
+ ### Fixed
16
+
17
+ - **clients:** Point default API URL at api.codeagent-mobile.com + centralize via shared constant (#38)
18
+
7
19
  ## [2.15.6] — 2026-05-20
8
20
 
9
21
  ### Added
package/dist/index.js CHANGED
@@ -389,7 +389,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
389
389
  // package.json
390
390
  var package_default = {
391
391
  name: "codeam-cli",
392
- version: "2.15.7",
392
+ version: "2.16.0",
393
393
  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
394
  type: "commonjs",
395
395
  main: "dist/index.js",
@@ -493,21 +493,30 @@ function showError(msg) {
493
493
  function showInfo(msg) {
494
494
  console.log(` ${import_picocolors.default.dim("\xB7")} ${msg}`);
495
495
  }
496
- function showPairingCode(code, expiresAt) {
497
- const secs = Math.max(0, Math.floor((expiresAt - Date.now()) / 1e3));
498
- const timer = `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, "0")}`;
499
- console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
500
- const codePad = " ".repeat(Math.max(0, 19 - code.length));
501
- const timerPad = " ".repeat(Math.max(0, 15 - timer.length));
502
- console.log(` \u2502 Code: ${import_picocolors.default.bold(import_picocolors.default.yellow(code))}${codePad}\u2502`);
503
- console.log(` \u2502 Expires in: ${import_picocolors.default.dim(timer)}${timerPad}\u2502`);
504
- console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
496
+ var BOX_INTERIOR = 30;
497
+ var BOX_BORDER_TOP = ` \u250C${"\u2500".repeat(BOX_INTERIOR)}\u2510`;
498
+ var BOX_BORDER_BOT = ` \u2514${"\u2500".repeat(BOX_INTERIOR)}\u2518`;
499
+ function boxRow(content, visibleLength) {
500
+ const pad = " ".repeat(Math.max(0, BOX_INTERIOR - visibleLength));
501
+ return ` \u2502${content}${pad}\u2502`;
502
+ }
503
+ function showPairingCode(code) {
504
+ console.log(BOX_BORDER_TOP);
505
+ const codeVisible = ` Code: ${code}`.length;
506
+ console.log(
507
+ boxRow(` Code: ${import_picocolors.default.bold(import_picocolors.default.yellow(code))}`, codeVisible)
508
+ );
509
+ console.log(BOX_BORDER_BOT);
505
510
  console.log("");
506
511
  import_qrcode_terminal.default.generate(code, { small: true }, (qr) => {
507
512
  qr.split("\n").forEach((line) => console.log(" " + line));
508
513
  });
509
514
  console.log("");
510
515
  }
516
+ function formatRemaining(expiresAt) {
517
+ const secs = Math.max(0, Math.floor((expiresAt - Date.now()) / 1e3));
518
+ return `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, "0")}`;
519
+ }
511
520
 
512
521
  // src/services/command-relay.service.ts
513
522
  var https2 = __toESM(require("https"));
@@ -637,8 +646,84 @@ function pollStatus(pluginId, onPaired, onTimeout) {
637
646
  }
638
647
  var _transport = {
639
648
  postJson: _postJson,
640
- getJson: _getJson
649
+ getJson: _getJson,
650
+ postJsonAuthed: _postJsonAuthed
641
651
  };
652
+ async function postLinkCredential(input) {
653
+ const body = {
654
+ sessionId: input.sessionId,
655
+ pluginId: input.pluginId,
656
+ method: input.method,
657
+ credential: input.credential
658
+ };
659
+ if (input.modelPreference) {
660
+ body.modelPreference = input.modelPreference;
661
+ }
662
+ try {
663
+ await _transport.postJsonAuthed(
664
+ `${API_BASE}/api/plugin/agents/${input.agentId}/link`,
665
+ body,
666
+ input.pluginAuthToken
667
+ );
668
+ return { ok: true };
669
+ } catch (err) {
670
+ const e = err;
671
+ return {
672
+ ok: false,
673
+ status: typeof e.statusCode === "number" ? e.statusCode : 0,
674
+ message: e.message || "unknown"
675
+ };
676
+ }
677
+ }
678
+ async function _postJsonAuthed(url, body, pluginAuthToken) {
679
+ return new Promise((resolve2, reject) => {
680
+ const data = JSON.stringify(body);
681
+ const u2 = new URL(url);
682
+ const transport = u2.protocol === "https:" ? https : http;
683
+ const req = transport.request(
684
+ {
685
+ hostname: u2.hostname,
686
+ port: u2.port || (u2.protocol === "https:" ? 443 : 80),
687
+ path: u2.pathname + u2.search,
688
+ method: "POST",
689
+ headers: {
690
+ "Content-Type": "application/json",
691
+ "Content-Length": Buffer.byteLength(data),
692
+ "X-Plugin-Auth-Token": pluginAuthToken,
693
+ ...vercelBypassHeader()
694
+ },
695
+ timeout: 15e3
696
+ },
697
+ (res) => {
698
+ res.on("error", reject);
699
+ let responseBody = "";
700
+ res.on("data", (chunk) => {
701
+ responseBody += chunk.toString();
702
+ });
703
+ res.on("end", () => {
704
+ if (res.statusCode && res.statusCode >= 400) {
705
+ const err = new Error(`HTTP ${res.statusCode}: ${responseBody.slice(0, 200)}`);
706
+ err.statusCode = res.statusCode;
707
+ reject(err);
708
+ return;
709
+ }
710
+ try {
711
+ resolve2(JSON.parse(responseBody));
712
+ } catch {
713
+ resolve2(null);
714
+ }
715
+ });
716
+ }
717
+ );
718
+ req.on("error", reject);
719
+ req.on("timeout", () => {
720
+ req.destroy();
721
+ reject(new Error("timeout"));
722
+ });
723
+ req.write(data);
724
+ req.end();
725
+ });
726
+ }
642
727
  async function _postJson(url, body) {
643
728
  return new Promise((resolve2, reject) => {
644
729
  const data = JSON.stringify(body);
@@ -8442,14 +8527,20 @@ async function pair(args2 = []) {
8442
8527
  process.exit(1);
8443
8528
  }
8444
8529
  spin.stop("Got pairing code");
8445
- showPairingCode(result.code, result.expiresAt);
8530
+ showPairingCode(result.code);
8446
8531
  console.log(import_picocolors3.default.dim(" Scan the QR code or enter the code in CodeAgent Mobile."));
8447
8532
  console.log("");
8448
8533
  const waitSpin = dist_exports.spinner();
8449
- waitSpin.start("Waiting for mobile app...");
8534
+ const waitMessage = () => `Waiting for mobile app... \xB7 expires in ${formatRemaining(result.expiresAt)}`;
8535
+ waitSpin.start(waitMessage());
8536
+ const countdownInterval = setInterval(() => {
8537
+ waitSpin.message(waitMessage());
8538
+ }, 1e3);
8539
+ countdownInterval.unref?.();
8450
8540
  await new Promise((resolve2) => {
8451
8541
  let stopPolling = null;
8452
8542
  function sigintHandler() {
8543
+ clearInterval(countdownInterval);
8453
8544
  stopPolling?.();
8454
8545
  console.log("");
8455
8546
  process.exit(0);
@@ -8458,6 +8549,7 @@ async function pair(args2 = []) {
8458
8549
  pluginId,
8459
8550
  (info) => {
8460
8551
  process.removeListener("SIGINT", sigintHandler);
8552
+ clearInterval(countdownInterval);
8461
8553
  waitSpin.stop("Paired!");
8462
8554
  addSession({
8463
8555
  id: info.sessionId,
@@ -8475,6 +8567,7 @@ async function pair(args2 = []) {
8475
8567
  resolve2();
8476
8568
  },
8477
8569
  () => {
8570
+ clearInterval(countdownInterval);
8478
8571
  waitSpin.stop("Timed out");
8479
8572
  showError("Pairing timed out after 5 minutes. Run codeam pair to try again.");
8480
8573
  process.exit(1);
@@ -8504,12 +8597,12 @@ function readTokenFromArgs(args2) {
8504
8597
  }
8505
8598
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
8506
8599
  if (fileFlag) {
8507
- const path25 = fileFlag.slice("--token-file=".length);
8600
+ const path27 = fileFlag.slice("--token-file=".length);
8508
8601
  try {
8509
- const content = fs15.readFileSync(path25, "utf8").trim();
8510
- if (content.length === 0) fail(`--token-file ${path25} is empty`);
8602
+ const content = fs15.readFileSync(path27, "utf8").trim();
8603
+ if (content.length === 0) fail(`--token-file ${path27} is empty`);
8511
8604
  try {
8512
- fs15.unlinkSync(path25);
8605
+ fs15.unlinkSync(path27);
8513
8606
  } catch {
8514
8607
  }
8515
8608
  return content;
@@ -10613,74 +10706,336 @@ async function probeCodeamPair(provider, workspace) {
10613
10706
  }
10614
10707
  async function stopWorkspaceFromLocal(target) {
10615
10708
  if (target.provider.id === "github-codespaces") {
10616
- const { execFile: execFile7 } = await import("child_process");
10617
- const { promisify: promisify7 } = await import("util");
10618
- const execFileP7 = promisify7(execFile7);
10619
- await execFileP7("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
10709
+ const { execFile: execFile8 } = await import("child_process");
10710
+ const { promisify: promisify8 } = await import("util");
10711
+ const execFileP8 = promisify8(execFile8);
10712
+ await execFileP8("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
10620
10713
  return;
10621
10714
  }
10622
10715
  }
10623
10716
 
10624
- // src/commands/version.ts
10717
+ // src/commands/link.ts
10718
+ var import_node_child_process3 = require("child_process");
10719
+ var import_node_crypto = require("crypto");
10625
10720
  var import_picocolors11 = __toESM(require("picocolors"));
10721
+
10722
+ // src/agents/claude/local-token.ts
10723
+ var import_node_child_process2 = require("child_process");
10724
+ var fs16 = __toESM(require("fs"));
10725
+ var os15 = __toESM(require("os"));
10726
+ var path24 = __toESM(require("path"));
10727
+ var import_node_util3 = require("util");
10728
+ var execFileP7 = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
10729
+ function claudeCredentialsPath() {
10730
+ return path24.join(os15.homedir(), ".claude", ".credentials.json");
10731
+ }
10732
+ async function extractLocalClaudeToken() {
10733
+ const flat = claudeCredentialsPath();
10734
+ if (fs16.existsSync(flat)) {
10735
+ const credential = fs16.readFileSync(flat, "utf8").trim();
10736
+ if (credential.length > 0) {
10737
+ return { method: "oauth", credential, source: "flat-file" };
10738
+ }
10739
+ }
10740
+ 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" };
10750
+ }
10751
+ } catch {
10752
+ }
10753
+ }
10754
+ return null;
10755
+ }
10756
+ function claudeCredentialsMtime() {
10757
+ const flat = claudeCredentialsPath();
10758
+ try {
10759
+ return fs16.statSync(flat).mtimeMs;
10760
+ } catch {
10761
+ return null;
10762
+ }
10763
+ }
10764
+
10765
+ // src/agents/codex/local-token.ts
10766
+ var fs17 = __toESM(require("fs"));
10767
+ var os16 = __toESM(require("os"));
10768
+ var path25 = __toESM(require("path"));
10769
+ function codexCredentialsPath() {
10770
+ return path25.join(os16.homedir(), ".codex", "auth.json");
10771
+ }
10772
+ async function extractLocalCodexToken() {
10773
+ const file = codexCredentialsPath();
10774
+ if (!fs17.existsSync(file)) return null;
10775
+ const credential = fs17.readFileSync(file, "utf8").trim();
10776
+ if (credential.length === 0) return null;
10777
+ return { method: "oauth", credential, source: "flat-file" };
10778
+ }
10779
+ function codexCredentialsMtime() {
10780
+ const file = codexCredentialsPath();
10781
+ try {
10782
+ return fs17.statSync(file).mtimeMs;
10783
+ } catch {
10784
+ return null;
10785
+ }
10786
+ }
10787
+
10788
+ // src/commands/link.ts
10789
+ var AGENT_META = {
10790
+ claude: {
10791
+ internalId: "claude",
10792
+ publicId: "claude_code",
10793
+ binary: "claude",
10794
+ loginArgs: ["login"],
10795
+ displayName: "Claude Code",
10796
+ vendor: "Anthropic",
10797
+ credentialsHint: "~/.claude/.credentials.json (or macOS Keychain)",
10798
+ extract: extractLocalClaudeToken,
10799
+ mtime: claudeCredentialsMtime
10800
+ },
10801
+ codex: {
10802
+ internalId: "codex",
10803
+ publicId: "codex",
10804
+ binary: "codex",
10805
+ loginArgs: ["login"],
10806
+ displayName: "Codex",
10807
+ vendor: "OpenAI",
10808
+ credentialsHint: "~/.codex/auth.json",
10809
+ extract: extractLocalCodexToken,
10810
+ mtime: codexCredentialsMtime
10811
+ }
10812
+ };
10813
+ function parseLinkArgs(args2) {
10814
+ const positional = args2.find((a) => !a.startsWith("--"));
10815
+ if (!positional) {
10816
+ throw new Error(
10817
+ `Usage: codeam link <agent>
10818
+ agent: ${Object.keys(AGENT_META).join(" | ")}`
10819
+ );
10820
+ }
10821
+ const normalised = positional === "claude_code" ? "claude" : positional;
10822
+ if (normalised !== "claude" && normalised !== "codex") {
10823
+ throw new Error(
10824
+ `Unknown agent "${positional}". Valid: ${Object.keys(AGENT_META).join(", ")}`
10825
+ );
10826
+ }
10827
+ const reuseExisting = args2.includes("--reuse-existing");
10828
+ return { agent: normalised, reuseExisting };
10829
+ }
10830
+ async function link(args2 = []) {
10831
+ const { agent, reuseExisting } = parseLinkArgs(args2);
10832
+ const meta = AGENT_META[agent];
10833
+ showIntro();
10834
+ console.log(
10835
+ import_picocolors11.default.bold(` Link ${meta.displayName}`) + import_picocolors11.default.dim(` \xB7 ${meta.vendor}`)
10836
+ );
10837
+ console.log("");
10838
+ const pluginId = (0, import_node_crypto.randomUUID)();
10839
+ const spin = dist_exports.spinner();
10840
+ spin.start("Requesting pairing code...");
10841
+ const pairing = await requestCode(pluginId);
10842
+ if (!pairing) {
10843
+ spin.stop("Failed");
10844
+ showError("Could not reach the server. Check your connection and try again.");
10845
+ process.exit(1);
10846
+ }
10847
+ spin.stop("Got pairing code");
10848
+ showPairingCode(pairing.code);
10849
+ console.log(import_picocolors11.default.dim(" Scan the QR or enter the code in CodeAgent Mobile."));
10850
+ console.log("");
10851
+ const waitSpin = dist_exports.spinner();
10852
+ const waitMsg = () => `Waiting for mobile pair... \xB7 expires in ${formatRemaining(pairing.expiresAt)}`;
10853
+ waitSpin.start(waitMsg());
10854
+ const countdown = setInterval(() => waitSpin.message(waitMsg()), 1e3);
10855
+ countdown.unref?.();
10856
+ const paired = await new Promise((resolve2, reject) => {
10857
+ let stopPoll = null;
10858
+ const sigint = () => {
10859
+ clearInterval(countdown);
10860
+ stopPoll?.();
10861
+ reject(new Error("cancelled"));
10862
+ };
10863
+ stopPoll = pollStatus(
10864
+ pluginId,
10865
+ (info) => {
10866
+ process.removeListener("SIGINT", sigint);
10867
+ clearInterval(countdown);
10868
+ waitSpin.stop("Paired");
10869
+ resolve2(info);
10870
+ },
10871
+ () => {
10872
+ clearInterval(countdown);
10873
+ waitSpin.stop("Timed out");
10874
+ reject(new Error("Pairing timed out after 5 minutes. Run codeam link again."));
10875
+ }
10876
+ );
10877
+ process.once("SIGINT", sigint);
10878
+ });
10879
+ if (!paired.pluginAuthToken) {
10880
+ showError(
10881
+ "Backend did not return a pluginAuthToken \u2014 upgrade api-v2 (deploy includes the link endpoint)."
10882
+ );
10883
+ process.exit(1);
10884
+ }
10885
+ addSession({
10886
+ id: paired.sessionId,
10887
+ pluginId,
10888
+ userName: paired.userName,
10889
+ userEmail: paired.userEmail,
10890
+ plan: paired.plan,
10891
+ pairedAt: Date.now(),
10892
+ pluginAuthToken: paired.pluginAuthToken,
10893
+ agent: meta.internalId
10894
+ });
10895
+ 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
+ );
10917
+ process.exit(1);
10918
+ }
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);
10924
+ }
10925
+ token = refreshed;
10926
+ }
10927
+ const uploadSpin = dist_exports.spinner();
10928
+ uploadSpin.start("Sealing credential in your vault...");
10929
+ const result = await postLinkCredential({
10930
+ agentId: meta.publicId,
10931
+ sessionId: paired.sessionId,
10932
+ pluginId,
10933
+ pluginAuthToken: paired.pluginAuthToken,
10934
+ method: token.method,
10935
+ credential: token.credential
10936
+ });
10937
+ if (!result.ok) {
10938
+ uploadSpin.stop("Failed");
10939
+ if (result.status === 401) {
10940
+ showError(
10941
+ "Pair token rejected by the backend (401). Re-run `codeam link` to start fresh."
10942
+ );
10943
+ } else if (result.status === 404) {
10944
+ 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.`
10946
+ );
10947
+ } else {
10948
+ showError(`Upload failed: ${result.message}`);
10949
+ }
10950
+ process.exit(1);
10951
+ }
10952
+ uploadSpin.stop("Linked");
10953
+ console.log("");
10954
+ showSuccess(`${meta.displayName} is now linked to ${paired.userEmail || paired.userName}.`);
10955
+ showInfo(
10956
+ `Your codespaces and @codeagent mentions can now use ${meta.displayName} without you signing in again.`
10957
+ );
10958
+ console.log("");
10959
+ }
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
+
10978
+ // src/commands/version.ts
10979
+ var import_picocolors12 = __toESM(require("picocolors"));
10626
10980
  function version() {
10627
- const v = true ? "2.15.7" : "unknown";
10628
- console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
10981
+ const v = true ? "2.16.0" : "unknown";
10982
+ console.log(`${import_picocolors12.default.bold("codeam-cli")} ${import_picocolors12.default.cyan(v)}`);
10629
10983
  }
10630
10984
 
10631
10985
  // src/commands/help.ts
10632
- var import_picocolors12 = __toESM(require("picocolors"));
10986
+ var import_picocolors13 = __toESM(require("picocolors"));
10633
10987
  function help() {
10634
10988
  const lines = [
10635
10989
  "",
10636
- ` ${import_picocolors12.default.bold(import_picocolors12.default.magenta("codeam-cli"))} ${import_picocolors12.default.dim("\u2014 remote-control AI coding agents from your phone")}`,
10990
+ ` ${import_picocolors13.default.bold(import_picocolors13.default.magenta("codeam-cli"))} ${import_picocolors13.default.dim("\u2014 remote-control AI coding agents from your phone")}`,
10637
10991
  "",
10638
- ` ${import_picocolors12.default.bold("Usage")}`,
10639
- ` ${import_picocolors12.default.cyan("codeam")} ${import_picocolors12.default.dim("[command]")}`,
10992
+ ` ${import_picocolors13.default.bold("Usage")}`,
10993
+ ` ${import_picocolors13.default.cyan("codeam")} ${import_picocolors13.default.dim("[command]")}`,
10640
10994
  "",
10641
- ` ${import_picocolors12.default.bold("Commands")}`,
10642
- ` ${import_picocolors12.default.white("codeam")} ${import_picocolors12.default.dim("start the active agent with mobile control")}`,
10643
- ` ${import_picocolors12.default.white("codeam <agent>")} ${import_picocolors12.default.dim("start a specific agent \u2014 e.g. claude, codex")}`,
10644
- ` ${import_picocolors12.default.white("codeam pair")} ${import_picocolors12.default.dim("pair a new mobile device (interactive)")}`,
10645
- ` ${import_picocolors12.default.white("codeam pair --agent <id>")} ${import_picocolors12.default.dim("pair non-interactively for a specific agent")}`,
10646
- ` ${import_picocolors12.default.white("codeam sessions")} ${import_picocolors12.default.dim("list paired devices")}`,
10647
- ` ${import_picocolors12.default.white("codeam sessions switch")} ${import_picocolors12.default.dim("switch the active paired session")}`,
10648
- ` ${import_picocolors12.default.white("codeam sessions delete <id>")} ${import_picocolors12.default.dim("remove a specific paired session")}`,
10649
- ` ${import_picocolors12.default.white("codeam status")} ${import_picocolors12.default.dim("show connection info")}`,
10650
- ` ${import_picocolors12.default.white("codeam logout")} ${import_picocolors12.default.dim("remove all paired sessions")}`,
10651
- ` ${import_picocolors12.default.white("codeam deploy")} ${import_picocolors12.default.dim("provision a cloud workspace (Codespaces) and pair it")}`,
10652
- ` ${import_picocolors12.default.white("codeam deploy ls | list")} ${import_picocolors12.default.dim("list deployed cloud workspaces")}`,
10653
- ` ${import_picocolors12.default.white("codeam deploy stop | remove")} ${import_picocolors12.default.dim("stop a deployed workspace session")}`,
10995
+ ` ${import_picocolors13.default.bold("Commands")}`,
10996
+ ` ${import_picocolors13.default.white("codeam")} ${import_picocolors13.default.dim("start the active agent with mobile control")}`,
10997
+ ` ${import_picocolors13.default.white("codeam <agent>")} ${import_picocolors13.default.dim("start a specific agent \u2014 e.g. claude, codex")}`,
10998
+ ` ${import_picocolors13.default.white("codeam pair")} ${import_picocolors13.default.dim("pair a new mobile device (interactive)")}`,
10999
+ ` ${import_picocolors13.default.white("codeam pair --agent <id>")} ${import_picocolors13.default.dim("pair non-interactively for a specific agent")}`,
11000
+ ` ${import_picocolors13.default.white("codeam sessions")} ${import_picocolors13.default.dim("list paired devices")}`,
11001
+ ` ${import_picocolors13.default.white("codeam sessions switch")} ${import_picocolors13.default.dim("switch the active paired session")}`,
11002
+ ` ${import_picocolors13.default.white("codeam sessions delete <id>")} ${import_picocolors13.default.dim("remove a specific paired session")}`,
11003
+ ` ${import_picocolors13.default.white("codeam status")} ${import_picocolors13.default.dim("show connection info")}`,
11004
+ ` ${import_picocolors13.default.white("codeam logout")} ${import_picocolors13.default.dim("remove all paired sessions")}`,
11005
+ ` ${import_picocolors13.default.white("codeam link <agent>")} ${import_picocolors13.default.dim("link an agent (claude, codex) to your CodeAgent account")}`,
11006
+ ` ${import_picocolors13.default.white("codeam deploy")} ${import_picocolors13.default.dim("provision a cloud workspace (Codespaces) and pair it")}`,
11007
+ ` ${import_picocolors13.default.white("codeam deploy ls | list")} ${import_picocolors13.default.dim("list deployed cloud workspaces")}`,
11008
+ ` ${import_picocolors13.default.white("codeam deploy stop | remove")} ${import_picocolors13.default.dim("stop a deployed workspace session")}`,
10654
11009
  "",
10655
- ` ${import_picocolors12.default.bold("Flags")}`,
10656
- ` ${import_picocolors12.default.white("-v, --version")} ${import_picocolors12.default.dim("print the CLI version")}`,
10657
- ` ${import_picocolors12.default.white("-h, --help")} ${import_picocolors12.default.dim("show this help")}`,
11010
+ ` ${import_picocolors13.default.bold("Flags")}`,
11011
+ ` ${import_picocolors13.default.white("-v, --version")} ${import_picocolors13.default.dim("print the CLI version")}`,
11012
+ ` ${import_picocolors13.default.white("-h, --help")} ${import_picocolors13.default.dim("show this help")}`,
10658
11013
  "",
10659
- ` ${import_picocolors12.default.bold("Links")}`,
10660
- ` ${import_picocolors12.default.dim("Docs:")} ${import_picocolors12.default.green("https://www.codeagent-mobile.com")}`,
10661
- ` ${import_picocolors12.default.dim("Issues:")} ${import_picocolors12.default.green("https://github.com/edgar-durand/codeagent-mobile-clients/issues")}`,
11014
+ ` ${import_picocolors13.default.bold("Links")}`,
11015
+ ` ${import_picocolors13.default.dim("Docs:")} ${import_picocolors13.default.green("https://www.codeagent-mobile.com")}`,
11016
+ ` ${import_picocolors13.default.dim("Issues:")} ${import_picocolors13.default.green("https://github.com/edgar-durand/codeagent-mobile-clients/issues")}`,
10662
11017
  ""
10663
11018
  ];
10664
11019
  process.stdout.write(lines.join("\n") + "\n");
10665
11020
  }
10666
11021
 
10667
11022
  // src/lib/updateNotifier.ts
10668
- var fs16 = __toESM(require("fs"));
10669
- var os15 = __toESM(require("os"));
10670
- var path24 = __toESM(require("path"));
11023
+ var fs18 = __toESM(require("fs"));
11024
+ var os17 = __toESM(require("os"));
11025
+ var path26 = __toESM(require("path"));
10671
11026
  var https7 = __toESM(require("https"));
10672
- var import_picocolors13 = __toESM(require("picocolors"));
11027
+ var import_picocolors14 = __toESM(require("picocolors"));
10673
11028
  var PKG_NAME = "codeam-cli";
10674
11029
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
10675
11030
  var TTL_MS = 24 * 60 * 60 * 1e3;
10676
11031
  var REQUEST_TIMEOUT_MS = 1500;
10677
11032
  function cachePath() {
10678
- const dir = path24.join(os15.homedir(), ".codeam");
10679
- return path24.join(dir, "update-check.json");
11033
+ const dir = path26.join(os17.homedir(), ".codeam");
11034
+ return path26.join(dir, "update-check.json");
10680
11035
  }
10681
11036
  function readCache() {
10682
11037
  try {
10683
- const raw = fs16.readFileSync(cachePath(), "utf8");
11038
+ const raw = fs18.readFileSync(cachePath(), "utf8");
10684
11039
  const parsed = JSON.parse(raw);
10685
11040
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
10686
11041
  return parsed;
@@ -10691,8 +11046,8 @@ function readCache() {
10691
11046
  function writeCache(cache) {
10692
11047
  try {
10693
11048
  const file = cachePath();
10694
- fs16.mkdirSync(path24.dirname(file), { recursive: true });
10695
- fs16.writeFileSync(file, JSON.stringify(cache));
11049
+ fs18.mkdirSync(path26.dirname(file), { recursive: true });
11050
+ fs18.writeFileSync(file, JSON.stringify(cache));
10696
11051
  } catch {
10697
11052
  }
10698
11053
  }
@@ -10748,11 +11103,11 @@ function fetchLatest() {
10748
11103
  }
10749
11104
  function notifyIfStale(currentVersion, latest) {
10750
11105
  if (compareSemver(latest, currentVersion) <= 0) return;
10751
- const arrow = import_picocolors13.default.dim("\u2192");
10752
- const cmd = import_picocolors13.default.cyan("npm install -g codeam-cli");
11106
+ const arrow = import_picocolors14.default.dim("\u2192");
11107
+ const cmd = import_picocolors14.default.cyan("npm install -g codeam-cli");
10753
11108
  const lines = [
10754
11109
  "",
10755
- ` ${import_picocolors13.default.yellow("\u25CF")} ${import_picocolors13.default.bold("Update available")} ${import_picocolors13.default.dim(currentVersion)} ${arrow} ${import_picocolors13.default.green(latest)}`,
11110
+ ` ${import_picocolors14.default.yellow("\u25CF")} ${import_picocolors14.default.bold("Update available")} ${import_picocolors14.default.dim(currentVersion)} ${arrow} ${import_picocolors14.default.green(latest)}`,
10756
11111
  ` Run ${cmd} to upgrade.`,
10757
11112
  ""
10758
11113
  ];
@@ -10763,7 +11118,7 @@ function checkForUpdates() {
10763
11118
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
10764
11119
  if (process.env.CI) return;
10765
11120
  if (!process.stdout.isTTY) return;
10766
- const current = true ? "2.15.7" : null;
11121
+ const current = true ? "2.16.0" : null;
10767
11122
  if (!current) return;
10768
11123
  const cache = readCache();
10769
11124
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -10801,6 +11156,8 @@ async function main() {
10801
11156
  return status();
10802
11157
  case "logout":
10803
11158
  return logout();
11159
+ case "link":
11160
+ return link(args);
10804
11161
  case "deploy":
10805
11162
  if (args[0] === "ls" || args[0] === "list") return deployList();
10806
11163
  if (args[0] === "stop" || args[0] === "remove") return deployStop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.15.7",
3
+ "version": "2.16.0",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",