codeam-cli 2.4.5 → 2.4.7

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 +284 -24
  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.4.5] — 2026-05-03
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Clearer guidance when gh refresh hits multi-account browser (v2.4.5)
12
+
13
+ ## [2.4.4] — 2026-05-03
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Unblock interactive gh prompts inside codeam deploy (v2.4.4)
18
+
7
19
  ## [2.4.3] — 2026-05-03
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
179
179
  // package.json
180
180
  var package_default = {
181
181
  name: "codeam-cli",
182
- version: "2.4.5",
182
+ version: "2.4.7",
183
183
  description: "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands \u2014 from anywhere.",
184
184
  main: "dist/index.js",
185
185
  bin: {
@@ -5077,15 +5077,18 @@ async function logout() {
5077
5077
  }
5078
5078
 
5079
5079
  // src/commands/deploy.ts
5080
+ var import_child_process6 = require("child_process");
5080
5081
  var fs8 = __toESM(require("fs"));
5081
5082
  var os6 = __toESM(require("os"));
5082
- var path8 = __toESM(require("path"));
5083
+ var path9 = __toESM(require("path"));
5084
+ var import_util3 = require("util");
5083
5085
  var import_picocolors8 = __toESM(require("picocolors"));
5084
5086
 
5085
5087
  // src/services/providers/github-codespaces.ts
5086
5088
  var import_child_process5 = require("child_process");
5087
5089
  var import_util2 = require("util");
5088
5090
  var import_picocolors7 = __toESM(require("picocolors"));
5091
+ var path8 = __toESM(require("path"));
5089
5092
  var execFileP2 = (0, import_util2.promisify)(import_child_process5.execFile);
5090
5093
  var MAX_BUFFER = 8 * 1024 * 1024;
5091
5094
  function resetStdinForChild() {
@@ -5464,21 +5467,130 @@ var GitHubCodespacesProvider = class {
5464
5467
  return new Promise((resolve2, reject) => {
5465
5468
  const proc = (0, import_child_process5.spawn)(
5466
5469
  "gh",
5467
- ["codespace", "ssh", "-c", workspaceId, "-t", "--", command2],
5470
+ ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
5468
5471
  { stdio: "inherit" }
5469
5472
  );
5470
5473
  proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
5471
5474
  proc.on("error", reject);
5472
5475
  });
5473
5476
  }
5474
- async uploadDirectory(workspaceId, localDir, remoteDir) {
5475
- await execFileP2(
5476
- "gh",
5477
- ["codespace", "cp", "-r", "-c", workspaceId, localDir, `remote:${remoteDir}`],
5478
- { maxBuffer: MAX_BUFFER, timeout: 3e5 }
5479
- );
5477
+ async uploadDirectory(workspaceId, localDir, remoteDir, options = {}) {
5478
+ const tarArgs = ["-czf", "-", "-C", localDir];
5479
+ for (const pattern of options.exclude ?? []) {
5480
+ tarArgs.push(`--exclude=${pattern}`);
5481
+ }
5482
+ tarArgs.push(".");
5483
+ const sshArgs = [
5484
+ "codespace",
5485
+ "ssh",
5486
+ "-c",
5487
+ workspaceId,
5488
+ "--",
5489
+ `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
5490
+ ];
5491
+ await new Promise((resolve2, reject) => {
5492
+ const tar = (0, import_child_process5.spawn)("tar", tarArgs, {
5493
+ stdio: ["ignore", "pipe", "pipe"]
5494
+ });
5495
+ const ssh = (0, import_child_process5.spawn)("gh", sshArgs, {
5496
+ stdio: [tar.stdout, "pipe", "pipe"]
5497
+ });
5498
+ let tarErr = "";
5499
+ let sshErr = "";
5500
+ tar.stderr?.on("data", (d3) => {
5501
+ tarErr += d3.toString();
5502
+ });
5503
+ ssh.stderr?.on("data", (d3) => {
5504
+ sshErr += d3.toString();
5505
+ });
5506
+ tar.on("error", reject);
5507
+ ssh.on("error", reject);
5508
+ ssh.on("exit", (code) => {
5509
+ if (code === 0) {
5510
+ resolve2();
5511
+ } else {
5512
+ const reason = (sshErr || tarErr || `exit ${code}`).trim().slice(0, 500);
5513
+ reject(new Error(`Remote tar failed: ${reason}`));
5514
+ }
5515
+ });
5516
+ });
5517
+ }
5518
+ async uploadFile(workspaceId, remotePath, contents, options = {}) {
5519
+ const remoteDir = path8.posix.dirname(remotePath);
5520
+ const parts = [
5521
+ `mkdir -p ${shellQuote(remoteDir)}`,
5522
+ `cat > ${shellQuote(remotePath)}`
5523
+ ];
5524
+ if (options.mode != null) {
5525
+ parts.push(`chmod ${options.mode.toString(8)} ${shellQuote(remotePath)}`);
5526
+ }
5527
+ const cmd = parts.join(" && ");
5528
+ await new Promise((resolve2, reject) => {
5529
+ const proc = (0, import_child_process5.spawn)(
5530
+ "gh",
5531
+ ["codespace", "ssh", "-c", workspaceId, "--", cmd],
5532
+ { stdio: ["pipe", "pipe", "pipe"] }
5533
+ );
5534
+ let stderr = "";
5535
+ proc.stderr?.on("data", (d3) => {
5536
+ stderr += d3.toString();
5537
+ });
5538
+ proc.on("error", reject);
5539
+ proc.on("exit", (code) => {
5540
+ if (code === 0) resolve2();
5541
+ else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
5542
+ });
5543
+ proc.stdin?.write(contents);
5544
+ proc.stdin?.end();
5545
+ });
5546
+ }
5547
+ async listExistingWorkspaces(projectId) {
5548
+ try {
5549
+ const { stdout } = await execFileP2(
5550
+ "gh",
5551
+ [
5552
+ "codespace",
5553
+ "list",
5554
+ "--repo",
5555
+ projectId,
5556
+ "--json",
5557
+ "name,displayName,state,lastUsedAt"
5558
+ ],
5559
+ { maxBuffer: MAX_BUFFER }
5560
+ );
5561
+ const list = JSON.parse(stdout);
5562
+ return list.map((c2) => ({
5563
+ id: c2.name,
5564
+ displayName: c2.displayName || c2.name,
5565
+ webUrl: `https://github.com/codespaces/${c2.name}`,
5566
+ state: c2.state,
5567
+ lastUsedAt: c2.lastUsedAt
5568
+ }));
5569
+ } catch {
5570
+ return [];
5571
+ }
5572
+ }
5573
+ async startWorkspace(workspaceId) {
5574
+ try {
5575
+ await execFileP2(
5576
+ "gh",
5577
+ ["api", "-X", "POST", `/user/codespaces/${workspaceId}/start`],
5578
+ { maxBuffer: MAX_BUFFER, timeout: 6e4 }
5579
+ );
5580
+ } catch (err) {
5581
+ void err;
5582
+ }
5583
+ await this.waitUntilAvailable(workspaceId);
5584
+ return {
5585
+ id: workspaceId,
5586
+ displayName: workspaceId,
5587
+ webUrl: `https://github.com/codespaces/${workspaceId}`
5588
+ };
5480
5589
  }
5481
5590
  };
5591
+ function shellQuote(s) {
5592
+ return `'${s.replace(/'/g, `'\\''`)}'`;
5593
+ }
5482
5594
 
5483
5595
  // src/services/providers/index.ts
5484
5596
  var PROVIDERS = [
@@ -5490,6 +5602,7 @@ var PROVIDERS = [
5490
5602
  ];
5491
5603
 
5492
5604
  // src/commands/deploy.ts
5605
+ var execFileP3 = (0, import_util3.promisify)(import_child_process6.execFile);
5493
5606
  async function deploy() {
5494
5607
  console.log();
5495
5608
  mt(import_picocolors8.default.bgMagenta(import_picocolors8.default.white(" codeam deploy ")));
@@ -5535,8 +5648,53 @@ async function deploy() {
5535
5648
  process.exit(0);
5536
5649
  }
5537
5650
  const project = projects.find((proj) => proj.id === projectId);
5651
+ let workspace = null;
5652
+ if (provider.listExistingWorkspaces && provider.startWorkspace) {
5653
+ const existingStep = fe();
5654
+ existingStep.start("Checking for existing workspaces\u2026");
5655
+ let existing = [];
5656
+ try {
5657
+ existing = await provider.listExistingWorkspaces(project.id);
5658
+ existingStep.stop(
5659
+ existing.length === 0 ? "\xB7 No existing workspaces \u2014 will create a fresh one" : `\u2713 ${existing.length} existing workspace${existing.length === 1 ? "" : "s"} found`
5660
+ );
5661
+ } catch {
5662
+ existingStep.stop("\xB7 Could not list existing workspaces \u2014 will create a fresh one");
5663
+ }
5664
+ if (existing.length > 0) {
5665
+ const choice = await _t({
5666
+ message: "Reuse an existing workspace or create a new one?",
5667
+ options: [
5668
+ ...existing.map((w3) => ({
5669
+ value: w3.id,
5670
+ label: w3.displayName ?? w3.id,
5671
+ hint: [w3.state, formatLastUsed(w3.lastUsedAt)].filter(Boolean).join(" \xB7 ")
5672
+ })),
5673
+ { value: "__new__", label: import_picocolors8.default.green("+ Create a new workspace"), hint: "fresh codespace" }
5674
+ ]
5675
+ });
5676
+ if (q(choice)) {
5677
+ pt("Cancelled.");
5678
+ process.exit(0);
5679
+ }
5680
+ if (choice !== "__new__") {
5681
+ const reuseStep = fe();
5682
+ const picked = existing.find((w3) => w3.id === choice);
5683
+ const needsStart = picked.state && picked.state !== "Available";
5684
+ reuseStep.start(needsStart ? `Starting ${picked.displayName ?? picked.id}\u2026` : `Connecting to ${picked.displayName ?? picked.id}\u2026`);
5685
+ try {
5686
+ workspace = await provider.startWorkspace(picked.id);
5687
+ reuseStep.stop(`\u2713 Reusing ${workspace.displayName ?? workspace.id}`);
5688
+ } catch (err) {
5689
+ reuseStep.stop("\u2717 Could not start the existing workspace");
5690
+ pt(err instanceof Error ? err.message : String(err));
5691
+ process.exit(1);
5692
+ }
5693
+ }
5694
+ }
5695
+ }
5538
5696
  let machineTypeId;
5539
- if (provider.listMachineTypes) {
5697
+ if (!workspace && provider.listMachineTypes) {
5540
5698
  const machineStep = fe();
5541
5699
  machineStep.start("Loading machine types\u2026");
5542
5700
  let machines = [];
@@ -5567,16 +5725,17 @@ async function deploy() {
5567
5725
  machineTypeId = machines[0].id;
5568
5726
  }
5569
5727
  }
5570
- const createStep = fe();
5571
- createStep.start(`Creating workspace for ${project.fullName}\u2026`);
5572
- let workspace;
5573
- try {
5574
- workspace = await provider.createWorkspace(project.id, machineTypeId);
5575
- createStep.stop(`\u2713 Workspace ready: ${workspace.displayName ?? workspace.id}`);
5576
- } catch (err) {
5577
- createStep.stop(`\u2717 Workspace creation failed`);
5578
- pt(err instanceof Error ? err.message : String(err));
5579
- process.exit(1);
5728
+ if (!workspace) {
5729
+ const createStep = fe();
5730
+ createStep.start(`Creating workspace for ${project.fullName}\u2026`);
5731
+ try {
5732
+ workspace = await provider.createWorkspace(project.id, machineTypeId);
5733
+ createStep.stop(`\u2713 Workspace ready: ${workspace.displayName ?? workspace.id}`);
5734
+ } catch (err) {
5735
+ createStep.stop(`\u2717 Workspace creation failed`);
5736
+ pt(err instanceof Error ? err.message : String(err));
5737
+ process.exit(1);
5738
+ }
5580
5739
  }
5581
5740
  const claudeStep = fe();
5582
5741
  claudeStep.start("Installing Claude CLI on workspace\u2026");
@@ -5590,19 +5749,74 @@ async function deploy() {
5590
5749
  process.exit(1);
5591
5750
  }
5592
5751
  claudeStep.stop("\u2713 Claude CLI installed");
5593
- const localClaudeDir = path8.join(os6.homedir(), ".claude");
5752
+ const localClaudeDir = path9.join(os6.homedir(), ".claude");
5594
5753
  const haveLocalClaude = fs8.existsSync(localClaudeDir) && fs8.statSync(localClaudeDir).isDirectory();
5595
5754
  if (haveLocalClaude) {
5596
5755
  const copyStep = fe();
5597
5756
  copyStep.start("Copying local Claude config to workspace\u2026");
5757
+ let configUploaded = false;
5598
5758
  try {
5599
- await provider.uploadDirectory(workspace.id, localClaudeDir, "/home/codespace/.claude");
5600
- copyStep.stop("\u2713 Claude config copied \u2014 no re-auth needed");
5759
+ await provider.uploadDirectory(
5760
+ workspace.id,
5761
+ localClaudeDir,
5762
+ "/home/codespace/.claude",
5763
+ {
5764
+ exclude: [
5765
+ "./projects",
5766
+ // per-project conversation history (often 700MB+)
5767
+ "./file-history",
5768
+ // per-project file diffs
5769
+ "./downloads",
5770
+ // downloaded artifacts
5771
+ "./image-cache",
5772
+ // cached images
5773
+ "./paste-cache",
5774
+ // clipboard/paste cache
5775
+ "./backups",
5776
+ // local backups
5777
+ "./shell-snapshots",
5778
+ // shell history snapshots
5779
+ "./telemetry",
5780
+ // analytics dumps
5781
+ "./statsig",
5782
+ // feature-flag cache
5783
+ "./cache",
5784
+ // generic cache dir
5785
+ "./history.jsonl",
5786
+ // global REPL history
5787
+ "./ide",
5788
+ // local IDE bridge state
5789
+ "./todos",
5790
+ // local todo state
5791
+ "./tasks"
5792
+ // local task state
5793
+ ]
5794
+ }
5795
+ );
5796
+ configUploaded = true;
5797
+ copyStep.stop("\u2713 Claude config uploaded");
5601
5798
  } catch (err) {
5602
- copyStep.stop("\u26A0 Could not copy Claude config \u2014 falling back to remote login");
5799
+ copyStep.stop("\u26A0 Could not upload Claude config \u2014 falling back to remote login");
5603
5800
  void err;
5604
5801
  await runRemoteClaudeLogin(provider, workspace.id);
5605
5802
  }
5803
+ if (configUploaded) {
5804
+ const credStep = fe();
5805
+ credStep.start("Bridging Claude credentials\u2026");
5806
+ const bridged = await bridgeClaudeCredentials(provider, workspace.id, localClaudeDir);
5807
+ switch (bridged) {
5808
+ case "flat-file":
5809
+ credStep.stop("\u2713 Credentials in tar \u2014 no re-auth needed");
5810
+ break;
5811
+ case "macos-keychain":
5812
+ credStep.stop("\u2713 Credentials extracted from macOS Keychain \u2014 no re-auth needed");
5813
+ break;
5814
+ case "none":
5815
+ credStep.stop("\u26A0 No transferable Claude credentials found \u2014 falling back to remote login");
5816
+ await runRemoteClaudeLogin(provider, workspace.id);
5817
+ break;
5818
+ }
5819
+ }
5606
5820
  } else {
5607
5821
  wt(
5608
5822
  [
@@ -5667,6 +5881,52 @@ async function runRemoteClaudeLogin(provider, workspaceId) {
5667
5881
  );
5668
5882
  }
5669
5883
  }
5884
+ async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
5885
+ const fileBased = path9.join(localClaudeDir, ".credentials.json");
5886
+ if (fs8.existsSync(fileBased)) return "flat-file";
5887
+ if (process.platform === "darwin") {
5888
+ try {
5889
+ const { stdout } = await execFileP3(
5890
+ "security",
5891
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
5892
+ { maxBuffer: 1024 * 1024 }
5893
+ );
5894
+ const json = stdout.trim();
5895
+ if (json.length === 0) return "none";
5896
+ await provider.uploadFile(
5897
+ workspaceId,
5898
+ "/home/codespace/.claude/.credentials.json",
5899
+ json,
5900
+ { mode: 384 }
5901
+ );
5902
+ return "macos-keychain";
5903
+ } catch {
5904
+ return "none";
5905
+ }
5906
+ }
5907
+ return "none";
5908
+ }
5909
+ function formatLastUsed(iso) {
5910
+ if (!iso) return "";
5911
+ const t2 = Date.parse(iso);
5912
+ if (Number.isNaN(t2)) return "";
5913
+ const diffMs = Date.now() - t2;
5914
+ if (diffMs < 0) return "in the future";
5915
+ const minute = 6e4;
5916
+ const hour = 60 * minute;
5917
+ const day = 24 * hour;
5918
+ if (diffMs < minute) return "just now";
5919
+ if (diffMs < hour) {
5920
+ const m = Math.round(diffMs / minute);
5921
+ return `${m} min${m === 1 ? "" : "s"} ago`;
5922
+ }
5923
+ if (diffMs < day) {
5924
+ const h = Math.round(diffMs / hour);
5925
+ return `${h} hour${h === 1 ? "" : "s"} ago`;
5926
+ }
5927
+ const d3 = Math.round(diffMs / day);
5928
+ return `${d3} day${d3 === 1 ? "" : "s"} ago`;
5929
+ }
5670
5930
  async function pickProvider() {
5671
5931
  const ready = PROVIDERS.filter((prov) => prov.available);
5672
5932
  if (ready.length === 1) return ready[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.4.5",
3
+ "version": "2.4.7",
4
4
  "description": "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands — from anywhere.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {