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.
- package/CHANGELOG.md +12 -0
- package/dist/index.js +160 -26
- 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.
|
|
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
|
|
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",
|
|
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(
|
|
5721
|
-
|
|
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
|
|
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
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
"
|
|
5734
|
-
"
|
|
5735
|
-
|
|
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.
|
|
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": {
|