codeam-cli 2.4.17 → 2.4.19
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 +18 -0
- package/README.md +20 -3
- package/dist/index.js +171 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ 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.18] — 2026-05-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Codeam deploy ls + stop, plus runtime tag for the apps (v2.4.18)
|
|
12
|
+
|
|
13
|
+
## [2.4.17] — 2026-05-03
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** Show the QR in codeam deploy (tail -n +1) (v2.4.17)
|
|
18
|
+
|
|
19
|
+
## [2.4.16] — 2026-05-03
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **cli:** Clean codeam deploy log output under PM2 (v2.4.16)
|
|
24
|
+
|
|
7
25
|
## [2.4.15] — 2026-05-03
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -47,12 +47,14 @@ That's it. Open the [CodeAgent Mobile app](https://codeagent-mobile.com), enter
|
|
|
47
47
|
| `codeam status` | Show connection status |
|
|
48
48
|
| `codeam logout` | Remove all paired sessions |
|
|
49
49
|
| `codeam deploy` | Provision a cloud workspace (GitHub Codespaces) and pair it to your phone |
|
|
50
|
+
| `codeam deploy ls` | List the cloud workspaces you've deployed (and which still have a session running) |
|
|
51
|
+
| `codeam deploy stop` | Pick a deployed workspace and stop its codeam session (and optionally the workspace itself) |
|
|
50
52
|
|
|
51
53
|
---
|
|
52
54
|
|
|
53
55
|
## `codeam deploy` — drive a cloud workspace from your phone
|
|
54
56
|
|
|
55
|
-
Don't want to keep your laptop running while you control Claude from the train? `codeam deploy` spins up a fresh **GitHub Codespace** for any of your repos, installs Claude Code + `codeam-cli` inside it, copies your local Claude credentials so you skip the re-auth (or runs `claude login` interactively if you don't have a local config yet),
|
|
57
|
+
Don't want to keep your laptop running while you control Claude from the train? `codeam deploy` spins up a fresh **GitHub Codespace** for any of your repos, installs Claude Code + `codeam-cli` inside it, copies your local Claude credentials so you skip the re-auth (or runs `claude login` interactively if you don't have a local config yet), supervises the agent with **PM2** so the session survives even after you close your laptop, and gives you a QR/code to pair your phone — straight from your local terminal.
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
codeam deploy
|
|
@@ -62,11 +64,26 @@ That's it. You'll be guided through:
|
|
|
62
64
|
|
|
63
65
|
1. **Pick a provider** (GitHub Codespaces today; more coming).
|
|
64
66
|
2. **Pick a repo** from your account.
|
|
65
|
-
3. **
|
|
66
|
-
4. **
|
|
67
|
+
3. **Reuse an existing codespace or create a new one** — re-runs of `codeam deploy` against the same project don't pile up codespaces.
|
|
68
|
+
4. **Wait ~1 minute** while the codespace boots and tools install.
|
|
69
|
+
5. **Scan the QR / enter the code** on the CodeAgent Mobile app.
|
|
70
|
+
6. Your local terminal **automatically disconnects** once Claude is ready — close the laptop, the agent keeps running on the codespace, and your phone stays connected.
|
|
67
71
|
|
|
68
72
|
Requirements: the [GitHub CLI (`gh`)](https://cli.github.com/) installed and authenticated (`gh auth login`). The deploy flow re-uses `gh`'s OAuth — we don't ask for a separate token.
|
|
69
73
|
|
|
74
|
+
### Managing your deployed workspaces
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Show every workspace you've deployed and whether codeam is still running on it.
|
|
78
|
+
codeam deploy ls
|
|
79
|
+
|
|
80
|
+
# Pick one and stop the codeam session — also offers to stop the workspace
|
|
81
|
+
# itself so you don't burn compute hours.
|
|
82
|
+
codeam deploy stop
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Stopping a workspace via `codeam deploy stop` is non-destructive: the GitHub Codespace stays around (preserving your branch, files, and dotfiles); only the running compute is paused. Re-running `codeam deploy` will offer to resume that same codespace.
|
|
86
|
+
|
|
70
87
|
Adding more cloud backends (Gitpod, Coder, your own SSH host, …) is a single new file in `apps/cli/src/services/providers/` — the `CloudProvider` interface keeps it pluggable.
|
|
71
88
|
|
|
72
89
|
---
|
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.19",
|
|
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: {
|
|
@@ -406,11 +406,15 @@ var os2 = __toESM(require("os"));
|
|
|
406
406
|
var API_BASE2 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
407
407
|
async function requestCode(pluginId) {
|
|
408
408
|
try {
|
|
409
|
+
const runtime = process.env.CODESPACES === "true" ? "github-codespaces" : "local";
|
|
410
|
+
const codespaceName = process.env.CODESPACE_NAME;
|
|
409
411
|
const result = await _transport.postJson(`${API_BASE2}/api/pairing/code`, {
|
|
410
412
|
pluginId,
|
|
411
413
|
ideName: "Terminal (codeam-cli)",
|
|
412
414
|
ideVersion: package_default.version,
|
|
413
|
-
hostname: os2.hostname()
|
|
415
|
+
hostname: os2.hostname(),
|
|
416
|
+
runtime,
|
|
417
|
+
...codespaceName ? { codespaceName } : {}
|
|
414
418
|
});
|
|
415
419
|
const data = result?.data;
|
|
416
420
|
if (!data?.code) return null;
|
|
@@ -2766,6 +2770,28 @@ except Exception:sys.exit(0)
|
|
|
2766
2770
|
case "stop_task":
|
|
2767
2771
|
claude.interrupt();
|
|
2768
2772
|
break;
|
|
2773
|
+
case "shutdown_session": {
|
|
2774
|
+
try {
|
|
2775
|
+
await relay.sendResult(cmd.id, "success", { ok: true });
|
|
2776
|
+
} catch {
|
|
2777
|
+
}
|
|
2778
|
+
try {
|
|
2779
|
+
claude.kill();
|
|
2780
|
+
} catch {
|
|
2781
|
+
}
|
|
2782
|
+
try {
|
|
2783
|
+
const proc = (0, import_child_process4.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
2784
|
+
detached: true,
|
|
2785
|
+
stdio: "ignore"
|
|
2786
|
+
});
|
|
2787
|
+
proc.unref();
|
|
2788
|
+
} catch {
|
|
2789
|
+
}
|
|
2790
|
+
outputSvc.dispose();
|
|
2791
|
+
relay.stop();
|
|
2792
|
+
ws.disconnect();
|
|
2793
|
+
process.exit(0);
|
|
2794
|
+
}
|
|
2769
2795
|
case "get_context": {
|
|
2770
2796
|
const usage = historySvc.getCurrentUsage();
|
|
2771
2797
|
const monthlyCost = historySvc.getMonthlyEstimatedCost();
|
|
@@ -5551,18 +5577,9 @@ var GitHubCodespacesProvider = class {
|
|
|
5551
5577
|
}
|
|
5552
5578
|
async listExistingWorkspaces(projectId) {
|
|
5553
5579
|
try {
|
|
5554
|
-
const
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
"codespace",
|
|
5558
|
-
"list",
|
|
5559
|
-
"--repo",
|
|
5560
|
-
projectId,
|
|
5561
|
-
"--json",
|
|
5562
|
-
"name,displayName,state,lastUsedAt"
|
|
5563
|
-
],
|
|
5564
|
-
{ maxBuffer: MAX_BUFFER }
|
|
5565
|
-
);
|
|
5580
|
+
const args2 = ["codespace", "list", "--json", "name,displayName,state,lastUsedAt"];
|
|
5581
|
+
if (projectId) args2.push("--repo", projectId);
|
|
5582
|
+
const { stdout } = await execFileP2("gh", args2, { maxBuffer: MAX_BUFFER });
|
|
5566
5583
|
const list = JSON.parse(stdout);
|
|
5567
5584
|
return list.map((c2) => ({
|
|
5568
5585
|
id: c2.name,
|
|
@@ -6134,6 +6151,144 @@ async function pickProvider() {
|
|
|
6134
6151
|
return found;
|
|
6135
6152
|
}
|
|
6136
6153
|
|
|
6154
|
+
// src/commands/deploy-manage.ts
|
|
6155
|
+
var import_picocolors9 = __toESM(require("picocolors"));
|
|
6156
|
+
async function deployList() {
|
|
6157
|
+
console.log();
|
|
6158
|
+
mt(import_picocolors9.default.bgMagenta(import_picocolors9.default.white(" codeam deploy ls ")));
|
|
6159
|
+
const workspaces = await collectWorkspacesWithStatus();
|
|
6160
|
+
if (workspaces.length === 0) {
|
|
6161
|
+
gt(import_picocolors9.default.dim("No deployed workspaces found."));
|
|
6162
|
+
return;
|
|
6163
|
+
}
|
|
6164
|
+
for (const w3 of workspaces) {
|
|
6165
|
+
const tag = w3.codeamRunning ? import_picocolors9.default.green("\u25CF running") : w3.state === "Available" ? import_picocolors9.default.dim("\u25CB idle") : import_picocolors9.default.dim(`\u25CB ${w3.state ?? "stopped"}`);
|
|
6166
|
+
console.log(` ${tag} ${import_picocolors9.default.cyan(w3.displayName ?? w3.id)} ${import_picocolors9.default.dim("(" + w3.providerName + ")")}`);
|
|
6167
|
+
}
|
|
6168
|
+
gt(import_picocolors9.default.dim("Use `codeam deploy stop` to terminate a session."));
|
|
6169
|
+
}
|
|
6170
|
+
async function deployStop() {
|
|
6171
|
+
console.log();
|
|
6172
|
+
mt(import_picocolors9.default.bgMagenta(import_picocolors9.default.white(" codeam deploy stop ")));
|
|
6173
|
+
const workspaces = await collectWorkspacesWithStatus();
|
|
6174
|
+
if (workspaces.length === 0) {
|
|
6175
|
+
gt(import_picocolors9.default.dim("No deployed workspaces found."));
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
const choice = await _t({
|
|
6179
|
+
message: "Pick a workspace to stop:",
|
|
6180
|
+
options: workspaces.map((w3) => ({
|
|
6181
|
+
value: w3.id,
|
|
6182
|
+
label: w3.displayName ?? w3.id,
|
|
6183
|
+
hint: [
|
|
6184
|
+
w3.providerName,
|
|
6185
|
+
w3.codeamRunning ? import_picocolors9.default.green("\u25CF codeam-pair running") : import_picocolors9.default.dim("\u25CB no codeam-pair"),
|
|
6186
|
+
w3.state ?? ""
|
|
6187
|
+
].filter(Boolean).join(" \xB7 ")
|
|
6188
|
+
}))
|
|
6189
|
+
});
|
|
6190
|
+
if (q(choice)) {
|
|
6191
|
+
pt("Cancelled.");
|
|
6192
|
+
process.exit(0);
|
|
6193
|
+
}
|
|
6194
|
+
const target = workspaces.find((w3) => w3.id === choice);
|
|
6195
|
+
if (target.codeamRunning) {
|
|
6196
|
+
const stopStep = fe();
|
|
6197
|
+
stopStep.start("Stopping codeam-pair on the workspace\u2026");
|
|
6198
|
+
try {
|
|
6199
|
+
const result = await target.provider.exec(
|
|
6200
|
+
target.id,
|
|
6201
|
+
"pm2 delete codeam-pair >/dev/null 2>&1; pm2 list 2>/dev/null | grep -c codeam-pair || true"
|
|
6202
|
+
);
|
|
6203
|
+
void result;
|
|
6204
|
+
stopStep.stop("\u2713 codeam-pair stopped \u2014 your phone is now disconnected from this workspace");
|
|
6205
|
+
} catch (err) {
|
|
6206
|
+
stopStep.stop("\u26A0 Could not reach the workspace to stop codeam-pair");
|
|
6207
|
+
void err;
|
|
6208
|
+
}
|
|
6209
|
+
} else {
|
|
6210
|
+
O2.info("No codeam-pair process to stop on this workspace.");
|
|
6211
|
+
}
|
|
6212
|
+
const alsoStop = await ot2({
|
|
6213
|
+
message: `Also stop the workspace ${import_picocolors9.default.cyan(target.displayName ?? target.id)} to save compute hours?`,
|
|
6214
|
+
initialValue: true
|
|
6215
|
+
});
|
|
6216
|
+
if (!q(alsoStop) && alsoStop) {
|
|
6217
|
+
const cs = fe();
|
|
6218
|
+
cs.start("Stopping workspace\u2026");
|
|
6219
|
+
try {
|
|
6220
|
+
const result = await target.provider.exec(
|
|
6221
|
+
target.id,
|
|
6222
|
+
// We'd ideally use a provider method; for now do it inline
|
|
6223
|
+
// — works for the github-codespaces provider, no-op-ish for
|
|
6224
|
+
// others (the command will fail and we fall back gracefully).
|
|
6225
|
+
"echo stopping"
|
|
6226
|
+
);
|
|
6227
|
+
void result;
|
|
6228
|
+
await stopWorkspaceFromLocal(target);
|
|
6229
|
+
cs.stop(`\u2713 Workspace ${target.displayName ?? target.id} is stopping`);
|
|
6230
|
+
} catch (err) {
|
|
6231
|
+
cs.stop("\u26A0 Could not stop the workspace");
|
|
6232
|
+
O2.warn(err instanceof Error ? err.message : String(err));
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
gt(import_picocolors9.default.green("\u2713 Done."));
|
|
6236
|
+
}
|
|
6237
|
+
async function collectWorkspacesWithStatus() {
|
|
6238
|
+
const out = [];
|
|
6239
|
+
const ready = PROVIDERS.filter((prov) => prov.available);
|
|
6240
|
+
for (const provider of ready) {
|
|
6241
|
+
if (!provider.listExistingWorkspaces) continue;
|
|
6242
|
+
const probeStep = fe();
|
|
6243
|
+
probeStep.start(`Listing ${provider.displayName} workspaces\u2026`);
|
|
6244
|
+
let workspaces = [];
|
|
6245
|
+
try {
|
|
6246
|
+
await provider.authorize();
|
|
6247
|
+
workspaces = await provider.listExistingWorkspaces();
|
|
6248
|
+
probeStep.stop(`\u2713 ${workspaces.length} workspace${workspaces.length === 1 ? "" : "s"} on ${provider.displayName}`);
|
|
6249
|
+
} catch (err) {
|
|
6250
|
+
probeStep.stop(`\u2717 Could not list ${provider.displayName} workspaces`);
|
|
6251
|
+
O2.warn(err instanceof Error ? err.message : String(err));
|
|
6252
|
+
continue;
|
|
6253
|
+
}
|
|
6254
|
+
for (const w3 of workspaces) {
|
|
6255
|
+
const codeamRunning = await probeCodeamPair(provider, w3);
|
|
6256
|
+
out.push({
|
|
6257
|
+
...w3,
|
|
6258
|
+
provider,
|
|
6259
|
+
providerName: provider.displayName,
|
|
6260
|
+
codeamRunning
|
|
6261
|
+
});
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
return out;
|
|
6265
|
+
}
|
|
6266
|
+
async function probeCodeamPair(provider, workspace) {
|
|
6267
|
+
if (workspace.state && workspace.state !== "Available") return false;
|
|
6268
|
+
try {
|
|
6269
|
+
const result = await provider.exec(
|
|
6270
|
+
workspace.id,
|
|
6271
|
+
// `online` is the only state we care about — `errored`, `stopped`,
|
|
6272
|
+
// `stopping` all mean it's not actively serving the user's phone.
|
|
6273
|
+
`pm2 jlist 2>/dev/null | grep -c '"name":"codeam-pair"[^}]*"status":"online"' || echo 0`
|
|
6274
|
+
);
|
|
6275
|
+
if (result.code !== 0) return false;
|
|
6276
|
+
const n = parseInt(result.stdout.trim(), 10);
|
|
6277
|
+
return Number.isFinite(n) && n > 0;
|
|
6278
|
+
} catch {
|
|
6279
|
+
return false;
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
async function stopWorkspaceFromLocal(target) {
|
|
6283
|
+
if (target.provider.id === "github-codespaces") {
|
|
6284
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
6285
|
+
const { promisify: promisify4 } = await import("util");
|
|
6286
|
+
const execFileP4 = promisify4(execFile4);
|
|
6287
|
+
await execFileP4("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
|
|
6288
|
+
return;
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
|
|
6137
6292
|
// src/index.ts
|
|
6138
6293
|
var [, , command, ...args] = process.argv;
|
|
6139
6294
|
async function main() {
|
|
@@ -6147,6 +6302,8 @@ async function main() {
|
|
|
6147
6302
|
case "logout":
|
|
6148
6303
|
return logout();
|
|
6149
6304
|
case "deploy":
|
|
6305
|
+
if (args[0] === "ls" || args[0] === "list") return deployList();
|
|
6306
|
+
if (args[0] === "stop" || args[0] === "remove") return deployStop();
|
|
6150
6307
|
return deploy();
|
|
6151
6308
|
default:
|
|
6152
6309
|
return start();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.19",
|
|
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": {
|