codeam-cli 2.4.6 → 2.4.8

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 +160 -26
  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.7] — 2026-05-03
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Cross-platform Claude credential bridge for codeam deploy (v2.4.7)
12
+
13
+ ## [2.4.5] — 2026-05-03
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Clearer guidance when gh refresh hits multi-account browser (v2.4.5)
18
+
7
19
  ## [2.4.4] — 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.6",
182
+ version: "2.4.8",
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() {
@@ -5471,7 +5474,15 @@ var GitHubCodespacesProvider = class {
5471
5474
  proc.on("error", reject);
5472
5475
  });
5473
5476
  }
5474
- async uploadDirectory(workspaceId, localDir, remoteDir) {
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
+ const stripped = pattern.replace(/^\.\/+/, "");
5482
+ if (stripped !== pattern) tarArgs.push(`--exclude=${stripped}`);
5483
+ }
5484
+ tarArgs.push(".");
5485
+ const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
5475
5486
  const sshArgs = [
5476
5487
  "codespace",
5477
5488
  "ssh",
@@ -5481,8 +5492,9 @@ var GitHubCodespacesProvider = class {
5481
5492
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
5482
5493
  ];
5483
5494
  await new Promise((resolve2, reject) => {
5484
- const tar = (0, import_child_process5.spawn)("tar", ["-czf", "-", "-C", localDir, "."], {
5485
- stdio: ["ignore", "pipe", "pipe"]
5495
+ const tar = (0, import_child_process5.spawn)("tar", tarArgs, {
5496
+ stdio: ["ignore", "pipe", "pipe"],
5497
+ env: tarEnv
5486
5498
  });
5487
5499
  const ssh = (0, import_child_process5.spawn)("gh", sshArgs, {
5488
5500
  stdio: [tar.stdout, "pipe", "pipe"]
@@ -5507,6 +5519,35 @@ var GitHubCodespacesProvider = class {
5507
5519
  });
5508
5520
  });
5509
5521
  }
5522
+ async uploadFile(workspaceId, remotePath, contents, options = {}) {
5523
+ const remoteDir = path8.posix.dirname(remotePath);
5524
+ const parts = [
5525
+ `mkdir -p ${shellQuote(remoteDir)}`,
5526
+ `cat > ${shellQuote(remotePath)}`
5527
+ ];
5528
+ if (options.mode != null) {
5529
+ parts.push(`chmod ${options.mode.toString(8)} ${shellQuote(remotePath)}`);
5530
+ }
5531
+ const cmd = parts.join(" && ");
5532
+ await new Promise((resolve2, reject) => {
5533
+ const proc = (0, import_child_process5.spawn)(
5534
+ "gh",
5535
+ ["codespace", "ssh", "-c", workspaceId, "--", cmd],
5536
+ { stdio: ["pipe", "pipe", "pipe"] }
5537
+ );
5538
+ let stderr = "";
5539
+ proc.stderr?.on("data", (d3) => {
5540
+ stderr += d3.toString();
5541
+ });
5542
+ proc.on("error", reject);
5543
+ proc.on("exit", (code) => {
5544
+ if (code === 0) resolve2();
5545
+ else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
5546
+ });
5547
+ proc.stdin?.write(contents);
5548
+ proc.stdin?.end();
5549
+ });
5550
+ }
5510
5551
  async listExistingWorkspaces(projectId) {
5511
5552
  try {
5512
5553
  const { stdout } = await execFileP2(
@@ -5565,6 +5606,7 @@ var PROVIDERS = [
5565
5606
  ];
5566
5607
 
5567
5608
  // src/commands/deploy.ts
5609
+ var execFileP3 = (0, import_util3.promisify)(import_child_process6.execFile);
5568
5610
  async function deploy() {
5569
5611
  console.log();
5570
5612
  mt(import_picocolors8.default.bgMagenta(import_picocolors8.default.white(" codeam deploy ")));
@@ -5699,6 +5741,21 @@ async function deploy() {
5699
5741
  process.exit(1);
5700
5742
  }
5701
5743
  }
5744
+ const localClaudeDir = path9.join(os6.homedir(), ".claude");
5745
+ const credStep = fe();
5746
+ credStep.start("Bridging Claude credentials\u2026");
5747
+ const bridged = await bridgeClaudeCredentials(provider, workspace.id, localClaudeDir);
5748
+ switch (bridged) {
5749
+ case "flat-file":
5750
+ credStep.stop("\u2713 Local credentials staged");
5751
+ break;
5752
+ case "macos-keychain":
5753
+ credStep.stop("\u2713 Credentials extracted from macOS Keychain and staged");
5754
+ break;
5755
+ case "none":
5756
+ credStep.stop("\xB7 No local credentials found \u2014 will run interactive login after install");
5757
+ break;
5758
+ }
5702
5759
  const claudeStep = fe();
5703
5760
  claudeStep.start("Installing Claude CLI on workspace\u2026");
5704
5761
  const installResult = await provider.exec(
@@ -5711,36 +5768,73 @@ async function deploy() {
5711
5768
  process.exit(1);
5712
5769
  }
5713
5770
  claudeStep.stop("\u2713 Claude CLI installed");
5714
- const localClaudeDir = path8.join(os6.homedir(), ".claude");
5715
5771
  const haveLocalClaude = fs8.existsSync(localClaudeDir) && fs8.statSync(localClaudeDir).isDirectory();
5716
5772
  if (haveLocalClaude) {
5717
5773
  const copyStep = fe();
5718
5774
  copyStep.start("Copying local Claude config to workspace\u2026");
5719
5775
  try {
5720
- await provider.uploadDirectory(workspace.id, localClaudeDir, "/home/codespace/.claude");
5721
- copyStep.stop("\u2713 Claude config copied \u2014 no re-auth needed");
5776
+ await provider.uploadDirectory(
5777
+ workspace.id,
5778
+ localClaudeDir,
5779
+ "/home/codespace/.claude",
5780
+ {
5781
+ exclude: [
5782
+ "./projects",
5783
+ // per-project conversation history (often 700MB+)
5784
+ "./file-history",
5785
+ // per-project file diffs
5786
+ "./downloads",
5787
+ // downloaded artifacts
5788
+ "./image-cache",
5789
+ // cached images
5790
+ "./paste-cache",
5791
+ // clipboard/paste cache
5792
+ "./backups",
5793
+ // local backups
5794
+ "./shell-snapshots",
5795
+ // shell history snapshots
5796
+ "./telemetry",
5797
+ // analytics dumps
5798
+ "./statsig",
5799
+ // feature-flag cache
5800
+ "./cache",
5801
+ // generic cache dir
5802
+ "./history.jsonl",
5803
+ // global REPL history
5804
+ "./ide",
5805
+ // local IDE bridge state
5806
+ "./todos",
5807
+ // local todo state
5808
+ "./tasks",
5809
+ // local task state
5810
+ // Don't overwrite the credentials we already staged in
5811
+ // step 4 — the local dir on macOS doesn't have a flat
5812
+ // credentials file anyway, but on Linux it would, and a
5813
+ // re-write here would be redundant.
5814
+ "./.credentials.json"
5815
+ ]
5816
+ }
5817
+ );
5818
+ copyStep.stop("\u2713 Claude config uploaded");
5722
5819
  } catch (err) {
5723
- copyStep.stop("\u26A0 Could not copy Claude config \u2014 falling back to remote login");
5820
+ copyStep.stop("\u26A0 Could not upload Claude config (continuing)");
5724
5821
  void err;
5725
- await runRemoteClaudeLogin(provider, workspace.id);
5726
5822
  }
5823
+ }
5824
+ const verifyStep = fe();
5825
+ verifyStep.start("Verifying Claude auth on workspace\u2026");
5826
+ const verified = await verifyClaudeAuth(provider, workspace.id);
5827
+ if (verified) {
5828
+ verifyStep.stop("\u2713 Claude is logged in \u2014 no re-auth needed");
5727
5829
  } else {
5728
- wt(
5729
- [
5730
- "No local ~/.claude config found.",
5731
- "We can run `claude login` inside the workspace right now \u2014 the URL",
5732
- "will print here, you open it in your browser, paste the code back,",
5733
- "and the workspace gets authenticated. (Skip if you'd rather do it",
5734
- "manually later from inside the codespace.)"
5735
- ].join("\n"),
5736
- "Claude credentials"
5737
- );
5738
- const proceed = await ot2({
5739
- message: "Run `claude login` on the workspace now?",
5740
- initialValue: true
5741
- });
5742
- if (!q(proceed) && proceed) {
5743
- await runRemoteClaudeLogin(provider, workspace.id);
5830
+ verifyStep.stop("\xB7 Claude not yet authenticated \u2014 running login flow");
5831
+ await runRemoteClaudeLogin(provider, workspace.id);
5832
+ const reverified = await verifyClaudeAuth(provider, workspace.id);
5833
+ if (!reverified) {
5834
+ wt(
5835
+ "Claude auth could not be confirmed. You may need to run `claude /login` manually inside the codespace.",
5836
+ "Heads up"
5837
+ );
5744
5838
  }
5745
5839
  }
5746
5840
  const cliStep = fe();
@@ -5788,6 +5882,46 @@ async function runRemoteClaudeLogin(provider, workspaceId) {
5788
5882
  );
5789
5883
  }
5790
5884
  }
5885
+ async function verifyClaudeAuth(provider, workspaceId) {
5886
+ const result = await provider.exec(
5887
+ workspaceId,
5888
+ 'bash -lc "claude auth status 2>/dev/null || true"'
5889
+ );
5890
+ if (result.code !== 0) return false;
5891
+ const jsonStart = result.stdout.indexOf("{");
5892
+ if (jsonStart < 0) return false;
5893
+ try {
5894
+ const parsed = JSON.parse(result.stdout.slice(jsonStart));
5895
+ return parsed.loggedIn === true;
5896
+ } catch {
5897
+ return false;
5898
+ }
5899
+ }
5900
+ async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
5901
+ const fileBased = path9.join(localClaudeDir, ".credentials.json");
5902
+ if (fs8.existsSync(fileBased)) return "flat-file";
5903
+ if (process.platform === "darwin") {
5904
+ try {
5905
+ const { stdout } = await execFileP3(
5906
+ "security",
5907
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
5908
+ { maxBuffer: 1024 * 1024 }
5909
+ );
5910
+ const json = stdout.trim();
5911
+ if (json.length === 0) return "none";
5912
+ await provider.uploadFile(
5913
+ workspaceId,
5914
+ "/home/codespace/.claude/.credentials.json",
5915
+ json,
5916
+ { mode: 384 }
5917
+ );
5918
+ return "macos-keychain";
5919
+ } catch {
5920
+ return "none";
5921
+ }
5922
+ }
5923
+ return "none";
5924
+ }
5791
5925
  function formatLastUsed(iso) {
5792
5926
  if (!iso) return "";
5793
5927
  const t2 = Date.parse(iso);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.4.6",
3
+ "version": "2.4.8",
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": {