codeam-cli 2.3.0 → 2.4.0
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/dist/index.js +291 -1
- package/package.json +1 -1
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.
|
|
182
|
+
version: "2.4.0",
|
|
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: {
|
|
@@ -5076,6 +5076,294 @@ async function logout() {
|
|
|
5076
5076
|
console.log(import_picocolors6.default.green("\n \u2713 Done. All sessions removed.\n"));
|
|
5077
5077
|
}
|
|
5078
5078
|
|
|
5079
|
+
// src/commands/deploy.ts
|
|
5080
|
+
var fs8 = __toESM(require("fs"));
|
|
5081
|
+
var os6 = __toESM(require("os"));
|
|
5082
|
+
var path8 = __toESM(require("path"));
|
|
5083
|
+
var import_picocolors7 = __toESM(require("picocolors"));
|
|
5084
|
+
|
|
5085
|
+
// src/services/providers/github-codespaces.ts
|
|
5086
|
+
var import_child_process5 = require("child_process");
|
|
5087
|
+
var import_util2 = require("util");
|
|
5088
|
+
var execFileP2 = (0, import_util2.promisify)(import_child_process5.execFile);
|
|
5089
|
+
var MAX_BUFFER = 8 * 1024 * 1024;
|
|
5090
|
+
var GitHubCodespacesProvider = class {
|
|
5091
|
+
id = "github-codespaces";
|
|
5092
|
+
displayName = "GitHub Codespaces";
|
|
5093
|
+
tagline = "Cloud dev environment from any GitHub repo";
|
|
5094
|
+
available = true;
|
|
5095
|
+
async authorize() {
|
|
5096
|
+
try {
|
|
5097
|
+
await execFileP2("gh", ["--version"], { maxBuffer: MAX_BUFFER });
|
|
5098
|
+
} catch {
|
|
5099
|
+
throw new Error(
|
|
5100
|
+
[
|
|
5101
|
+
"GitHub CLI (`gh`) is required for Codespaces deploys.",
|
|
5102
|
+
"Install it with:",
|
|
5103
|
+
" \u2022 macOS: brew install gh",
|
|
5104
|
+
" \u2022 Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md",
|
|
5105
|
+
" \u2022 Windows: winget install --id GitHub.cli",
|
|
5106
|
+
"Then run `gh auth login` and try `codeam deploy` again."
|
|
5107
|
+
].join("\n")
|
|
5108
|
+
);
|
|
5109
|
+
}
|
|
5110
|
+
try {
|
|
5111
|
+
await execFileP2("gh", ["auth", "status"], { maxBuffer: MAX_BUFFER });
|
|
5112
|
+
return;
|
|
5113
|
+
} catch {
|
|
5114
|
+
}
|
|
5115
|
+
await new Promise((resolve2, reject) => {
|
|
5116
|
+
const proc = (0, import_child_process5.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
|
|
5117
|
+
stdio: "inherit"
|
|
5118
|
+
});
|
|
5119
|
+
proc.on("exit", (code) => {
|
|
5120
|
+
if (code === 0) resolve2();
|
|
5121
|
+
else reject(new Error("gh auth login failed."));
|
|
5122
|
+
});
|
|
5123
|
+
proc.on("error", reject);
|
|
5124
|
+
});
|
|
5125
|
+
}
|
|
5126
|
+
async listProjects() {
|
|
5127
|
+
const { stdout } = await execFileP2(
|
|
5128
|
+
"gh",
|
|
5129
|
+
[
|
|
5130
|
+
"repo",
|
|
5131
|
+
"list",
|
|
5132
|
+
"--json",
|
|
5133
|
+
"name,nameWithOwner,description,defaultBranchRef,isPrivate",
|
|
5134
|
+
"--limit",
|
|
5135
|
+
"200"
|
|
5136
|
+
],
|
|
5137
|
+
{ maxBuffer: MAX_BUFFER }
|
|
5138
|
+
);
|
|
5139
|
+
const raw = JSON.parse(stdout);
|
|
5140
|
+
return raw.map((r) => ({
|
|
5141
|
+
id: r.nameWithOwner,
|
|
5142
|
+
name: r.name,
|
|
5143
|
+
fullName: r.nameWithOwner,
|
|
5144
|
+
description: r.description ?? void 0,
|
|
5145
|
+
defaultBranch: r.defaultBranchRef?.name,
|
|
5146
|
+
private: !!r.isPrivate
|
|
5147
|
+
}));
|
|
5148
|
+
}
|
|
5149
|
+
async createWorkspace(projectId) {
|
|
5150
|
+
const { stdout } = await execFileP2(
|
|
5151
|
+
"gh",
|
|
5152
|
+
["codespace", "create", "-R", projectId, "--default-permissions"],
|
|
5153
|
+
{ maxBuffer: MAX_BUFFER, timeout: 12e4 }
|
|
5154
|
+
);
|
|
5155
|
+
const name = stdout.trim().split("\n").filter(Boolean).pop() ?? "";
|
|
5156
|
+
if (!name) {
|
|
5157
|
+
throw new Error("GitHub did not return a codespace name.");
|
|
5158
|
+
}
|
|
5159
|
+
await this.waitUntilAvailable(name);
|
|
5160
|
+
return {
|
|
5161
|
+
id: name,
|
|
5162
|
+
displayName: name,
|
|
5163
|
+
webUrl: `https://github.com/codespaces/${name}`
|
|
5164
|
+
};
|
|
5165
|
+
}
|
|
5166
|
+
async waitUntilAvailable(name) {
|
|
5167
|
+
const deadline = Date.now() + 5 * 60 * 1e3;
|
|
5168
|
+
while (Date.now() < deadline) {
|
|
5169
|
+
const { stdout } = await execFileP2(
|
|
5170
|
+
"gh",
|
|
5171
|
+
["codespace", "list", "--json", "name,state"],
|
|
5172
|
+
{ maxBuffer: MAX_BUFFER }
|
|
5173
|
+
);
|
|
5174
|
+
const list = JSON.parse(stdout);
|
|
5175
|
+
const me2 = list.find((c2) => c2.name === name);
|
|
5176
|
+
if (!me2) throw new Error("Codespace disappeared from the list.");
|
|
5177
|
+
if (me2.state === "Available") return;
|
|
5178
|
+
if (me2.state === "Failed" || me2.state === "Unavailable") {
|
|
5179
|
+
throw new Error(`Codespace state: ${me2.state}.`);
|
|
5180
|
+
}
|
|
5181
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
5182
|
+
}
|
|
5183
|
+
throw new Error("Codespace did not become Available within 5 minutes.");
|
|
5184
|
+
}
|
|
5185
|
+
async exec(workspaceId, command2) {
|
|
5186
|
+
try {
|
|
5187
|
+
const { stdout, stderr } = await execFileP2(
|
|
5188
|
+
"gh",
|
|
5189
|
+
["codespace", "ssh", "-c", workspaceId, "--", command2],
|
|
5190
|
+
{ maxBuffer: MAX_BUFFER, timeout: 6e5 }
|
|
5191
|
+
);
|
|
5192
|
+
return { stdout, stderr, code: 0 };
|
|
5193
|
+
} catch (err) {
|
|
5194
|
+
const e = err;
|
|
5195
|
+
return {
|
|
5196
|
+
stdout: e.stdout ?? "",
|
|
5197
|
+
stderr: e.stderr ?? e.message ?? "gh codespace ssh failed",
|
|
5198
|
+
code: typeof e.code === "number" ? e.code : 1
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
async streamCommand(workspaceId, command2) {
|
|
5203
|
+
return new Promise((resolve2, reject) => {
|
|
5204
|
+
const proc = (0, import_child_process5.spawn)(
|
|
5205
|
+
"gh",
|
|
5206
|
+
["codespace", "ssh", "-c", workspaceId, "-t", "--", command2],
|
|
5207
|
+
{ stdio: "inherit" }
|
|
5208
|
+
);
|
|
5209
|
+
proc.on("exit", (code) => resolve2({ code: code ?? 0 }));
|
|
5210
|
+
proc.on("error", reject);
|
|
5211
|
+
});
|
|
5212
|
+
}
|
|
5213
|
+
async uploadDirectory(workspaceId, localDir, remoteDir) {
|
|
5214
|
+
await execFileP2(
|
|
5215
|
+
"gh",
|
|
5216
|
+
["codespace", "cp", "-r", "-c", workspaceId, localDir, `remote:${remoteDir}`],
|
|
5217
|
+
{ maxBuffer: MAX_BUFFER, timeout: 3e5 }
|
|
5218
|
+
);
|
|
5219
|
+
}
|
|
5220
|
+
};
|
|
5221
|
+
|
|
5222
|
+
// src/services/providers/index.ts
|
|
5223
|
+
var PROVIDERS = [
|
|
5224
|
+
new GitHubCodespacesProvider()
|
|
5225
|
+
// Sketches for future providers — uncomment + implement when ready.
|
|
5226
|
+
// new GitpodProvider(),
|
|
5227
|
+
// new CoderProvider(),
|
|
5228
|
+
// new GitLabWebIDEProvider(),
|
|
5229
|
+
];
|
|
5230
|
+
|
|
5231
|
+
// src/commands/deploy.ts
|
|
5232
|
+
async function deploy() {
|
|
5233
|
+
console.log();
|
|
5234
|
+
mt(import_picocolors7.default.bgMagenta(import_picocolors7.default.white(" codeam deploy ")));
|
|
5235
|
+
const provider = await pickProvider();
|
|
5236
|
+
if (!provider) {
|
|
5237
|
+
pt("No provider selected.");
|
|
5238
|
+
process.exit(0);
|
|
5239
|
+
}
|
|
5240
|
+
const authStep = fe();
|
|
5241
|
+
authStep.start(`Authorizing with ${provider.displayName}\u2026`);
|
|
5242
|
+
try {
|
|
5243
|
+
await provider.authorize();
|
|
5244
|
+
authStep.stop(`\u2713 Authorized with ${provider.displayName}`);
|
|
5245
|
+
} catch (err) {
|
|
5246
|
+
authStep.stop(`\u2717 Authorization failed`);
|
|
5247
|
+
pt(err instanceof Error ? err.message : String(err));
|
|
5248
|
+
process.exit(1);
|
|
5249
|
+
}
|
|
5250
|
+
const listStep = fe();
|
|
5251
|
+
listStep.start("Loading your projects\u2026");
|
|
5252
|
+
let projects = [];
|
|
5253
|
+
try {
|
|
5254
|
+
projects = await provider.listProjects();
|
|
5255
|
+
listStep.stop(`\u2713 ${projects.length} project${projects.length === 1 ? "" : "s"} available`);
|
|
5256
|
+
} catch (err) {
|
|
5257
|
+
listStep.stop(`\u2717 Could not list projects`);
|
|
5258
|
+
pt(err instanceof Error ? err.message : String(err));
|
|
5259
|
+
process.exit(1);
|
|
5260
|
+
}
|
|
5261
|
+
if (projects.length === 0) {
|
|
5262
|
+
pt("No projects found on the account.");
|
|
5263
|
+
process.exit(0);
|
|
5264
|
+
}
|
|
5265
|
+
const projectId = await _t({
|
|
5266
|
+
message: "Select a project to deploy:",
|
|
5267
|
+
options: projects.slice(0, 50).map((proj) => ({
|
|
5268
|
+
value: proj.id,
|
|
5269
|
+
label: proj.fullName,
|
|
5270
|
+
hint: proj.description ? proj.description.slice(0, 80) : proj.private ? "private" : "public"
|
|
5271
|
+
}))
|
|
5272
|
+
});
|
|
5273
|
+
if (q(projectId) || typeof projectId !== "string") {
|
|
5274
|
+
pt("Cancelled.");
|
|
5275
|
+
process.exit(0);
|
|
5276
|
+
}
|
|
5277
|
+
const project = projects.find((proj) => proj.id === projectId);
|
|
5278
|
+
const createStep = fe();
|
|
5279
|
+
createStep.start(`Creating workspace for ${project.fullName}\u2026`);
|
|
5280
|
+
let workspace;
|
|
5281
|
+
try {
|
|
5282
|
+
workspace = await provider.createWorkspace(project.id);
|
|
5283
|
+
createStep.stop(`\u2713 Workspace ready: ${workspace.displayName ?? workspace.id}`);
|
|
5284
|
+
} catch (err) {
|
|
5285
|
+
createStep.stop(`\u2717 Workspace creation failed`);
|
|
5286
|
+
pt(err instanceof Error ? err.message : String(err));
|
|
5287
|
+
process.exit(1);
|
|
5288
|
+
}
|
|
5289
|
+
const claudeStep = fe();
|
|
5290
|
+
claudeStep.start("Installing Claude CLI on workspace\u2026");
|
|
5291
|
+
const installResult = await provider.exec(
|
|
5292
|
+
workspace.id,
|
|
5293
|
+
"curl -fsSL https://claude.ai/install.sh | bash"
|
|
5294
|
+
);
|
|
5295
|
+
if (installResult.code !== 0) {
|
|
5296
|
+
claudeStep.stop("\u2717 Claude CLI install failed");
|
|
5297
|
+
pt(installResult.stderr.slice(0, 1e3));
|
|
5298
|
+
process.exit(1);
|
|
5299
|
+
}
|
|
5300
|
+
claudeStep.stop("\u2713 Claude CLI installed");
|
|
5301
|
+
const localClaudeDir = path8.join(os6.homedir(), ".claude");
|
|
5302
|
+
if (fs8.existsSync(localClaudeDir) && fs8.statSync(localClaudeDir).isDirectory()) {
|
|
5303
|
+
const copyStep = fe();
|
|
5304
|
+
copyStep.start("Copying local Claude config to workspace\u2026");
|
|
5305
|
+
try {
|
|
5306
|
+
await provider.uploadDirectory(workspace.id, localClaudeDir, "/home/codespace/.claude");
|
|
5307
|
+
copyStep.stop("\u2713 Claude config copied \u2014 no re-auth needed");
|
|
5308
|
+
} catch (err) {
|
|
5309
|
+
copyStep.stop("\u26A0 Could not copy Claude config \u2014 you may need to login on the workspace");
|
|
5310
|
+
void err;
|
|
5311
|
+
}
|
|
5312
|
+
} else {
|
|
5313
|
+
wt(
|
|
5314
|
+
"No local ~/.claude config found. You can authenticate Claude in the workspace shell when needed.",
|
|
5315
|
+
"Heads up"
|
|
5316
|
+
);
|
|
5317
|
+
}
|
|
5318
|
+
const cliStep = fe();
|
|
5319
|
+
cliStep.start("Installing codeam-cli on workspace\u2026");
|
|
5320
|
+
const cliInstall = await provider.exec(workspace.id, "npm install -g codeam-cli@latest");
|
|
5321
|
+
if (cliInstall.code !== 0) {
|
|
5322
|
+
cliStep.stop("\u2717 codeam-cli install failed");
|
|
5323
|
+
pt(cliInstall.stderr.slice(0, 1e3));
|
|
5324
|
+
process.exit(1);
|
|
5325
|
+
}
|
|
5326
|
+
cliStep.stop("\u2713 codeam-cli installed");
|
|
5327
|
+
wt(
|
|
5328
|
+
[
|
|
5329
|
+
`Workspace: ${import_picocolors7.default.cyan(workspace.displayName ?? workspace.id)}`,
|
|
5330
|
+
workspace.webUrl ? `Web: ${import_picocolors7.default.cyan(workspace.webUrl)}` : "",
|
|
5331
|
+
"",
|
|
5332
|
+
"Starting `codeam pair` on the workspace.",
|
|
5333
|
+
"Scan the QR code below with the CodeAgent Mobile app to finish pairing."
|
|
5334
|
+
].filter(Boolean).join("\n"),
|
|
5335
|
+
"Almost there"
|
|
5336
|
+
);
|
|
5337
|
+
const code = (await provider.streamCommand(workspace.id, "codeam pair")).code;
|
|
5338
|
+
if (code === 0) {
|
|
5339
|
+
gt(import_picocolors7.default.green(`\u2713 Workspace deployed and paired. Drive from your phone, anywhere.`));
|
|
5340
|
+
} else {
|
|
5341
|
+
gt(import_picocolors7.default.yellow(`Pairing exited with code ${code}. Run "codeam pair" inside the codespace if needed.`));
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
async function pickProvider() {
|
|
5345
|
+
const ready = PROVIDERS.filter((prov) => prov.available);
|
|
5346
|
+
if (ready.length === 1) return ready[0];
|
|
5347
|
+
const selection = await _t({
|
|
5348
|
+
message: "Where do you want to deploy?",
|
|
5349
|
+
options: PROVIDERS.map((prov) => ({
|
|
5350
|
+
value: prov.id,
|
|
5351
|
+
label: prov.available ? prov.displayName : `${prov.displayName} ${import_picocolors7.default.dim("(coming soon)")}`,
|
|
5352
|
+
hint: prov.tagline
|
|
5353
|
+
}))
|
|
5354
|
+
});
|
|
5355
|
+
if (q(selection) || typeof selection !== "string") return null;
|
|
5356
|
+
const found = PROVIDERS.find((prov) => prov.id === selection);
|
|
5357
|
+
if (!found || !found.available) {
|
|
5358
|
+
wt(
|
|
5359
|
+
`${found?.displayName ?? "That provider"} isn\u2019t implemented yet \u2014 we'll ping you on Twitter/X when it ships.`,
|
|
5360
|
+
"Heads up"
|
|
5361
|
+
);
|
|
5362
|
+
return null;
|
|
5363
|
+
}
|
|
5364
|
+
return found;
|
|
5365
|
+
}
|
|
5366
|
+
|
|
5079
5367
|
// src/index.ts
|
|
5080
5368
|
var [, , command, ...args] = process.argv;
|
|
5081
5369
|
async function main() {
|
|
@@ -5088,6 +5376,8 @@ async function main() {
|
|
|
5088
5376
|
return status();
|
|
5089
5377
|
case "logout":
|
|
5090
5378
|
return logout();
|
|
5379
|
+
case "deploy":
|
|
5380
|
+
return deploy();
|
|
5091
5381
|
default:
|
|
5092
5382
|
return start();
|
|
5093
5383
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
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": {
|