codeam-cli 2.4.5 → 2.4.6
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 +6 -0
- package/dist/index.js +160 -18
- package/package.json +1 -1
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.4] — 2026-05-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Unblock interactive gh prompts inside codeam deploy (v2.4.4)
|
|
12
|
+
|
|
7
13
|
## [2.4.3] — 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.
|
|
182
|
+
version: "2.4.6",
|
|
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: {
|
|
@@ -5464,7 +5464,7 @@ var GitHubCodespacesProvider = class {
|
|
|
5464
5464
|
return new Promise((resolve2, reject) => {
|
|
5465
5465
|
const proc = (0, import_child_process5.spawn)(
|
|
5466
5466
|
"gh",
|
|
5467
|
-
["codespace", "ssh", "-c", workspaceId, "
|
|
5467
|
+
["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
|
|
5468
5468
|
{ stdio: "inherit" }
|
|
5469
5469
|
);
|
|
5470
5470
|
proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
|
|
@@ -5472,13 +5472,88 @@ var GitHubCodespacesProvider = class {
|
|
|
5472
5472
|
});
|
|
5473
5473
|
}
|
|
5474
5474
|
async uploadDirectory(workspaceId, localDir, remoteDir) {
|
|
5475
|
-
|
|
5476
|
-
"
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5475
|
+
const sshArgs = [
|
|
5476
|
+
"codespace",
|
|
5477
|
+
"ssh",
|
|
5478
|
+
"-c",
|
|
5479
|
+
workspaceId,
|
|
5480
|
+
"--",
|
|
5481
|
+
`mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
|
|
5482
|
+
];
|
|
5483
|
+
await new Promise((resolve2, reject) => {
|
|
5484
|
+
const tar = (0, import_child_process5.spawn)("tar", ["-czf", "-", "-C", localDir, "."], {
|
|
5485
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5486
|
+
});
|
|
5487
|
+
const ssh = (0, import_child_process5.spawn)("gh", sshArgs, {
|
|
5488
|
+
stdio: [tar.stdout, "pipe", "pipe"]
|
|
5489
|
+
});
|
|
5490
|
+
let tarErr = "";
|
|
5491
|
+
let sshErr = "";
|
|
5492
|
+
tar.stderr?.on("data", (d3) => {
|
|
5493
|
+
tarErr += d3.toString();
|
|
5494
|
+
});
|
|
5495
|
+
ssh.stderr?.on("data", (d3) => {
|
|
5496
|
+
sshErr += d3.toString();
|
|
5497
|
+
});
|
|
5498
|
+
tar.on("error", reject);
|
|
5499
|
+
ssh.on("error", reject);
|
|
5500
|
+
ssh.on("exit", (code) => {
|
|
5501
|
+
if (code === 0) {
|
|
5502
|
+
resolve2();
|
|
5503
|
+
} else {
|
|
5504
|
+
const reason = (sshErr || tarErr || `exit ${code}`).trim().slice(0, 500);
|
|
5505
|
+
reject(new Error(`Remote tar failed: ${reason}`));
|
|
5506
|
+
}
|
|
5507
|
+
});
|
|
5508
|
+
});
|
|
5509
|
+
}
|
|
5510
|
+
async listExistingWorkspaces(projectId) {
|
|
5511
|
+
try {
|
|
5512
|
+
const { stdout } = await execFileP2(
|
|
5513
|
+
"gh",
|
|
5514
|
+
[
|
|
5515
|
+
"codespace",
|
|
5516
|
+
"list",
|
|
5517
|
+
"--repo",
|
|
5518
|
+
projectId,
|
|
5519
|
+
"--json",
|
|
5520
|
+
"name,displayName,state,lastUsedAt"
|
|
5521
|
+
],
|
|
5522
|
+
{ maxBuffer: MAX_BUFFER }
|
|
5523
|
+
);
|
|
5524
|
+
const list = JSON.parse(stdout);
|
|
5525
|
+
return list.map((c2) => ({
|
|
5526
|
+
id: c2.name,
|
|
5527
|
+
displayName: c2.displayName || c2.name,
|
|
5528
|
+
webUrl: `https://github.com/codespaces/${c2.name}`,
|
|
5529
|
+
state: c2.state,
|
|
5530
|
+
lastUsedAt: c2.lastUsedAt
|
|
5531
|
+
}));
|
|
5532
|
+
} catch {
|
|
5533
|
+
return [];
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
async startWorkspace(workspaceId) {
|
|
5537
|
+
try {
|
|
5538
|
+
await execFileP2(
|
|
5539
|
+
"gh",
|
|
5540
|
+
["api", "-X", "POST", `/user/codespaces/${workspaceId}/start`],
|
|
5541
|
+
{ maxBuffer: MAX_BUFFER, timeout: 6e4 }
|
|
5542
|
+
);
|
|
5543
|
+
} catch (err) {
|
|
5544
|
+
void err;
|
|
5545
|
+
}
|
|
5546
|
+
await this.waitUntilAvailable(workspaceId);
|
|
5547
|
+
return {
|
|
5548
|
+
id: workspaceId,
|
|
5549
|
+
displayName: workspaceId,
|
|
5550
|
+
webUrl: `https://github.com/codespaces/${workspaceId}`
|
|
5551
|
+
};
|
|
5480
5552
|
}
|
|
5481
5553
|
};
|
|
5554
|
+
function shellQuote(s) {
|
|
5555
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
5556
|
+
}
|
|
5482
5557
|
|
|
5483
5558
|
// src/services/providers/index.ts
|
|
5484
5559
|
var PROVIDERS = [
|
|
@@ -5535,8 +5610,53 @@ async function deploy() {
|
|
|
5535
5610
|
process.exit(0);
|
|
5536
5611
|
}
|
|
5537
5612
|
const project = projects.find((proj) => proj.id === projectId);
|
|
5613
|
+
let workspace = null;
|
|
5614
|
+
if (provider.listExistingWorkspaces && provider.startWorkspace) {
|
|
5615
|
+
const existingStep = fe();
|
|
5616
|
+
existingStep.start("Checking for existing workspaces\u2026");
|
|
5617
|
+
let existing = [];
|
|
5618
|
+
try {
|
|
5619
|
+
existing = await provider.listExistingWorkspaces(project.id);
|
|
5620
|
+
existingStep.stop(
|
|
5621
|
+
existing.length === 0 ? "\xB7 No existing workspaces \u2014 will create a fresh one" : `\u2713 ${existing.length} existing workspace${existing.length === 1 ? "" : "s"} found`
|
|
5622
|
+
);
|
|
5623
|
+
} catch {
|
|
5624
|
+
existingStep.stop("\xB7 Could not list existing workspaces \u2014 will create a fresh one");
|
|
5625
|
+
}
|
|
5626
|
+
if (existing.length > 0) {
|
|
5627
|
+
const choice = await _t({
|
|
5628
|
+
message: "Reuse an existing workspace or create a new one?",
|
|
5629
|
+
options: [
|
|
5630
|
+
...existing.map((w3) => ({
|
|
5631
|
+
value: w3.id,
|
|
5632
|
+
label: w3.displayName ?? w3.id,
|
|
5633
|
+
hint: [w3.state, formatLastUsed(w3.lastUsedAt)].filter(Boolean).join(" \xB7 ")
|
|
5634
|
+
})),
|
|
5635
|
+
{ value: "__new__", label: import_picocolors8.default.green("+ Create a new workspace"), hint: "fresh codespace" }
|
|
5636
|
+
]
|
|
5637
|
+
});
|
|
5638
|
+
if (q(choice)) {
|
|
5639
|
+
pt("Cancelled.");
|
|
5640
|
+
process.exit(0);
|
|
5641
|
+
}
|
|
5642
|
+
if (choice !== "__new__") {
|
|
5643
|
+
const reuseStep = fe();
|
|
5644
|
+
const picked = existing.find((w3) => w3.id === choice);
|
|
5645
|
+
const needsStart = picked.state && picked.state !== "Available";
|
|
5646
|
+
reuseStep.start(needsStart ? `Starting ${picked.displayName ?? picked.id}\u2026` : `Connecting to ${picked.displayName ?? picked.id}\u2026`);
|
|
5647
|
+
try {
|
|
5648
|
+
workspace = await provider.startWorkspace(picked.id);
|
|
5649
|
+
reuseStep.stop(`\u2713 Reusing ${workspace.displayName ?? workspace.id}`);
|
|
5650
|
+
} catch (err) {
|
|
5651
|
+
reuseStep.stop("\u2717 Could not start the existing workspace");
|
|
5652
|
+
pt(err instanceof Error ? err.message : String(err));
|
|
5653
|
+
process.exit(1);
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5538
5658
|
let machineTypeId;
|
|
5539
|
-
if (provider.listMachineTypes) {
|
|
5659
|
+
if (!workspace && provider.listMachineTypes) {
|
|
5540
5660
|
const machineStep = fe();
|
|
5541
5661
|
machineStep.start("Loading machine types\u2026");
|
|
5542
5662
|
let machines = [];
|
|
@@ -5567,16 +5687,17 @@ async function deploy() {
|
|
|
5567
5687
|
machineTypeId = machines[0].id;
|
|
5568
5688
|
}
|
|
5569
5689
|
}
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5690
|
+
if (!workspace) {
|
|
5691
|
+
const createStep = fe();
|
|
5692
|
+
createStep.start(`Creating workspace for ${project.fullName}\u2026`);
|
|
5693
|
+
try {
|
|
5694
|
+
workspace = await provider.createWorkspace(project.id, machineTypeId);
|
|
5695
|
+
createStep.stop(`\u2713 Workspace ready: ${workspace.displayName ?? workspace.id}`);
|
|
5696
|
+
} catch (err) {
|
|
5697
|
+
createStep.stop(`\u2717 Workspace creation failed`);
|
|
5698
|
+
pt(err instanceof Error ? err.message : String(err));
|
|
5699
|
+
process.exit(1);
|
|
5700
|
+
}
|
|
5580
5701
|
}
|
|
5581
5702
|
const claudeStep = fe();
|
|
5582
5703
|
claudeStep.start("Installing Claude CLI on workspace\u2026");
|
|
@@ -5667,6 +5788,27 @@ async function runRemoteClaudeLogin(provider, workspaceId) {
|
|
|
5667
5788
|
);
|
|
5668
5789
|
}
|
|
5669
5790
|
}
|
|
5791
|
+
function formatLastUsed(iso) {
|
|
5792
|
+
if (!iso) return "";
|
|
5793
|
+
const t2 = Date.parse(iso);
|
|
5794
|
+
if (Number.isNaN(t2)) return "";
|
|
5795
|
+
const diffMs = Date.now() - t2;
|
|
5796
|
+
if (diffMs < 0) return "in the future";
|
|
5797
|
+
const minute = 6e4;
|
|
5798
|
+
const hour = 60 * minute;
|
|
5799
|
+
const day = 24 * hour;
|
|
5800
|
+
if (diffMs < minute) return "just now";
|
|
5801
|
+
if (diffMs < hour) {
|
|
5802
|
+
const m = Math.round(diffMs / minute);
|
|
5803
|
+
return `${m} min${m === 1 ? "" : "s"} ago`;
|
|
5804
|
+
}
|
|
5805
|
+
if (diffMs < day) {
|
|
5806
|
+
const h = Math.round(diffMs / hour);
|
|
5807
|
+
return `${h} hour${h === 1 ? "" : "s"} ago`;
|
|
5808
|
+
}
|
|
5809
|
+
const d3 = Math.round(diffMs / day);
|
|
5810
|
+
return `${d3} day${d3 === 1 ? "" : "s"} ago`;
|
|
5811
|
+
}
|
|
5670
5812
|
async function pickProvider() {
|
|
5671
5813
|
const ready = PROVIDERS.filter((prov) => prov.available);
|
|
5672
5814
|
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.6",
|
|
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": {
|