codeam-cli 2.4.7 → 2.4.9

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.
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.4.7] — 2026-05-03
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Cross-platform Claude credential bridge for codeam deploy (v2.4.7)
12
+
7
13
  ## [2.4.5] — 2026-05-03
8
14
 
9
15
  ### 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.7",
182
+ version: "2.4.9",
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: {
@@ -5478,8 +5478,11 @@ var GitHubCodespacesProvider = class {
5478
5478
  const tarArgs = ["-czf", "-", "-C", localDir];
5479
5479
  for (const pattern of options.exclude ?? []) {
5480
5480
  tarArgs.push(`--exclude=${pattern}`);
5481
+ const stripped = pattern.replace(/^\.\/+/, "");
5482
+ if (stripped !== pattern) tarArgs.push(`--exclude=${stripped}`);
5481
5483
  }
5482
5484
  tarArgs.push(".");
5485
+ const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
5483
5486
  const sshArgs = [
5484
5487
  "codespace",
5485
5488
  "ssh",
@@ -5490,7 +5493,8 @@ var GitHubCodespacesProvider = class {
5490
5493
  ];
5491
5494
  await new Promise((resolve2, reject) => {
5492
5495
  const tar = (0, import_child_process5.spawn)("tar", tarArgs, {
5493
- stdio: ["ignore", "pipe", "pipe"]
5496
+ stdio: ["ignore", "pipe", "pipe"],
5497
+ env: tarEnv
5494
5498
  });
5495
5499
  const ssh = (0, import_child_process5.spawn)("gh", sshArgs, {
5496
5500
  stdio: [tar.stdout, "pipe", "pipe"]
@@ -5737,6 +5741,38 @@ async function deploy() {
5737
5741
  process.exit(1);
5738
5742
  }
5739
5743
  }
5744
+ const localClaudeDir = path9.join(os6.homedir(), ".claude");
5745
+ const localCredsKind = await detectLocalClaudeCredentials(localClaudeDir);
5746
+ let bridged = "none";
5747
+ if (localCredsKind !== "none") {
5748
+ const sourceLabel = localCredsKind === "flat-file" ? "~/.claude/.credentials.json" : "macOS Keychain";
5749
+ const useLocal = await ot2({
5750
+ message: `Copy your local Claude credentials (${sourceLabel}) to the workspace?`,
5751
+ active: "Yes \u2014 same account, no re-auth",
5752
+ inactive: "No \u2014 log in with a different account",
5753
+ initialValue: true
5754
+ });
5755
+ if (q(useLocal)) {
5756
+ pt("Cancelled.");
5757
+ process.exit(0);
5758
+ }
5759
+ if (useLocal) {
5760
+ const credStep = fe();
5761
+ credStep.start("Bridging Claude credentials\u2026");
5762
+ bridged = await bridgeClaudeCredentials(provider, workspace.id, localClaudeDir);
5763
+ switch (bridged) {
5764
+ case "flat-file":
5765
+ credStep.stop("\u2713 Local credentials staged");
5766
+ break;
5767
+ case "macos-keychain":
5768
+ credStep.stop("\u2713 Credentials extracted from macOS Keychain and staged");
5769
+ break;
5770
+ case "none":
5771
+ credStep.stop("\u26A0 Could not extract local credentials \u2014 falling back to remote login");
5772
+ break;
5773
+ }
5774
+ }
5775
+ }
5740
5776
  const claudeStep = fe();
5741
5777
  claudeStep.start("Installing Claude CLI on workspace\u2026");
5742
5778
  const installResult = await provider.exec(
@@ -5749,12 +5785,10 @@ async function deploy() {
5749
5785
  process.exit(1);
5750
5786
  }
5751
5787
  claudeStep.stop("\u2713 Claude CLI installed");
5752
- const localClaudeDir = path9.join(os6.homedir(), ".claude");
5753
5788
  const haveLocalClaude = fs8.existsSync(localClaudeDir) && fs8.statSync(localClaudeDir).isDirectory();
5754
5789
  if (haveLocalClaude) {
5755
5790
  const copyStep = fe();
5756
5791
  copyStep.start("Copying local Claude config to workspace\u2026");
5757
- let configUploaded = false;
5758
5792
  try {
5759
5793
  await provider.uploadDirectory(
5760
5794
  workspace.id,
@@ -5788,52 +5822,36 @@ async function deploy() {
5788
5822
  // local IDE bridge state
5789
5823
  "./todos",
5790
5824
  // local todo state
5791
- "./tasks"
5825
+ "./tasks",
5792
5826
  // local task state
5827
+ // Don't overwrite the credentials we already staged in
5828
+ // step 4 — the local dir on macOS doesn't have a flat
5829
+ // credentials file anyway, but on Linux it would, and a
5830
+ // re-write here would be redundant.
5831
+ "./.credentials.json"
5793
5832
  ]
5794
5833
  }
5795
5834
  );
5796
- configUploaded = true;
5797
5835
  copyStep.stop("\u2713 Claude config uploaded");
5798
5836
  } catch (err) {
5799
- copyStep.stop("\u26A0 Could not upload Claude config \u2014 falling back to remote login");
5837
+ copyStep.stop("\u26A0 Could not upload Claude config (continuing)");
5800
5838
  void err;
5801
- await runRemoteClaudeLogin(provider, workspace.id);
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
5839
  }
5840
+ }
5841
+ const verifyStep = fe();
5842
+ verifyStep.start("Verifying Claude auth on workspace\u2026");
5843
+ const verified = await verifyClaudeAuth(provider, workspace.id);
5844
+ if (verified) {
5845
+ verifyStep.stop("\u2713 Claude is logged in \u2014 no re-auth needed");
5820
5846
  } else {
5821
- wt(
5822
- [
5823
- "No local ~/.claude config found.",
5824
- "We can run `claude login` inside the workspace right now \u2014 the URL",
5825
- "will print here, you open it in your browser, paste the code back,",
5826
- "and the workspace gets authenticated. (Skip if you'd rather do it",
5827
- "manually later from inside the codespace.)"
5828
- ].join("\n"),
5829
- "Claude credentials"
5830
- );
5831
- const proceed = await ot2({
5832
- message: "Run `claude login` on the workspace now?",
5833
- initialValue: true
5834
- });
5835
- if (!q(proceed) && proceed) {
5836
- await runRemoteClaudeLogin(provider, workspace.id);
5847
+ verifyStep.stop("\xB7 Claude not yet authenticated \u2014 running login flow");
5848
+ await runRemoteClaudeLogin(provider, workspace.id);
5849
+ const reverified = await verifyClaudeAuth(provider, workspace.id);
5850
+ if (!reverified) {
5851
+ wt(
5852
+ "Claude auth could not be confirmed. You may need to run `claude /login` manually inside the codespace.",
5853
+ "Heads up"
5854
+ );
5837
5855
  }
5838
5856
  }
5839
5857
  const cliStep = fe();
@@ -5881,6 +5899,39 @@ async function runRemoteClaudeLogin(provider, workspaceId) {
5881
5899
  );
5882
5900
  }
5883
5901
  }
5902
+ async function detectLocalClaudeCredentials(localClaudeDir) {
5903
+ if (fs8.existsSync(path9.join(localClaudeDir, ".credentials.json"))) {
5904
+ return "flat-file";
5905
+ }
5906
+ if (process.platform === "darwin") {
5907
+ try {
5908
+ await execFileP3(
5909
+ "security",
5910
+ ["find-generic-password", "-s", "Claude Code-credentials"],
5911
+ { maxBuffer: 1024 * 1024 }
5912
+ );
5913
+ return "macos-keychain";
5914
+ } catch {
5915
+ return "none";
5916
+ }
5917
+ }
5918
+ return "none";
5919
+ }
5920
+ async function verifyClaudeAuth(provider, workspaceId) {
5921
+ const result = await provider.exec(
5922
+ workspaceId,
5923
+ 'bash -lc "claude auth status 2>/dev/null || true"'
5924
+ );
5925
+ if (result.code !== 0) return false;
5926
+ const jsonStart = result.stdout.indexOf("{");
5927
+ if (jsonStart < 0) return false;
5928
+ try {
5929
+ const parsed = JSON.parse(result.stdout.slice(jsonStart));
5930
+ return parsed.loggedIn === true;
5931
+ } catch {
5932
+ return false;
5933
+ }
5934
+ }
5884
5935
  async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
5885
5936
  const fileBased = path9.join(localClaudeDir, ".credentials.json");
5886
5937
  if (fs8.existsSync(fileBased)) return "flat-file";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.4.7",
3
+ "version": "2.4.9",
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": {