replicas-cli 0.2.39 → 0.2.41

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.mjs ADDED
@@ -0,0 +1,2201 @@
1
+ #!/usr/bin/env bun
2
+ import {
3
+ REPLICAS_CONFIG_FILENAMES,
4
+ SANDBOX_PATHS,
5
+ deleteConfig,
6
+ getCurrentUser,
7
+ getIdeCommand,
8
+ getOrganizationId,
9
+ getValidToken,
10
+ isAgentMode,
11
+ isAuthenticated,
12
+ parseAgentEvents,
13
+ readAgentConfig,
14
+ readConfig,
15
+ setIdeCommand,
16
+ setOrganizationId,
17
+ writeConfig
18
+ } from "./chunk-IO4QHXPW.mjs";
19
+
20
+ // src/index.ts
21
+ import "dotenv/config";
22
+ import { Command } from "commander";
23
+ import chalk18 from "chalk";
24
+
25
+ // src/commands/login.ts
26
+ import http from "http";
27
+ import { URL as URL2 } from "url";
28
+ import open from "open";
29
+ import chalk from "chalk";
30
+
31
+ // src/lib/api.ts
32
+ var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
33
+ async function authenticatedFetch(url, options) {
34
+ const token = await getValidToken();
35
+ if (!token) {
36
+ throw new Error("No access token available");
37
+ }
38
+ const headers = {
39
+ "Authorization": `Bearer ${token}`,
40
+ "Content-Type": "application/json",
41
+ ...options?.headers || {}
42
+ };
43
+ const absoluteUrl = `${MONOLITH_URL}${url}`;
44
+ const requestInit = {
45
+ ...options,
46
+ headers,
47
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
48
+ };
49
+ const response = await fetchWithRedirects(absoluteUrl, requestInit);
50
+ if (!response.ok) {
51
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
52
+ throw new Error(error.error || `Request failed with status ${response.status}`);
53
+ }
54
+ return response.json();
55
+ }
56
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
57
+ var MAX_REDIRECTS = 5;
58
+ async function fetchWithRedirects(url, init, attempt = 0) {
59
+ const response = await fetch(url, { ...init, redirect: "manual" });
60
+ if (REDIRECT_STATUSES.has(response.status)) {
61
+ if (attempt >= MAX_REDIRECTS) {
62
+ throw new Error("Too many redirects while contacting the Replicas API");
63
+ }
64
+ const location = response.headers.get("location");
65
+ if (!location) {
66
+ throw new Error(`Redirect status ${response.status} missing Location header`);
67
+ }
68
+ const nextUrl = new URL(location, url).toString();
69
+ const shouldForceGet = response.status === 303 && init.method !== void 0 && init.method.toUpperCase() !== "GET";
70
+ const nextInit = {
71
+ ...init,
72
+ method: shouldForceGet ? "GET" : init.method,
73
+ body: shouldForceGet ? void 0 : init.body
74
+ };
75
+ return fetchWithRedirects(nextUrl, nextInit, attempt + 1);
76
+ }
77
+ return response;
78
+ }
79
+ async function orgAuthenticatedFetch(url, options) {
80
+ const token = await getValidToken();
81
+ const organizationId = getOrganizationId();
82
+ if (!organizationId) {
83
+ throw new Error(
84
+ 'No organization selected. Please run "replicas org switch" to select an organization.'
85
+ );
86
+ }
87
+ const headers = {
88
+ "Authorization": `Bearer ${token}`,
89
+ "Replicas-Org-Id": organizationId,
90
+ "Content-Type": "application/json",
91
+ ...options?.headers || {}
92
+ };
93
+ const absoluteUrl = `${MONOLITH_URL}${url}`;
94
+ const requestInit = {
95
+ ...options,
96
+ headers,
97
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
98
+ };
99
+ const response = await fetchWithRedirects(absoluteUrl, requestInit);
100
+ if (!response.ok) {
101
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
102
+ throw new Error(error.error || `Request failed with status ${response.status}`);
103
+ }
104
+ return response.json();
105
+ }
106
+
107
+ // src/lib/organization.ts
108
+ async function fetchOrganizations() {
109
+ const response = await authenticatedFetch(
110
+ "/v1/user/organizations"
111
+ );
112
+ return response.organizations;
113
+ }
114
+ async function setActiveOrganization(organizationId) {
115
+ const organizations = await fetchOrganizations();
116
+ const organization = organizations.find((org2) => org2.id === organizationId);
117
+ if (!organization) {
118
+ throw new Error(`Organization with ID ${organizationId} not found or you don't have access to it.`);
119
+ }
120
+ setOrganizationId(organizationId);
121
+ }
122
+ async function ensureOrganization() {
123
+ const organizations = await fetchOrganizations();
124
+ if (organizations.length === 0) {
125
+ throw new Error("You are not a member of any organization. Please contact support.");
126
+ }
127
+ const defaultOrg = organizations[0];
128
+ setOrganizationId(defaultOrg.id);
129
+ return defaultOrg.id;
130
+ }
131
+
132
+ // src/commands/login.ts
133
+ var WEB_APP_URL = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
134
+ function generateState() {
135
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
136
+ }
137
+ async function loginCommand() {
138
+ const state = generateState();
139
+ return new Promise((resolve, reject) => {
140
+ let authTimeout;
141
+ let hasHandledCallback = false;
142
+ let lastRedirectUrl = null;
143
+ let lastMessage = null;
144
+ const pendingResponses = /* @__PURE__ */ new Set();
145
+ function respondWithRedirect(res) {
146
+ if (lastRedirectUrl) {
147
+ res.writeHead(302, {
148
+ "Location": lastRedirectUrl,
149
+ "Connection": "close"
150
+ });
151
+ res.end();
152
+ } else {
153
+ res.writeHead(200, {
154
+ "Content-Type": "text/html; charset=utf-8",
155
+ "Connection": "close"
156
+ });
157
+ res.end(`
158
+ <!DOCTYPE html>
159
+ <html lang="en">
160
+ <head>
161
+ <meta charset="utf-8" />
162
+ <title>Replicas CLI Login</title>
163
+ <style>
164
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background:#09090b; color:#f4f4f5; display:flex; align-items:center; justify-content:center; height:100vh; margin:0; }
165
+ .card { border:1px solid #27272a; padding:32px; border-radius:12px; background:#0f0f12; max-width:420px; text-align:center; }
166
+ .title { font-size:20px; margin-bottom:12px; letter-spacing:0.08em; text-transform:uppercase; color:#a855f7; }
167
+ .message { font-family: 'Menlo', 'Source Code Pro', monospace; font-size:14px; line-height:1.6; color:#d4d4d8; white-space:pre-line; }
168
+ </style>
169
+ </head>
170
+ <body>
171
+ <div class="card">
172
+ <div class="title">Replicas CLI</div>
173
+ <div class="message">${lastMessage || "Authentication already processed. You can close this tab."}</div>
174
+ </div>
175
+ </body>
176
+ </html>
177
+ `);
178
+ }
179
+ }
180
+ function flushPendingResponses() {
181
+ for (const response of pendingResponses) {
182
+ respondWithRedirect(response);
183
+ }
184
+ pendingResponses.clear();
185
+ }
186
+ const server = http.createServer(async (req, res) => {
187
+ const url = new URL2(req.url || "", `http://${req.headers.host}`);
188
+ if (url.pathname === "/callback") {
189
+ if (hasHandledCallback) {
190
+ if (!lastRedirectUrl) {
191
+ pendingResponses.add(res);
192
+ res.on("close", () => pendingResponses.delete(res));
193
+ return;
194
+ }
195
+ respondWithRedirect(res);
196
+ return;
197
+ }
198
+ const returnedState = url.searchParams.get("state");
199
+ const accessToken = url.searchParams.get("access_token");
200
+ const refreshToken = url.searchParams.get("refresh_token");
201
+ const expiresAt = url.searchParams.get("expires_at");
202
+ const error = url.searchParams.get("error");
203
+ if (error) {
204
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent(error)}`;
205
+ lastRedirectUrl = errorUrl;
206
+ lastMessage = `Authentication failed: ${error}`;
207
+ respondWithRedirect(res);
208
+ flushPendingResponses();
209
+ clearTimeout(authTimeout);
210
+ setImmediate(() => {
211
+ server.closeAllConnections?.();
212
+ server.close();
213
+ reject(new Error(`Authentication failed: ${error}`));
214
+ });
215
+ return;
216
+ }
217
+ if (returnedState !== state) {
218
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Invalid state parameter. This might be a CSRF attack.")}`;
219
+ lastRedirectUrl = errorUrl;
220
+ lastMessage = "Invalid state parameter. This might be a CSRF attack.";
221
+ respondWithRedirect(res);
222
+ flushPendingResponses();
223
+ clearTimeout(authTimeout);
224
+ setImmediate(() => {
225
+ server.closeAllConnections?.();
226
+ server.close();
227
+ reject(new Error("Invalid state parameter"));
228
+ });
229
+ return;
230
+ }
231
+ if (!accessToken || !refreshToken || !expiresAt) {
232
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Missing required authentication tokens.")}`;
233
+ lastRedirectUrl = errorUrl;
234
+ lastMessage = "Missing required authentication tokens.";
235
+ respondWithRedirect(res);
236
+ flushPendingResponses();
237
+ clearTimeout(authTimeout);
238
+ setImmediate(() => {
239
+ server.closeAllConnections?.();
240
+ server.close();
241
+ reject(new Error("Missing authentication tokens"));
242
+ });
243
+ return;
244
+ }
245
+ hasHandledCallback = true;
246
+ const existingConfig = readConfig();
247
+ const config2 = {
248
+ access_token: accessToken,
249
+ refresh_token: refreshToken,
250
+ expires_at: parseInt(expiresAt, 10),
251
+ organization_id: existingConfig?.organization_id,
252
+ ide_command: existingConfig?.ide_command
253
+ };
254
+ try {
255
+ writeConfig(config2);
256
+ const user = await getCurrentUser();
257
+ try {
258
+ const orgId = await ensureOrganization();
259
+ console.log(chalk.gray(` Organization: ${orgId}`));
260
+ } catch (orgError) {
261
+ console.log(chalk.yellow(" Warning: Could not fetch organizations"));
262
+ console.log(chalk.gray(" You can set your organization later with: replicas org switch"));
263
+ }
264
+ const successUrl = `${WEB_APP_URL}/cli-login/success?email=${encodeURIComponent(user.email)}`;
265
+ lastRedirectUrl = successUrl;
266
+ lastMessage = `Successfully logged in as ${user.email}. You can close this tab.`;
267
+ respondWithRedirect(res);
268
+ flushPendingResponses();
269
+ console.log(chalk.green("\n\u2713 Successfully logged in!"));
270
+ console.log(chalk.gray(` Email: ${user.email}
271
+ `));
272
+ clearTimeout(authTimeout);
273
+ setImmediate(() => {
274
+ server.closeAllConnections?.();
275
+ server.close();
276
+ resolve();
277
+ });
278
+ } catch (error2) {
279
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Failed to verify authentication.")}`;
280
+ lastRedirectUrl = errorUrl;
281
+ lastMessage = "Failed to verify authentication.";
282
+ respondWithRedirect(res);
283
+ flushPendingResponses();
284
+ clearTimeout(authTimeout);
285
+ setImmediate(() => {
286
+ server.closeAllConnections?.();
287
+ server.close();
288
+ reject(error2);
289
+ });
290
+ }
291
+ } else {
292
+ res.writeHead(404);
293
+ res.end("Not found");
294
+ }
295
+ });
296
+ server.listen(0, "localhost", () => {
297
+ const address = server.address();
298
+ if (!address || typeof address === "string") {
299
+ reject(new Error("Failed to start server"));
300
+ return;
301
+ }
302
+ const port = address.port;
303
+ const loginUrl = `${WEB_APP_URL}/cli-login?port=${port}&state=${state}`;
304
+ console.log(chalk.blue("\nOpening browser for authentication..."));
305
+ console.log(chalk.gray(`If the browser doesn't open automatically, visit:
306
+ ${loginUrl}
307
+ `));
308
+ open(loginUrl).catch((error) => {
309
+ console.log(chalk.yellow("Failed to open browser automatically."));
310
+ console.log(chalk.gray(`Please open this URL manually:
311
+ ${loginUrl}
312
+ `));
313
+ });
314
+ });
315
+ authTimeout = setTimeout(() => {
316
+ server.closeAllConnections?.();
317
+ server.close();
318
+ reject(new Error("Authentication timeout. Please try again."));
319
+ }, 5 * 60 * 1e3);
320
+ });
321
+ }
322
+
323
+ // src/commands/logout.ts
324
+ import chalk2 from "chalk";
325
+ function logoutCommand() {
326
+ if (!isAuthenticated()) {
327
+ console.log(chalk2.yellow("You are not logged in."));
328
+ return;
329
+ }
330
+ deleteConfig();
331
+ console.log(chalk2.green("\u2713 Successfully logged out!"));
332
+ }
333
+
334
+ // src/commands/whoami.ts
335
+ import chalk3 from "chalk";
336
+ async function whoamiCommand() {
337
+ if (!isAuthenticated()) {
338
+ console.log(chalk3.yellow("You are not logged in."));
339
+ console.log(chalk3.gray('Run "replicas login" to authenticate.'));
340
+ return;
341
+ }
342
+ try {
343
+ const user = await getCurrentUser();
344
+ console.log(chalk3.blue("\nCurrent User:"));
345
+ console.log(chalk3.gray(` ID: ${user.id}`));
346
+ console.log(chalk3.gray(` Email: ${user.email}
347
+ `));
348
+ } catch (error) {
349
+ console.error(chalk3.red("Failed to get user information."));
350
+ if (error instanceof Error) {
351
+ console.error(chalk3.gray(error.message));
352
+ }
353
+ console.log(chalk3.gray('\nTry running "replicas login" again.'));
354
+ process.exit(1);
355
+ }
356
+ }
357
+
358
+ // src/commands/connect.ts
359
+ import chalk5 from "chalk";
360
+
361
+ // src/lib/ssh.ts
362
+ import { spawn } from "child_process";
363
+ var SSH_OPTIONS = ["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"];
364
+ async function connectSSH(token, host) {
365
+ return new Promise((resolve, reject) => {
366
+ const sshArgs = [...SSH_OPTIONS, `${token}@${host}`];
367
+ const ssh = spawn("ssh", sshArgs, {
368
+ stdio: "inherit"
369
+ });
370
+ ssh.on("close", () => {
371
+ resolve();
372
+ });
373
+ ssh.on("error", reject);
374
+ });
375
+ }
376
+
377
+ // src/lib/workspace-connection.ts
378
+ import chalk4 from "chalk";
379
+ import prompts from "prompts";
380
+
381
+ // src/lib/git.ts
382
+ import { execSync } from "child_process";
383
+ import path from "path";
384
+ function getGitRoot() {
385
+ try {
386
+ const root = execSync("git rev-parse --show-toplevel", {
387
+ encoding: "utf-8",
388
+ stdio: ["pipe", "pipe", "pipe"]
389
+ }).trim();
390
+ return root;
391
+ } catch (error) {
392
+ throw new Error("Not inside a git repository");
393
+ }
394
+ }
395
+ function getGitRepoName() {
396
+ const root = getGitRoot();
397
+ return path.basename(root);
398
+ }
399
+ function isInsideGitRepo() {
400
+ try {
401
+ execSync("git rev-parse --is-inside-work-tree", {
402
+ encoding: "utf-8",
403
+ stdio: ["pipe", "pipe", "pipe"]
404
+ });
405
+ return true;
406
+ } catch {
407
+ return false;
408
+ }
409
+ }
410
+
411
+ // src/lib/workspace-connection.ts
412
+ async function prepareWorkspaceConnection(workspaceName) {
413
+ const orgId = getOrganizationId();
414
+ if (!orgId) {
415
+ throw new Error('No organization selected. Please run "replicas org switch" first.');
416
+ }
417
+ console.log(chalk4.blue(`
418
+ Searching for workspace: ${workspaceName}...`));
419
+ const response = await orgAuthenticatedFetch(
420
+ `/v1/workspaces?name=${encodeURIComponent(workspaceName)}`
421
+ );
422
+ if (response.workspaces.length === 0) {
423
+ throw new Error(`No workspaces found with name matching "${workspaceName}".`);
424
+ }
425
+ let selectedWorkspace;
426
+ if (response.workspaces.length === 1) {
427
+ selectedWorkspace = response.workspaces[0];
428
+ } else {
429
+ console.log(chalk4.yellow(`
430
+ Found ${response.workspaces.length} workspaces matching "${workspaceName}":`));
431
+ const selectResponse = await prompts({
432
+ type: "select",
433
+ name: "workspaceId",
434
+ message: "Select a workspace:",
435
+ choices: response.workspaces.map((ws) => ({
436
+ title: `${ws.name} (${ws.status || "unknown"})`,
437
+ value: ws.id,
438
+ description: `Status: ${ws.status || "unknown"}`
439
+ }))
440
+ });
441
+ if (!selectResponse.workspaceId) {
442
+ throw new Error("Workspace selection cancelled.");
443
+ }
444
+ selectedWorkspace = response.workspaces.find((ws) => ws.id === selectResponse.workspaceId);
445
+ }
446
+ console.log(chalk4.green(`
447
+ \u2713 Selected workspace: ${selectedWorkspace.name}`));
448
+ console.log(chalk4.gray(` Status: ${selectedWorkspace.status || "unknown"}`));
449
+ if (selectedWorkspace.status === "sleeping") {
450
+ throw new Error(
451
+ "Workspace is currently sleeping. Wake it using `replicas app` (press w on a sleeping workspace) or visit https://replicas.dev"
452
+ );
453
+ }
454
+ console.log(chalk4.blue("\nRequesting SSH access token..."));
455
+ const tokenResponse = await orgAuthenticatedFetch(
456
+ `/v1/workspaces/${selectedWorkspace.id}/ssh-token`,
457
+ { method: "POST" }
458
+ );
459
+ console.log(chalk4.green("\u2713 SSH token received"));
460
+ let repoName = null;
461
+ if (isInsideGitRepo()) {
462
+ try {
463
+ repoName = getGitRepoName();
464
+ } catch {
465
+ }
466
+ }
467
+ return {
468
+ workspace: selectedWorkspace,
469
+ sshToken: tokenResponse.token,
470
+ sshHost: tokenResponse.host,
471
+ repoName
472
+ };
473
+ }
474
+
475
+ // src/commands/connect.ts
476
+ async function connectCommand(workspaceName) {
477
+ if (!isAuthenticated()) {
478
+ console.log(chalk5.red('Not logged in. Please run "replicas login" first.'));
479
+ process.exit(1);
480
+ }
481
+ try {
482
+ const { workspace, sshToken, sshHost } = await prepareWorkspaceConnection(workspaceName);
483
+ console.log(chalk5.blue(`
484
+ Connecting to ${workspace.name}...`));
485
+ const sshCommand = `ssh ${sshToken}@${sshHost}`;
486
+ console.log(chalk5.gray(`SSH command: ${sshCommand}`));
487
+ console.log(chalk5.gray("\nPress Ctrl+D to disconnect.\n"));
488
+ await connectSSH(sshToken, sshHost);
489
+ console.log(chalk5.green("\n\u2713 Disconnected from workspace.\n"));
490
+ } catch (error) {
491
+ console.error(chalk5.red(`
492
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
493
+ process.exit(1);
494
+ }
495
+ }
496
+
497
+ // src/commands/code.ts
498
+ import chalk6 from "chalk";
499
+ import { spawn as spawn2 } from "child_process";
500
+
501
+ // src/lib/ssh-config.ts
502
+ import fs from "fs";
503
+ import path2 from "path";
504
+ import os from "os";
505
+ var SSH_CONFIG_PATH = path2.join(os.homedir(), ".ssh", "config");
506
+ var REPLICAS_MARKER_START = "# === REPLICAS CLI MANAGED ENTRY START ===";
507
+ var REPLICAS_MARKER_END = "# === REPLICAS CLI MANAGED ENTRY END ===";
508
+ function ensureSSHDir() {
509
+ const sshDir = path2.join(os.homedir(), ".ssh");
510
+ if (!fs.existsSync(sshDir)) {
511
+ fs.mkdirSync(sshDir, { recursive: true, mode: 448 });
512
+ }
513
+ }
514
+ function readSSHConfig() {
515
+ ensureSSHDir();
516
+ if (!fs.existsSync(SSH_CONFIG_PATH)) {
517
+ return "";
518
+ }
519
+ return fs.readFileSync(SSH_CONFIG_PATH, "utf-8");
520
+ }
521
+ function writeSSHConfig(content) {
522
+ ensureSSHDir();
523
+ fs.writeFileSync(SSH_CONFIG_PATH, content, { mode: 384 });
524
+ }
525
+ function generateConfigBlock(entry) {
526
+ const lines = [
527
+ REPLICAS_MARKER_START,
528
+ `Host ${entry.host}`,
529
+ ` HostName ${entry.hostname}`,
530
+ ` User ${entry.user}`,
531
+ ` StrictHostKeyChecking no`,
532
+ ` UserKnownHostsFile /dev/null`
533
+ ];
534
+ lines.push(REPLICAS_MARKER_END);
535
+ return lines.join("\n");
536
+ }
537
+ function addOrUpdateSSHConfigEntry(entry) {
538
+ let config2 = readSSHConfig();
539
+ const lines = config2.split("\n");
540
+ const result = [];
541
+ let inTargetBlock = false;
542
+ for (const line of lines) {
543
+ if (line.trim() === REPLICAS_MARKER_START) {
544
+ const nextLines = lines.slice(lines.indexOf(line) + 1, lines.indexOf(line) + 3);
545
+ const hostLine = nextLines.find((l) => l.trim().startsWith("Host "));
546
+ if (hostLine && hostLine.includes(entry.host)) {
547
+ inTargetBlock = true;
548
+ continue;
549
+ }
550
+ result.push(line);
551
+ continue;
552
+ }
553
+ if (line.trim() === REPLICAS_MARKER_END) {
554
+ if (inTargetBlock) {
555
+ inTargetBlock = false;
556
+ continue;
557
+ }
558
+ result.push(line);
559
+ continue;
560
+ }
561
+ if (inTargetBlock) {
562
+ continue;
563
+ }
564
+ result.push(line);
565
+ }
566
+ config2 = result.join("\n");
567
+ const newEntry = generateConfigBlock(entry);
568
+ let finalConfig = config2.trim();
569
+ if (finalConfig.length > 0) {
570
+ finalConfig += "\n\n";
571
+ }
572
+ finalConfig += newEntry + "\n";
573
+ writeSSHConfig(finalConfig);
574
+ }
575
+ function getWorkspaceHostAlias(workspaceName) {
576
+ return `replicas-${workspaceName.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
577
+ }
578
+
579
+ // src/commands/code.ts
580
+ async function codeCommand(workspaceName) {
581
+ if (!isAuthenticated()) {
582
+ console.log(chalk6.red('Not logged in. Please run "replicas login" first.'));
583
+ process.exit(1);
584
+ }
585
+ try {
586
+ const { workspace, sshToken, sshHost, repoName } = await prepareWorkspaceConnection(workspaceName);
587
+ const hostAlias = getWorkspaceHostAlias(workspace.name);
588
+ console.log(chalk6.blue("\nConfiguring SSH connection..."));
589
+ addOrUpdateSSHConfigEntry({
590
+ host: hostAlias,
591
+ hostname: sshHost,
592
+ user: sshToken
593
+ });
594
+ console.log(chalk6.green(`\u2713 SSH config entry created: ${hostAlias}`));
595
+ const remotePath = repoName ? `${SANDBOX_PATHS.WORKSPACES_DIR}/${repoName}` : SANDBOX_PATHS.HOME_DIR;
596
+ const ideCommand = getIdeCommand();
597
+ const fullCommand = `${ideCommand} --remote ssh-remote+${hostAlias} ${remotePath}`;
598
+ console.log(chalk6.blue(`
599
+ Opening ${ideCommand} for workspace ${workspace.name}...`));
600
+ console.log(chalk6.gray(`Command: ${fullCommand}`));
601
+ console.log(chalk6.yellow(`
602
+ Note: SSH tokens expire after 3 hours. Run this command again to refresh.`));
603
+ const ide = spawn2(ideCommand, [
604
+ "--remote",
605
+ `ssh-remote+${hostAlias}`,
606
+ remotePath
607
+ ], {
608
+ stdio: "inherit",
609
+ detached: false
610
+ });
611
+ ide.on("error", (error) => {
612
+ console.error(chalk6.red(`
613
+ Failed to launch ${ideCommand}: ${error.message}`));
614
+ console.log(chalk6.yellow(`
615
+ Make sure ${ideCommand} is installed and available in your PATH.`));
616
+ console.log(chalk6.gray(`You can configure a different IDE with: replicas config set ide <command>`));
617
+ process.exit(1);
618
+ });
619
+ ide.on("close", (code) => {
620
+ if (code === 0) {
621
+ console.log(chalk6.green(`
622
+ \u2713 ${ideCommand} closed successfully.
623
+ `));
624
+ }
625
+ });
626
+ } catch (error) {
627
+ console.error(chalk6.red(`
628
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
629
+ process.exit(1);
630
+ }
631
+ }
632
+
633
+ // src/commands/org.ts
634
+ import chalk7 from "chalk";
635
+ import prompts2 from "prompts";
636
+ async function orgCommand() {
637
+ if (!isAuthenticated()) {
638
+ console.log(chalk7.red('Not logged in. Please run "replicas login" first.'));
639
+ process.exit(1);
640
+ }
641
+ try {
642
+ const currentOrgId = getOrganizationId();
643
+ const organizations = await fetchOrganizations();
644
+ if (!currentOrgId) {
645
+ console.log(chalk7.yellow("No organization selected."));
646
+ console.log(chalk7.gray('Run "replicas org switch" to select an organization.\n'));
647
+ return;
648
+ }
649
+ const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
650
+ if (!currentOrg) {
651
+ console.log(chalk7.yellow(`Current organization ID (${currentOrgId}) not found.`));
652
+ console.log(chalk7.gray('Run "replicas org switch" to select a new organization.\n'));
653
+ return;
654
+ }
655
+ console.log(chalk7.green("\nCurrent organization:"));
656
+ console.log(chalk7.gray(` Name: ${currentOrg.name}`));
657
+ console.log(chalk7.gray(` ID: ${currentOrg.id}
658
+ `));
659
+ } catch (error) {
660
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
661
+ process.exit(1);
662
+ }
663
+ }
664
+ async function orgSwitchCommand() {
665
+ if (!isAuthenticated()) {
666
+ console.log(chalk7.red('Not logged in. Please run "replicas login" first.'));
667
+ process.exit(1);
668
+ }
669
+ try {
670
+ const organizations = await fetchOrganizations();
671
+ if (organizations.length === 0) {
672
+ console.log(chalk7.yellow("You are not a member of any organization."));
673
+ console.log(chalk7.gray("Please contact support.\n"));
674
+ return;
675
+ }
676
+ if (organizations.length === 1) {
677
+ await setActiveOrganization(organizations[0].id);
678
+ console.log(chalk7.green(`
679
+ \u2713 Organization set to: ${organizations[0].name}
680
+ `));
681
+ return;
682
+ }
683
+ const currentOrgId = getOrganizationId();
684
+ const response = await prompts2({
685
+ type: "select",
686
+ name: "organizationId",
687
+ message: "Select an organization:",
688
+ choices: organizations.map((org2) => ({
689
+ title: `${org2.name}${org2.id === currentOrgId ? " (current)" : ""}`,
690
+ value: org2.id,
691
+ description: org2.id
692
+ })),
693
+ initial: organizations.findIndex((org2) => org2.id === currentOrgId)
694
+ });
695
+ if (!response.organizationId) {
696
+ console.log(chalk7.yellow("\nCancelled."));
697
+ return;
698
+ }
699
+ await setActiveOrganization(response.organizationId);
700
+ const selectedOrg = organizations.find((org2) => org2.id === response.organizationId);
701
+ console.log(chalk7.green(`
702
+ \u2713 Switched to organization: ${selectedOrg?.name}
703
+ `));
704
+ } catch (error) {
705
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
706
+ process.exit(1);
707
+ }
708
+ }
709
+
710
+ // src/commands/config.ts
711
+ import chalk8 from "chalk";
712
+ async function configGetCommand(key) {
713
+ if (!isAuthenticated()) {
714
+ console.log(chalk8.red('Not logged in. Please run "replicas login" first.'));
715
+ process.exit(1);
716
+ }
717
+ try {
718
+ if (key === "ide") {
719
+ const ideCommand = getIdeCommand();
720
+ console.log(chalk8.green(`
721
+ IDE command: ${ideCommand}
722
+ `));
723
+ } else {
724
+ console.log(chalk8.red(`Unknown config key: ${key}`));
725
+ console.log(chalk8.gray("Available keys: ide"));
726
+ process.exit(1);
727
+ }
728
+ } catch (error) {
729
+ console.error(chalk8.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
730
+ process.exit(1);
731
+ }
732
+ }
733
+ async function configSetCommand(key, value) {
734
+ if (!isAuthenticated()) {
735
+ console.log(chalk8.red('Not logged in. Please run "replicas login" first.'));
736
+ process.exit(1);
737
+ }
738
+ try {
739
+ if (key === "ide") {
740
+ setIdeCommand(value);
741
+ console.log(chalk8.green(`
742
+ \u2713 IDE command set to: ${value}
743
+ `));
744
+ } else {
745
+ console.log(chalk8.red(`Unknown config key: ${key}`));
746
+ console.log(chalk8.gray("Available keys: ide"));
747
+ process.exit(1);
748
+ }
749
+ } catch (error) {
750
+ console.error(chalk8.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
751
+ process.exit(1);
752
+ }
753
+ }
754
+ async function configListCommand() {
755
+ if (!isAuthenticated()) {
756
+ console.log(chalk8.red('Not logged in. Please run "replicas login" first.'));
757
+ process.exit(1);
758
+ }
759
+ try {
760
+ const config2 = readConfig();
761
+ if (!config2) {
762
+ console.log(chalk8.red("No config found. Please login first."));
763
+ process.exit(1);
764
+ }
765
+ console.log(chalk8.green("\nCurrent configuration:"));
766
+ console.log(chalk8.gray(` Organization ID: ${config2.organization_id || "(not set)"}`));
767
+ console.log(chalk8.gray(` IDE command: ${config2.ide_command || "code (default)"}`));
768
+ console.log();
769
+ } catch (error) {
770
+ console.error(chalk8.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
771
+ process.exit(1);
772
+ }
773
+ }
774
+
775
+ // src/commands/codex-auth.ts
776
+ import chalk9 from "chalk";
777
+
778
+ // src/lib/codex-oauth.ts
779
+ import http2 from "http";
780
+ import { URL as URL3 } from "url";
781
+ import open2 from "open";
782
+
783
+ // src/lib/pkce.ts
784
+ import { createHash, randomBytes } from "crypto";
785
+ function generatePkceCodes() {
786
+ const verifierBytes = randomBytes(32);
787
+ const codeVerifier = verifierBytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
788
+ const hash = createHash("sha256");
789
+ hash.update(codeVerifier);
790
+ const codeChallenge = hash.digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
791
+ return {
792
+ codeVerifier,
793
+ codeChallenge
794
+ };
795
+ }
796
+ function generateState2() {
797
+ return randomBytes(16).toString("hex");
798
+ }
799
+
800
+ // src/lib/codex-oauth.ts
801
+ var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
802
+ var AUTHORIZATION_ENDPOINT = "https://auth.openai.com/oauth/authorize";
803
+ var TOKEN_ENDPOINT = "https://auth.openai.com/oauth/token";
804
+ var CALLBACK_PORT = 1455;
805
+ var CALLBACK_PATH = "/auth/callback";
806
+ var WEB_APP_URL2 = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
807
+ function buildAuthorizationUrl(codeChallenge, state) {
808
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
809
+ const params = new URLSearchParams({
810
+ response_type: "code",
811
+ client_id: CODEX_OAUTH_CLIENT_ID,
812
+ redirect_uri: redirectUri,
813
+ scope: "openid profile email offline_access",
814
+ code_challenge: codeChallenge,
815
+ code_challenge_method: "S256",
816
+ id_token_add_organizations: "true",
817
+ codex_cli_simplified_flow: "true",
818
+ state,
819
+ originator: "codex_cli_rs"
820
+ });
821
+ return `${AUTHORIZATION_ENDPOINT}?${params.toString()}`;
822
+ }
823
+ async function exchangeCodeForTokens(code, codeVerifier) {
824
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
825
+ const params = new URLSearchParams({
826
+ grant_type: "authorization_code",
827
+ code,
828
+ redirect_uri: redirectUri,
829
+ client_id: CODEX_OAUTH_CLIENT_ID,
830
+ code_verifier: codeVerifier
831
+ });
832
+ const response = await fetch(TOKEN_ENDPOINT, {
833
+ method: "POST",
834
+ headers: {
835
+ "Content-Type": "application/x-www-form-urlencoded"
836
+ },
837
+ body: params.toString()
838
+ });
839
+ if (!response.ok) {
840
+ const errorText = await response.text();
841
+ throw new Error(`Failed to exchange code for tokens: ${response.status} ${errorText}`);
842
+ }
843
+ const data = await response.json();
844
+ return {
845
+ idToken: data.id_token,
846
+ accessToken: data.access_token,
847
+ refreshToken: data.refresh_token
848
+ };
849
+ }
850
+ function startCallbackServer(expectedState, codeVerifier) {
851
+ return new Promise((resolve, reject) => {
852
+ const server = http2.createServer(async (req, res) => {
853
+ try {
854
+ if (!req.url) {
855
+ res.writeHead(400);
856
+ res.end("Bad Request");
857
+ return;
858
+ }
859
+ const url = new URL3(req.url, `http://127.0.0.1:${CALLBACK_PORT}`);
860
+ if (url.pathname === CALLBACK_PATH) {
861
+ const code = url.searchParams.get("code");
862
+ const state = url.searchParams.get("state");
863
+ const error = url.searchParams.get("error");
864
+ const errorDescription = url.searchParams.get("error_description");
865
+ if (error) {
866
+ const errorMessage = encodeURIComponent(`${error}: ${errorDescription || "Unknown error"}`);
867
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
868
+ res.end();
869
+ server.close();
870
+ reject(new Error(`OAuth error: ${error} - ${errorDescription}`));
871
+ return;
872
+ }
873
+ if (state !== expectedState) {
874
+ const errorMessage = encodeURIComponent("State parameter mismatch. Possible CSRF attack.");
875
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
876
+ res.end();
877
+ server.close();
878
+ reject(new Error("State mismatch - possible CSRF attack"));
879
+ return;
880
+ }
881
+ if (!code) {
882
+ const errorMessage = encodeURIComponent("No authorization code received.");
883
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
884
+ res.end();
885
+ server.close();
886
+ reject(new Error("No authorization code received"));
887
+ return;
888
+ }
889
+ try {
890
+ const tokens = await exchangeCodeForTokens(code, codeVerifier);
891
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/success` });
892
+ res.end();
893
+ server.close();
894
+ resolve(tokens);
895
+ } catch (tokenError) {
896
+ const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
897
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
898
+ res.end();
899
+ server.close();
900
+ reject(tokenError);
901
+ }
902
+ } else {
903
+ res.writeHead(404);
904
+ res.end("Not Found");
905
+ }
906
+ } catch (serverError) {
907
+ res.writeHead(500);
908
+ res.end("Internal Server Error");
909
+ server.close();
910
+ reject(serverError);
911
+ }
912
+ });
913
+ server.on("error", (err) => {
914
+ if (err.code === "EADDRINUSE") {
915
+ reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close any other OAuth flows and try again.`));
916
+ } else {
917
+ reject(err);
918
+ }
919
+ });
920
+ server.listen(CALLBACK_PORT, "127.0.0.1");
921
+ });
922
+ }
923
+ async function runCodexOAuthFlow() {
924
+ const pkce = generatePkceCodes();
925
+ const state = generateState2();
926
+ const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
927
+ const tokensPromise = startCallbackServer(state, pkce.codeVerifier);
928
+ await new Promise((resolve) => setTimeout(resolve, 500));
929
+ console.log("If the browser does not open automatically, visit:");
930
+ console.log(authUrl);
931
+ console.log();
932
+ try {
933
+ await open2(authUrl);
934
+ } catch (openError) {
935
+ console.warn("Failed to open browser automatically. Please open the URL manually.");
936
+ }
937
+ console.log("Waiting for authentication...");
938
+ const tokens = await tokensPromise;
939
+ console.log("\u2713 Successfully obtained Codex credentials");
940
+ return tokens;
941
+ }
942
+
943
+ // src/commands/codex-auth.ts
944
+ async function codexAuthCommand(options = {}) {
945
+ const isUserScoped = options.user === true;
946
+ const scopeLabel = isUserScoped ? "personal" : "organization";
947
+ console.log(chalk9.cyan(`Authenticating with Codex (${scopeLabel})...
948
+ `));
949
+ try {
950
+ const tokens = await runCodexOAuthFlow();
951
+ console.log(chalk9.gray("\nUploading credentials to Replicas..."));
952
+ const endpoint = isUserScoped ? "/v1/codex/user/credentials" : "/v1/codex/credentials";
953
+ const response = await orgAuthenticatedFetch(
954
+ endpoint,
955
+ {
956
+ method: "POST",
957
+ body: {
958
+ access_token: tokens.accessToken,
959
+ refresh_token: tokens.refreshToken,
960
+ id_token: tokens.idToken
961
+ }
962
+ }
963
+ );
964
+ if (response.success) {
965
+ console.log(chalk9.green("\n\u2713 Codex authentication complete!"));
966
+ if (response.email) {
967
+ console.log(chalk9.gray(` Account: ${response.email}`));
968
+ }
969
+ if (response.planType) {
970
+ console.log(chalk9.gray(` Plan: ${response.planType}`));
971
+ }
972
+ if (isUserScoped) {
973
+ console.log(chalk9.gray("\nYour personal Codex credentials have been saved.\n"));
974
+ } else {
975
+ console.log(chalk9.gray("\nYou can now use Codex in your workspaces.\n"));
976
+ }
977
+ } else {
978
+ throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
979
+ }
980
+ } catch (error) {
981
+ console.log(chalk9.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
982
+ process.exit(1);
983
+ }
984
+ }
985
+
986
+ // src/commands/claude-auth.ts
987
+ import chalk11 from "chalk";
988
+
989
+ // src/lib/claude-oauth.ts
990
+ import open3 from "open";
991
+ import readline from "readline";
992
+ import chalk10 from "chalk";
993
+ var DEFAULT_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
994
+ var CLAUDE_CLIENT_ID = process.env.CLAUDE_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID;
995
+ var AUTHORIZATION_ENDPOINT2 = "https://claude.ai/oauth/authorize";
996
+ var TOKEN_ENDPOINT2 = "https://console.anthropic.com/v1/oauth/token";
997
+ var REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
998
+ var SCOPES = "org:create_api_key user:profile user:inference";
999
+ function buildAuthorizationUrl2(codeChallenge, state) {
1000
+ const params = new URLSearchParams({
1001
+ code: "true",
1002
+ client_id: CLAUDE_CLIENT_ID,
1003
+ response_type: "code",
1004
+ redirect_uri: REDIRECT_URI,
1005
+ scope: SCOPES,
1006
+ code_challenge: codeChallenge,
1007
+ code_challenge_method: "S256",
1008
+ state
1009
+ });
1010
+ return `${AUTHORIZATION_ENDPOINT2}?${params.toString()}`;
1011
+ }
1012
+ async function promptForAuthorizationCode(instruction) {
1013
+ const rl = readline.createInterface({
1014
+ input: process.stdin,
1015
+ output: process.stdout
1016
+ });
1017
+ return new Promise((resolve) => {
1018
+ rl.question(instruction, (answer) => {
1019
+ rl.close();
1020
+ resolve(answer.trim());
1021
+ });
1022
+ });
1023
+ }
1024
+ function parseUserInput(input) {
1025
+ if (!input.includes("#") && !input.includes("code=")) {
1026
+ return { code: input };
1027
+ }
1028
+ if (input.startsWith("http://") || input.startsWith("https://")) {
1029
+ try {
1030
+ const url = new URL(input);
1031
+ const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
1032
+ const params = new URLSearchParams(fragment);
1033
+ const code2 = params.get("code") ?? void 0;
1034
+ const state2 = params.get("state") ?? void 0;
1035
+ if (code2) {
1036
+ return { code: code2, state: state2 };
1037
+ }
1038
+ } catch {
1039
+ }
1040
+ }
1041
+ const [code, state] = input.split("#");
1042
+ return { code, state };
1043
+ }
1044
+ async function exchangeCodeForTokens2(code, state, expectedState, codeVerifier) {
1045
+ if (state && state !== expectedState) {
1046
+ throw new Error("State mismatch detected. Please restart the authentication flow.");
1047
+ }
1048
+ const payload = {
1049
+ code,
1050
+ state: state ?? expectedState,
1051
+ grant_type: "authorization_code",
1052
+ client_id: CLAUDE_CLIENT_ID,
1053
+ redirect_uri: REDIRECT_URI,
1054
+ code_verifier: codeVerifier
1055
+ };
1056
+ const response = await fetch(TOKEN_ENDPOINT2, {
1057
+ method: "POST",
1058
+ headers: {
1059
+ "Content-Type": "application/json"
1060
+ },
1061
+ body: JSON.stringify(payload)
1062
+ });
1063
+ if (!response.ok) {
1064
+ const text = await response.text();
1065
+ throw new Error(`Failed to exchange authorization code: ${response.status} ${text}`);
1066
+ }
1067
+ const data = await response.json();
1068
+ const now = Date.now();
1069
+ const expiresAt = new Date(now + data.expires_in * 1e3).toISOString();
1070
+ const refreshTokenExpiresAt = data.refresh_token_expires_in ? new Date(now + data.refresh_token_expires_in * 1e3).toISOString() : void 0;
1071
+ return {
1072
+ accessToken: data.access_token,
1073
+ refreshToken: data.refresh_token,
1074
+ tokenType: data.token_type,
1075
+ scope: data.scope,
1076
+ expiresAt,
1077
+ refreshTokenExpiresAt
1078
+ };
1079
+ }
1080
+ async function runClaudeOAuthFlow() {
1081
+ const pkce = generatePkceCodes();
1082
+ const state = generateState2();
1083
+ const authUrl = buildAuthorizationUrl2(pkce.codeChallenge, state);
1084
+ console.log(chalk10.gray("We will open your browser so you can authenticate with Claude Code."));
1085
+ console.log(chalk10.gray("If the browser does not open automatically, use the URL below:\n"));
1086
+ console.log(authUrl);
1087
+ console.log();
1088
+ try {
1089
+ await open3(authUrl);
1090
+ } catch {
1091
+ console.warn(chalk10.yellow("Failed to open browser automatically. Please copy and open the URL manually."));
1092
+ }
1093
+ console.log(chalk10.cyan("After completing authentication, copy the code shown on the success page."));
1094
+ console.log(chalk10.cyan("You can paste either the full URL, or a value formatted as CODE#STATE.\n"));
1095
+ const userInput = await promptForAuthorizationCode("Paste the authorization code (or URL) here: ");
1096
+ if (!userInput) {
1097
+ throw new Error("No authorization code provided.");
1098
+ }
1099
+ const { code, state: returnedState } = parseUserInput(userInput);
1100
+ if (!code) {
1101
+ throw new Error("Unable to parse authorization code. Please try again.");
1102
+ }
1103
+ console.log(chalk10.gray("\nExchanging authorization code for tokens..."));
1104
+ const tokens = await exchangeCodeForTokens2(code, returnedState, state, pkce.codeVerifier);
1105
+ console.log(chalk10.green("\u2713 Successfully obtained Claude credentials"));
1106
+ return tokens;
1107
+ }
1108
+
1109
+ // src/commands/claude-auth.ts
1110
+ async function claudeAuthCommand(options = {}) {
1111
+ const isUserScoped = options.user === true;
1112
+ const scopeLabel = isUserScoped ? "personal" : "organization";
1113
+ console.log(chalk11.cyan(`Authenticating with Claude Code (${scopeLabel})...
1114
+ `));
1115
+ try {
1116
+ const tokens = await runClaudeOAuthFlow();
1117
+ console.log(chalk11.gray("\nUploading credentials to Replicas..."));
1118
+ const endpoint = isUserScoped ? "/v1/claude/user/credentials" : "/v1/claude/credentials";
1119
+ const response = await orgAuthenticatedFetch(
1120
+ endpoint,
1121
+ {
1122
+ method: "POST",
1123
+ body: {
1124
+ access_token: tokens.accessToken,
1125
+ refresh_token: tokens.refreshToken,
1126
+ token_type: tokens.tokenType,
1127
+ scope: tokens.scope,
1128
+ expires_at: tokens.expiresAt,
1129
+ refresh_token_expires_at: tokens.refreshTokenExpiresAt
1130
+ }
1131
+ }
1132
+ );
1133
+ if (!response.success) {
1134
+ throw new Error(response.error || "Failed to upload Claude credentials to Replicas");
1135
+ }
1136
+ console.log(chalk11.green("\n\u2713 Claude authentication complete!"));
1137
+ if (response.email) {
1138
+ console.log(chalk11.gray(` Account: ${response.email}`));
1139
+ }
1140
+ if (response.planType) {
1141
+ console.log(chalk11.gray(` Plan: ${response.planType}`));
1142
+ }
1143
+ if (isUserScoped) {
1144
+ console.log(chalk11.gray("\nYour personal Claude credentials have been saved.\n"));
1145
+ } else {
1146
+ console.log(chalk11.gray("\nYou can now use Claude Code in your workspaces.\n"));
1147
+ }
1148
+ } catch (error) {
1149
+ console.log(chalk11.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1150
+ process.exit(1);
1151
+ }
1152
+ }
1153
+
1154
+ // src/commands/init.ts
1155
+ import chalk12 from "chalk";
1156
+ import fs2 from "fs";
1157
+ import path3 from "path";
1158
+ function getDefaultConfig() {
1159
+ return {
1160
+ systemPrompt: "",
1161
+ startHook: {
1162
+ timeout: 3e5,
1163
+ commands: []
1164
+ }
1165
+ };
1166
+ }
1167
+ function getDefaultYamlContent() {
1168
+ return `# Replicas workspace configuration
1169
+ # See: https://docs.replicas.dev/features/workspaces/repository-configuration
1170
+
1171
+ systemPrompt: |
1172
+ Add custom instructions for coding agents here.
1173
+
1174
+ startHook:
1175
+ timeout: 300000
1176
+ commands: []
1177
+ `;
1178
+ }
1179
+ function initCommand(options) {
1180
+ const isYaml = options.yaml ?? false;
1181
+ const targetFilename = isYaml ? "replicas.yaml" : "replicas.json";
1182
+ const configPath = path3.join(process.cwd(), targetFilename);
1183
+ for (const filename of REPLICAS_CONFIG_FILENAMES) {
1184
+ const existingPath = path3.join(process.cwd(), filename);
1185
+ if (fs2.existsSync(existingPath) && !options.force) {
1186
+ console.log(
1187
+ chalk12.yellow(
1188
+ `${filename} already exists in this directory.`
1189
+ )
1190
+ );
1191
+ console.log(chalk12.gray("Use --force to overwrite the existing file."));
1192
+ return;
1193
+ }
1194
+ }
1195
+ let configContent;
1196
+ if (isYaml) {
1197
+ configContent = getDefaultYamlContent();
1198
+ } else {
1199
+ const defaultConfig = getDefaultConfig();
1200
+ configContent = JSON.stringify(defaultConfig, null, 2) + "\n";
1201
+ }
1202
+ try {
1203
+ fs2.writeFileSync(configPath, configContent, "utf-8");
1204
+ console.log(chalk12.green(`\u2713 Created ${targetFilename}`));
1205
+ for (const filename of REPLICAS_CONFIG_FILENAMES) {
1206
+ if (filename === targetFilename) break;
1207
+ const higherPath = path3.join(process.cwd(), filename);
1208
+ if (fs2.existsSync(higherPath)) {
1209
+ console.log("");
1210
+ console.log(
1211
+ chalk12.yellow(
1212
+ `Warning: ${filename} already exists and takes priority over ${targetFilename}.`
1213
+ )
1214
+ );
1215
+ console.log(chalk12.gray(`Remove or rename ${filename} for ${targetFilename} to take effect.`));
1216
+ }
1217
+ }
1218
+ console.log("");
1219
+ console.log(chalk12.gray("Configuration options:"));
1220
+ console.log(
1221
+ chalk12.gray(" systemPrompt - Custom instructions for AI coding assistants")
1222
+ );
1223
+ console.log(
1224
+ chalk12.gray(" startHook - Commands to run on workspace startup")
1225
+ );
1226
+ if (!isYaml) {
1227
+ console.log("");
1228
+ console.log(chalk12.gray("Tip: Use --yaml for YAML format with multiline system prompt support."));
1229
+ }
1230
+ } catch (error) {
1231
+ throw new Error(
1232
+ `Failed to create ${targetFilename}: ${error instanceof Error ? error.message : "Unknown error"}`
1233
+ );
1234
+ }
1235
+ }
1236
+
1237
+ // src/lib/version-check.ts
1238
+ import chalk13 from "chalk";
1239
+ var MONOLITH_URL2 = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
1240
+ var VERSION_CHECK_TIMEOUT = 2e3;
1241
+ function compareVersions(v1, v2) {
1242
+ const parts1 = v1.split(".").map(Number);
1243
+ const parts2 = v2.split(".").map(Number);
1244
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1245
+ const num1 = parts1[i] || 0;
1246
+ const num2 = parts2[i] || 0;
1247
+ if (num1 > num2) return 1;
1248
+ if (num1 < num2) return -1;
1249
+ }
1250
+ return 0;
1251
+ }
1252
+ async function checkForUpdates(currentVersion) {
1253
+ try {
1254
+ const controller = new AbortController();
1255
+ const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT);
1256
+ const response = await fetch(`${MONOLITH_URL2}/v1/cli/version`, {
1257
+ signal: controller.signal
1258
+ });
1259
+ clearTimeout(timeoutId);
1260
+ if (!response.ok) return;
1261
+ const data = await response.json();
1262
+ const latestVersion = data.version;
1263
+ if (compareVersions(latestVersion, currentVersion) > 0) {
1264
+ console.error(chalk13.yellow(`
1265
+ Update available: ${currentVersion} \u2192 ${latestVersion}`));
1266
+ console.error(chalk13.cyan(`Run: curl -fsSL https://replicas.dev/install.sh | bash
1267
+ `));
1268
+ }
1269
+ } catch {
1270
+ }
1271
+ }
1272
+
1273
+ // src/commands/replica.ts
1274
+ import chalk14 from "chalk";
1275
+ import prompts3 from "prompts";
1276
+ function formatDate(dateString) {
1277
+ return new Date(dateString).toLocaleString();
1278
+ }
1279
+ function formatStatus(status) {
1280
+ switch (status) {
1281
+ case "active":
1282
+ return chalk14.green(status);
1283
+ case "sleeping":
1284
+ return chalk14.gray(status);
1285
+ default:
1286
+ return status;
1287
+ }
1288
+ }
1289
+ function truncate(text, maxLength) {
1290
+ if (text.length <= maxLength) return text;
1291
+ return text.substring(0, maxLength) + "...";
1292
+ }
1293
+ function formatDisplayMessage(message) {
1294
+ const time = new Date(message.timestamp).toLocaleTimeString();
1295
+ switch (message.type) {
1296
+ case "user":
1297
+ console.log(chalk14.blue(`
1298
+ [${time}] USER:`));
1299
+ console.log(chalk14.white(` ${truncate(message.content, 500)}`));
1300
+ break;
1301
+ case "agent":
1302
+ console.log(chalk14.green(`
1303
+ [${time}] ASSISTANT:`));
1304
+ console.log(chalk14.white(` ${truncate(message.content, 500)}`));
1305
+ break;
1306
+ case "reasoning":
1307
+ console.log(chalk14.gray(`
1308
+ [${time}] THINKING:`));
1309
+ console.log(chalk14.gray(` ${truncate(message.content, 300)}`));
1310
+ break;
1311
+ case "command":
1312
+ console.log(chalk14.magenta(`
1313
+ [${time}] COMMAND:`));
1314
+ console.log(chalk14.white(` $ ${message.command}`));
1315
+ if (message.output) {
1316
+ console.log(chalk14.gray(` ${truncate(message.output, 200)}`));
1317
+ }
1318
+ if (message.exitCode !== void 0) {
1319
+ const exitColor = message.exitCode === 0 ? chalk14.green : chalk14.red;
1320
+ console.log(exitColor(` Exit code: ${message.exitCode}`));
1321
+ }
1322
+ break;
1323
+ case "file_change":
1324
+ console.log(chalk14.yellow(`
1325
+ [${time}] FILE CHANGES:`));
1326
+ for (const change of message.changes) {
1327
+ const icon = change.kind === "add" ? "+" : change.kind === "delete" ? "-" : "~";
1328
+ const color = change.kind === "add" ? chalk14.green : change.kind === "delete" ? chalk14.red : chalk14.yellow;
1329
+ console.log(color(` ${icon} ${change.path}`));
1330
+ }
1331
+ break;
1332
+ case "patch":
1333
+ console.log(chalk14.yellow(`
1334
+ [${time}] PATCH:`));
1335
+ for (const op of message.operations) {
1336
+ const icon = op.action === "add" ? "+" : op.action === "delete" ? "-" : "~";
1337
+ const color = op.action === "add" ? chalk14.green : op.action === "delete" ? chalk14.red : chalk14.yellow;
1338
+ console.log(color(` ${icon} ${op.path}`));
1339
+ }
1340
+ break;
1341
+ case "tool_call":
1342
+ console.log(chalk14.cyan(`
1343
+ [${time}] TOOL: ${message.tool}`));
1344
+ if (message.output) {
1345
+ console.log(chalk14.gray(` ${truncate(message.output, 200)}`));
1346
+ }
1347
+ break;
1348
+ case "web_search":
1349
+ console.log(chalk14.cyan(`
1350
+ [${time}] WEB SEARCH:`));
1351
+ console.log(chalk14.white(` "${message.query}"`));
1352
+ break;
1353
+ case "todo_list":
1354
+ console.log(chalk14.blue(`
1355
+ [${time}] PLAN:`));
1356
+ for (const item of message.items) {
1357
+ const icon = item.completed ? chalk14.green("[x]") : chalk14.gray("[ ]");
1358
+ console.log(` ${icon} ${item.text}`);
1359
+ }
1360
+ break;
1361
+ case "subagent":
1362
+ console.log(chalk14.magenta(`
1363
+ [${time}] SUBAGENT: ${message.description}`));
1364
+ if (message.output) {
1365
+ console.log(chalk14.gray(` ${truncate(message.output, 200)}`));
1366
+ }
1367
+ break;
1368
+ case "error":
1369
+ console.log(chalk14.red(`
1370
+ [${time}] ERROR:`));
1371
+ console.log(chalk14.red(` ${message.message}`));
1372
+ break;
1373
+ }
1374
+ }
1375
+ async function replicaListCommand(options) {
1376
+ if (!isAuthenticated()) {
1377
+ console.log(chalk14.red('Not logged in. Please run "replicas login" first.'));
1378
+ process.exit(1);
1379
+ }
1380
+ try {
1381
+ const params = new URLSearchParams();
1382
+ if (options.page) params.set("page", options.page);
1383
+ if (options.limit) params.set("limit", options.limit);
1384
+ const query = params.toString();
1385
+ const response = await orgAuthenticatedFetch(
1386
+ `/v1/replica${query ? "?" + query : ""}`
1387
+ );
1388
+ if (response.replicas.length === 0) {
1389
+ console.log(chalk14.yellow("\nNo replicas found.\n"));
1390
+ return;
1391
+ }
1392
+ console.log(chalk14.green(`
1393
+ Replicas (Page ${response.page} of ${response.totalPages}, Total: ${response.total}):
1394
+ `));
1395
+ for (const replica of response.replicas) {
1396
+ console.log(chalk14.white(` ${replica.name}`));
1397
+ console.log(chalk14.gray(` ID: ${replica.id}`));
1398
+ if (replica.repositories.length > 0) {
1399
+ console.log(chalk14.gray(` Repositories: ${replica.repositories.map((repository) => repository.name).join(", ")}`));
1400
+ }
1401
+ console.log(chalk14.gray(` Status: ${formatStatus(replica.status)}`));
1402
+ console.log(chalk14.gray(` Created: ${formatDate(replica.created_at)}`));
1403
+ if (replica.pull_requests && replica.pull_requests.length > 0) {
1404
+ console.log(chalk14.gray(` Pull Requests:`));
1405
+ for (const pr of replica.pull_requests) {
1406
+ console.log(chalk14.cyan(` - ${pr.repository} #${pr.number}: ${pr.url}`));
1407
+ }
1408
+ }
1409
+ console.log();
1410
+ }
1411
+ } catch (error) {
1412
+ console.error(chalk14.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1413
+ process.exit(1);
1414
+ }
1415
+ }
1416
+ async function replicaGetCommand(id) {
1417
+ if (!isAuthenticated()) {
1418
+ console.log(chalk14.red('Not logged in. Please run "replicas login" first.'));
1419
+ process.exit(1);
1420
+ }
1421
+ try {
1422
+ const response = await orgAuthenticatedFetch(`/v1/replica/${id}`);
1423
+ const replica = response.replica;
1424
+ console.log(chalk14.green(`
1425
+ Replica: ${replica.name}
1426
+ `));
1427
+ console.log(chalk14.gray(` ID: ${replica.id}`));
1428
+ if (replica.repositories.length > 0) {
1429
+ console.log(chalk14.gray(` Repositories: ${replica.repositories.map((repository) => repository.name).join(", ")}`));
1430
+ }
1431
+ console.log(chalk14.gray(` Status: ${formatStatus(replica.status)}`));
1432
+ console.log(chalk14.gray(` Created: ${formatDate(replica.created_at)}`));
1433
+ if (replica.waking) {
1434
+ console.log(chalk14.yellow("\n Workspace is waking from sleep. Retry in 30-90 seconds for full details.\n"));
1435
+ } else {
1436
+ if (replica.coding_agent) {
1437
+ console.log(chalk14.gray(` Coding Agent: ${replica.coding_agent}`));
1438
+ }
1439
+ if (replica.repository_statuses && replica.repository_statuses.length > 0) {
1440
+ console.log(chalk14.gray(" Repository Statuses:"));
1441
+ for (const repositoryStatus of replica.repository_statuses) {
1442
+ const changeText = repositoryStatus.git_diff ? ` (${chalk14.green(`+${repositoryStatus.git_diff.added}`)} / ${chalk14.red(`-${repositoryStatus.git_diff.removed}`)})` : "";
1443
+ console.log(chalk14.gray(` - ${repositoryStatus.repository}: ${repositoryStatus.branch || "unknown"}${changeText}`));
1444
+ }
1445
+ }
1446
+ }
1447
+ if (replica.pull_requests && replica.pull_requests.length > 0) {
1448
+ console.log(chalk14.gray(` Pull Requests:`));
1449
+ for (const pr of replica.pull_requests) {
1450
+ console.log(chalk14.cyan(` - ${pr.repository} #${pr.number}: ${pr.url}`));
1451
+ }
1452
+ }
1453
+ console.log();
1454
+ } catch (error) {
1455
+ console.error(chalk14.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1456
+ process.exit(1);
1457
+ }
1458
+ }
1459
+ async function replicaCreateCommand(name, options) {
1460
+ if (!isAuthenticated()) {
1461
+ console.log(chalk14.red('Not logged in. Please run "replicas login" first.'));
1462
+ process.exit(1);
1463
+ }
1464
+ try {
1465
+ const repoResponse = await orgAuthenticatedFetch("/v1/repositories");
1466
+ const repositories = repoResponse.repositories;
1467
+ if (repositories.length === 0) {
1468
+ console.log(chalk14.red("No repositories found. Please add a repository first."));
1469
+ process.exit(1);
1470
+ }
1471
+ let replicaName = name;
1472
+ let message = options.message;
1473
+ let selectedRepositories = options.repositories?.split(",").map((repository) => repository.trim()).filter((repository) => repository.length > 0);
1474
+ let codingAgent = options.agent;
1475
+ if (replicaName && /\s/.test(replicaName)) {
1476
+ console.log(chalk14.red("Replica name cannot contain spaces."));
1477
+ process.exit(1);
1478
+ }
1479
+ if (!replicaName) {
1480
+ const response2 = await prompts3({
1481
+ type: "text",
1482
+ name: "name",
1483
+ message: "Enter replica name (without spaces):",
1484
+ validate: (value) => {
1485
+ if (!value.trim()) return "Name is required";
1486
+ if (/\s/.test(value)) return "Name cannot contain spaces";
1487
+ return true;
1488
+ }
1489
+ });
1490
+ if (!response2.name) {
1491
+ console.log(chalk14.yellow("\nCancelled."));
1492
+ return;
1493
+ }
1494
+ replicaName = response2.name;
1495
+ }
1496
+ if (!selectedRepositories || selectedRepositories.length === 0) {
1497
+ const response2 = await prompts3({
1498
+ type: "multiselect",
1499
+ name: "repositories",
1500
+ message: "Select repositories:",
1501
+ choices: repositories.map((repo) => ({
1502
+ title: repo.name,
1503
+ value: repo.name,
1504
+ description: repo.url
1505
+ })),
1506
+ min: 1
1507
+ });
1508
+ if (!response2.repositories || response2.repositories.length === 0) {
1509
+ console.log(chalk14.yellow("\nCancelled."));
1510
+ return;
1511
+ }
1512
+ selectedRepositories = response2.repositories;
1513
+ }
1514
+ if (!message) {
1515
+ const response2 = await prompts3({
1516
+ type: "text",
1517
+ name: "message",
1518
+ message: "Enter initial message for the replica:",
1519
+ validate: (value) => value.trim() ? true : "Message is required"
1520
+ });
1521
+ if (!response2.message) {
1522
+ console.log(chalk14.yellow("\nCancelled."));
1523
+ return;
1524
+ }
1525
+ message = response2.message;
1526
+ }
1527
+ if (!codingAgent) {
1528
+ const response2 = await prompts3({
1529
+ type: "select",
1530
+ name: "agent",
1531
+ message: "Select coding agent:",
1532
+ choices: [
1533
+ { title: "Claude", value: "claude" },
1534
+ { title: "Codex", value: "codex" }
1535
+ ],
1536
+ initial: 0
1537
+ });
1538
+ if (!response2.agent) {
1539
+ console.log(chalk14.yellow("\nCancelled."));
1540
+ return;
1541
+ }
1542
+ codingAgent = response2.agent;
1543
+ }
1544
+ if (!codingAgent || !["claude", "codex"].includes(codingAgent)) {
1545
+ console.log(chalk14.red(`Invalid coding agent: ${codingAgent}. Must be one of: claude, codex`));
1546
+ process.exit(1);
1547
+ }
1548
+ const body = {
1549
+ name: replicaName,
1550
+ message,
1551
+ repositories: selectedRepositories,
1552
+ coding_agent: codingAgent
1553
+ };
1554
+ console.log(chalk14.gray("\nCreating replica..."));
1555
+ const response = await orgAuthenticatedFetch("/v1/replica", {
1556
+ method: "POST",
1557
+ body
1558
+ });
1559
+ const replica = response.replica;
1560
+ console.log(chalk14.green(`
1561
+ Created replica: ${replica.name}`));
1562
+ console.log(chalk14.gray(` ID: ${replica.id}`));
1563
+ console.log(chalk14.gray(` Status: ${formatStatus(replica.status)}`));
1564
+ if (replica.repositories.length > 0) {
1565
+ console.log(chalk14.gray(` Repositories: ${replica.repositories.map((repository) => repository.name).join(", ")}`));
1566
+ }
1567
+ console.log();
1568
+ } catch (error) {
1569
+ console.error(chalk14.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1570
+ process.exit(1);
1571
+ }
1572
+ }
1573
+ async function replicaSendCommand(id, options) {
1574
+ if (!isAuthenticated()) {
1575
+ console.log(chalk14.red('Not logged in. Please run "replicas login" first.'));
1576
+ process.exit(1);
1577
+ }
1578
+ try {
1579
+ let message = options.message;
1580
+ if (!message) {
1581
+ const response2 = await prompts3({
1582
+ type: "text",
1583
+ name: "message",
1584
+ message: "Enter message to send:",
1585
+ validate: (value) => value.trim() ? true : "Message is required"
1586
+ });
1587
+ if (!response2.message) {
1588
+ console.log(chalk14.yellow("\nCancelled."));
1589
+ return;
1590
+ }
1591
+ message = response2.message;
1592
+ }
1593
+ const body = {
1594
+ message
1595
+ };
1596
+ if (options.agent) {
1597
+ if (!["claude", "codex"].includes(options.agent)) {
1598
+ console.log(chalk14.red(`Invalid coding agent: ${options.agent}. Must be one of: claude, codex`));
1599
+ process.exit(1);
1600
+ }
1601
+ body.coding_agent = options.agent;
1602
+ }
1603
+ const response = await orgAuthenticatedFetch(
1604
+ `/v1/replica/${id}/send`,
1605
+ {
1606
+ method: "POST",
1607
+ body
1608
+ }
1609
+ );
1610
+ const statusColor = response.status === "sent" ? chalk14.green : chalk14.yellow;
1611
+ console.log(statusColor(`
1612
+ Message ${response.status}`));
1613
+ if (response.position !== void 0 && response.position > 0) {
1614
+ console.log(chalk14.gray(` Queue position: ${response.position}`));
1615
+ }
1616
+ console.log();
1617
+ } catch (error) {
1618
+ console.error(chalk14.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1619
+ process.exit(1);
1620
+ }
1621
+ }
1622
+ async function replicaDeleteCommand(id, options) {
1623
+ if (!isAuthenticated()) {
1624
+ console.log(chalk14.red('Not logged in. Please run "replicas login" first.'));
1625
+ process.exit(1);
1626
+ }
1627
+ try {
1628
+ if (!options.force) {
1629
+ const response = await prompts3({
1630
+ type: "confirm",
1631
+ name: "confirm",
1632
+ message: `Are you sure you want to delete replica ${id}?`,
1633
+ initial: false
1634
+ });
1635
+ if (!response.confirm) {
1636
+ console.log(chalk14.yellow("\nCancelled."));
1637
+ return;
1638
+ }
1639
+ }
1640
+ await orgAuthenticatedFetch(`/v1/replica/${id}`, {
1641
+ method: "DELETE"
1642
+ });
1643
+ console.log(chalk14.green(`
1644
+ Replica ${id} deleted.
1645
+ `));
1646
+ } catch (error) {
1647
+ console.error(chalk14.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1648
+ process.exit(1);
1649
+ }
1650
+ }
1651
+ async function replicaReadCommand(id, options) {
1652
+ if (!isAuthenticated()) {
1653
+ console.log(chalk14.red('Not logged in. Please run "replicas login" first.'));
1654
+ process.exit(1);
1655
+ }
1656
+ try {
1657
+ const params = new URLSearchParams();
1658
+ if (options.limit) params.set("limit", options.limit);
1659
+ if (options.offset) params.set("offset", options.offset);
1660
+ const query = params.toString();
1661
+ const response = await orgAuthenticatedFetch(
1662
+ `/v1/replica/${id}/read${query ? "?" + query : ""}`
1663
+ );
1664
+ if (response.waking) {
1665
+ console.log(chalk14.yellow("\nWorkspace is waking from sleep. Retry in 30-90 seconds.\n"));
1666
+ return;
1667
+ }
1668
+ console.log(chalk14.green(`
1669
+ Conversation History
1670
+ `));
1671
+ if (response.coding_agent) {
1672
+ console.log(chalk14.gray(` Agent: ${response.coding_agent}`));
1673
+ }
1674
+ if (response.thread_id) {
1675
+ console.log(chalk14.gray(` Thread ID: ${response.thread_id}`));
1676
+ }
1677
+ console.log(chalk14.gray(` Total Events: ${response.total}`));
1678
+ console.log(chalk14.gray(` Showing: ${response.events.length} events`));
1679
+ if (response.hasMore) {
1680
+ console.log(chalk14.gray(` Has More: yes (use --offset to paginate)`));
1681
+ }
1682
+ console.log();
1683
+ if (response.events.length === 0) {
1684
+ console.log(chalk14.yellow(" No events found.\n"));
1685
+ return;
1686
+ }
1687
+ console.log(chalk14.gray("-".repeat(60)));
1688
+ const agentType = response.coding_agent || "claude";
1689
+ const displayMessages = parseAgentEvents(response.events, agentType);
1690
+ for (const message of displayMessages) {
1691
+ formatDisplayMessage(message);
1692
+ }
1693
+ console.log(chalk14.gray("\n" + "-".repeat(60)));
1694
+ console.log();
1695
+ } catch (error) {
1696
+ console.error(chalk14.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1697
+ process.exit(1);
1698
+ }
1699
+ }
1700
+
1701
+ // src/commands/repositories.ts
1702
+ import chalk15 from "chalk";
1703
+ function formatDate2(dateString) {
1704
+ return new Date(dateString).toLocaleString();
1705
+ }
1706
+ async function repositoriesListCommand() {
1707
+ if (!isAuthenticated()) {
1708
+ console.log(chalk15.red('Not logged in. Please run "replicas login" first.'));
1709
+ process.exit(1);
1710
+ }
1711
+ try {
1712
+ const response = await orgAuthenticatedFetch("/v1/repositories");
1713
+ if (response.repositories.length === 0) {
1714
+ console.log(chalk15.yellow("\nNo repositories found.\n"));
1715
+ return;
1716
+ }
1717
+ console.log(chalk15.green(`
1718
+ Repositories (${response.repositories.length}):
1719
+ `));
1720
+ for (const repo of response.repositories) {
1721
+ console.log(chalk15.white(` ${repo.name}`));
1722
+ console.log(chalk15.gray(` URL: ${repo.url}`));
1723
+ console.log(chalk15.gray(` Default Branch: ${repo.default_branch}`));
1724
+ console.log(chalk15.gray(` Created: ${formatDate2(repo.created_at)}`));
1725
+ if (repo.github_repository_id) {
1726
+ console.log(chalk15.gray(` GitHub ID: ${repo.github_repository_id}`));
1727
+ }
1728
+ console.log();
1729
+ }
1730
+ } catch (error) {
1731
+ console.error(chalk15.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1732
+ process.exit(1);
1733
+ }
1734
+ }
1735
+
1736
+ // src/commands/preview.ts
1737
+ import chalk16 from "chalk";
1738
+
1739
+ // src/lib/agent-api.ts
1740
+ var MONOLITH_URL3 = process.env.MONOLITH_URL || process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
1741
+ var ENGINE_PORT = process.env.REPLICAS_ENGINE_PORT || "3737";
1742
+ async function agentFetch(path4, options) {
1743
+ const config2 = readAgentConfig();
1744
+ if (!config2) {
1745
+ throw new Error("Agent mode config not found");
1746
+ }
1747
+ const headers = {
1748
+ "Authorization": `Bearer ${config2.engine_secret}`,
1749
+ "X-Workspace-Id": config2.workspace_id,
1750
+ "Content-Type": "application/json",
1751
+ ...options?.headers || {}
1752
+ };
1753
+ const response = await fetch(`${MONOLITH_URL3}${path4}`, {
1754
+ ...options,
1755
+ headers,
1756
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
1757
+ });
1758
+ if (!response.ok) {
1759
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
1760
+ throw new Error(error.error || `Request failed with status ${response.status}`);
1761
+ }
1762
+ return response.json();
1763
+ }
1764
+ async function engineFetch(path4, options) {
1765
+ const config2 = readAgentConfig();
1766
+ if (!config2) {
1767
+ throw new Error("Agent mode config not found");
1768
+ }
1769
+ const headers = {
1770
+ "X-Replicas-Engine-Secret": config2.engine_secret,
1771
+ "Content-Type": "application/json",
1772
+ ...options?.headers || {}
1773
+ };
1774
+ const response = await fetch(`http://localhost:${ENGINE_PORT}${path4}`, {
1775
+ ...options,
1776
+ headers,
1777
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
1778
+ });
1779
+ if (!response.ok) {
1780
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
1781
+ throw new Error(error.error || `Request failed with status ${response.status}`);
1782
+ }
1783
+ return response.json();
1784
+ }
1785
+
1786
+ // src/commands/preview.ts
1787
+ async function previewCreateCommand(port, options) {
1788
+ const portNum = parseInt(port, 10);
1789
+ if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
1790
+ throw new Error("Port must be a number between 1 and 65535");
1791
+ }
1792
+ if (isAgentMode()) {
1793
+ const result = await agentFetch("/v1/previews", {
1794
+ method: "POST",
1795
+ body: { port: portNum, authenticated: options.authenticated ?? false }
1796
+ });
1797
+ console.log(result.preview.publicUrl);
1798
+ } else {
1799
+ throw new Error("In human mode, use: replicas preview add <workspaceId> --port <port>");
1800
+ }
1801
+ }
1802
+ async function previewListCommand(workspaceId) {
1803
+ if (isAgentMode()) {
1804
+ const result = await engineFetch("/previews");
1805
+ if (result.previews.length === 0) {
1806
+ console.log("No active previews");
1807
+ return;
1808
+ }
1809
+ for (const preview2 of result.previews) {
1810
+ console.log(`${preview2.port} -> ${preview2.publicUrl}`);
1811
+ }
1812
+ } else {
1813
+ if (!workspaceId) {
1814
+ throw new Error("Workspace ID is required: replicas preview list <workspaceId>");
1815
+ }
1816
+ const result = await orgAuthenticatedFetch(
1817
+ `/v1/workspaces/${workspaceId}/previews`
1818
+ );
1819
+ if (result.previews.length === 0) {
1820
+ console.log(chalk16.dim("No active previews"));
1821
+ return;
1822
+ }
1823
+ for (const preview2 of result.previews) {
1824
+ console.log(` ${chalk16.cyan(String(preview2.port))} \u2192 ${chalk16.underline(preview2.publicUrl)}`);
1825
+ }
1826
+ }
1827
+ }
1828
+ async function previewAddCommand(workspaceId, options) {
1829
+ const portNum = parseInt(options.port, 10);
1830
+ if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
1831
+ throw new Error("Port must be a number between 1 and 65535");
1832
+ }
1833
+ const result = await orgAuthenticatedFetch(
1834
+ `/v1/workspaces/${workspaceId}/previews`,
1835
+ {
1836
+ method: "POST",
1837
+ body: { port: portNum, authenticated: options.authenticated ?? false }
1838
+ }
1839
+ );
1840
+ console.log(chalk16.green(`Preview created: ${result.preview.publicUrl}`));
1841
+ }
1842
+
1843
+ // src/commands/interactive.ts
1844
+ import chalk17 from "chalk";
1845
+ async function interactiveCommand() {
1846
+ if (!isAuthenticated()) {
1847
+ throw new Error(
1848
+ 'Not authenticated. Please run "replicas login" first.'
1849
+ );
1850
+ }
1851
+ const organizationId = getOrganizationId();
1852
+ if (!organizationId) {
1853
+ throw new Error(
1854
+ 'No organization selected. Please run "replicas org switch" to select an organization.'
1855
+ );
1856
+ }
1857
+ console.log(chalk17.gray("Starting interactive mode..."));
1858
+ const { launchInteractive } = await import("./interactive-EQDMW2E3.mjs");
1859
+ await launchInteractive();
1860
+ }
1861
+
1862
+ // src/index.ts
1863
+ var CLI_VERSION = "0.2.41";
1864
+ var program = new Command();
1865
+ program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
1866
+ program.command("login").description("Authenticate with your Replicas account").action(async () => {
1867
+ try {
1868
+ await loginCommand();
1869
+ } catch (error) {
1870
+ if (error instanceof Error) {
1871
+ console.error(chalk18.red(`
1872
+ \u2717 ${error.message}
1873
+ `));
1874
+ }
1875
+ process.exit(1);
1876
+ }
1877
+ });
1878
+ program.command("init").description("Create a replicas.json or replicas.yaml configuration file").option("-f, --force", "Overwrite existing config file").option("-y, --yaml", "Create replicas.yaml instead of replicas.json").action((options) => {
1879
+ try {
1880
+ initCommand(options);
1881
+ } catch (error) {
1882
+ if (error instanceof Error) {
1883
+ console.error(chalk18.red(`
1884
+ \u2717 ${error.message}
1885
+ `));
1886
+ }
1887
+ process.exit(1);
1888
+ }
1889
+ });
1890
+ program.command("logout").description("Clear stored credentials").action(() => {
1891
+ try {
1892
+ logoutCommand();
1893
+ } catch (error) {
1894
+ if (error instanceof Error) {
1895
+ console.error(chalk18.red(`
1896
+ \u2717 ${error.message}
1897
+ `));
1898
+ }
1899
+ process.exit(1);
1900
+ }
1901
+ });
1902
+ program.command("whoami").description("Display current authenticated user").action(async () => {
1903
+ try {
1904
+ await whoamiCommand();
1905
+ } catch (error) {
1906
+ if (error instanceof Error) {
1907
+ console.error(chalk18.red(`
1908
+ \u2717 ${error.message}
1909
+ `));
1910
+ }
1911
+ process.exit(1);
1912
+ }
1913
+ });
1914
+ program.command("codex-auth").description("Authenticate Replicas with your Codex credentials").option("-u, --user", "Store credentials for your personal account").action(async (options) => {
1915
+ try {
1916
+ await codexAuthCommand(options);
1917
+ } catch (error) {
1918
+ if (error instanceof Error) {
1919
+ console.error(chalk18.red(`
1920
+ \u2717 ${error.message}
1921
+ `));
1922
+ }
1923
+ process.exit(1);
1924
+ }
1925
+ });
1926
+ program.command("claude-auth").description("Authenticate Replicas with your Claude Code credentials").option("-u, --user", "Store credentials for your personal account").action(async (options) => {
1927
+ try {
1928
+ await claudeAuthCommand(options);
1929
+ } catch (error) {
1930
+ if (error instanceof Error) {
1931
+ console.error(chalk18.red(`
1932
+ \u2717 ${error.message}
1933
+ `));
1934
+ }
1935
+ process.exit(1);
1936
+ }
1937
+ });
1938
+ var org = program.command("org").description("Manage organizations");
1939
+ org.command("switch").description("Switch to a different organization").action(async () => {
1940
+ try {
1941
+ await orgSwitchCommand();
1942
+ } catch (error) {
1943
+ if (error instanceof Error) {
1944
+ console.error(chalk18.red(`
1945
+ \u2717 ${error.message}
1946
+ `));
1947
+ }
1948
+ process.exit(1);
1949
+ }
1950
+ });
1951
+ org.action(async () => {
1952
+ try {
1953
+ await orgCommand();
1954
+ } catch (error) {
1955
+ if (error instanceof Error) {
1956
+ console.error(chalk18.red(`
1957
+ \u2717 ${error.message}
1958
+ `));
1959
+ }
1960
+ process.exit(1);
1961
+ }
1962
+ });
1963
+ program.command("connect <workspace-name>").description("Connect to a workspace via SSH").action(async (workspaceName) => {
1964
+ try {
1965
+ await connectCommand(workspaceName);
1966
+ } catch (error) {
1967
+ if (error instanceof Error) {
1968
+ console.error(chalk18.red(`
1969
+ \u2717 ${error.message}
1970
+ `));
1971
+ }
1972
+ process.exit(1);
1973
+ }
1974
+ });
1975
+ program.command("code <workspace-name>").description("Open a workspace in VSCode/Cursor via Remote SSH").action(async (workspaceName) => {
1976
+ try {
1977
+ await codeCommand(workspaceName);
1978
+ } catch (error) {
1979
+ if (error instanceof Error) {
1980
+ console.error(chalk18.red(`
1981
+ \u2717 ${error.message}
1982
+ `));
1983
+ }
1984
+ process.exit(1);
1985
+ }
1986
+ });
1987
+ var config = program.command("config").description("Manage CLI configuration");
1988
+ config.command("get <key>").description("Get a configuration value").action(async (key) => {
1989
+ try {
1990
+ await configGetCommand(key);
1991
+ } catch (error) {
1992
+ if (error instanceof Error) {
1993
+ console.error(chalk18.red(`
1994
+ \u2717 ${error.message}
1995
+ `));
1996
+ }
1997
+ process.exit(1);
1998
+ }
1999
+ });
2000
+ config.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
2001
+ try {
2002
+ await configSetCommand(key, value);
2003
+ } catch (error) {
2004
+ if (error instanceof Error) {
2005
+ console.error(chalk18.red(`
2006
+ \u2717 ${error.message}
2007
+ `));
2008
+ }
2009
+ process.exit(1);
2010
+ }
2011
+ });
2012
+ config.command("list").description("List all configuration values").action(async () => {
2013
+ try {
2014
+ await configListCommand();
2015
+ } catch (error) {
2016
+ if (error instanceof Error) {
2017
+ console.error(chalk18.red(`
2018
+ \u2717 ${error.message}
2019
+ `));
2020
+ }
2021
+ process.exit(1);
2022
+ }
2023
+ });
2024
+ program.command("list").description("List all replicas").option("-p, --page <page>", "Page number").option("-l, --limit <limit>", "Number of items per page").action(async (options) => {
2025
+ try {
2026
+ await replicaListCommand(options);
2027
+ } catch (error) {
2028
+ if (error instanceof Error) {
2029
+ console.error(chalk18.red(`
2030
+ \u2717 ${error.message}
2031
+ `));
2032
+ }
2033
+ process.exit(1);
2034
+ }
2035
+ });
2036
+ program.command("get <id>").description("Get replica details by ID").action(async (id) => {
2037
+ try {
2038
+ await replicaGetCommand(id);
2039
+ } catch (error) {
2040
+ if (error instanceof Error) {
2041
+ console.error(chalk18.red(`
2042
+ \u2717 ${error.message}
2043
+ `));
2044
+ }
2045
+ process.exit(1);
2046
+ }
2047
+ });
2048
+ program.command("create [name]").description("Create a new replica").option("-m, --message <message>", "Initial message for the replica").option("-r, --repositories <repositories>", "Comma-separated repository names").option("-a, --agent <agent>", "Coding agent (claude, codex)").action(async (name, options) => {
2049
+ try {
2050
+ await replicaCreateCommand(name, options);
2051
+ } catch (error) {
2052
+ if (error instanceof Error) {
2053
+ console.error(chalk18.red(`
2054
+ \u2717 ${error.message}
2055
+ `));
2056
+ }
2057
+ process.exit(1);
2058
+ }
2059
+ });
2060
+ program.command("send <id>").description("Send a message to a replica").option("-m, --message <message>", "Message to send").option("-a, --agent <agent>", "Coding agent (claude, codex)").action(async (id, options) => {
2061
+ try {
2062
+ await replicaSendCommand(id, options);
2063
+ } catch (error) {
2064
+ if (error instanceof Error) {
2065
+ console.error(chalk18.red(`
2066
+ \u2717 ${error.message}
2067
+ `));
2068
+ }
2069
+ process.exit(1);
2070
+ }
2071
+ });
2072
+ program.command("delete <id>").description("Delete a replica").option("-f, --force", "Skip confirmation prompt").action(async (id, options) => {
2073
+ try {
2074
+ await replicaDeleteCommand(id, options);
2075
+ } catch (error) {
2076
+ if (error instanceof Error) {
2077
+ console.error(chalk18.red(`
2078
+ \u2717 ${error.message}
2079
+ `));
2080
+ }
2081
+ process.exit(1);
2082
+ }
2083
+ });
2084
+ program.command("read <id>").description("Read conversation history of a replica").option("-l, --limit <limit>", "Maximum number of events to return").option("-o, --offset <offset>", "Number of events to skip from the end").action(async (id, options) => {
2085
+ try {
2086
+ await replicaReadCommand(id, options);
2087
+ } catch (error) {
2088
+ if (error instanceof Error) {
2089
+ console.error(chalk18.red(`
2090
+ \u2717 ${error.message}
2091
+ `));
2092
+ }
2093
+ process.exit(1);
2094
+ }
2095
+ });
2096
+ var repos = program.command("repos").description("Manage repositories");
2097
+ repos.command("list").description("List all repositories").action(async () => {
2098
+ try {
2099
+ await repositoriesListCommand();
2100
+ } catch (error) {
2101
+ if (error instanceof Error) {
2102
+ console.error(chalk18.red(`
2103
+ \u2717 ${error.message}
2104
+ `));
2105
+ }
2106
+ process.exit(1);
2107
+ }
2108
+ });
2109
+ repos.action(async () => {
2110
+ try {
2111
+ await repositoriesListCommand();
2112
+ } catch (error) {
2113
+ if (error instanceof Error) {
2114
+ console.error(chalk18.red(`
2115
+ \u2717 ${error.message}
2116
+ `));
2117
+ }
2118
+ process.exit(1);
2119
+ }
2120
+ });
2121
+ program.command("interactive").alias("i").description("Launch the interactive terminal dashboard").action(async () => {
2122
+ try {
2123
+ await interactiveCommand();
2124
+ } catch (error) {
2125
+ if (error instanceof Error) {
2126
+ console.error(chalk18.red(`
2127
+ \u2717 ${error.message}
2128
+ `));
2129
+ }
2130
+ process.exit(1);
2131
+ }
2132
+ });
2133
+ var preview = program.command("preview").description("Manage preview URLs");
2134
+ if (isAgentMode()) {
2135
+ preview.command("create <port>").description("Register a preview port").option(
2136
+ "-a, --authenticated",
2137
+ "Require cookie authentication for this preview. Use for user-facing frontends (e.g. web apps) where you want only logged-in Replicas users to access the preview. Do not use for backend APIs that are called by frontend code, as the frontend cannot forward the auth cookie."
2138
+ ).action(async (port, options) => {
2139
+ try {
2140
+ await previewCreateCommand(port, options);
2141
+ } catch (error) {
2142
+ if (error instanceof Error) {
2143
+ console.error(`Error: ${error.message}`);
2144
+ }
2145
+ process.exit(1);
2146
+ }
2147
+ });
2148
+ preview.command("list").description("List active preview URLs").action(async () => {
2149
+ try {
2150
+ await previewListCommand();
2151
+ } catch (error) {
2152
+ if (error instanceof Error) {
2153
+ console.error(`Error: ${error.message}`);
2154
+ }
2155
+ process.exit(1);
2156
+ }
2157
+ });
2158
+ } else {
2159
+ preview.command("add <workspaceId>").description("Register a preview port for a workspace").requiredOption("-p, --port <port>", "Port number to preview").option(
2160
+ "-a, --authenticated",
2161
+ "Require cookie authentication for this preview. Use for user-facing frontends (e.g. web apps) where you want only logged-in Replicas users to access the preview. Do not use for backend APIs that are called by frontend code, as the frontend cannot forward the auth cookie."
2162
+ ).action(async (workspaceId, options) => {
2163
+ try {
2164
+ await previewAddCommand(workspaceId, options);
2165
+ } catch (error) {
2166
+ if (error instanceof Error) {
2167
+ console.error(chalk18.red(`
2168
+ \u2717 ${error.message}
2169
+ `));
2170
+ }
2171
+ process.exit(1);
2172
+ }
2173
+ });
2174
+ preview.command("list [workspaceId]").description("List active preview URLs for a workspace").action(async (workspaceId) => {
2175
+ try {
2176
+ await previewListCommand(workspaceId);
2177
+ } catch (error) {
2178
+ if (error instanceof Error) {
2179
+ console.error(chalk18.red(`
2180
+ \u2717 ${error.message}
2181
+ `));
2182
+ }
2183
+ process.exit(1);
2184
+ }
2185
+ });
2186
+ }
2187
+ if (isAgentMode()) {
2188
+ const previewCmd = program.commands.find((cmd) => cmd.name() === "preview");
2189
+ const cmds = program.commands;
2190
+ cmds.length = 0;
2191
+ if (previewCmd) {
2192
+ cmds.push(previewCmd);
2193
+ }
2194
+ }
2195
+ var versionCheckPromise = checkForUpdates(CLI_VERSION);
2196
+ program.parse();
2197
+ versionCheckPromise.catch(() => {
2198
+ });
2199
+ export {
2200
+ CLI_VERSION
2201
+ };