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.
- package/CHANGELOG.md +12 -0
- package/dist/index.js +284 -24
- 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.
|
|
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
|
|
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, "
|
|
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
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
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
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
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 =
|
|
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(
|
|
5600
|
-
|
|
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
|
|
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.
|
|
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": {
|