replicas-cli 0.2.23 → 0.2.26

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.
Files changed (125) hide show
  1. package/dist/index.js +2877 -300
  2. package/package.json +4 -2
  3. package/dist/commands/claude-auth.d.ts +0 -5
  4. package/dist/commands/claude-auth.d.ts.map +0 -1
  5. package/dist/commands/claude-auth.js +0 -52
  6. package/dist/commands/claude-auth.js.map +0 -1
  7. package/dist/commands/code.d.ts +0 -4
  8. package/dist/commands/code.d.ts.map +0 -1
  9. package/dist/commands/code.js +0 -67
  10. package/dist/commands/code.js.map +0 -1
  11. package/dist/commands/codex-auth.d.ts +0 -5
  12. package/dist/commands/codex-auth.d.ts.map +0 -1
  13. package/dist/commands/codex-auth.js +0 -52
  14. package/dist/commands/codex-auth.js.map +0 -1
  15. package/dist/commands/config.d.ts +0 -4
  16. package/dist/commands/config.d.ts.map +0 -1
  17. package/dist/commands/config.js +0 -74
  18. package/dist/commands/config.js.map +0 -1
  19. package/dist/commands/connect.d.ts +0 -4
  20. package/dist/commands/connect.d.ts.map +0 -1
  21. package/dist/commands/connect.js +0 -40
  22. package/dist/commands/connect.js.map +0 -1
  23. package/dist/commands/dev-sync.d.ts +0 -2
  24. package/dist/commands/dev-sync.d.ts.map +0 -1
  25. package/dist/commands/dev-sync.js +0 -78
  26. package/dist/commands/dev-sync.js.map +0 -1
  27. package/dist/commands/init.d.ts +0 -4
  28. package/dist/commands/init.d.ts.map +0 -1
  29. package/dist/commands/init.js +0 -45
  30. package/dist/commands/init.js.map +0 -1
  31. package/dist/commands/login.d.ts +0 -2
  32. package/dist/commands/login.d.ts.map +0 -1
  33. package/dist/commands/login.js +0 -203
  34. package/dist/commands/login.js.map +0 -1
  35. package/dist/commands/logout.d.ts +0 -2
  36. package/dist/commands/logout.d.ts.map +0 -1
  37. package/dist/commands/logout.js +0 -17
  38. package/dist/commands/logout.js.map +0 -1
  39. package/dist/commands/org.d.ts +0 -3
  40. package/dist/commands/org.d.ts.map +0 -1
  41. package/dist/commands/org.js +0 -83
  42. package/dist/commands/org.js.map +0 -1
  43. package/dist/commands/replica.d.ts +0 -19
  44. package/dist/commands/replica.d.ts.map +0 -1
  45. package/dist/commands/replica.js +0 -289
  46. package/dist/commands/replica.js.map +0 -1
  47. package/dist/commands/repositories.d.ts +0 -2
  48. package/dist/commands/repositories.d.ts.map +0 -1
  49. package/dist/commands/repositories.js +0 -41
  50. package/dist/commands/repositories.js.map +0 -1
  51. package/dist/commands/tools.d.ts +0 -2
  52. package/dist/commands/tools.d.ts.map +0 -1
  53. package/dist/commands/tools.js +0 -70
  54. package/dist/commands/tools.js.map +0 -1
  55. package/dist/commands/whoami.d.ts +0 -2
  56. package/dist/commands/whoami.d.ts.map +0 -1
  57. package/dist/commands/whoami.js +0 -31
  58. package/dist/commands/whoami.js.map +0 -1
  59. package/dist/index.d.ts +0 -4
  60. package/dist/index.d.ts.map +0 -1
  61. package/dist/index.js.map +0 -1
  62. package/dist/lib/api.d.ts +0 -7
  63. package/dist/lib/api.d.ts.map +0 -1
  64. package/dist/lib/api.js +0 -81
  65. package/dist/lib/api.js.map +0 -1
  66. package/dist/lib/auth.d.ts +0 -4
  67. package/dist/lib/auth.d.ts.map +0 -1
  68. package/dist/lib/auth.js +0 -62
  69. package/dist/lib/auth.js.map +0 -1
  70. package/dist/lib/claude-oauth.d.ts +0 -10
  71. package/dist/lib/claude-oauth.d.ts.map +0 -1
  72. package/dist/lib/claude-oauth.js +0 -131
  73. package/dist/lib/claude-oauth.js.map +0 -1
  74. package/dist/lib/codex-oauth.d.ts +0 -11
  75. package/dist/lib/codex-oauth.d.ts.map +0 -1
  76. package/dist/lib/codex-oauth.js +0 -183
  77. package/dist/lib/codex-oauth.js.map +0 -1
  78. package/dist/lib/config.d.ts +0 -12
  79. package/dist/lib/config.d.ts.map +0 -1
  80. package/dist/lib/config.js +0 -84
  81. package/dist/lib/config.js.map +0 -1
  82. package/dist/lib/git.d.ts +0 -4
  83. package/dist/lib/git.d.ts.map +0 -1
  84. package/dist/lib/git.js +0 -39
  85. package/dist/lib/git.js.map +0 -1
  86. package/dist/lib/organization.d.ts +0 -5
  87. package/dist/lib/organization.d.ts.map +0 -1
  88. package/dist/lib/organization.js +0 -29
  89. package/dist/lib/organization.js.map +0 -1
  90. package/dist/lib/pkce.d.ts +0 -14
  91. package/dist/lib/pkce.d.ts.map +0 -1
  92. package/dist/lib/pkce.js +0 -37
  93. package/dist/lib/pkce.js.map +0 -1
  94. package/dist/lib/ports.d.ts +0 -3
  95. package/dist/lib/ports.d.ts.map +0 -1
  96. package/dist/lib/ports.js +0 -44
  97. package/dist/lib/ports.js.map +0 -1
  98. package/dist/lib/replicas-config.d.ts +0 -8
  99. package/dist/lib/replicas-config.d.ts.map +0 -1
  100. package/dist/lib/replicas-config.js +0 -64
  101. package/dist/lib/replicas-config.js.map +0 -1
  102. package/dist/lib/ssh-config.d.ts +0 -12
  103. package/dist/lib/ssh-config.d.ts.map +0 -1
  104. package/dist/lib/ssh-config.js +0 -122
  105. package/dist/lib/ssh-config.js.map +0 -1
  106. package/dist/lib/ssh.d.ts +0 -5
  107. package/dist/lib/ssh.d.ts.map +0 -1
  108. package/dist/lib/ssh.js +0 -82
  109. package/dist/lib/ssh.js.map +0 -1
  110. package/dist/lib/supabase.d.ts +0 -5
  111. package/dist/lib/supabase.d.ts.map +0 -1
  112. package/dist/lib/supabase.js +0 -15
  113. package/dist/lib/supabase.js.map +0 -1
  114. package/dist/lib/version-check.d.ts +0 -2
  115. package/dist/lib/version-check.d.ts.map +0 -1
  116. package/dist/lib/version-check.js +0 -44
  117. package/dist/lib/version-check.js.map +0 -1
  118. package/dist/lib/workspace-connection.d.ts +0 -11
  119. package/dist/lib/workspace-connection.d.ts.map +0 -1
  120. package/dist/lib/workspace-connection.js +0 -132
  121. package/dist/lib/workspace-connection.js.map +0 -1
  122. package/dist/types/index.d.ts +0 -52
  123. package/dist/types/index.d.ts.map +0 -1
  124. package/dist/types/index.js +0 -3
  125. package/dist/types/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,371 +1,2948 @@
1
1
  #!/usr/bin/env node
2
+ #!/usr/bin/env node
2
3
  "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
5
13
  };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.CLI_VERSION = void 0;
8
- require("dotenv/config");
9
- const commander_1 = require("commander");
10
- const chalk_1 = __importDefault(require("chalk"));
11
- const login_1 = require("./commands/login");
12
- const logout_1 = require("./commands/logout");
13
- const whoami_1 = require("./commands/whoami");
14
- const connect_1 = require("./commands/connect");
15
- const code_1 = require("./commands/code");
16
- const org_1 = require("./commands/org");
17
- const config_1 = require("./commands/config");
18
- const tools_1 = require("./commands/tools");
19
- const codex_auth_1 = require("./commands/codex-auth");
20
- const claude_auth_1 = require("./commands/claude-auth");
21
- const dev_sync_1 = require("./commands/dev-sync");
22
- const init_1 = require("./commands/init");
23
- const version_check_1 = require("./lib/version-check");
24
- const replica_1 = require("./commands/replica");
25
- const repositories_1 = require("./commands/repositories");
26
- exports.CLI_VERSION = '0.2.23';
27
- const program = new commander_1.Command();
28
- program
29
- .name('replicas')
30
- .description('CLI for managing Replicas workspaces')
31
- .version(exports.CLI_VERSION);
32
- program
33
- .command('login')
34
- .description('Authenticate with your Replicas account')
35
- .action(async () => {
36
- try {
37
- await (0, login_1.loginCommand)();
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
+ mod
29
+ ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
+
32
+ // src/index.ts
33
+ var index_exports = {};
34
+ __export(index_exports, {
35
+ CLI_VERSION: () => CLI_VERSION
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+ var import_config17 = require("dotenv/config");
39
+ var import_commander = require("commander");
40
+ var import_chalk18 = __toESM(require("chalk"));
41
+
42
+ // src/commands/login.ts
43
+ var import_http = __toESM(require("http"));
44
+ var import_url = require("url");
45
+ var import_open = __toESM(require("open"));
46
+ var import_chalk = __toESM(require("chalk"));
47
+
48
+ // src/lib/config.ts
49
+ var import_fs = __toESM(require("fs"));
50
+ var import_path = __toESM(require("path"));
51
+ var import_os = __toESM(require("os"));
52
+ var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".replicas");
53
+ var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
54
+ function ensureConfigDir() {
55
+ if (!import_fs.default.existsSync(CONFIG_DIR)) {
56
+ import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
57
+ }
58
+ }
59
+ function readConfig() {
60
+ if (!import_fs.default.existsSync(CONFIG_FILE)) {
61
+ return null;
62
+ }
63
+ try {
64
+ const data = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
65
+ return JSON.parse(data);
66
+ } catch (error) {
67
+ console.error("Error reading config file:", error);
68
+ return null;
69
+ }
70
+ }
71
+ function writeConfig(config2) {
72
+ ensureConfigDir();
73
+ try {
74
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), {
75
+ mode: 384
76
+ // Only owner can read/write
77
+ });
78
+ } catch (error) {
79
+ console.error("Error writing config file:", error);
80
+ throw error;
81
+ }
82
+ }
83
+ function deleteConfig() {
84
+ if (import_fs.default.existsSync(CONFIG_FILE)) {
85
+ import_fs.default.unlinkSync(CONFIG_FILE);
86
+ }
87
+ }
88
+ function isAuthenticated() {
89
+ const config2 = readConfig();
90
+ return config2 !== null && !!config2.access_token;
91
+ }
92
+ function setOrganizationId(organizationId) {
93
+ const config2 = readConfig();
94
+ if (!config2) {
95
+ throw new Error("No config file found. Please login first.");
96
+ }
97
+ config2.organization_id = organizationId;
98
+ writeConfig(config2);
99
+ }
100
+ function getOrganizationId() {
101
+ const config2 = readConfig();
102
+ return config2?.organization_id || null;
103
+ }
104
+ function setIdeCommand(ideCommand) {
105
+ const config2 = readConfig();
106
+ if (!config2) {
107
+ throw new Error("No config file found. Please login first.");
108
+ }
109
+ config2.ide_command = ideCommand;
110
+ writeConfig(config2);
111
+ }
112
+ function getIdeCommand() {
113
+ const config2 = readConfig();
114
+ return config2?.ide_command || "code";
115
+ }
116
+
117
+ // src/lib/supabase.ts
118
+ var import_supabase_js = require("@supabase/supabase-js");
119
+ var supabaseUrl = process.env.REPLICAS_SUPABASE_URL || "https://mxeqiomwgdgaesecwtct.supabase.co";
120
+ var supabaseAnonKey = process.env.REPLICAS_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im14ZXFpb213Z2RnYWVzZWN3dGN0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTk4OTk2ODAsImV4cCI6MjA3NTQ3NTY4MH0.zLRoK4r9Zep-WaLNkvWcb7DXlJBZFH26QXWO1ljMctM";
121
+ var supabase = (0, import_supabase_js.createClient)(supabaseUrl, supabaseAnonKey, {
122
+ auth: {
123
+ persistSession: false,
124
+ autoRefreshToken: false
125
+ }
126
+ });
127
+
128
+ // src/lib/auth.ts
129
+ function isTokenExpired(expiresAt) {
130
+ const now = Math.floor(Date.now() / 1e3);
131
+ return expiresAt - now < 60;
132
+ }
133
+ async function refreshToken() {
134
+ const config2 = readConfig();
135
+ if (!config2) {
136
+ throw new Error('Not authenticated. Please run "replicas login"');
137
+ }
138
+ try {
139
+ const { data, error } = await supabase.auth.refreshSession({
140
+ refresh_token: config2.refresh_token
141
+ });
142
+ if (error) throw error;
143
+ if (!data.session) throw new Error("Failed to refresh session");
144
+ writeConfig({
145
+ ...config2,
146
+ access_token: data.session.access_token,
147
+ refresh_token: data.session.refresh_token,
148
+ expires_at: data.session.expires_at || 0
149
+ });
150
+ } catch (error) {
151
+ throw new Error('Failed to refresh token. Please run "replicas login" again.');
152
+ }
153
+ }
154
+ async function getValidToken() {
155
+ const config2 = readConfig();
156
+ if (!config2) {
157
+ throw new Error('Not authenticated. Please run "replicas login"');
158
+ }
159
+ if (isTokenExpired(config2.expires_at)) {
160
+ await refreshToken();
161
+ const newConfig = readConfig();
162
+ if (!newConfig) {
163
+ throw new Error("Failed to get valid token");
38
164
  }
39
- catch (error) {
40
- if (error instanceof Error) {
41
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
42
- }
43
- process.exit(1);
165
+ return newConfig.access_token;
166
+ }
167
+ return config2.access_token;
168
+ }
169
+ async function getCurrentUser() {
170
+ const token = await getValidToken();
171
+ const { data: { user }, error } = await supabase.auth.getUser(token);
172
+ if (error) throw error;
173
+ if (!user) throw new Error("User not found");
174
+ return {
175
+ id: user.id,
176
+ email: user.email || "Unknown"
177
+ };
178
+ }
179
+
180
+ // src/lib/api.ts
181
+ var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
182
+ async function authenticatedFetch(url, options) {
183
+ const token = await getValidToken();
184
+ if (!token) {
185
+ throw new Error("No access token available");
186
+ }
187
+ const headers = {
188
+ "Authorization": `Bearer ${token}`,
189
+ "Content-Type": "application/json",
190
+ ...options?.headers || {}
191
+ };
192
+ const absoluteUrl = `${MONOLITH_URL}${url}`;
193
+ const requestInit = {
194
+ ...options,
195
+ headers,
196
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
197
+ };
198
+ const response = await fetchWithRedirects(absoluteUrl, requestInit);
199
+ if (!response.ok) {
200
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
201
+ throw new Error(error.error || `Request failed with status ${response.status}`);
202
+ }
203
+ return response.json();
204
+ }
205
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
206
+ var MAX_REDIRECTS = 5;
207
+ async function fetchWithRedirects(url, init, attempt = 0) {
208
+ const response = await fetch(url, { ...init, redirect: "manual" });
209
+ if (REDIRECT_STATUSES.has(response.status)) {
210
+ if (attempt >= MAX_REDIRECTS) {
211
+ throw new Error("Too many redirects while contacting the Replicas API");
44
212
  }
45
- });
46
- program
47
- .command('init')
48
- .description('Create a replicas.json configuration file')
49
- .option('-f, --force', 'Overwrite existing replicas.json file')
50
- .action((options) => {
51
- try {
52
- (0, init_1.initCommand)(options);
213
+ const location = response.headers.get("location");
214
+ if (!location) {
215
+ throw new Error(`Redirect status ${response.status} missing Location header`);
53
216
  }
54
- catch (error) {
55
- if (error instanceof Error) {
56
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
57
- }
58
- process.exit(1);
217
+ const nextUrl = new URL(location, url).toString();
218
+ const shouldForceGet = response.status === 303 && init.method !== void 0 && init.method.toUpperCase() !== "GET";
219
+ const nextInit = {
220
+ ...init,
221
+ method: shouldForceGet ? "GET" : init.method,
222
+ body: shouldForceGet ? void 0 : init.body
223
+ };
224
+ return fetchWithRedirects(nextUrl, nextInit, attempt + 1);
225
+ }
226
+ return response;
227
+ }
228
+ async function orgAuthenticatedFetch(url, options) {
229
+ const token = await getValidToken();
230
+ const organizationId = getOrganizationId();
231
+ if (!organizationId) {
232
+ throw new Error(
233
+ 'No organization selected. Please run "replicas org switch" to select an organization.'
234
+ );
235
+ }
236
+ const headers = {
237
+ "Authorization": `Bearer ${token}`,
238
+ "Replicas-Org-Id": organizationId,
239
+ "Content-Type": "application/json",
240
+ ...options?.headers || {}
241
+ };
242
+ const absoluteUrl = `${MONOLITH_URL}${url}`;
243
+ const requestInit = {
244
+ ...options,
245
+ headers,
246
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
247
+ };
248
+ const response = await fetchWithRedirects(absoluteUrl, requestInit);
249
+ if (!response.ok) {
250
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
251
+ throw new Error(error.error || `Request failed with status ${response.status}`);
252
+ }
253
+ return response.json();
254
+ }
255
+
256
+ // src/lib/organization.ts
257
+ async function fetchOrganizations() {
258
+ const response = await authenticatedFetch(
259
+ "/v1/user/organizations"
260
+ );
261
+ return response.organizations;
262
+ }
263
+ async function setActiveOrganization(organizationId) {
264
+ const organizations = await fetchOrganizations();
265
+ const organization = organizations.find((org2) => org2.id === organizationId);
266
+ if (!organization) {
267
+ throw new Error(`Organization with ID ${organizationId} not found or you don't have access to it.`);
268
+ }
269
+ setOrganizationId(organizationId);
270
+ }
271
+ async function ensureOrganization() {
272
+ const organizations = await fetchOrganizations();
273
+ if (organizations.length === 0) {
274
+ throw new Error("You are not a member of any organization. Please contact support.");
275
+ }
276
+ const defaultOrg = organizations[0];
277
+ setOrganizationId(defaultOrg.id);
278
+ return defaultOrg.id;
279
+ }
280
+
281
+ // src/commands/login.ts
282
+ var WEB_APP_URL = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
283
+ function generateState() {
284
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
285
+ }
286
+ async function loginCommand() {
287
+ const state = generateState();
288
+ return new Promise((resolve, reject) => {
289
+ let authTimeout;
290
+ let hasHandledCallback = false;
291
+ let lastRedirectUrl = null;
292
+ let lastMessage = null;
293
+ const pendingResponses = /* @__PURE__ */ new Set();
294
+ function respondWithRedirect(res) {
295
+ if (lastRedirectUrl) {
296
+ res.writeHead(302, {
297
+ "Location": lastRedirectUrl,
298
+ "Connection": "close"
299
+ });
300
+ res.end();
301
+ } else {
302
+ res.writeHead(200, {
303
+ "Content-Type": "text/html; charset=utf-8",
304
+ "Connection": "close"
305
+ });
306
+ res.end(`
307
+ <!DOCTYPE html>
308
+ <html lang="en">
309
+ <head>
310
+ <meta charset="utf-8" />
311
+ <title>Replicas CLI Login</title>
312
+ <style>
313
+ 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; }
314
+ .card { border:1px solid #27272a; padding:32px; border-radius:12px; background:#0f0f12; max-width:420px; text-align:center; }
315
+ .title { font-size:20px; margin-bottom:12px; letter-spacing:0.08em; text-transform:uppercase; color:#a855f7; }
316
+ .message { font-family: 'Menlo', 'Source Code Pro', monospace; font-size:14px; line-height:1.6; color:#d4d4d8; white-space:pre-line; }
317
+ </style>
318
+ </head>
319
+ <body>
320
+ <div class="card">
321
+ <div class="title">Replicas CLI</div>
322
+ <div class="message">${lastMessage || "Authentication already processed. You can close this tab."}</div>
323
+ </div>
324
+ </body>
325
+ </html>
326
+ `);
327
+ }
59
328
  }
60
- });
61
- program
62
- .command('logout')
63
- .description('Clear stored credentials')
64
- .action(() => {
65
- try {
66
- (0, logout_1.logoutCommand)();
329
+ function flushPendingResponses() {
330
+ for (const response of pendingResponses) {
331
+ respondWithRedirect(response);
332
+ }
333
+ pendingResponses.clear();
67
334
  }
68
- catch (error) {
69
- if (error instanceof Error) {
70
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
335
+ const server = import_http.default.createServer(async (req, res) => {
336
+ const url = new import_url.URL(req.url || "", `http://${req.headers.host}`);
337
+ if (url.pathname === "/callback") {
338
+ if (hasHandledCallback) {
339
+ if (!lastRedirectUrl) {
340
+ pendingResponses.add(res);
341
+ res.on("close", () => pendingResponses.delete(res));
342
+ return;
343
+ }
344
+ respondWithRedirect(res);
345
+ return;
71
346
  }
72
- process.exit(1);
347
+ const returnedState = url.searchParams.get("state");
348
+ const accessToken = url.searchParams.get("access_token");
349
+ const refreshToken2 = url.searchParams.get("refresh_token");
350
+ const expiresAt = url.searchParams.get("expires_at");
351
+ const error = url.searchParams.get("error");
352
+ if (error) {
353
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent(error)}`;
354
+ lastRedirectUrl = errorUrl;
355
+ lastMessage = `Authentication failed: ${error}`;
356
+ respondWithRedirect(res);
357
+ flushPendingResponses();
358
+ clearTimeout(authTimeout);
359
+ setImmediate(() => {
360
+ server.closeAllConnections?.();
361
+ server.close();
362
+ reject(new Error(`Authentication failed: ${error}`));
363
+ });
364
+ return;
365
+ }
366
+ if (returnedState !== state) {
367
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Invalid state parameter. This might be a CSRF attack.")}`;
368
+ lastRedirectUrl = errorUrl;
369
+ lastMessage = "Invalid state parameter. This might be a CSRF attack.";
370
+ respondWithRedirect(res);
371
+ flushPendingResponses();
372
+ clearTimeout(authTimeout);
373
+ setImmediate(() => {
374
+ server.closeAllConnections?.();
375
+ server.close();
376
+ reject(new Error("Invalid state parameter"));
377
+ });
378
+ return;
379
+ }
380
+ if (!accessToken || !refreshToken2 || !expiresAt) {
381
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Missing required authentication tokens.")}`;
382
+ lastRedirectUrl = errorUrl;
383
+ lastMessage = "Missing required authentication tokens.";
384
+ respondWithRedirect(res);
385
+ flushPendingResponses();
386
+ clearTimeout(authTimeout);
387
+ setImmediate(() => {
388
+ server.closeAllConnections?.();
389
+ server.close();
390
+ reject(new Error("Missing authentication tokens"));
391
+ });
392
+ return;
393
+ }
394
+ hasHandledCallback = true;
395
+ const existingConfig = readConfig();
396
+ const config2 = {
397
+ access_token: accessToken,
398
+ refresh_token: refreshToken2,
399
+ expires_at: parseInt(expiresAt, 10),
400
+ organization_id: existingConfig?.organization_id,
401
+ ide_command: existingConfig?.ide_command
402
+ };
403
+ try {
404
+ writeConfig(config2);
405
+ const user = await getCurrentUser();
406
+ try {
407
+ const orgId = await ensureOrganization();
408
+ console.log(import_chalk.default.gray(` Organization: ${orgId}`));
409
+ } catch (orgError) {
410
+ console.log(import_chalk.default.yellow(" Warning: Could not fetch organizations"));
411
+ console.log(import_chalk.default.gray(" You can set your organization later with: replicas org switch"));
412
+ }
413
+ const successUrl = `${WEB_APP_URL}/cli-login/success?email=${encodeURIComponent(user.email)}`;
414
+ lastRedirectUrl = successUrl;
415
+ lastMessage = `Successfully logged in as ${user.email}. You can close this tab.`;
416
+ respondWithRedirect(res);
417
+ flushPendingResponses();
418
+ console.log(import_chalk.default.green("\n\u2713 Successfully logged in!"));
419
+ console.log(import_chalk.default.gray(` Email: ${user.email}
420
+ `));
421
+ clearTimeout(authTimeout);
422
+ setImmediate(() => {
423
+ server.closeAllConnections?.();
424
+ server.close();
425
+ resolve();
426
+ });
427
+ } catch (error2) {
428
+ const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Failed to verify authentication.")}`;
429
+ lastRedirectUrl = errorUrl;
430
+ lastMessage = "Failed to verify authentication.";
431
+ respondWithRedirect(res);
432
+ flushPendingResponses();
433
+ clearTimeout(authTimeout);
434
+ setImmediate(() => {
435
+ server.closeAllConnections?.();
436
+ server.close();
437
+ reject(error2);
438
+ });
439
+ }
440
+ } else {
441
+ res.writeHead(404);
442
+ res.end("Not found");
443
+ }
444
+ });
445
+ server.listen(0, "localhost", () => {
446
+ const address = server.address();
447
+ if (!address || typeof address === "string") {
448
+ reject(new Error("Failed to start server"));
449
+ return;
450
+ }
451
+ const port = address.port;
452
+ const loginUrl = `${WEB_APP_URL}/cli-login?port=${port}&state=${state}`;
453
+ console.log(import_chalk.default.blue("\nOpening browser for authentication..."));
454
+ console.log(import_chalk.default.gray(`If the browser doesn't open automatically, visit:
455
+ ${loginUrl}
456
+ `));
457
+ (0, import_open.default)(loginUrl).catch((error) => {
458
+ console.log(import_chalk.default.yellow("Failed to open browser automatically."));
459
+ console.log(import_chalk.default.gray(`Please open this URL manually:
460
+ ${loginUrl}
461
+ `));
462
+ });
463
+ });
464
+ authTimeout = setTimeout(() => {
465
+ server.closeAllConnections?.();
466
+ server.close();
467
+ reject(new Error("Authentication timeout. Please try again."));
468
+ }, 5 * 60 * 1e3);
469
+ });
470
+ }
471
+
472
+ // src/commands/logout.ts
473
+ var import_chalk2 = __toESM(require("chalk"));
474
+ function logoutCommand() {
475
+ if (!isAuthenticated()) {
476
+ console.log(import_chalk2.default.yellow("You are not logged in."));
477
+ return;
478
+ }
479
+ deleteConfig();
480
+ console.log(import_chalk2.default.green("\u2713 Successfully logged out!"));
481
+ }
482
+
483
+ // src/commands/whoami.ts
484
+ var import_chalk3 = __toESM(require("chalk"));
485
+ async function whoamiCommand() {
486
+ if (!isAuthenticated()) {
487
+ console.log(import_chalk3.default.yellow("You are not logged in."));
488
+ console.log(import_chalk3.default.gray('Run "replicas login" to authenticate.'));
489
+ return;
490
+ }
491
+ try {
492
+ const user = await getCurrentUser();
493
+ console.log(import_chalk3.default.blue("\nCurrent User:"));
494
+ console.log(import_chalk3.default.gray(` ID: ${user.id}`));
495
+ console.log(import_chalk3.default.gray(` Email: ${user.email}
496
+ `));
497
+ } catch (error) {
498
+ console.error(import_chalk3.default.red("Failed to get user information."));
499
+ if (error instanceof Error) {
500
+ console.error(import_chalk3.default.gray(error.message));
73
501
  }
74
- });
75
- program
76
- .command('whoami')
77
- .description('Display current authenticated user')
78
- .action(async () => {
79
- try {
80
- await (0, whoami_1.whoamiCommand)();
502
+ console.log(import_chalk3.default.gray('\nTry running "replicas login" again.'));
503
+ process.exit(1);
504
+ }
505
+ }
506
+
507
+ // src/commands/connect.ts
508
+ var import_chalk5 = __toESM(require("chalk"));
509
+
510
+ // src/lib/ssh.ts
511
+ var import_fs2 = __toESM(require("fs"));
512
+ var import_path2 = __toESM(require("path"));
513
+ var import_child_process = require("child_process");
514
+ var SSH_KEYS_DIR = import_path2.default.join(CONFIG_DIR, "keys");
515
+ function ensureSSHKeysDir() {
516
+ if (!import_fs2.default.existsSync(SSH_KEYS_DIR)) {
517
+ import_fs2.default.mkdirSync(SSH_KEYS_DIR, { recursive: true, mode: 448 });
518
+ }
519
+ }
520
+ function getSSHKeyPath(sshKeyId) {
521
+ return import_path2.default.join(SSH_KEYS_DIR, `${sshKeyId}.pem`);
522
+ }
523
+ async function getSSHKey(sshKeyId) {
524
+ const keyPath = getSSHKeyPath(sshKeyId);
525
+ if (import_fs2.default.existsSync(keyPath)) {
526
+ return import_fs2.default.readFileSync(keyPath, "utf-8");
527
+ }
528
+ const response = await orgAuthenticatedFetch(
529
+ `/v1/keys/${sshKeyId}?includePrivateKey=true`
530
+ );
531
+ if (!response.privateKey) {
532
+ throw new Error("Failed to retrieve private key from server");
533
+ }
534
+ ensureSSHKeysDir();
535
+ import_fs2.default.writeFileSync(keyPath, response.privateKey, { mode: 384 });
536
+ return response.privateKey;
537
+ }
538
+ async function scpCopyFile(privateKeyPath, localPath, remotePath, remoteHost) {
539
+ return new Promise((resolve, reject) => {
540
+ const scp = (0, import_child_process.spawn)("scp", [
541
+ "-i",
542
+ privateKeyPath,
543
+ "-o",
544
+ "StrictHostKeyChecking=no",
545
+ "-o",
546
+ "UserKnownHostsFile=/dev/null",
547
+ localPath,
548
+ `ubuntu@${remoteHost}:${remotePath}`
549
+ ], {
550
+ stdio: "inherit"
551
+ });
552
+ scp.on("close", (code) => {
553
+ if (code === 0) {
554
+ resolve();
555
+ } else {
556
+ reject(new Error(`scp failed with exit code ${code}`));
557
+ }
558
+ });
559
+ scp.on("error", reject);
560
+ });
561
+ }
562
+ async function connectSSH(privateKeyPath, remoteHost, portMappings) {
563
+ return new Promise((resolve, reject) => {
564
+ const sshArgs = [
565
+ "-i",
566
+ privateKeyPath,
567
+ "-o",
568
+ "StrictHostKeyChecking=no",
569
+ "-o",
570
+ "UserKnownHostsFile=/dev/null"
571
+ ];
572
+ for (const [remotePort, localPort] of portMappings) {
573
+ sshArgs.push("-L", `${localPort}:localhost:${remotePort}`);
81
574
  }
82
- catch (error) {
83
- if (error instanceof Error) {
84
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
85
- }
86
- process.exit(1);
575
+ sshArgs.push(`ubuntu@${remoteHost}`);
576
+ const ssh = (0, import_child_process.spawn)("ssh", sshArgs, {
577
+ stdio: "inherit"
578
+ });
579
+ ssh.on("close", (code) => {
580
+ resolve();
581
+ });
582
+ ssh.on("error", reject);
583
+ });
584
+ }
585
+
586
+ // src/lib/workspace-connection.ts
587
+ var import_chalk4 = __toESM(require("chalk"));
588
+ var import_prompts = __toESM(require("prompts"));
589
+ var import_path5 = __toESM(require("path"));
590
+ var import_fs4 = __toESM(require("fs"));
591
+
592
+ // src/lib/git.ts
593
+ var import_child_process2 = require("child_process");
594
+ var import_path3 = __toESM(require("path"));
595
+ function getGitRoot() {
596
+ try {
597
+ const root = (0, import_child_process2.execSync)("git rev-parse --show-toplevel", {
598
+ encoding: "utf-8",
599
+ stdio: ["pipe", "pipe", "pipe"]
600
+ }).trim();
601
+ return root;
602
+ } catch (error) {
603
+ throw new Error("Not inside a git repository");
604
+ }
605
+ }
606
+ function getGitRepoName() {
607
+ const root = getGitRoot();
608
+ return import_path3.default.basename(root);
609
+ }
610
+ function isInsideGitRepo() {
611
+ try {
612
+ (0, import_child_process2.execSync)("git rev-parse --is-inside-work-tree", {
613
+ encoding: "utf-8",
614
+ stdio: ["pipe", "pipe", "pipe"]
615
+ });
616
+ return true;
617
+ } catch {
618
+ return false;
619
+ }
620
+ }
621
+
622
+ // src/lib/replicas-config.ts
623
+ var import_fs3 = __toESM(require("fs"));
624
+ var import_path4 = __toESM(require("path"));
625
+ function readReplicasConfig() {
626
+ try {
627
+ const gitRoot = getGitRoot();
628
+ const configPath = import_path4.default.join(gitRoot, "replicas.json");
629
+ if (!import_fs3.default.existsSync(configPath)) {
630
+ return null;
87
631
  }
88
- });
89
- program
90
- .command('tools')
91
- .description('List tools available on the Replicas Machine Image')
92
- .action(async () => {
93
- try {
94
- await (0, tools_1.toolsCommand)();
632
+ const data = import_fs3.default.readFileSync(configPath, "utf-8");
633
+ const config2 = JSON.parse(data);
634
+ if (config2.copy && !Array.isArray(config2.copy)) {
635
+ throw new Error('Invalid replicas.json: "copy" must be an array of file paths');
95
636
  }
96
- catch (error) {
97
- if (error instanceof Error) {
98
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
99
- }
100
- process.exit(1);
637
+ if (config2.ports && !Array.isArray(config2.ports)) {
638
+ throw new Error('Invalid replicas.json: "ports" must be an array of port numbers');
101
639
  }
102
- });
103
- program
104
- .command('codex-auth')
105
- .description('Authenticate Replicas with your Codex credentials')
106
- .option('-u, --user', 'Store credentials for your personal account')
107
- .action(async (options) => {
108
- try {
109
- await (0, codex_auth_1.codexAuthCommand)(options);
640
+ if (config2.ports && !config2.ports.every((p) => typeof p === "number")) {
641
+ throw new Error("Invalid replicas.json: all ports must be numbers");
110
642
  }
111
- catch (error) {
112
- if (error instanceof Error) {
113
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
114
- }
115
- process.exit(1);
643
+ if (config2.organizationId && typeof config2.organizationId !== "string") {
644
+ throw new Error('Invalid replicas.json: "organizationId" must be a string');
116
645
  }
117
- });
118
- program
119
- .command('claude-auth')
120
- .description('Authenticate Replicas with your Claude Code credentials')
121
- .option('-u, --user', 'Store credentials for your personal account')
122
- .action(async (options) => {
123
- try {
124
- await (0, claude_auth_1.claudeAuthCommand)(options);
646
+ if (config2.systemPrompt && typeof config2.systemPrompt !== "string") {
647
+ throw new Error('Invalid replicas.json: "systemPrompt" must be a string');
648
+ }
649
+ return config2;
650
+ } catch (error) {
651
+ if (error instanceof Error) {
652
+ throw error;
653
+ }
654
+ throw new Error("Failed to read replicas.json");
655
+ }
656
+ }
657
+ function validateCopyFiles(copyFiles) {
658
+ const gitRoot = getGitRoot();
659
+ const valid = [];
660
+ const invalid = [];
661
+ for (const file of copyFiles) {
662
+ const fullPath = import_path4.default.join(gitRoot, file);
663
+ if (import_fs3.default.existsSync(fullPath)) {
664
+ valid.push(file);
665
+ } else {
666
+ invalid.push(file);
667
+ }
668
+ }
669
+ return { valid, invalid };
670
+ }
671
+ function getAbsoluteCopyPath(relativePath) {
672
+ const gitRoot = getGitRoot();
673
+ return import_path4.default.join(gitRoot, relativePath);
674
+ }
675
+
676
+ // src/lib/ports.ts
677
+ var import_child_process3 = require("child_process");
678
+ async function isPortAvailable(port) {
679
+ try {
680
+ (0, import_child_process3.execSync)(`lsof -i :${port} -P -n | grep LISTEN`, {
681
+ encoding: "utf-8",
682
+ stdio: ["pipe", "pipe", "pipe"]
683
+ });
684
+ return false;
685
+ } catch (error) {
686
+ return true;
687
+ }
688
+ }
689
+ async function findAvailablePort(desiredPort) {
690
+ const baseHundred = Math.floor(desiredPort / 100) * 100;
691
+ const maxPort = desiredPort >= baseHundred && desiredPort < baseHundred + 100 ? baseHundred + 99 : desiredPort + 100;
692
+ for (let port = desiredPort; port <= maxPort; port++) {
693
+ if (await isPortAvailable(port)) {
694
+ return port;
125
695
  }
126
- catch (error) {
127
- if (error instanceof Error) {
128
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
696
+ }
697
+ throw new Error(
698
+ `Could not find available port in range ${desiredPort}-${maxPort}`
699
+ );
700
+ }
701
+ async function mapPortsToLocal(remotePorts) {
702
+ const portMap = /* @__PURE__ */ new Map();
703
+ for (const remotePort of remotePorts) {
704
+ const localPort = await findAvailablePort(remotePort);
705
+ portMap.set(remotePort, localPort);
706
+ }
707
+ return portMap;
708
+ }
709
+
710
+ // src/lib/workspace-connection.ts
711
+ async function prepareWorkspaceConnection(workspaceName, options) {
712
+ const orgId = getOrganizationId();
713
+ if (!orgId) {
714
+ throw new Error('No organization selected. Please run "replicas org switch" first.');
715
+ }
716
+ console.log(import_chalk4.default.blue(`
717
+ Searching for workspace: ${workspaceName}...`));
718
+ const response = await orgAuthenticatedFetch(
719
+ `/v1/workspaces?name=${encodeURIComponent(workspaceName)}`
720
+ );
721
+ if (response.workspaces.length === 0) {
722
+ throw new Error(`No workspaces found with name matching "${workspaceName}".`);
723
+ }
724
+ let selectedWorkspace;
725
+ if (response.workspaces.length === 1) {
726
+ selectedWorkspace = response.workspaces[0];
727
+ } else {
728
+ console.log(import_chalk4.default.yellow(`
729
+ Found ${response.workspaces.length} workspaces matching "${workspaceName}":`));
730
+ const selectResponse = await (0, import_prompts.default)({
731
+ type: "select",
732
+ name: "workspaceId",
733
+ message: "Select a workspace:",
734
+ choices: response.workspaces.map((ws) => ({
735
+ title: `${ws.name} (${ws.status || "unknown"})`,
736
+ value: ws.id,
737
+ description: `IP: ${ws.ipv4_address || "N/A"} | Instance: ${ws.instance_type || "N/A"}`
738
+ }))
739
+ });
740
+ if (!selectResponse.workspaceId) {
741
+ throw new Error("Workspace selection cancelled.");
742
+ }
743
+ selectedWorkspace = response.workspaces.find((ws) => ws.id === selectResponse.workspaceId);
744
+ }
745
+ console.log(import_chalk4.default.green(`
746
+ \u2713 Selected workspace: ${selectedWorkspace.name}`));
747
+ console.log(import_chalk4.default.gray(` IP: ${selectedWorkspace.ipv4_address}`));
748
+ console.log(import_chalk4.default.gray(` Status: ${selectedWorkspace.status || "unknown"}`));
749
+ if (selectedWorkspace.status === "sleeping") {
750
+ throw new Error(
751
+ "Workspace is currently sleeping. Please wake it up in the dashboard at https://replicas.dev before connecting."
752
+ );
753
+ }
754
+ if (!selectedWorkspace.ipv4_address) {
755
+ throw new Error("Workspace does not have an IP address. It may not be running yet.");
756
+ }
757
+ console.log(import_chalk4.default.blue("\nRetrieving SSH key..."));
758
+ const privateKey = await getSSHKey(selectedWorkspace.ssh_key_id);
759
+ const keyPath = import_path5.default.join(CONFIG_DIR, "keys", `${selectedWorkspace.ssh_key_id}.pem`);
760
+ import_fs4.default.writeFileSync(keyPath, privateKey, { mode: 384 });
761
+ console.log(import_chalk4.default.green("\u2713 SSH key ready"));
762
+ let repoName = null;
763
+ if (options.copy) {
764
+ if (!isInsideGitRepo()) {
765
+ console.log(import_chalk4.default.yellow("\nWarning: Not inside a git repository. Skipping file copy."));
766
+ } else {
767
+ repoName = getGitRepoName();
768
+ const replicasConfig = readReplicasConfig();
769
+ if (!replicasConfig || !replicasConfig.copy || replicasConfig.copy.length === 0) {
770
+ console.log(import_chalk4.default.yellow("\nNo files to copy (replicas.json not found or empty)."));
771
+ } else {
772
+ console.log(import_chalk4.default.blue(`
773
+ Copying files to workspace (repo: ${repoName})...`));
774
+ const { valid, invalid } = validateCopyFiles(replicasConfig.copy);
775
+ if (invalid.length > 0) {
776
+ console.log(import_chalk4.default.yellow(`
777
+ Warning: ${invalid.length} file(s) not found locally:`));
778
+ invalid.forEach((file) => console.log(import_chalk4.default.gray(` - ${file}`)));
129
779
  }
130
- process.exit(1);
780
+ for (const file of valid) {
781
+ const localPath = getAbsoluteCopyPath(file);
782
+ const remotePath = `/home/ubuntu/workspaces/${repoName}/${file}`;
783
+ console.log(import_chalk4.default.gray(` Copying ${file}...`));
784
+ try {
785
+ await scpCopyFile(keyPath, localPath, remotePath, selectedWorkspace.ipv4_address);
786
+ console.log(import_chalk4.default.green(` \u2713 ${file}`));
787
+ } catch (error) {
788
+ console.log(import_chalk4.default.red(` \u2717 Failed to copy ${file}: ${error instanceof Error ? error.message : "Unknown error"}`));
789
+ }
790
+ }
791
+ console.log(import_chalk4.default.green(`
792
+ \u2713 File copy complete (${valid.length} files)`));
793
+ }
131
794
  }
132
- });
133
- // Organization commands
134
- const org = program
135
- .command('org')
136
- .description('Manage organizations');
137
- org
138
- .command('switch')
139
- .description('Switch to a different organization')
140
- .action(async () => {
795
+ }
796
+ if (!repoName && isInsideGitRepo()) {
141
797
  try {
142
- await (0, org_1.orgSwitchCommand)();
798
+ repoName = getGitRepoName();
799
+ } catch {
800
+ }
801
+ }
802
+ const portMappings = /* @__PURE__ */ new Map();
803
+ console.log(import_chalk4.default.blue("\nSetting up port forwarding..."));
804
+ if (isInsideGitRepo()) {
805
+ const replicasConfig = readReplicasConfig();
806
+ if (replicasConfig && replicasConfig.ports && replicasConfig.ports.length > 0) {
807
+ const mappings = await mapPortsToLocal(replicasConfig.ports);
808
+ for (const [remotePort, localPort] of mappings) {
809
+ portMappings.set(remotePort, localPort);
810
+ console.log(import_chalk4.default.gray(` Forwarding localhost:${localPort} -> workspace:${remotePort}`));
811
+ }
812
+ }
813
+ }
814
+ console.log(import_chalk4.default.green("\u2713 Port forwarding configured"));
815
+ return {
816
+ workspace: selectedWorkspace,
817
+ sshKeyPath: keyPath,
818
+ portMappings,
819
+ repoName
820
+ };
821
+ }
822
+
823
+ // src/commands/connect.ts
824
+ async function connectCommand(workspaceName, options) {
825
+ if (!isAuthenticated()) {
826
+ console.log(import_chalk5.default.red('Not logged in. Please run "replicas login" first.'));
827
+ process.exit(1);
828
+ }
829
+ try {
830
+ const { workspace, sshKeyPath, portMappings } = await prepareWorkspaceConnection(
831
+ workspaceName,
832
+ options
833
+ );
834
+ console.log(import_chalk5.default.blue(`
835
+ Connecting to ${workspace.name}...`));
836
+ let sshCommand = `ssh -i ${sshKeyPath}`;
837
+ for (const [remotePort, localPort] of portMappings) {
838
+ sshCommand += ` -L ${localPort}:localhost:${remotePort}`;
839
+ }
840
+ sshCommand += ` ubuntu@${workspace.ipv4_address}`;
841
+ console.log(import_chalk5.default.gray(`SSH command: ${sshCommand}`));
842
+ if (portMappings.size > 0) {
843
+ console.log(import_chalk5.default.cyan("\nPort forwarding active:"));
844
+ for (const [remotePort, localPort] of portMappings) {
845
+ console.log(import_chalk5.default.cyan(` \u2022 localhost:${localPort} \u2192 workspace:${remotePort}`));
846
+ }
847
+ }
848
+ console.log(import_chalk5.default.gray("\nPress Ctrl+D to disconnect.\n"));
849
+ await connectSSH(sshKeyPath, workspace.ipv4_address, portMappings);
850
+ console.log(import_chalk5.default.green("\n\u2713 Disconnected from workspace.\n"));
851
+ } catch (error) {
852
+ console.error(import_chalk5.default.red(`
853
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
854
+ process.exit(1);
855
+ }
856
+ }
857
+
858
+ // src/commands/code.ts
859
+ var import_chalk6 = __toESM(require("chalk"));
860
+ var import_child_process4 = require("child_process");
861
+
862
+ // src/lib/ssh-config.ts
863
+ var import_fs5 = __toESM(require("fs"));
864
+ var import_path6 = __toESM(require("path"));
865
+ var import_os2 = __toESM(require("os"));
866
+ var SSH_CONFIG_PATH = import_path6.default.join(import_os2.default.homedir(), ".ssh", "config");
867
+ var REPLICAS_MARKER_START = "# === REPLICAS CLI MANAGED ENTRY START ===";
868
+ var REPLICAS_MARKER_END = "# === REPLICAS CLI MANAGED ENTRY END ===";
869
+ function ensureSSHDir() {
870
+ const sshDir = import_path6.default.join(import_os2.default.homedir(), ".ssh");
871
+ if (!import_fs5.default.existsSync(sshDir)) {
872
+ import_fs5.default.mkdirSync(sshDir, { recursive: true, mode: 448 });
873
+ }
874
+ }
875
+ function readSSHConfig() {
876
+ ensureSSHDir();
877
+ if (!import_fs5.default.existsSync(SSH_CONFIG_PATH)) {
878
+ return "";
879
+ }
880
+ return import_fs5.default.readFileSync(SSH_CONFIG_PATH, "utf-8");
881
+ }
882
+ function writeSSHConfig(content) {
883
+ ensureSSHDir();
884
+ import_fs5.default.writeFileSync(SSH_CONFIG_PATH, content, { mode: 384 });
885
+ }
886
+ function generateConfigBlock(entry) {
887
+ const lines = [
888
+ REPLICAS_MARKER_START,
889
+ `Host ${entry.host}`,
890
+ ` HostName ${entry.hostname}`,
891
+ ` User ${entry.user}`,
892
+ ` IdentityFile ${entry.identityFile}`,
893
+ ` StrictHostKeyChecking no`,
894
+ ` UserKnownHostsFile /dev/null`
895
+ ];
896
+ if (entry.portForwards && entry.portForwards.size > 0) {
897
+ for (const [localPort, remotePort] of entry.portForwards) {
898
+ lines.push(` LocalForward ${localPort} localhost:${remotePort}`);
899
+ }
900
+ }
901
+ lines.push(REPLICAS_MARKER_END);
902
+ return lines.join("\n");
903
+ }
904
+ function addOrUpdateSSHConfigEntry(entry) {
905
+ let config2 = readSSHConfig();
906
+ const lines = config2.split("\n");
907
+ const result = [];
908
+ let inTargetBlock = false;
909
+ for (const line of lines) {
910
+ if (line.trim() === REPLICAS_MARKER_START) {
911
+ const nextLines = lines.slice(lines.indexOf(line) + 1, lines.indexOf(line) + 3);
912
+ const hostLine = nextLines.find((l) => l.trim().startsWith("Host "));
913
+ if (hostLine && hostLine.includes(entry.host)) {
914
+ inTargetBlock = true;
915
+ continue;
916
+ }
917
+ result.push(line);
918
+ continue;
919
+ }
920
+ if (line.trim() === REPLICAS_MARKER_END) {
921
+ if (inTargetBlock) {
922
+ inTargetBlock = false;
923
+ continue;
924
+ }
925
+ result.push(line);
926
+ continue;
927
+ }
928
+ if (inTargetBlock) {
929
+ continue;
930
+ }
931
+ result.push(line);
932
+ }
933
+ config2 = result.join("\n");
934
+ const newEntry = generateConfigBlock(entry);
935
+ let finalConfig = config2.trim();
936
+ if (finalConfig.length > 0) {
937
+ finalConfig += "\n\n";
938
+ }
939
+ finalConfig += newEntry + "\n";
940
+ writeSSHConfig(finalConfig);
941
+ }
942
+ function getWorkspaceHostAlias(workspaceName) {
943
+ return `replicas-${workspaceName.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
944
+ }
945
+
946
+ // src/commands/code.ts
947
+ async function codeCommand(workspaceName, options) {
948
+ if (!isAuthenticated()) {
949
+ console.log(import_chalk6.default.red('Not logged in. Please run "replicas login" first.'));
950
+ process.exit(1);
951
+ }
952
+ try {
953
+ const { workspace, sshKeyPath, portMappings, repoName } = await prepareWorkspaceConnection(
954
+ workspaceName,
955
+ options
956
+ );
957
+ const hostAlias = getWorkspaceHostAlias(workspace.name);
958
+ console.log(import_chalk6.default.blue("\nConfiguring SSH connection..."));
959
+ addOrUpdateSSHConfigEntry({
960
+ host: hostAlias,
961
+ hostname: workspace.ipv4_address,
962
+ user: "ubuntu",
963
+ identityFile: sshKeyPath,
964
+ portForwards: portMappings
965
+ });
966
+ console.log(import_chalk6.default.green(`\u2713 SSH config entry created: ${hostAlias}`));
967
+ const remotePath = repoName ? `/home/ubuntu/workspaces/${repoName}` : "/home/ubuntu";
968
+ const ideCommand = getIdeCommand();
969
+ const fullCommand = `${ideCommand} --remote ssh-remote+${hostAlias} ${remotePath}`;
970
+ console.log(import_chalk6.default.blue(`
971
+ Opening ${ideCommand} for workspace ${workspace.name}...`));
972
+ console.log(import_chalk6.default.gray(`Command: ${fullCommand}`));
973
+ if (portMappings.size > 0) {
974
+ console.log(import_chalk6.default.cyan("\nPort forwarding configured:"));
975
+ for (const [remotePort, localPort] of portMappings) {
976
+ console.log(import_chalk6.default.cyan(` \u2022 localhost:${localPort} \u2192 workspace:${remotePort}`));
977
+ }
978
+ }
979
+ const ide = (0, import_child_process4.spawn)(ideCommand, [
980
+ "--remote",
981
+ `ssh-remote+${hostAlias}`,
982
+ remotePath
983
+ ], {
984
+ stdio: "inherit",
985
+ detached: false
986
+ });
987
+ ide.on("error", (error) => {
988
+ console.error(import_chalk6.default.red(`
989
+ Failed to launch ${ideCommand}: ${error.message}`));
990
+ console.log(import_chalk6.default.yellow(`
991
+ Make sure ${ideCommand} is installed and available in your PATH.`));
992
+ console.log(import_chalk6.default.gray(`You can configure a different IDE with: replicas config set ide <command>`));
993
+ process.exit(1);
994
+ });
995
+ ide.on("close", (code) => {
996
+ if (code === 0) {
997
+ console.log(import_chalk6.default.green(`
998
+ \u2713 ${ideCommand} closed successfully.
999
+ `));
1000
+ }
1001
+ });
1002
+ } catch (error) {
1003
+ console.error(import_chalk6.default.red(`
1004
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1005
+ process.exit(1);
1006
+ }
1007
+ }
1008
+
1009
+ // src/commands/org.ts
1010
+ var import_chalk7 = __toESM(require("chalk"));
1011
+ var import_prompts2 = __toESM(require("prompts"));
1012
+ async function orgCommand() {
1013
+ if (!isAuthenticated()) {
1014
+ console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
1015
+ process.exit(1);
1016
+ }
1017
+ try {
1018
+ const currentOrgId = getOrganizationId();
1019
+ const organizations = await fetchOrganizations();
1020
+ if (!currentOrgId) {
1021
+ console.log(import_chalk7.default.yellow("No organization selected."));
1022
+ console.log(import_chalk7.default.gray('Run "replicas org switch" to select an organization.\n'));
1023
+ return;
1024
+ }
1025
+ const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
1026
+ if (!currentOrg) {
1027
+ console.log(import_chalk7.default.yellow(`Current organization ID (${currentOrgId}) not found.`));
1028
+ console.log(import_chalk7.default.gray('Run "replicas org switch" to select a new organization.\n'));
1029
+ return;
1030
+ }
1031
+ console.log(import_chalk7.default.green("\nCurrent organization:"));
1032
+ console.log(import_chalk7.default.gray(` Name: ${currentOrg.name}`));
1033
+ console.log(import_chalk7.default.gray(` ID: ${currentOrg.id}
1034
+ `));
1035
+ } catch (error) {
1036
+ console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1037
+ process.exit(1);
1038
+ }
1039
+ }
1040
+ async function orgSwitchCommand() {
1041
+ if (!isAuthenticated()) {
1042
+ console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
1043
+ process.exit(1);
1044
+ }
1045
+ try {
1046
+ const organizations = await fetchOrganizations();
1047
+ if (organizations.length === 0) {
1048
+ console.log(import_chalk7.default.yellow("You are not a member of any organization."));
1049
+ console.log(import_chalk7.default.gray("Please contact support.\n"));
1050
+ return;
1051
+ }
1052
+ if (organizations.length === 1) {
1053
+ await setActiveOrganization(organizations[0].id);
1054
+ console.log(import_chalk7.default.green(`
1055
+ \u2713 Organization set to: ${organizations[0].name}
1056
+ `));
1057
+ return;
1058
+ }
1059
+ const currentOrgId = getOrganizationId();
1060
+ const response = await (0, import_prompts2.default)({
1061
+ type: "select",
1062
+ name: "organizationId",
1063
+ message: "Select an organization:",
1064
+ choices: organizations.map((org2) => ({
1065
+ title: `${org2.name}${org2.id === currentOrgId ? " (current)" : ""}`,
1066
+ value: org2.id,
1067
+ description: org2.id
1068
+ })),
1069
+ initial: organizations.findIndex((org2) => org2.id === currentOrgId)
1070
+ });
1071
+ if (!response.organizationId) {
1072
+ console.log(import_chalk7.default.yellow("\nCancelled."));
1073
+ return;
1074
+ }
1075
+ await setActiveOrganization(response.organizationId);
1076
+ const selectedOrg = organizations.find((org2) => org2.id === response.organizationId);
1077
+ console.log(import_chalk7.default.green(`
1078
+ \u2713 Switched to organization: ${selectedOrg?.name}
1079
+ `));
1080
+ } catch (error) {
1081
+ console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1082
+ process.exit(1);
1083
+ }
1084
+ }
1085
+
1086
+ // src/commands/config.ts
1087
+ var import_chalk8 = __toESM(require("chalk"));
1088
+ async function configGetCommand(key) {
1089
+ if (!isAuthenticated()) {
1090
+ console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1091
+ process.exit(1);
1092
+ }
1093
+ try {
1094
+ if (key === "ide") {
1095
+ const ideCommand = getIdeCommand();
1096
+ console.log(import_chalk8.default.green(`
1097
+ IDE command: ${ideCommand}
1098
+ `));
1099
+ } else {
1100
+ console.log(import_chalk8.default.red(`Unknown config key: ${key}`));
1101
+ console.log(import_chalk8.default.gray("Available keys: ide"));
1102
+ process.exit(1);
1103
+ }
1104
+ } catch (error) {
1105
+ console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1106
+ process.exit(1);
1107
+ }
1108
+ }
1109
+ async function configSetCommand(key, value) {
1110
+ if (!isAuthenticated()) {
1111
+ console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1112
+ process.exit(1);
1113
+ }
1114
+ try {
1115
+ if (key === "ide") {
1116
+ setIdeCommand(value);
1117
+ console.log(import_chalk8.default.green(`
1118
+ \u2713 IDE command set to: ${value}
1119
+ `));
1120
+ } else {
1121
+ console.log(import_chalk8.default.red(`Unknown config key: ${key}`));
1122
+ console.log(import_chalk8.default.gray("Available keys: ide"));
1123
+ process.exit(1);
1124
+ }
1125
+ } catch (error) {
1126
+ console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1127
+ process.exit(1);
1128
+ }
1129
+ }
1130
+ async function configListCommand() {
1131
+ if (!isAuthenticated()) {
1132
+ console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1133
+ process.exit(1);
1134
+ }
1135
+ try {
1136
+ const config2 = readConfig();
1137
+ if (!config2) {
1138
+ console.log(import_chalk8.default.red("No config found. Please login first."));
1139
+ process.exit(1);
143
1140
  }
144
- catch (error) {
145
- if (error instanceof Error) {
146
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
1141
+ console.log(import_chalk8.default.green("\nCurrent configuration:"));
1142
+ console.log(import_chalk8.default.gray(` Organization ID: ${config2.organization_id || "(not set)"}`));
1143
+ console.log(import_chalk8.default.gray(` IDE command: ${config2.ide_command || "code (default)"}`));
1144
+ console.log();
1145
+ } catch (error) {
1146
+ console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1147
+ process.exit(1);
1148
+ }
1149
+ }
1150
+
1151
+ // src/commands/tools.ts
1152
+ var import_chalk9 = __toESM(require("chalk"));
1153
+ var import_figlet = __toESM(require("figlet"));
1154
+ async function toolsCommand() {
1155
+ const banner = import_figlet.default.textSync("Replicas Machine Image", {
1156
+ horizontalLayout: "default",
1157
+ verticalLayout: "default",
1158
+ whitespaceBreak: true
1159
+ });
1160
+ console.log(import_chalk9.default.cyan(banner));
1161
+ console.log();
1162
+ console.log(import_chalk9.default.bold("RMI Version: v0.1.3"));
1163
+ console.log();
1164
+ console.log(
1165
+ import_chalk9.default.gray(
1166
+ "The following tooling and dependencies are provided by default on the Replicas Machine Image.\n"
1167
+ )
1168
+ );
1169
+ const sections = [
1170
+ {
1171
+ title: "Python",
1172
+ tools: ["Python 3.12.3", "pip 24", "Poetry 2.2.1", "uv 0.9.5"]
1173
+ },
1174
+ {
1175
+ title: "Node.js",
1176
+ tools: [
1177
+ "Node.js 22.21.0",
1178
+ "npm 10.9.4",
1179
+ "nvm 0.40.3",
1180
+ "Yarn 1.22.22",
1181
+ "pnpm 10.18.3",
1182
+ "ESLint 9.38.0",
1183
+ "Prettier 3.6.2"
1184
+ ]
1185
+ },
1186
+ {
1187
+ title: "C/C++",
1188
+ tools: ["gcc 13.3.0", "g++ 13.3.0", "make 4.3"]
1189
+ },
1190
+ {
1191
+ title: "Perl",
1192
+ tools: ["Perl 5.38.2"]
1193
+ },
1194
+ {
1195
+ title: "AI Coding Assistants",
1196
+ tools: ["Claude Code (@anthropic-ai/claude-code)", "OpenAI Codex (@openai/codex)"]
1197
+ },
1198
+ {
1199
+ title: "Misc",
1200
+ tools: [
1201
+ "git 2.43.0",
1202
+ "vim 9.1.697",
1203
+ "nano 7.2",
1204
+ "curl",
1205
+ "wget",
1206
+ "GitHub CLI (gh)",
1207
+ "Docker Engine"
1208
+ ]
1209
+ }
1210
+ ];
1211
+ sections.forEach((section) => {
1212
+ console.log(import_chalk9.default.blue(section.title));
1213
+ section.tools.forEach((tool) => {
1214
+ console.log(` - ${tool}`);
1215
+ });
1216
+ console.log();
1217
+ });
1218
+ }
1219
+
1220
+ // src/commands/codex-auth.ts
1221
+ var import_chalk10 = __toESM(require("chalk"));
1222
+
1223
+ // src/lib/codex-oauth.ts
1224
+ var import_http2 = __toESM(require("http"));
1225
+ var import_url2 = require("url");
1226
+ var import_open2 = __toESM(require("open"));
1227
+
1228
+ // src/lib/pkce.ts
1229
+ var import_crypto = require("crypto");
1230
+ function generatePkceCodes() {
1231
+ const verifierBytes = (0, import_crypto.randomBytes)(32);
1232
+ const codeVerifier = verifierBytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1233
+ const hash = (0, import_crypto.createHash)("sha256");
1234
+ hash.update(codeVerifier);
1235
+ const codeChallenge = hash.digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1236
+ return {
1237
+ codeVerifier,
1238
+ codeChallenge
1239
+ };
1240
+ }
1241
+ function generateState2() {
1242
+ return (0, import_crypto.randomBytes)(16).toString("hex");
1243
+ }
1244
+
1245
+ // src/lib/codex-oauth.ts
1246
+ var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
1247
+ var AUTHORIZATION_ENDPOINT = "https://auth.openai.com/oauth/authorize";
1248
+ var TOKEN_ENDPOINT = "https://auth.openai.com/oauth/token";
1249
+ var CALLBACK_PORT = 1455;
1250
+ var CALLBACK_PATH = "/auth/callback";
1251
+ var WEB_APP_URL2 = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1252
+ function buildAuthorizationUrl(codeChallenge, state) {
1253
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1254
+ const params = new URLSearchParams({
1255
+ response_type: "code",
1256
+ client_id: CODEX_OAUTH_CLIENT_ID,
1257
+ redirect_uri: redirectUri,
1258
+ scope: "openid profile email offline_access",
1259
+ code_challenge: codeChallenge,
1260
+ code_challenge_method: "S256",
1261
+ id_token_add_organizations: "true",
1262
+ codex_cli_simplified_flow: "true",
1263
+ state,
1264
+ originator: "codex_cli_rs"
1265
+ });
1266
+ return `${AUTHORIZATION_ENDPOINT}?${params.toString()}`;
1267
+ }
1268
+ async function exchangeCodeForTokens(code, codeVerifier) {
1269
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1270
+ const params = new URLSearchParams({
1271
+ grant_type: "authorization_code",
1272
+ code,
1273
+ redirect_uri: redirectUri,
1274
+ client_id: CODEX_OAUTH_CLIENT_ID,
1275
+ code_verifier: codeVerifier
1276
+ });
1277
+ const response = await fetch(TOKEN_ENDPOINT, {
1278
+ method: "POST",
1279
+ headers: {
1280
+ "Content-Type": "application/x-www-form-urlencoded"
1281
+ },
1282
+ body: params.toString()
1283
+ });
1284
+ if (!response.ok) {
1285
+ const errorText = await response.text();
1286
+ throw new Error(`Failed to exchange code for tokens: ${response.status} ${errorText}`);
1287
+ }
1288
+ const data = await response.json();
1289
+ return {
1290
+ idToken: data.id_token,
1291
+ accessToken: data.access_token,
1292
+ refreshToken: data.refresh_token
1293
+ };
1294
+ }
1295
+ function startCallbackServer(expectedState, codeVerifier) {
1296
+ return new Promise((resolve, reject) => {
1297
+ const server = import_http2.default.createServer(async (req, res) => {
1298
+ try {
1299
+ if (!req.url) {
1300
+ res.writeHead(400);
1301
+ res.end("Bad Request");
1302
+ return;
147
1303
  }
148
- process.exit(1);
1304
+ const url = new import_url2.URL(req.url, `http://127.0.0.1:${CALLBACK_PORT}`);
1305
+ if (url.pathname === CALLBACK_PATH) {
1306
+ const code = url.searchParams.get("code");
1307
+ const state = url.searchParams.get("state");
1308
+ const error = url.searchParams.get("error");
1309
+ const errorDescription = url.searchParams.get("error_description");
1310
+ if (error) {
1311
+ const errorMessage = encodeURIComponent(`${error}: ${errorDescription || "Unknown error"}`);
1312
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1313
+ res.end();
1314
+ server.close();
1315
+ reject(new Error(`OAuth error: ${error} - ${errorDescription}`));
1316
+ return;
1317
+ }
1318
+ if (state !== expectedState) {
1319
+ const errorMessage = encodeURIComponent("State parameter mismatch. Possible CSRF attack.");
1320
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1321
+ res.end();
1322
+ server.close();
1323
+ reject(new Error("State mismatch - possible CSRF attack"));
1324
+ return;
1325
+ }
1326
+ if (!code) {
1327
+ const errorMessage = encodeURIComponent("No authorization code received.");
1328
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1329
+ res.end();
1330
+ server.close();
1331
+ reject(new Error("No authorization code received"));
1332
+ return;
1333
+ }
1334
+ try {
1335
+ const tokens = await exchangeCodeForTokens(code, codeVerifier);
1336
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/success` });
1337
+ res.end();
1338
+ server.close();
1339
+ resolve(tokens);
1340
+ } catch (tokenError) {
1341
+ const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
1342
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1343
+ res.end();
1344
+ server.close();
1345
+ reject(tokenError);
1346
+ }
1347
+ } else {
1348
+ res.writeHead(404);
1349
+ res.end("Not Found");
1350
+ }
1351
+ } catch (serverError) {
1352
+ res.writeHead(500);
1353
+ res.end("Internal Server Error");
1354
+ server.close();
1355
+ reject(serverError);
1356
+ }
1357
+ });
1358
+ server.on("error", (err) => {
1359
+ if (err.code === "EADDRINUSE") {
1360
+ reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close any other OAuth flows and try again.`));
1361
+ } else {
1362
+ reject(err);
1363
+ }
1364
+ });
1365
+ server.listen(CALLBACK_PORT, "127.0.0.1");
1366
+ });
1367
+ }
1368
+ async function runCodexOAuthFlow() {
1369
+ const pkce = generatePkceCodes();
1370
+ const state = generateState2();
1371
+ const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
1372
+ const tokensPromise = startCallbackServer(state, pkce.codeVerifier);
1373
+ await new Promise((resolve) => setTimeout(resolve, 500));
1374
+ console.log("If the browser does not open automatically, visit:");
1375
+ console.log(authUrl);
1376
+ console.log();
1377
+ try {
1378
+ await (0, import_open2.default)(authUrl);
1379
+ } catch (openError) {
1380
+ console.warn("Failed to open browser automatically. Please open the URL manually.");
1381
+ }
1382
+ console.log("Waiting for authentication...");
1383
+ const tokens = await tokensPromise;
1384
+ console.log("\u2713 Successfully obtained Codex credentials");
1385
+ return tokens;
1386
+ }
1387
+
1388
+ // src/commands/codex-auth.ts
1389
+ async function codexAuthCommand(options = {}) {
1390
+ const isUserScoped = options.user === true;
1391
+ const scopeLabel = isUserScoped ? "personal" : "organization";
1392
+ console.log(import_chalk10.default.cyan(`Authenticating with Codex (${scopeLabel})...
1393
+ `));
1394
+ try {
1395
+ const tokens = await runCodexOAuthFlow();
1396
+ console.log(import_chalk10.default.gray("\nUploading credentials to Replicas..."));
1397
+ const endpoint = isUserScoped ? "/v1/codex/user/credentials" : "/v1/codex/credentials";
1398
+ const response = await orgAuthenticatedFetch(
1399
+ endpoint,
1400
+ {
1401
+ method: "POST",
1402
+ body: {
1403
+ access_token: tokens.accessToken,
1404
+ refresh_token: tokens.refreshToken,
1405
+ id_token: tokens.idToken
1406
+ }
1407
+ }
1408
+ );
1409
+ if (response.success) {
1410
+ console.log(import_chalk10.default.green("\n\u2713 Codex authentication complete!"));
1411
+ if (response.email) {
1412
+ console.log(import_chalk10.default.gray(` Account: ${response.email}`));
1413
+ }
1414
+ if (response.planType) {
1415
+ console.log(import_chalk10.default.gray(` Plan: ${response.planType}`));
1416
+ }
1417
+ if (isUserScoped) {
1418
+ console.log(import_chalk10.default.gray("\nYour personal Codex credentials have been saved.\n"));
1419
+ } else {
1420
+ console.log(import_chalk10.default.gray("\nYou can now use Codex in your workspaces.\n"));
1421
+ }
1422
+ } else {
1423
+ throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
149
1424
  }
150
- });
151
- // Default action for 'org' command (show current org)
152
- org.action(async () => {
1425
+ } catch (error) {
1426
+ console.log(import_chalk10.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1427
+ process.exit(1);
1428
+ }
1429
+ }
1430
+
1431
+ // src/commands/claude-auth.ts
1432
+ var import_chalk12 = __toESM(require("chalk"));
1433
+
1434
+ // src/lib/claude-oauth.ts
1435
+ var import_open3 = __toESM(require("open"));
1436
+ var import_readline = __toESM(require("readline"));
1437
+ var import_chalk11 = __toESM(require("chalk"));
1438
+ var DEFAULT_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1439
+ var CLAUDE_CLIENT_ID = process.env.CLAUDE_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID;
1440
+ var AUTHORIZATION_ENDPOINT2 = "https://claude.ai/oauth/authorize";
1441
+ var TOKEN_ENDPOINT2 = "https://console.anthropic.com/v1/oauth/token";
1442
+ var REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
1443
+ var SCOPES = "org:create_api_key user:profile user:inference";
1444
+ function buildAuthorizationUrl2(codeChallenge, state) {
1445
+ const params = new URLSearchParams({
1446
+ code: "true",
1447
+ client_id: CLAUDE_CLIENT_ID,
1448
+ response_type: "code",
1449
+ redirect_uri: REDIRECT_URI,
1450
+ scope: SCOPES,
1451
+ code_challenge: codeChallenge,
1452
+ code_challenge_method: "S256",
1453
+ state
1454
+ });
1455
+ return `${AUTHORIZATION_ENDPOINT2}?${params.toString()}`;
1456
+ }
1457
+ async function promptForAuthorizationCode(instruction) {
1458
+ const rl = import_readline.default.createInterface({
1459
+ input: process.stdin,
1460
+ output: process.stdout
1461
+ });
1462
+ return new Promise((resolve) => {
1463
+ rl.question(instruction, (answer) => {
1464
+ rl.close();
1465
+ resolve(answer.trim());
1466
+ });
1467
+ });
1468
+ }
1469
+ function parseUserInput(input) {
1470
+ if (!input.includes("#") && !input.includes("code=")) {
1471
+ return { code: input };
1472
+ }
1473
+ if (input.startsWith("http://") || input.startsWith("https://")) {
153
1474
  try {
154
- await (0, org_1.orgCommand)();
1475
+ const url = new URL(input);
1476
+ const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
1477
+ const params = new URLSearchParams(fragment);
1478
+ const code2 = params.get("code") ?? void 0;
1479
+ const state2 = params.get("state") ?? void 0;
1480
+ if (code2) {
1481
+ return { code: code2, state: state2 };
1482
+ }
1483
+ } catch {
155
1484
  }
156
- catch (error) {
157
- if (error instanceof Error) {
158
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
1485
+ }
1486
+ const [code, state] = input.split("#");
1487
+ return { code, state };
1488
+ }
1489
+ async function exchangeCodeForTokens2(code, state, expectedState, codeVerifier) {
1490
+ if (state && state !== expectedState) {
1491
+ throw new Error("State mismatch detected. Please restart the authentication flow.");
1492
+ }
1493
+ const payload = {
1494
+ code,
1495
+ state: state ?? expectedState,
1496
+ grant_type: "authorization_code",
1497
+ client_id: CLAUDE_CLIENT_ID,
1498
+ redirect_uri: REDIRECT_URI,
1499
+ code_verifier: codeVerifier
1500
+ };
1501
+ const response = await fetch(TOKEN_ENDPOINT2, {
1502
+ method: "POST",
1503
+ headers: {
1504
+ "Content-Type": "application/json"
1505
+ },
1506
+ body: JSON.stringify(payload)
1507
+ });
1508
+ if (!response.ok) {
1509
+ const text = await response.text();
1510
+ throw new Error(`Failed to exchange authorization code: ${response.status} ${text}`);
1511
+ }
1512
+ const data = await response.json();
1513
+ const now = Date.now();
1514
+ const expiresAt = new Date(now + data.expires_in * 1e3).toISOString();
1515
+ const refreshTokenExpiresAt = data.refresh_token_expires_in ? new Date(now + data.refresh_token_expires_in * 1e3).toISOString() : void 0;
1516
+ return {
1517
+ accessToken: data.access_token,
1518
+ refreshToken: data.refresh_token,
1519
+ tokenType: data.token_type,
1520
+ scope: data.scope,
1521
+ expiresAt,
1522
+ refreshTokenExpiresAt
1523
+ };
1524
+ }
1525
+ async function runClaudeOAuthFlow() {
1526
+ const pkce = generatePkceCodes();
1527
+ const state = generateState2();
1528
+ const authUrl = buildAuthorizationUrl2(pkce.codeChallenge, state);
1529
+ console.log(import_chalk11.default.gray("We will open your browser so you can authenticate with Claude Code."));
1530
+ console.log(import_chalk11.default.gray("If the browser does not open automatically, use the URL below:\n"));
1531
+ console.log(authUrl);
1532
+ console.log();
1533
+ try {
1534
+ await (0, import_open3.default)(authUrl);
1535
+ } catch {
1536
+ console.warn(import_chalk11.default.yellow("Failed to open browser automatically. Please copy and open the URL manually."));
1537
+ }
1538
+ console.log(import_chalk11.default.cyan("After completing authentication, copy the code shown on the success page."));
1539
+ console.log(import_chalk11.default.cyan("You can paste either the full URL, or a value formatted as CODE#STATE.\n"));
1540
+ const userInput = await promptForAuthorizationCode("Paste the authorization code (or URL) here: ");
1541
+ if (!userInput) {
1542
+ throw new Error("No authorization code provided.");
1543
+ }
1544
+ const { code, state: returnedState } = parseUserInput(userInput);
1545
+ if (!code) {
1546
+ throw new Error("Unable to parse authorization code. Please try again.");
1547
+ }
1548
+ console.log(import_chalk11.default.gray("\nExchanging authorization code for tokens..."));
1549
+ const tokens = await exchangeCodeForTokens2(code, returnedState, state, pkce.codeVerifier);
1550
+ console.log(import_chalk11.default.green("\u2713 Successfully obtained Claude credentials"));
1551
+ return tokens;
1552
+ }
1553
+
1554
+ // src/commands/claude-auth.ts
1555
+ async function claudeAuthCommand(options = {}) {
1556
+ const isUserScoped = options.user === true;
1557
+ const scopeLabel = isUserScoped ? "personal" : "organization";
1558
+ console.log(import_chalk12.default.cyan(`Authenticating with Claude Code (${scopeLabel})...
1559
+ `));
1560
+ try {
1561
+ const tokens = await runClaudeOAuthFlow();
1562
+ console.log(import_chalk12.default.gray("\nUploading credentials to Replicas..."));
1563
+ const endpoint = isUserScoped ? "/v1/claude/user/credentials" : "/v1/claude/credentials";
1564
+ const response = await orgAuthenticatedFetch(
1565
+ endpoint,
1566
+ {
1567
+ method: "POST",
1568
+ body: {
1569
+ access_token: tokens.accessToken,
1570
+ refresh_token: tokens.refreshToken,
1571
+ token_type: tokens.tokenType,
1572
+ scope: tokens.scope,
1573
+ expires_at: tokens.expiresAt,
1574
+ refresh_token_expires_at: tokens.refreshTokenExpiresAt
159
1575
  }
160
- process.exit(1);
1576
+ }
1577
+ );
1578
+ if (!response.success) {
1579
+ throw new Error(response.error || "Failed to upload Claude credentials to Replicas");
161
1580
  }
162
- });
163
- // Connect command
164
- program
165
- .command('connect <workspace-name>')
166
- .description('Connect to a workspace via SSH')
167
- .option('-c, --copy', 'Copy files from replicas.json to the workspace')
168
- .action(async (workspaceName, options) => {
169
- try {
170
- await (0, connect_1.connectCommand)(workspaceName, options);
1581
+ console.log(import_chalk12.default.green("\n\u2713 Claude authentication complete!"));
1582
+ if (response.email) {
1583
+ console.log(import_chalk12.default.gray(` Account: ${response.email}`));
1584
+ }
1585
+ if (response.planType) {
1586
+ console.log(import_chalk12.default.gray(` Plan: ${response.planType}`));
1587
+ }
1588
+ if (isUserScoped) {
1589
+ console.log(import_chalk12.default.gray("\nYour personal Claude credentials have been saved.\n"));
1590
+ } else {
1591
+ console.log(import_chalk12.default.gray("\nYou can now use Claude Code in your workspaces.\n"));
1592
+ }
1593
+ } catch (error) {
1594
+ console.log(import_chalk12.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1595
+ process.exit(1);
1596
+ }
1597
+ }
1598
+
1599
+ // src/commands/dev-sync.ts
1600
+ var import_chalk13 = __toESM(require("chalk"));
1601
+ var import_child_process5 = require("child_process");
1602
+ var import_path7 = __toESM(require("path"));
1603
+ async function devSyncCommand(workspaceName) {
1604
+ if (!isAuthenticated()) {
1605
+ console.log(import_chalk13.default.red('Not logged in. Please run "replicas login" first.'));
1606
+ process.exit(1);
1607
+ }
1608
+ try {
1609
+ const { workspace, sshKeyPath } = await prepareWorkspaceConnection(workspaceName, {});
1610
+ if (!workspace.ipv4_address) {
1611
+ throw new Error("Workspace does not have an IP address");
171
1612
  }
172
- catch (error) {
173
- if (error instanceof Error) {
174
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
1613
+ console.log(import_chalk13.default.blue(`
1614
+ Syncing engine to ${workspace.name}...`));
1615
+ const scriptPath = import_path7.default.resolve(__dirname, "../../../engine/scripts/dev-sync.sh");
1616
+ const devSync = (0, import_child_process5.spawn)(scriptPath, [
1617
+ "--ip",
1618
+ workspace.ipv4_address,
1619
+ "--key",
1620
+ sshKeyPath
1621
+ ], {
1622
+ stdio: "inherit",
1623
+ shell: true
1624
+ });
1625
+ await new Promise((resolve, reject) => {
1626
+ devSync.on("close", (code) => {
1627
+ if (code === 0) {
1628
+ resolve();
1629
+ } else {
1630
+ reject(new Error(`dev-sync script exited with code ${code}`));
175
1631
  }
176
- process.exit(1);
1632
+ });
1633
+ devSync.on("error", (error) => {
1634
+ reject(error);
1635
+ });
1636
+ });
1637
+ console.log(import_chalk13.default.green("\n\u2713 Engine synced successfully.\n"));
1638
+ console.log(import_chalk13.default.blue("Viewing logs (Ctrl+C to exit)...\n"));
1639
+ const logs = (0, import_child_process5.spawn)("ssh", [
1640
+ "-i",
1641
+ sshKeyPath,
1642
+ `ubuntu@${workspace.ipv4_address}`,
1643
+ "sudo journalctl -fu replicas-engine"
1644
+ ], {
1645
+ stdio: "inherit"
1646
+ });
1647
+ process.on("SIGINT", () => {
1648
+ logs.kill("SIGTERM");
1649
+ console.log(import_chalk13.default.green("\n\n\u2713 Disconnected from logs.\n"));
1650
+ process.exit(0);
1651
+ });
1652
+ await new Promise((resolve) => {
1653
+ logs.on("close", () => {
1654
+ resolve();
1655
+ });
1656
+ });
1657
+ } catch (error) {
1658
+ console.error(import_chalk13.default.red(`
1659
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1660
+ process.exit(1);
1661
+ }
1662
+ }
1663
+
1664
+ // src/commands/init.ts
1665
+ var import_chalk14 = __toESM(require("chalk"));
1666
+ var import_fs6 = __toESM(require("fs"));
1667
+ var import_path8 = __toESM(require("path"));
1668
+ var REPLICAS_CONFIG_FILENAME = "replicas.json";
1669
+ function getDefaultConfig() {
1670
+ return {
1671
+ copy: [],
1672
+ ports: [],
1673
+ systemPrompt: "",
1674
+ startHook: {
1675
+ timeout: 3e5,
1676
+ commands: []
177
1677
  }
178
- });
179
- // Code command
180
- program
181
- .command('code <workspace-name>')
182
- .description('Open a workspace in VSCode/Cursor via Remote SSH')
183
- .option('-c, --copy', 'Copy files from replicas.json to the workspace')
184
- .action(async (workspaceName, options) => {
185
- try {
186
- await (0, code_1.codeCommand)(workspaceName, options);
1678
+ };
1679
+ }
1680
+ function initCommand(options) {
1681
+ const configPath = import_path8.default.join(process.cwd(), REPLICAS_CONFIG_FILENAME);
1682
+ if (import_fs6.default.existsSync(configPath) && !options.force) {
1683
+ console.log(
1684
+ import_chalk14.default.yellow(
1685
+ `${REPLICAS_CONFIG_FILENAME} already exists in this directory.`
1686
+ )
1687
+ );
1688
+ console.log(import_chalk14.default.gray("Use --force to overwrite the existing file."));
1689
+ return;
1690
+ }
1691
+ const defaultConfig = getDefaultConfig();
1692
+ const configContent = JSON.stringify(defaultConfig, null, 2);
1693
+ try {
1694
+ import_fs6.default.writeFileSync(configPath, configContent + "\n", "utf-8");
1695
+ console.log(import_chalk14.default.green(`\u2713 Created ${REPLICAS_CONFIG_FILENAME}`));
1696
+ console.log("");
1697
+ console.log(import_chalk14.default.gray("Configuration options:"));
1698
+ console.log(
1699
+ import_chalk14.default.gray(' copy - Files to sync to the workspace (e.g., [".env"])')
1700
+ );
1701
+ console.log(
1702
+ import_chalk14.default.gray(" ports - Ports to forward from the workspace (e.g., [3000, 8080])")
1703
+ );
1704
+ console.log(
1705
+ import_chalk14.default.gray(" systemPrompt - Custom instructions for AI coding assistants")
1706
+ );
1707
+ console.log(
1708
+ import_chalk14.default.gray(" startHook - Commands to run on workspace startup")
1709
+ );
1710
+ } catch (error) {
1711
+ throw new Error(
1712
+ `Failed to create ${REPLICAS_CONFIG_FILENAME}: ${error instanceof Error ? error.message : "Unknown error"}`
1713
+ );
1714
+ }
1715
+ }
1716
+
1717
+ // src/lib/version-check.ts
1718
+ var import_chalk15 = __toESM(require("chalk"));
1719
+ var MONOLITH_URL2 = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
1720
+ var VERSION_CHECK_TIMEOUT = 2e3;
1721
+ function compareVersions(v1, v2) {
1722
+ const parts1 = v1.split(".").map(Number);
1723
+ const parts2 = v2.split(".").map(Number);
1724
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1725
+ const num1 = parts1[i] || 0;
1726
+ const num2 = parts2[i] || 0;
1727
+ if (num1 > num2) return 1;
1728
+ if (num1 < num2) return -1;
1729
+ }
1730
+ return 0;
1731
+ }
1732
+ async function checkForUpdates(currentVersion) {
1733
+ try {
1734
+ const controller = new AbortController();
1735
+ const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT);
1736
+ const response = await fetch(`${MONOLITH_URL2}/v1/cli/version`, {
1737
+ signal: controller.signal
1738
+ });
1739
+ clearTimeout(timeoutId);
1740
+ if (!response.ok) return;
1741
+ const data = await response.json();
1742
+ const latestVersion = data.version;
1743
+ if (compareVersions(latestVersion, currentVersion) > 0) {
1744
+ console.error(import_chalk15.default.gray(`
1745
+ Update available: ${currentVersion} \u2192 ${latestVersion}`));
1746
+ console.error(import_chalk15.default.gray(`Run: npm i -g replicas-cli@latest
1747
+ `));
1748
+ }
1749
+ } catch {
1750
+ }
1751
+ }
1752
+
1753
+ // src/commands/replica.ts
1754
+ var import_chalk16 = __toESM(require("chalk"));
1755
+ var import_prompts3 = __toESM(require("prompts"));
1756
+
1757
+ // ../shared/src/display-message/parsers/codex-parser.ts
1758
+ function safeJsonParse(str, fallback) {
1759
+ try {
1760
+ return JSON.parse(str);
1761
+ } catch {
1762
+ return fallback;
1763
+ }
1764
+ }
1765
+ function getStatusFromExitCode(exitCode) {
1766
+ return exitCode === 0 ? "completed" : "failed";
1767
+ }
1768
+ function findLastMessageByType(messages, type) {
1769
+ return messages.findLast((m) => m.type === type);
1770
+ }
1771
+ function parsePatch(input) {
1772
+ const operations = [];
1773
+ const lines = input.split("\n");
1774
+ let currentOp = null;
1775
+ let diffLines = [];
1776
+ for (let i = 0; i < lines.length; i++) {
1777
+ const line = lines[i];
1778
+ if (line.startsWith("*** Add File:")) {
1779
+ if (currentOp) {
1780
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1781
+ operations.push(currentOp);
1782
+ diffLines = [];
1783
+ }
1784
+ currentOp = {
1785
+ action: "add",
1786
+ path: line.replace("*** Add File:", "").trim()
1787
+ };
1788
+ } else if (line.startsWith("*** Update File:")) {
1789
+ if (currentOp) {
1790
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1791
+ operations.push(currentOp);
1792
+ diffLines = [];
1793
+ }
1794
+ currentOp = {
1795
+ action: "update",
1796
+ path: line.replace("*** Update File:", "").trim()
1797
+ };
1798
+ } else if (line.startsWith("*** Delete File:")) {
1799
+ if (currentOp) {
1800
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1801
+ operations.push(currentOp);
1802
+ diffLines = [];
1803
+ }
1804
+ currentOp = {
1805
+ action: "delete",
1806
+ path: line.replace("*** Delete File:", "").trim()
1807
+ };
1808
+ } else if (line.startsWith("*** Move to:")) {
1809
+ if (currentOp) {
1810
+ currentOp.moveTo = line.replace("*** Move to:", "").trim();
1811
+ }
1812
+ } else if (line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
1813
+ continue;
1814
+ } else if (currentOp && (line.startsWith("+") || line.startsWith("-") || line.startsWith("@@") || line.trim() === "")) {
1815
+ diffLines.push(line);
1816
+ }
1817
+ }
1818
+ if (currentOp) {
1819
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1820
+ operations.push(currentOp);
1821
+ }
1822
+ return operations;
1823
+ }
1824
+ function parseCodexEvents(events) {
1825
+ const messages = [];
1826
+ const pendingPatches = /* @__PURE__ */ new Map();
1827
+ events.forEach((event) => {
1828
+ if (event.type === "event_msg" && event.payload?.type === "user_message") {
1829
+ messages.push({
1830
+ id: `user-${event.timestamp}`,
1831
+ type: "user",
1832
+ content: event.payload.message || "",
1833
+ timestamp: event.timestamp
1834
+ });
1835
+ }
1836
+ if (event.type === "event_msg" && event.payload?.type === "agent_reasoning") {
1837
+ messages.push({
1838
+ id: `reasoning-${event.timestamp}-${messages.length}`,
1839
+ type: "reasoning",
1840
+ content: event.payload.text || "",
1841
+ status: "completed",
1842
+ timestamp: event.timestamp
1843
+ });
187
1844
  }
188
- catch (error) {
189
- if (error instanceof Error) {
190
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
1845
+ if (event.type === "response_item") {
1846
+ const payloadType = event.payload?.type;
1847
+ if (payloadType === "message" && event.payload?.role === "assistant") {
1848
+ const content = event.payload.content || [];
1849
+ const textContent = content.filter((c) => c.type === "output_text").map((c) => c.text || "").join("\n");
1850
+ if (textContent) {
1851
+ messages.push({
1852
+ id: `agent-${event.timestamp}`,
1853
+ type: "agent",
1854
+ content: textContent,
1855
+ timestamp: event.timestamp
1856
+ });
191
1857
  }
192
- process.exit(1);
1858
+ }
1859
+ if (payloadType === "function_call" && event.payload?.name === "shell") {
1860
+ const args = safeJsonParse(event.payload.arguments || "{}", {});
1861
+ const command = Array.isArray(args.command) ? args.command.join(" ") : args.command || "";
1862
+ messages.push({
1863
+ id: `command-${event.timestamp}`,
1864
+ type: "command",
1865
+ command,
1866
+ output: "",
1867
+ // Will be filled by function_call_output
1868
+ status: "in_progress",
1869
+ timestamp: event.timestamp
1870
+ });
1871
+ }
1872
+ if (payloadType === "function_call_output") {
1873
+ const output = safeJsonParse(
1874
+ event.payload.output || "{}",
1875
+ {}
1876
+ );
1877
+ if (output.output !== void 0 && output.metadata) {
1878
+ const commandMsg = findLastMessageByType(messages, "command");
1879
+ if (commandMsg) {
1880
+ commandMsg.output = output.output || "";
1881
+ commandMsg.exitCode = output.metadata?.exit_code;
1882
+ commandMsg.status = getStatusFromExitCode(output.metadata?.exit_code);
1883
+ }
1884
+ }
1885
+ }
1886
+ if (payloadType === "custom_tool_call") {
1887
+ const callId = event.payload.call_id;
1888
+ const name = event.payload.name;
1889
+ const input = event.payload.input || "";
1890
+ const status = event.payload.status || "in_progress";
1891
+ if (name === "apply_patch") {
1892
+ const operations = parsePatch(input);
1893
+ pendingPatches.set(callId, { input, status, timestamp: event.timestamp, operations });
1894
+ } else {
1895
+ messages.push({
1896
+ id: `toolcall-${event.timestamp}`,
1897
+ type: "tool_call",
1898
+ server: "custom",
1899
+ tool: name,
1900
+ status,
1901
+ timestamp: event.timestamp
1902
+ });
1903
+ }
1904
+ }
1905
+ if (payloadType === "custom_tool_call_output") {
1906
+ const callId = event.payload.call_id;
1907
+ const output = safeJsonParse(
1908
+ event.payload.output || "{}",
1909
+ {}
1910
+ );
1911
+ const pendingPatch = pendingPatches.get(callId);
1912
+ if (pendingPatch) {
1913
+ messages.push({
1914
+ id: `patch-${pendingPatch.timestamp}`,
1915
+ type: "patch",
1916
+ operations: pendingPatch.operations,
1917
+ output: output.output || "",
1918
+ exitCode: output.metadata?.exit_code,
1919
+ status: getStatusFromExitCode(output.metadata?.exit_code),
1920
+ timestamp: pendingPatch.timestamp
1921
+ });
1922
+ pendingPatches.delete(callId);
1923
+ } else {
1924
+ const toolCallMsg = findLastMessageByType(messages, "tool_call");
1925
+ if (toolCallMsg) {
1926
+ toolCallMsg.status = getStatusFromExitCode(output.metadata?.exit_code);
1927
+ }
1928
+ }
1929
+ }
1930
+ if (payloadType === "function_call" && event.payload?.name === "update_plan") {
1931
+ const args = safeJsonParse(
1932
+ event.payload.arguments || "{}",
1933
+ {}
1934
+ );
1935
+ if (args.plan && Array.isArray(args.plan)) {
1936
+ const todoItems = args.plan.map((item) => ({
1937
+ text: item.step,
1938
+ completed: item.status === "completed"
1939
+ }));
1940
+ messages.push({
1941
+ id: `todo-${event.timestamp}`,
1942
+ type: "todo_list",
1943
+ items: todoItems,
1944
+ status: "completed",
1945
+ timestamp: event.timestamp
1946
+ });
1947
+ }
1948
+ }
193
1949
  }
194
- });
195
- // Dev-sync command (only available if ENABLE_DEV_SYNC_COMMAND is set)
196
- if (process.env.ENABLE_DEV_SYNC_COMMAND === 'true') {
197
- program
198
- .command('dev-sync <workspace-name>')
199
- .description('[Dev] Sync local engine to workspace VM')
200
- .action(async (workspaceName) => {
201
- try {
202
- await (0, dev_sync_1.devSyncCommand)(workspaceName);
1950
+ });
1951
+ return messages;
1952
+ }
1953
+
1954
+ // ../shared/src/display-message/parsers/claude-parser.ts
1955
+ function safeJsonParse2(str, fallback) {
1956
+ try {
1957
+ return JSON.parse(str);
1958
+ } catch {
1959
+ return fallback;
1960
+ }
1961
+ }
1962
+ function extractToolResultContent(content) {
1963
+ if (!content) return "";
1964
+ if (typeof content === "string") return content;
1965
+ if (Array.isArray(content)) {
1966
+ return content.filter((item) => item.type === "text").map((item) => item.text || "").join("\n");
1967
+ }
1968
+ return "";
1969
+ }
1970
+ function parseClaudeEvents(events, parentToolUseId) {
1971
+ const messages = [];
1972
+ const filterValue = parentToolUseId !== void 0 ? parentToolUseId : null;
1973
+ const filteredEvents = events.filter(
1974
+ (e) => e.payload.parent_tool_use_id === filterValue
1975
+ );
1976
+ const toolCallMap = /* @__PURE__ */ new Map();
1977
+ filteredEvents.forEach((event) => {
1978
+ if (event.type === "claude-user") {
1979
+ const content = event.payload.message?.content || [];
1980
+ const toolResult = content.find((c) => c.type === "tool_result");
1981
+ if (toolResult && toolResult.tool_use_id) {
1982
+ return;
1983
+ }
1984
+ const textContent = content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
1985
+ const images = content.filter((c) => c.type === "image" && c.source).map((c) => {
1986
+ const source = c.source;
1987
+ return {
1988
+ type: "image",
1989
+ mediaType: source.media_type || "image/png",
1990
+ data: source.data || ""
1991
+ };
1992
+ }).filter((img) => img.data);
1993
+ if (textContent || images.length > 0) {
1994
+ messages.push({
1995
+ id: `user-${event.timestamp}`,
1996
+ type: "user",
1997
+ content: textContent || (images.length > 0 ? `[${images.length} image${images.length > 1 ? "s" : ""} attached]` : ""),
1998
+ images: images.length > 0 ? images : void 0,
1999
+ timestamp: event.timestamp
2000
+ });
2001
+ }
2002
+ }
2003
+ if (event.type === "claude-assistant") {
2004
+ const contentBlocks = event.payload.message?.content || [];
2005
+ contentBlocks.forEach((block) => {
2006
+ if (block.type === "text" && block.text) {
2007
+ messages.push({
2008
+ id: `agent-${event.timestamp}-${messages.length}`,
2009
+ type: "agent",
2010
+ content: block.text,
2011
+ timestamp: event.timestamp
2012
+ });
2013
+ }
2014
+ if (block.type === "thinking" && block.text) {
2015
+ messages.push({
2016
+ id: `reasoning-${event.timestamp}-${messages.length}`,
2017
+ type: "reasoning",
2018
+ content: block.text,
2019
+ status: "completed",
2020
+ timestamp: event.timestamp
2021
+ });
203
2022
  }
204
- catch (error) {
205
- if (error instanceof Error) {
206
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
2023
+ if (block.type === "tool_use" && block.id) {
2024
+ const toolName = block.name || "unknown";
2025
+ const toolInput = block.input || {};
2026
+ const toolUseId = block.id;
2027
+ toolCallMap.set(toolUseId, {
2028
+ messageIndex: messages.length,
2029
+ toolName,
2030
+ input: toolInput
2031
+ });
2032
+ if (toolName === "Task") {
2033
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
2034
+ const nestedEvents = events.filter((e) => e.payload.parent_tool_use_id === toolUseId).map((e) => ({
2035
+ timestamp: e.timestamp,
2036
+ type: e.type,
2037
+ payload: e.payload
2038
+ }));
2039
+ messages.push({
2040
+ id: `subagent-${event.timestamp}-${messages.length}`,
2041
+ type: "subagent",
2042
+ toolUseId,
2043
+ description: inputObj.description || "Subagent Task",
2044
+ prompt: inputObj.prompt || "",
2045
+ subagentType: inputObj.subagent_type || "general",
2046
+ model: inputObj.model,
2047
+ status: "in_progress",
2048
+ nestedEvents,
2049
+ timestamp: event.timestamp
2050
+ });
2051
+ } else if (toolName === "Bash" || toolName === "bash" || toolName === "shell") {
2052
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
2053
+ const command = inputObj.command || "";
2054
+ messages.push({
2055
+ id: `command-${event.timestamp}-${messages.length}`,
2056
+ type: "command",
2057
+ command,
2058
+ output: "",
2059
+ status: "in_progress",
2060
+ timestamp: event.timestamp
2061
+ });
2062
+ } else if (toolName === "Write" || toolName === "Edit") {
2063
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
2064
+ const filePath = inputObj.file_path || "";
2065
+ const action = toolName === "Write" ? "add" : "update";
2066
+ messages.push({
2067
+ id: `file-${event.timestamp}-${messages.length}`,
2068
+ type: "file_change",
2069
+ changes: [{
2070
+ path: filePath,
2071
+ kind: action
2072
+ }],
2073
+ status: "in_progress",
2074
+ timestamp: event.timestamp
2075
+ });
2076
+ } else if (toolName === "TodoWrite") {
2077
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
2078
+ const todos = inputObj.todos || [];
2079
+ const todoItems = todos.map((todo) => ({
2080
+ text: todo.content,
2081
+ completed: todo.status === "completed"
2082
+ }));
2083
+ if (todoItems.length > 0) {
2084
+ messages.push({
2085
+ id: `todo-${event.timestamp}-${messages.length}`,
2086
+ type: "todo_list",
2087
+ items: todoItems,
2088
+ status: "completed",
2089
+ timestamp: event.timestamp
2090
+ });
207
2091
  }
208
- process.exit(1);
2092
+ } else if (toolName === "WebSearch") {
2093
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
2094
+ const query = inputObj.query || "";
2095
+ messages.push({
2096
+ id: `search-${event.timestamp}-${messages.length}`,
2097
+ type: "web_search",
2098
+ query,
2099
+ status: "in_progress",
2100
+ timestamp: event.timestamp
2101
+ });
2102
+ } else {
2103
+ messages.push({
2104
+ id: `toolcall-${event.timestamp}-${messages.length}`,
2105
+ type: "tool_call",
2106
+ server: "claude",
2107
+ tool: toolName,
2108
+ input: toolInput,
2109
+ status: "in_progress",
2110
+ timestamp: event.timestamp
2111
+ });
2112
+ }
209
2113
  }
210
- });
2114
+ });
2115
+ }
2116
+ if (event.type === "claude-result") {
2117
+ const payload = event.payload;
2118
+ if (payload.is_error || payload.subtype !== "success") {
2119
+ const errorList = payload.errors || [];
2120
+ const errorMessage = errorList.length > 0 ? errorList.join("\n") : "Claude session encountered an unexpected error.";
2121
+ messages.push({
2122
+ id: `error-${event.timestamp}`,
2123
+ type: "error",
2124
+ message: errorMessage,
2125
+ timestamp: event.timestamp
2126
+ });
2127
+ }
2128
+ }
2129
+ });
2130
+ filteredEvents.forEach((event) => {
2131
+ if (event.type === "claude-user") {
2132
+ const content = event.payload.message?.content || [];
2133
+ const toolResult = content.find((c) => c.type === "tool_result");
2134
+ if (toolResult && toolResult.tool_use_id) {
2135
+ const toolInfo = toolCallMap.get(toolResult.tool_use_id);
2136
+ if (!toolInfo) return;
2137
+ const resultContent = extractToolResultContent(toolResult.content);
2138
+ const isError = toolResult.is_error || false;
2139
+ const status = isError ? "failed" : "completed";
2140
+ const message = messages[toolInfo.messageIndex];
2141
+ if (!message) return;
2142
+ if (message.type === "command") {
2143
+ message.output = resultContent;
2144
+ message.status = status;
2145
+ const exitCodeMatch = resultContent.match(/exit code:?\s*(\d+)/i);
2146
+ if (exitCodeMatch) {
2147
+ message.exitCode = parseInt(exitCodeMatch[1], 10);
2148
+ } else {
2149
+ message.exitCode = isError ? 1 : 0;
2150
+ }
2151
+ } else if (message.type === "file_change") {
2152
+ message.status = status;
2153
+ } else if (message.type === "web_search") {
2154
+ message.status = status;
2155
+ } else if (message.type === "tool_call") {
2156
+ message.output = resultContent;
2157
+ message.status = status;
2158
+ } else if (message.type === "subagent") {
2159
+ message.output = resultContent;
2160
+ message.status = status;
2161
+ }
2162
+ }
2163
+ }
2164
+ });
2165
+ return messages;
211
2166
  }
212
- // Config commands
213
- const config = program
214
- .command('config')
215
- .description('Manage CLI configuration');
216
- config
217
- .command('get <key>')
218
- .description('Get a configuration value')
219
- .action(async (key) => {
220
- try {
221
- await (0, config_1.configGetCommand)(key);
2167
+
2168
+ // ../shared/src/display-message/parsers/index.ts
2169
+ function parseAgentEvents(events, agentType) {
2170
+ if (agentType === "codex") {
2171
+ return parseCodexEvents(events);
2172
+ } else if (agentType === "claude") {
2173
+ return parseClaudeEvents(events);
2174
+ }
2175
+ return [];
2176
+ }
2177
+
2178
+ // src/commands/replica.ts
2179
+ function formatDate(dateString) {
2180
+ return new Date(dateString).toLocaleString();
2181
+ }
2182
+ function formatStatus(status) {
2183
+ switch (status) {
2184
+ case "active":
2185
+ return import_chalk16.default.green(status);
2186
+ case "booting":
2187
+ case "initializing":
2188
+ return import_chalk16.default.yellow(status);
2189
+ case "sleeping":
2190
+ return import_chalk16.default.gray(status);
2191
+ default:
2192
+ return status;
2193
+ }
2194
+ }
2195
+ function truncate(text, maxLength) {
2196
+ if (text.length <= maxLength) return text;
2197
+ return text.substring(0, maxLength) + "...";
2198
+ }
2199
+ function formatDisplayMessage(message) {
2200
+ const time = new Date(message.timestamp).toLocaleTimeString();
2201
+ switch (message.type) {
2202
+ case "user":
2203
+ console.log(import_chalk16.default.blue(`
2204
+ [${time}] USER:`));
2205
+ console.log(import_chalk16.default.white(` ${truncate(message.content, 500)}`));
2206
+ break;
2207
+ case "agent":
2208
+ console.log(import_chalk16.default.green(`
2209
+ [${time}] ASSISTANT:`));
2210
+ console.log(import_chalk16.default.white(` ${truncate(message.content, 500)}`));
2211
+ break;
2212
+ case "reasoning":
2213
+ console.log(import_chalk16.default.gray(`
2214
+ [${time}] THINKING:`));
2215
+ console.log(import_chalk16.default.gray(` ${truncate(message.content, 300)}`));
2216
+ break;
2217
+ case "command":
2218
+ console.log(import_chalk16.default.magenta(`
2219
+ [${time}] COMMAND:`));
2220
+ console.log(import_chalk16.default.white(` $ ${message.command}`));
2221
+ if (message.output) {
2222
+ console.log(import_chalk16.default.gray(` ${truncate(message.output, 200)}`));
2223
+ }
2224
+ if (message.exitCode !== void 0) {
2225
+ const exitColor = message.exitCode === 0 ? import_chalk16.default.green : import_chalk16.default.red;
2226
+ console.log(exitColor(` Exit code: ${message.exitCode}`));
2227
+ }
2228
+ break;
2229
+ case "file_change":
2230
+ console.log(import_chalk16.default.yellow(`
2231
+ [${time}] FILE CHANGES:`));
2232
+ for (const change of message.changes) {
2233
+ const icon = change.kind === "add" ? "+" : change.kind === "delete" ? "-" : "~";
2234
+ const color = change.kind === "add" ? import_chalk16.default.green : change.kind === "delete" ? import_chalk16.default.red : import_chalk16.default.yellow;
2235
+ console.log(color(` ${icon} ${change.path}`));
2236
+ }
2237
+ break;
2238
+ case "patch":
2239
+ console.log(import_chalk16.default.yellow(`
2240
+ [${time}] PATCH:`));
2241
+ for (const op of message.operations) {
2242
+ const icon = op.action === "add" ? "+" : op.action === "delete" ? "-" : "~";
2243
+ const color = op.action === "add" ? import_chalk16.default.green : op.action === "delete" ? import_chalk16.default.red : import_chalk16.default.yellow;
2244
+ console.log(color(` ${icon} ${op.path}`));
2245
+ }
2246
+ break;
2247
+ case "tool_call":
2248
+ console.log(import_chalk16.default.cyan(`
2249
+ [${time}] TOOL: ${message.tool}`));
2250
+ if (message.output) {
2251
+ console.log(import_chalk16.default.gray(` ${truncate(message.output, 200)}`));
2252
+ }
2253
+ break;
2254
+ case "web_search":
2255
+ console.log(import_chalk16.default.cyan(`
2256
+ [${time}] WEB SEARCH:`));
2257
+ console.log(import_chalk16.default.white(` "${message.query}"`));
2258
+ break;
2259
+ case "todo_list":
2260
+ console.log(import_chalk16.default.blue(`
2261
+ [${time}] PLAN:`));
2262
+ for (const item of message.items) {
2263
+ const icon = item.completed ? import_chalk16.default.green("[x]") : import_chalk16.default.gray("[ ]");
2264
+ console.log(` ${icon} ${item.text}`);
2265
+ }
2266
+ break;
2267
+ case "subagent":
2268
+ console.log(import_chalk16.default.magenta(`
2269
+ [${time}] SUBAGENT: ${message.description}`));
2270
+ if (message.output) {
2271
+ console.log(import_chalk16.default.gray(` ${truncate(message.output, 200)}`));
2272
+ }
2273
+ break;
2274
+ case "error":
2275
+ console.log(import_chalk16.default.red(`
2276
+ [${time}] ERROR:`));
2277
+ console.log(import_chalk16.default.red(` ${message.message}`));
2278
+ break;
2279
+ }
2280
+ }
2281
+ async function replicaListCommand(options) {
2282
+ if (!isAuthenticated()) {
2283
+ console.log(import_chalk16.default.red('Not logged in. Please run "replicas login" first.'));
2284
+ process.exit(1);
2285
+ }
2286
+ try {
2287
+ const params = new URLSearchParams();
2288
+ if (options.page) params.set("page", options.page);
2289
+ if (options.limit) params.set("limit", options.limit);
2290
+ const query = params.toString();
2291
+ const response = await orgAuthenticatedFetch(
2292
+ `/v1/replica${query ? "?" + query : ""}`
2293
+ );
2294
+ if (response.replicas.length === 0) {
2295
+ console.log(import_chalk16.default.yellow("\nNo replicas found.\n"));
2296
+ return;
222
2297
  }
223
- catch (error) {
224
- if (error instanceof Error) {
225
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
2298
+ console.log(import_chalk16.default.green(`
2299
+ Replicas (Page ${response.page} of ${response.totalPages}, Total: ${response.total}):
2300
+ `));
2301
+ for (const replica of response.replicas) {
2302
+ console.log(import_chalk16.default.white(` ${replica.name}`));
2303
+ console.log(import_chalk16.default.gray(` ID: ${replica.id}`));
2304
+ if (replica.repository) {
2305
+ console.log(import_chalk16.default.gray(` Repository: ${replica.repository}`));
2306
+ }
2307
+ console.log(import_chalk16.default.gray(` Instance: ${replica.instance_type}`));
2308
+ console.log(import_chalk16.default.gray(` Status: ${formatStatus(replica.status)}`));
2309
+ console.log(import_chalk16.default.gray(` Created: ${formatDate(replica.created_at)}`));
2310
+ if (replica.pull_requests && replica.pull_requests.length > 0) {
2311
+ console.log(import_chalk16.default.gray(` Pull Requests:`));
2312
+ for (const pr of replica.pull_requests) {
2313
+ console.log(import_chalk16.default.cyan(` - #${pr.number}: ${pr.url}`));
226
2314
  }
2315
+ }
2316
+ console.log();
2317
+ }
2318
+ } catch (error) {
2319
+ console.error(import_chalk16.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2320
+ process.exit(1);
2321
+ }
2322
+ }
2323
+ async function replicaGetCommand(id) {
2324
+ if (!isAuthenticated()) {
2325
+ console.log(import_chalk16.default.red('Not logged in. Please run "replicas login" first.'));
2326
+ process.exit(1);
2327
+ }
2328
+ try {
2329
+ const response = await orgAuthenticatedFetch(`/v1/replica/${id}`);
2330
+ const replica = response.replica;
2331
+ console.log(import_chalk16.default.green(`
2332
+ Replica: ${replica.name}
2333
+ `));
2334
+ console.log(import_chalk16.default.gray(` ID: ${replica.id}`));
2335
+ if (replica.repository) {
2336
+ console.log(import_chalk16.default.gray(` Repository: ${replica.repository}`));
2337
+ }
2338
+ console.log(import_chalk16.default.gray(` Instance: ${replica.instance_type}`));
2339
+ console.log(import_chalk16.default.gray(` Status: ${formatStatus(replica.status)}`));
2340
+ console.log(import_chalk16.default.gray(` Created: ${formatDate(replica.created_at)}`));
2341
+ if (replica.waking) {
2342
+ console.log(import_chalk16.default.yellow("\n Workspace is waking from sleep. Retry in 30-90 seconds for full details.\n"));
2343
+ } else {
2344
+ if (replica.coding_agent) {
2345
+ console.log(import_chalk16.default.gray(` Coding Agent: ${replica.coding_agent}`));
2346
+ }
2347
+ if (replica.branch) {
2348
+ console.log(import_chalk16.default.gray(` Branch: ${replica.branch}`));
2349
+ }
2350
+ if (replica.git_diff) {
2351
+ console.log(import_chalk16.default.gray(` Changes: ${import_chalk16.default.green(`+${replica.git_diff.added}`)} / ${import_chalk16.default.red(`-${replica.git_diff.removed}`)}`));
2352
+ }
2353
+ }
2354
+ if (replica.pull_requests && replica.pull_requests.length > 0) {
2355
+ console.log(import_chalk16.default.gray(` Pull Requests:`));
2356
+ for (const pr of replica.pull_requests) {
2357
+ console.log(import_chalk16.default.cyan(` - #${pr.number}: ${pr.url}`));
2358
+ }
2359
+ }
2360
+ console.log();
2361
+ } catch (error) {
2362
+ console.error(import_chalk16.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2363
+ process.exit(1);
2364
+ }
2365
+ }
2366
+ async function replicaCreateCommand(name, options) {
2367
+ if (!isAuthenticated()) {
2368
+ console.log(import_chalk16.default.red('Not logged in. Please run "replicas login" first.'));
2369
+ process.exit(1);
2370
+ }
2371
+ try {
2372
+ const repoResponse = await orgAuthenticatedFetch("/v1/repositories");
2373
+ const repositories = repoResponse.repositories;
2374
+ if (repositories.length === 0) {
2375
+ console.log(import_chalk16.default.red("No repositories found. Please add a repository first."));
2376
+ process.exit(1);
2377
+ }
2378
+ let replicaName = name;
2379
+ let message = options.message;
2380
+ let repository = options.repository;
2381
+ let instanceType = options.instanceType;
2382
+ let codingAgent = options.agent;
2383
+ if (!replicaName) {
2384
+ const response2 = await (0, import_prompts3.default)({
2385
+ type: "text",
2386
+ name: "name",
2387
+ message: "Enter replica name (without spaces):",
2388
+ validate: (value) => value.trim() ? true : "Name is required"
2389
+ });
2390
+ if (!response2.name) {
2391
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2392
+ return;
2393
+ }
2394
+ replicaName = response2.name;
2395
+ }
2396
+ if (!repository) {
2397
+ const response2 = await (0, import_prompts3.default)({
2398
+ type: "select",
2399
+ name: "repository",
2400
+ message: "Select a repository:",
2401
+ choices: repositories.map((repo) => ({
2402
+ title: repo.name,
2403
+ value: repo.name,
2404
+ description: repo.url
2405
+ }))
2406
+ });
2407
+ if (!response2.repository) {
2408
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2409
+ return;
2410
+ }
2411
+ repository = response2.repository;
2412
+ }
2413
+ if (!message) {
2414
+ const response2 = await (0, import_prompts3.default)({
2415
+ type: "text",
2416
+ name: "message",
2417
+ message: "Enter initial message for the replica:",
2418
+ validate: (value) => value.trim() ? true : "Message is required"
2419
+ });
2420
+ if (!response2.message) {
2421
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2422
+ return;
2423
+ }
2424
+ message = response2.message;
2425
+ }
2426
+ if (!instanceType) {
2427
+ const response2 = await (0, import_prompts3.default)({
2428
+ type: "select",
2429
+ name: "instanceType",
2430
+ message: "Select instance type:",
2431
+ choices: [
2432
+ { title: "Standard (2 vCPU, 4GB RAM)", value: "standard" },
2433
+ { title: "Large (2 vCPU, 8GB RAM)", value: "large" },
2434
+ { title: "Power (4 vCPU, 16GB RAM)", value: "power" }
2435
+ ],
2436
+ initial: 0
2437
+ });
2438
+ if (!response2.instanceType) {
2439
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2440
+ return;
2441
+ }
2442
+ instanceType = response2.instanceType;
2443
+ }
2444
+ if (!codingAgent) {
2445
+ const response2 = await (0, import_prompts3.default)({
2446
+ type: "select",
2447
+ name: "agent",
2448
+ message: "Select coding agent:",
2449
+ choices: [
2450
+ { title: "Claude", value: "claude" },
2451
+ { title: "Codex", value: "codex" }
2452
+ ],
2453
+ initial: 0
2454
+ });
2455
+ if (!response2.agent) {
2456
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2457
+ return;
2458
+ }
2459
+ codingAgent = response2.agent;
2460
+ }
2461
+ if (!instanceType || !["standard", "large", "power"].includes(instanceType)) {
2462
+ console.log(import_chalk16.default.red(`Invalid instance type: ${instanceType}. Must be one of: standard, large, power`));
2463
+ process.exit(1);
2464
+ }
2465
+ if (!codingAgent || !["claude", "codex"].includes(codingAgent)) {
2466
+ console.log(import_chalk16.default.red(`Invalid coding agent: ${codingAgent}. Must be one of: claude, codex`));
2467
+ process.exit(1);
2468
+ }
2469
+ const body = {
2470
+ name: replicaName,
2471
+ message,
2472
+ repository,
2473
+ instance_type: instanceType,
2474
+ coding_agent: codingAgent
2475
+ };
2476
+ console.log(import_chalk16.default.gray("\nCreating replica..."));
2477
+ const response = await orgAuthenticatedFetch("/v1/replica", {
2478
+ method: "POST",
2479
+ body
2480
+ });
2481
+ const replica = response.replica;
2482
+ console.log(import_chalk16.default.green(`
2483
+ Created replica: ${replica.name}`));
2484
+ console.log(import_chalk16.default.gray(` ID: ${replica.id}`));
2485
+ console.log(import_chalk16.default.gray(` Instance: ${replica.instance_type}`));
2486
+ console.log(import_chalk16.default.gray(` Status: ${formatStatus(replica.status)}`));
2487
+ console.log();
2488
+ } catch (error) {
2489
+ console.error(import_chalk16.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2490
+ process.exit(1);
2491
+ }
2492
+ }
2493
+ async function replicaSendCommand(id, options) {
2494
+ if (!isAuthenticated()) {
2495
+ console.log(import_chalk16.default.red('Not logged in. Please run "replicas login" first.'));
2496
+ process.exit(1);
2497
+ }
2498
+ try {
2499
+ let message = options.message;
2500
+ if (!message) {
2501
+ const response2 = await (0, import_prompts3.default)({
2502
+ type: "text",
2503
+ name: "message",
2504
+ message: "Enter message to send:",
2505
+ validate: (value) => value.trim() ? true : "Message is required"
2506
+ });
2507
+ if (!response2.message) {
2508
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2509
+ return;
2510
+ }
2511
+ message = response2.message;
2512
+ }
2513
+ const body = {
2514
+ message
2515
+ };
2516
+ if (options.agent) {
2517
+ if (!["claude", "codex"].includes(options.agent)) {
2518
+ console.log(import_chalk16.default.red(`Invalid coding agent: ${options.agent}. Must be one of: claude, codex`));
227
2519
  process.exit(1);
2520
+ }
2521
+ body.coding_agent = options.agent;
2522
+ }
2523
+ const response = await orgAuthenticatedFetch(
2524
+ `/v1/replica/${id}/send`,
2525
+ {
2526
+ method: "POST",
2527
+ body
2528
+ }
2529
+ );
2530
+ const statusColor = response.status === "sent" ? import_chalk16.default.green : import_chalk16.default.yellow;
2531
+ console.log(statusColor(`
2532
+ Message ${response.status}`));
2533
+ if (response.position !== void 0 && response.position > 0) {
2534
+ console.log(import_chalk16.default.gray(` Queue position: ${response.position}`));
2535
+ }
2536
+ console.log();
2537
+ } catch (error) {
2538
+ console.error(import_chalk16.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2539
+ process.exit(1);
2540
+ }
2541
+ }
2542
+ async function replicaDeleteCommand(id, options) {
2543
+ if (!isAuthenticated()) {
2544
+ console.log(import_chalk16.default.red('Not logged in. Please run "replicas login" first.'));
2545
+ process.exit(1);
2546
+ }
2547
+ try {
2548
+ if (!options.force) {
2549
+ const response = await (0, import_prompts3.default)({
2550
+ type: "confirm",
2551
+ name: "confirm",
2552
+ message: `Are you sure you want to delete replica ${id}?`,
2553
+ initial: false
2554
+ });
2555
+ if (!response.confirm) {
2556
+ console.log(import_chalk16.default.yellow("\nCancelled."));
2557
+ return;
2558
+ }
2559
+ }
2560
+ await orgAuthenticatedFetch(`/v1/replica/${id}`, {
2561
+ method: "DELETE"
2562
+ });
2563
+ console.log(import_chalk16.default.green(`
2564
+ Replica ${id} deleted.
2565
+ `));
2566
+ } catch (error) {
2567
+ console.error(import_chalk16.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2568
+ process.exit(1);
2569
+ }
2570
+ }
2571
+ async function replicaReadCommand(id, options) {
2572
+ if (!isAuthenticated()) {
2573
+ console.log(import_chalk16.default.red('Not logged in. Please run "replicas login" first.'));
2574
+ process.exit(1);
2575
+ }
2576
+ try {
2577
+ const params = new URLSearchParams();
2578
+ if (options.limit) params.set("limit", options.limit);
2579
+ if (options.offset) params.set("offset", options.offset);
2580
+ const query = params.toString();
2581
+ const response = await orgAuthenticatedFetch(
2582
+ `/v1/replica/${id}/read${query ? "?" + query : ""}`
2583
+ );
2584
+ if (response.waking) {
2585
+ console.log(import_chalk16.default.yellow("\nWorkspace is waking from sleep. Retry in 30-90 seconds.\n"));
2586
+ return;
2587
+ }
2588
+ console.log(import_chalk16.default.green(`
2589
+ Conversation History
2590
+ `));
2591
+ if (response.coding_agent) {
2592
+ console.log(import_chalk16.default.gray(` Agent: ${response.coding_agent}`));
2593
+ }
2594
+ if (response.thread_id) {
2595
+ console.log(import_chalk16.default.gray(` Thread ID: ${response.thread_id}`));
2596
+ }
2597
+ console.log(import_chalk16.default.gray(` Total Events: ${response.total}`));
2598
+ console.log(import_chalk16.default.gray(` Showing: ${response.events.length} events`));
2599
+ if (response.hasMore) {
2600
+ console.log(import_chalk16.default.gray(` Has More: yes (use --offset to paginate)`));
228
2601
  }
2602
+ console.log();
2603
+ if (response.events.length === 0) {
2604
+ console.log(import_chalk16.default.yellow(" No events found.\n"));
2605
+ return;
2606
+ }
2607
+ console.log(import_chalk16.default.gray("-".repeat(60)));
2608
+ const agentType = response.coding_agent || "claude";
2609
+ const displayMessages = parseAgentEvents(response.events, agentType);
2610
+ for (const message of displayMessages) {
2611
+ formatDisplayMessage(message);
2612
+ }
2613
+ console.log(import_chalk16.default.gray("\n" + "-".repeat(60)));
2614
+ console.log();
2615
+ } catch (error) {
2616
+ console.error(import_chalk16.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2617
+ process.exit(1);
2618
+ }
2619
+ }
2620
+
2621
+ // src/commands/repositories.ts
2622
+ var import_chalk17 = __toESM(require("chalk"));
2623
+ function formatDate2(dateString) {
2624
+ return new Date(dateString).toLocaleString();
2625
+ }
2626
+ async function repositoriesListCommand() {
2627
+ if (!isAuthenticated()) {
2628
+ console.log(import_chalk17.default.red('Not logged in. Please run "replicas login" first.'));
2629
+ process.exit(1);
2630
+ }
2631
+ try {
2632
+ const response = await orgAuthenticatedFetch("/v1/repositories");
2633
+ if (response.repositories.length === 0) {
2634
+ console.log(import_chalk17.default.yellow("\nNo repositories found.\n"));
2635
+ return;
2636
+ }
2637
+ console.log(import_chalk17.default.green(`
2638
+ Repositories (${response.repositories.length}):
2639
+ `));
2640
+ for (const repo of response.repositories) {
2641
+ console.log(import_chalk17.default.white(` ${repo.name}`));
2642
+ console.log(import_chalk17.default.gray(` URL: ${repo.url}`));
2643
+ console.log(import_chalk17.default.gray(` Default Branch: ${repo.default_branch}`));
2644
+ console.log(import_chalk17.default.gray(` Created: ${formatDate2(repo.created_at)}`));
2645
+ if (repo.github_repository_id) {
2646
+ console.log(import_chalk17.default.gray(` GitHub ID: ${repo.github_repository_id}`));
2647
+ }
2648
+ console.log();
2649
+ }
2650
+ } catch (error) {
2651
+ console.error(import_chalk17.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2652
+ process.exit(1);
2653
+ }
2654
+ }
2655
+
2656
+ // src/index.ts
2657
+ var CLI_VERSION = "0.2.26";
2658
+ var program = new import_commander.Command();
2659
+ program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
2660
+ program.command("login").description("Authenticate with your Replicas account").action(async () => {
2661
+ try {
2662
+ await loginCommand();
2663
+ } catch (error) {
2664
+ if (error instanceof Error) {
2665
+ console.error(import_chalk18.default.red(`
2666
+ \u2717 ${error.message}
2667
+ `));
2668
+ }
2669
+ process.exit(1);
2670
+ }
229
2671
  });
230
- config
231
- .command('set <key> <value>')
232
- .description('Set a configuration value')
233
- .action(async (key, value) => {
234
- try {
235
- await (0, config_1.configSetCommand)(key, value);
2672
+ program.command("init").description("Create a replicas.json configuration file").option("-f, --force", "Overwrite existing replicas.json file").action((options) => {
2673
+ try {
2674
+ initCommand(options);
2675
+ } catch (error) {
2676
+ if (error instanceof Error) {
2677
+ console.error(import_chalk18.default.red(`
2678
+ \u2717 ${error.message}
2679
+ `));
236
2680
  }
237
- catch (error) {
238
- if (error instanceof Error) {
239
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
240
- }
241
- process.exit(1);
2681
+ process.exit(1);
2682
+ }
2683
+ });
2684
+ program.command("logout").description("Clear stored credentials").action(() => {
2685
+ try {
2686
+ logoutCommand();
2687
+ } catch (error) {
2688
+ if (error instanceof Error) {
2689
+ console.error(import_chalk18.default.red(`
2690
+ \u2717 ${error.message}
2691
+ `));
242
2692
  }
2693
+ process.exit(1);
2694
+ }
243
2695
  });
244
- config
245
- .command('list')
246
- .description('List all configuration values')
247
- .action(async () => {
248
- try {
249
- await (0, config_1.configListCommand)();
2696
+ program.command("whoami").description("Display current authenticated user").action(async () => {
2697
+ try {
2698
+ await whoamiCommand();
2699
+ } catch (error) {
2700
+ if (error instanceof Error) {
2701
+ console.error(import_chalk18.default.red(`
2702
+ \u2717 ${error.message}
2703
+ `));
250
2704
  }
251
- catch (error) {
252
- if (error instanceof Error) {
253
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
254
- }
255
- process.exit(1);
2705
+ process.exit(1);
2706
+ }
2707
+ });
2708
+ program.command("tools").description("List tools available on the Replicas Machine Image").action(async () => {
2709
+ try {
2710
+ await toolsCommand();
2711
+ } catch (error) {
2712
+ if (error instanceof Error) {
2713
+ console.error(import_chalk18.default.red(`
2714
+ \u2717 ${error.message}
2715
+ `));
256
2716
  }
2717
+ process.exit(1);
2718
+ }
257
2719
  });
258
- // Replica management commands (top-level since replicas are the main primitive)
259
- program
260
- .command('list')
261
- .description('List all replicas')
262
- .option('-p, --page <page>', 'Page number')
263
- .option('-l, --limit <limit>', 'Number of items per page')
264
- .action(async (options) => {
265
- try {
266
- await (0, replica_1.replicaListCommand)(options);
2720
+ program.command("codex-auth").description("Authenticate Replicas with your Codex credentials").option("-u, --user", "Store credentials for your personal account").action(async (options) => {
2721
+ try {
2722
+ await codexAuthCommand(options);
2723
+ } catch (error) {
2724
+ if (error instanceof Error) {
2725
+ console.error(import_chalk18.default.red(`
2726
+ \u2717 ${error.message}
2727
+ `));
267
2728
  }
268
- catch (error) {
269
- if (error instanceof Error) {
270
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
271
- }
272
- process.exit(1);
2729
+ process.exit(1);
2730
+ }
2731
+ });
2732
+ program.command("claude-auth").description("Authenticate Replicas with your Claude Code credentials").option("-u, --user", "Store credentials for your personal account").action(async (options) => {
2733
+ try {
2734
+ await claudeAuthCommand(options);
2735
+ } catch (error) {
2736
+ if (error instanceof Error) {
2737
+ console.error(import_chalk18.default.red(`
2738
+ \u2717 ${error.message}
2739
+ `));
273
2740
  }
2741
+ process.exit(1);
2742
+ }
274
2743
  });
275
- program
276
- .command('get <id>')
277
- .description('Get replica details by ID')
278
- .action(async (id) => {
279
- try {
280
- await (0, replica_1.replicaGetCommand)(id);
2744
+ var org = program.command("org").description("Manage organizations");
2745
+ org.command("switch").description("Switch to a different organization").action(async () => {
2746
+ try {
2747
+ await orgSwitchCommand();
2748
+ } catch (error) {
2749
+ if (error instanceof Error) {
2750
+ console.error(import_chalk18.default.red(`
2751
+ \u2717 ${error.message}
2752
+ `));
281
2753
  }
282
- catch (error) {
283
- if (error instanceof Error) {
284
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
285
- }
286
- process.exit(1);
2754
+ process.exit(1);
2755
+ }
2756
+ });
2757
+ org.action(async () => {
2758
+ try {
2759
+ await orgCommand();
2760
+ } catch (error) {
2761
+ if (error instanceof Error) {
2762
+ console.error(import_chalk18.default.red(`
2763
+ \u2717 ${error.message}
2764
+ `));
287
2765
  }
2766
+ process.exit(1);
2767
+ }
288
2768
  });
289
- program
290
- .command('create [name]')
291
- .description('Create a new replica')
292
- .option('-m, --message <message>', 'Initial message for the replica')
293
- .option('-r, --repository <repository>', 'Repository name')
294
- .option('-i, --instance-type <type>', 'Instance type (standard, large, power)')
295
- .option('-a, --agent <agent>', 'Coding agent (claude, codex)')
296
- .action(async (name, options) => {
297
- try {
298
- await (0, replica_1.replicaCreateCommand)(name, options);
2769
+ program.command("connect <workspace-name>").description("Connect to a workspace via SSH").option("-c, --copy", "Copy files from replicas.json to the workspace").action(async (workspaceName, options) => {
2770
+ try {
2771
+ await connectCommand(workspaceName, options);
2772
+ } catch (error) {
2773
+ if (error instanceof Error) {
2774
+ console.error(import_chalk18.default.red(`
2775
+ \u2717 ${error.message}
2776
+ `));
299
2777
  }
300
- catch (error) {
301
- if (error instanceof Error) {
302
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
303
- }
304
- process.exit(1);
2778
+ process.exit(1);
2779
+ }
2780
+ });
2781
+ program.command("code <workspace-name>").description("Open a workspace in VSCode/Cursor via Remote SSH").option("-c, --copy", "Copy files from replicas.json to the workspace").action(async (workspaceName, options) => {
2782
+ try {
2783
+ await codeCommand(workspaceName, options);
2784
+ } catch (error) {
2785
+ if (error instanceof Error) {
2786
+ console.error(import_chalk18.default.red(`
2787
+ \u2717 ${error.message}
2788
+ `));
305
2789
  }
2790
+ process.exit(1);
2791
+ }
306
2792
  });
307
- program
308
- .command('send <id>')
309
- .description('Send a message to a replica')
310
- .option('-m, --message <message>', 'Message to send')
311
- .option('-a, --agent <agent>', 'Coding agent (claude, codex)')
312
- .action(async (id, options) => {
2793
+ if (process.env.ENABLE_DEV_SYNC_COMMAND === "true") {
2794
+ program.command("dev-sync <workspace-name>").description("[Dev] Sync local engine to workspace VM").action(async (workspaceName) => {
313
2795
  try {
314
- await (0, replica_1.replicaSendCommand)(id, options);
2796
+ await devSyncCommand(workspaceName);
2797
+ } catch (error) {
2798
+ if (error instanceof Error) {
2799
+ console.error(import_chalk18.default.red(`
2800
+ \u2717 ${error.message}
2801
+ `));
2802
+ }
2803
+ process.exit(1);
315
2804
  }
316
- catch (error) {
317
- if (error instanceof Error) {
318
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
319
- }
320
- process.exit(1);
2805
+ });
2806
+ }
2807
+ var config = program.command("config").description("Manage CLI configuration");
2808
+ config.command("get <key>").description("Get a configuration value").action(async (key) => {
2809
+ try {
2810
+ await configGetCommand(key);
2811
+ } catch (error) {
2812
+ if (error instanceof Error) {
2813
+ console.error(import_chalk18.default.red(`
2814
+ \u2717 ${error.message}
2815
+ `));
321
2816
  }
2817
+ process.exit(1);
2818
+ }
322
2819
  });
323
- program
324
- .command('delete <id>')
325
- .description('Delete a replica')
326
- .option('-f, --force', 'Skip confirmation prompt')
327
- .action(async (id, options) => {
328
- try {
329
- await (0, replica_1.replicaDeleteCommand)(id, options);
2820
+ config.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
2821
+ try {
2822
+ await configSetCommand(key, value);
2823
+ } catch (error) {
2824
+ if (error instanceof Error) {
2825
+ console.error(import_chalk18.default.red(`
2826
+ \u2717 ${error.message}
2827
+ `));
330
2828
  }
331
- catch (error) {
332
- if (error instanceof Error) {
333
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
334
- }
335
- process.exit(1);
2829
+ process.exit(1);
2830
+ }
2831
+ });
2832
+ config.command("list").description("List all configuration values").action(async () => {
2833
+ try {
2834
+ await configListCommand();
2835
+ } catch (error) {
2836
+ if (error instanceof Error) {
2837
+ console.error(import_chalk18.default.red(`
2838
+ \u2717 ${error.message}
2839
+ `));
336
2840
  }
2841
+ process.exit(1);
2842
+ }
337
2843
  });
338
- // Repositories commands
339
- const repos = program
340
- .command('repos')
341
- .description('Manage repositories');
342
- repos
343
- .command('list')
344
- .description('List all repositories')
345
- .action(async () => {
346
- try {
347
- await (0, repositories_1.repositoriesListCommand)();
2844
+ 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) => {
2845
+ try {
2846
+ await replicaListCommand(options);
2847
+ } catch (error) {
2848
+ if (error instanceof Error) {
2849
+ console.error(import_chalk18.default.red(`
2850
+ \u2717 ${error.message}
2851
+ `));
348
2852
  }
349
- catch (error) {
350
- if (error instanceof Error) {
351
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
352
- }
353
- process.exit(1);
2853
+ process.exit(1);
2854
+ }
2855
+ });
2856
+ program.command("get <id>").description("Get replica details by ID").action(async (id) => {
2857
+ try {
2858
+ await replicaGetCommand(id);
2859
+ } catch (error) {
2860
+ if (error instanceof Error) {
2861
+ console.error(import_chalk18.default.red(`
2862
+ \u2717 ${error.message}
2863
+ `));
354
2864
  }
2865
+ process.exit(1);
2866
+ }
355
2867
  });
356
- // Default action for 'repos' command (list repos)
357
- repos.action(async () => {
358
- try {
359
- await (0, repositories_1.repositoriesListCommand)();
2868
+ program.command("create [name]").description("Create a new replica").option("-m, --message <message>", "Initial message for the replica").option("-r, --repository <repository>", "Repository name").option("-i, --instance-type <type>", "Instance type (standard, large, power)").option("-a, --agent <agent>", "Coding agent (claude, codex)").action(async (name, options) => {
2869
+ try {
2870
+ await replicaCreateCommand(name, options);
2871
+ } catch (error) {
2872
+ if (error instanceof Error) {
2873
+ console.error(import_chalk18.default.red(`
2874
+ \u2717 ${error.message}
2875
+ `));
360
2876
  }
361
- catch (error) {
362
- if (error instanceof Error) {
363
- console.error(chalk_1.default.red(`\n✗ ${error.message}\n`));
364
- }
365
- process.exit(1);
2877
+ process.exit(1);
2878
+ }
2879
+ });
2880
+ 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) => {
2881
+ try {
2882
+ await replicaSendCommand(id, options);
2883
+ } catch (error) {
2884
+ if (error instanceof Error) {
2885
+ console.error(import_chalk18.default.red(`
2886
+ \u2717 ${error.message}
2887
+ `));
2888
+ }
2889
+ process.exit(1);
2890
+ }
2891
+ });
2892
+ program.command("delete <id>").description("Delete a replica").option("-f, --force", "Skip confirmation prompt").action(async (id, options) => {
2893
+ try {
2894
+ await replicaDeleteCommand(id, options);
2895
+ } catch (error) {
2896
+ if (error instanceof Error) {
2897
+ console.error(import_chalk18.default.red(`
2898
+ \u2717 ${error.message}
2899
+ `));
366
2900
  }
2901
+ process.exit(1);
2902
+ }
367
2903
  });
368
- const versionCheckPromise = (0, version_check_1.checkForUpdates)(exports.CLI_VERSION);
2904
+ 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) => {
2905
+ try {
2906
+ await replicaReadCommand(id, options);
2907
+ } catch (error) {
2908
+ if (error instanceof Error) {
2909
+ console.error(import_chalk18.default.red(`
2910
+ \u2717 ${error.message}
2911
+ `));
2912
+ }
2913
+ process.exit(1);
2914
+ }
2915
+ });
2916
+ var repos = program.command("repos").description("Manage repositories");
2917
+ repos.command("list").description("List all repositories").action(async () => {
2918
+ try {
2919
+ await repositoriesListCommand();
2920
+ } catch (error) {
2921
+ if (error instanceof Error) {
2922
+ console.error(import_chalk18.default.red(`
2923
+ \u2717 ${error.message}
2924
+ `));
2925
+ }
2926
+ process.exit(1);
2927
+ }
2928
+ });
2929
+ repos.action(async () => {
2930
+ try {
2931
+ await repositoriesListCommand();
2932
+ } catch (error) {
2933
+ if (error instanceof Error) {
2934
+ console.error(import_chalk18.default.red(`
2935
+ \u2717 ${error.message}
2936
+ `));
2937
+ }
2938
+ process.exit(1);
2939
+ }
2940
+ });
2941
+ var versionCheckPromise = checkForUpdates(CLI_VERSION);
369
2942
  program.parse();
370
- versionCheckPromise.catch(() => { });
371
- //# sourceMappingURL=index.js.map
2943
+ versionCheckPromise.catch(() => {
2944
+ });
2945
+ // Annotate the CommonJS export names for ESM import in node:
2946
+ 0 && (module.exports = {
2947
+ CLI_VERSION
2948
+ });