codeam-cli 2.15.8 → 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 +389 -49
  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.8",
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",
@@ -646,8 +646,84 @@ function pollStatus(pluginId, onPaired, onTimeout) {
646
646
  }
647
647
  var _transport = {
648
648
  postJson: _postJson,
649
- getJson: _getJson
649
+ getJson: _getJson,
650
+ postJsonAuthed: _postJsonAuthed
650
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
+ }
651
727
  async function _postJson(url, body) {
652
728
  return new Promise((resolve2, reject) => {
653
729
  const data = JSON.stringify(body);
@@ -8521,12 +8597,12 @@ function readTokenFromArgs(args2) {
8521
8597
  }
8522
8598
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
8523
8599
  if (fileFlag) {
8524
- const path25 = fileFlag.slice("--token-file=".length);
8600
+ const path27 = fileFlag.slice("--token-file=".length);
8525
8601
  try {
8526
- const content = fs15.readFileSync(path25, "utf8").trim();
8527
- 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`);
8528
8604
  try {
8529
- fs15.unlinkSync(path25);
8605
+ fs15.unlinkSync(path27);
8530
8606
  } catch {
8531
8607
  }
8532
8608
  return content;
@@ -10630,74 +10706,336 @@ async function probeCodeamPair(provider, workspace) {
10630
10706
  }
10631
10707
  async function stopWorkspaceFromLocal(target) {
10632
10708
  if (target.provider.id === "github-codespaces") {
10633
- const { execFile: execFile7 } = await import("child_process");
10634
- const { promisify: promisify7 } = await import("util");
10635
- const execFileP7 = promisify7(execFile7);
10636
- 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 });
10637
10713
  return;
10638
10714
  }
10639
10715
  }
10640
10716
 
10641
- // 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");
10642
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"));
10643
10980
  function version() {
10644
- const v = true ? "2.15.8" : "unknown";
10645
- 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)}`);
10646
10983
  }
10647
10984
 
10648
10985
  // src/commands/help.ts
10649
- var import_picocolors12 = __toESM(require("picocolors"));
10986
+ var import_picocolors13 = __toESM(require("picocolors"));
10650
10987
  function help() {
10651
10988
  const lines = [
10652
10989
  "",
10653
- ` ${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")}`,
10654
10991
  "",
10655
- ` ${import_picocolors12.default.bold("Usage")}`,
10656
- ` ${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]")}`,
10657
10994
  "",
10658
- ` ${import_picocolors12.default.bold("Commands")}`,
10659
- ` ${import_picocolors12.default.white("codeam")} ${import_picocolors12.default.dim("start the active agent with mobile control")}`,
10660
- ` ${import_picocolors12.default.white("codeam <agent>")} ${import_picocolors12.default.dim("start a specific agent \u2014 e.g. claude, codex")}`,
10661
- ` ${import_picocolors12.default.white("codeam pair")} ${import_picocolors12.default.dim("pair a new mobile device (interactive)")}`,
10662
- ` ${import_picocolors12.default.white("codeam pair --agent <id>")} ${import_picocolors12.default.dim("pair non-interactively for a specific agent")}`,
10663
- ` ${import_picocolors12.default.white("codeam sessions")} ${import_picocolors12.default.dim("list paired devices")}`,
10664
- ` ${import_picocolors12.default.white("codeam sessions switch")} ${import_picocolors12.default.dim("switch the active paired session")}`,
10665
- ` ${import_picocolors12.default.white("codeam sessions delete <id>")} ${import_picocolors12.default.dim("remove a specific paired session")}`,
10666
- ` ${import_picocolors12.default.white("codeam status")} ${import_picocolors12.default.dim("show connection info")}`,
10667
- ` ${import_picocolors12.default.white("codeam logout")} ${import_picocolors12.default.dim("remove all paired sessions")}`,
10668
- ` ${import_picocolors12.default.white("codeam deploy")} ${import_picocolors12.default.dim("provision a cloud workspace (Codespaces) and pair it")}`,
10669
- ` ${import_picocolors12.default.white("codeam deploy ls | list")} ${import_picocolors12.default.dim("list deployed cloud workspaces")}`,
10670
- ` ${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")}`,
10671
11009
  "",
10672
- ` ${import_picocolors12.default.bold("Flags")}`,
10673
- ` ${import_picocolors12.default.white("-v, --version")} ${import_picocolors12.default.dim("print the CLI version")}`,
10674
- ` ${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")}`,
10675
11013
  "",
10676
- ` ${import_picocolors12.default.bold("Links")}`,
10677
- ` ${import_picocolors12.default.dim("Docs:")} ${import_picocolors12.default.green("https://www.codeagent-mobile.com")}`,
10678
- ` ${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")}`,
10679
11017
  ""
10680
11018
  ];
10681
11019
  process.stdout.write(lines.join("\n") + "\n");
10682
11020
  }
10683
11021
 
10684
11022
  // src/lib/updateNotifier.ts
10685
- var fs16 = __toESM(require("fs"));
10686
- var os15 = __toESM(require("os"));
10687
- var path24 = __toESM(require("path"));
11023
+ var fs18 = __toESM(require("fs"));
11024
+ var os17 = __toESM(require("os"));
11025
+ var path26 = __toESM(require("path"));
10688
11026
  var https7 = __toESM(require("https"));
10689
- var import_picocolors13 = __toESM(require("picocolors"));
11027
+ var import_picocolors14 = __toESM(require("picocolors"));
10690
11028
  var PKG_NAME = "codeam-cli";
10691
11029
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
10692
11030
  var TTL_MS = 24 * 60 * 60 * 1e3;
10693
11031
  var REQUEST_TIMEOUT_MS = 1500;
10694
11032
  function cachePath() {
10695
- const dir = path24.join(os15.homedir(), ".codeam");
10696
- return path24.join(dir, "update-check.json");
11033
+ const dir = path26.join(os17.homedir(), ".codeam");
11034
+ return path26.join(dir, "update-check.json");
10697
11035
  }
10698
11036
  function readCache() {
10699
11037
  try {
10700
- const raw = fs16.readFileSync(cachePath(), "utf8");
11038
+ const raw = fs18.readFileSync(cachePath(), "utf8");
10701
11039
  const parsed = JSON.parse(raw);
10702
11040
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
10703
11041
  return parsed;
@@ -10708,8 +11046,8 @@ function readCache() {
10708
11046
  function writeCache(cache) {
10709
11047
  try {
10710
11048
  const file = cachePath();
10711
- fs16.mkdirSync(path24.dirname(file), { recursive: true });
10712
- fs16.writeFileSync(file, JSON.stringify(cache));
11049
+ fs18.mkdirSync(path26.dirname(file), { recursive: true });
11050
+ fs18.writeFileSync(file, JSON.stringify(cache));
10713
11051
  } catch {
10714
11052
  }
10715
11053
  }
@@ -10765,11 +11103,11 @@ function fetchLatest() {
10765
11103
  }
10766
11104
  function notifyIfStale(currentVersion, latest) {
10767
11105
  if (compareSemver(latest, currentVersion) <= 0) return;
10768
- const arrow = import_picocolors13.default.dim("\u2192");
10769
- 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");
10770
11108
  const lines = [
10771
11109
  "",
10772
- ` ${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)}`,
10773
11111
  ` Run ${cmd} to upgrade.`,
10774
11112
  ""
10775
11113
  ];
@@ -10780,7 +11118,7 @@ function checkForUpdates() {
10780
11118
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
10781
11119
  if (process.env.CI) return;
10782
11120
  if (!process.stdout.isTTY) return;
10783
- const current = true ? "2.15.8" : null;
11121
+ const current = true ? "2.16.0" : null;
10784
11122
  if (!current) return;
10785
11123
  const cache = readCache();
10786
11124
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -10818,6 +11156,8 @@ async function main() {
10818
11156
  return status();
10819
11157
  case "logout":
10820
11158
  return logout();
11159
+ case "link":
11160
+ return link(args);
10821
11161
  case "deploy":
10822
11162
  if (args[0] === "ls" || args[0] === "list") return deployList();
10823
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.8",
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",