cyrus-ai 0.2.0 ā 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Application.d.ts +3 -2
- package/dist/src/Application.d.ts.map +1 -1
- package/dist/src/Application.js +12 -11
- package/dist/src/Application.js.map +1 -1
- package/dist/src/app.js +47 -75
- package/dist/src/app.js.map +1 -1
- package/package.json +7 -6
- package/dist/app.d.ts +0 -3
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js +0 -1697
- package/dist/app.js.map +0 -1
package/dist/app.js
DELETED
|
@@ -1,1697 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import http from "node:http";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { basename, dirname, resolve } from "node:path";
|
|
6
|
-
import readline from "node:readline";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { DEFAULT_PROXY_URL, } from "cyrus-core";
|
|
9
|
-
import { EdgeWorker, SharedApplicationServer } from "cyrus-edge-worker";
|
|
10
|
-
import dotenv from "dotenv";
|
|
11
|
-
import open from "open";
|
|
12
|
-
// Parse command line arguments
|
|
13
|
-
const args = process.argv.slice(2);
|
|
14
|
-
const envFileArg = args.find((arg) => arg.startsWith("--env-file="));
|
|
15
|
-
const cyrusHomeArg = args.find((arg) => arg.startsWith("--cyrus-home="));
|
|
16
|
-
// Constants are imported from cyrus-core
|
|
17
|
-
// Determine the Cyrus home directory once at startup
|
|
18
|
-
let CYRUS_HOME;
|
|
19
|
-
if (cyrusHomeArg) {
|
|
20
|
-
const customPath = cyrusHomeArg.split("=")[1];
|
|
21
|
-
if (customPath) {
|
|
22
|
-
CYRUS_HOME = resolve(customPath);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
console.error("Error: --cyrus-home flag requires a directory path");
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
CYRUS_HOME = resolve(homedir(), ".cyrus");
|
|
31
|
-
}
|
|
32
|
-
// CRITICAL: Source .env file from CYRUS_HOME before any other operations
|
|
33
|
-
// This ensures CLOUDFLARE_TOKEN, CYRUS_API_KEY, and other credentials are available
|
|
34
|
-
const cyrusEnvPath = resolve(CYRUS_HOME, ".env");
|
|
35
|
-
if (existsSync(cyrusEnvPath)) {
|
|
36
|
-
dotenv.config({ path: cyrusEnvPath });
|
|
37
|
-
}
|
|
38
|
-
// Get the directory of the current module for reading package.json
|
|
39
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
40
|
-
const __dirname = dirname(__filename);
|
|
41
|
-
// Read package.json to get the actual version
|
|
42
|
-
const packageJsonPath = resolve(__dirname, "..", "package.json");
|
|
43
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
44
|
-
// Handle --version argument
|
|
45
|
-
if (args.includes("--version")) {
|
|
46
|
-
console.log(packageJson.version);
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
// Handle --help argument
|
|
50
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
51
|
-
console.log(`
|
|
52
|
-
cyrus - AI-powered Linear issue automation using Claude
|
|
53
|
-
|
|
54
|
-
Usage: cyrus [command] [options]
|
|
55
|
-
|
|
56
|
-
Commands:
|
|
57
|
-
start Start the edge worker (default)
|
|
58
|
-
auth <auth-key> Authenticate with Cyrus Pro plan using auth key
|
|
59
|
-
check-tokens Check the status of all Linear tokens
|
|
60
|
-
refresh-token Refresh a specific Linear token
|
|
61
|
-
add-repository Add a new repository configuration
|
|
62
|
-
billing Open Stripe billing portal (Pro plan only)
|
|
63
|
-
set-customer-id Set your Stripe customer ID
|
|
64
|
-
|
|
65
|
-
Options:
|
|
66
|
-
--version Show version number
|
|
67
|
-
--help, -h Show help
|
|
68
|
-
--env-file=<path> Load environment variables from file
|
|
69
|
-
--cyrus-home=<dir> Specify custom Cyrus config directory (default: ~/.cyrus)
|
|
70
|
-
|
|
71
|
-
Examples:
|
|
72
|
-
cyrus Start the edge worker
|
|
73
|
-
cyrus auth <your-auth-key> Authenticate and start using Pro plan
|
|
74
|
-
cyrus check-tokens Check all Linear token statuses
|
|
75
|
-
cyrus refresh-token Interactive token refresh
|
|
76
|
-
cyrus add-repository Add a new repository interactively
|
|
77
|
-
cyrus --cyrus-home=/tmp/cyrus Use custom config directory
|
|
78
|
-
`);
|
|
79
|
-
process.exit(0);
|
|
80
|
-
}
|
|
81
|
-
// Load environment variables only if --env-file is specified
|
|
82
|
-
if (envFileArg) {
|
|
83
|
-
const envFile = envFileArg.split("=")[1];
|
|
84
|
-
if (envFile) {
|
|
85
|
-
dotenv.config({ path: envFile });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Edge application that uses EdgeWorker from package
|
|
90
|
-
*/
|
|
91
|
-
class EdgeApp {
|
|
92
|
-
edgeWorker = null;
|
|
93
|
-
isShuttingDown = false;
|
|
94
|
-
cyrusHome;
|
|
95
|
-
constructor(cyrusHome) {
|
|
96
|
-
this.cyrusHome = cyrusHome;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Get the edge configuration file path
|
|
100
|
-
*/
|
|
101
|
-
getEdgeConfigPath() {
|
|
102
|
-
return resolve(this.cyrusHome, "config.json");
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Get the legacy edge configuration file path (for migration)
|
|
106
|
-
*/
|
|
107
|
-
getLegacyEdgeConfigPath() {
|
|
108
|
-
return resolve(process.cwd(), ".edge-config.json");
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Migrate configuration from legacy location if needed
|
|
112
|
-
*/
|
|
113
|
-
migrateConfigIfNeeded() {
|
|
114
|
-
const newConfigPath = this.getEdgeConfigPath();
|
|
115
|
-
const legacyConfigPath = this.getLegacyEdgeConfigPath();
|
|
116
|
-
// If new config already exists, no migration needed
|
|
117
|
-
if (existsSync(newConfigPath)) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
// If legacy config doesn't exist, no migration needed
|
|
121
|
-
if (!existsSync(legacyConfigPath)) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
// Ensure the ~/.cyrus directory exists
|
|
126
|
-
const configDir = dirname(newConfigPath);
|
|
127
|
-
if (!existsSync(configDir)) {
|
|
128
|
-
mkdirSync(configDir, { recursive: true });
|
|
129
|
-
}
|
|
130
|
-
// Copy the legacy config to the new location
|
|
131
|
-
copyFileSync(legacyConfigPath, newConfigPath);
|
|
132
|
-
console.log(`š¦ Migrated configuration from ${legacyConfigPath} to ${newConfigPath}`);
|
|
133
|
-
console.log(`š” You can safely remove the old ${legacyConfigPath} file if desired`);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
console.warn(`ā ļø Failed to migrate config from ${legacyConfigPath}:`, error.message);
|
|
137
|
-
console.warn(` Please manually copy your configuration to ${newConfigPath}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Load edge configuration (credentials and repositories)
|
|
142
|
-
* Note: Strips promptTemplatePath from all repositories to ensure built-in template is used
|
|
143
|
-
*/
|
|
144
|
-
loadEdgeConfig() {
|
|
145
|
-
// Migrate from legacy location if needed
|
|
146
|
-
this.migrateConfigIfNeeded();
|
|
147
|
-
const edgeConfigPath = this.getEdgeConfigPath();
|
|
148
|
-
let config = { repositories: [] };
|
|
149
|
-
if (existsSync(edgeConfigPath)) {
|
|
150
|
-
try {
|
|
151
|
-
config = JSON.parse(readFileSync(edgeConfigPath, "utf-8"));
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
console.error("Failed to load edge config:", e.message);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Strip promptTemplatePath from all repositories to ensure built-in template is used
|
|
158
|
-
if (config.repositories) {
|
|
159
|
-
config.repositories = config.repositories.map((repo) => {
|
|
160
|
-
const { promptTemplatePath, ...repoWithoutTemplate } = repo;
|
|
161
|
-
if (promptTemplatePath) {
|
|
162
|
-
console.log(`Ignoring custom prompt template for repository: ${repo.name} (using built-in template)`);
|
|
163
|
-
}
|
|
164
|
-
return repoWithoutTemplate;
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
return config;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Save edge configuration
|
|
171
|
-
*/
|
|
172
|
-
saveEdgeConfig(config) {
|
|
173
|
-
const edgeConfigPath = this.getEdgeConfigPath();
|
|
174
|
-
const configDir = dirname(edgeConfigPath);
|
|
175
|
-
// Ensure the ~/.cyrus directory exists
|
|
176
|
-
if (!existsSync(configDir)) {
|
|
177
|
-
mkdirSync(configDir, { recursive: true });
|
|
178
|
-
}
|
|
179
|
-
writeFileSync(edgeConfigPath, JSON.stringify(config, null, 2));
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Interactive setup wizard for repository configuration
|
|
183
|
-
*/
|
|
184
|
-
async setupRepositoryWizard(linearCredentials, rl) {
|
|
185
|
-
const shouldCloseRl = !rl;
|
|
186
|
-
if (!rl) {
|
|
187
|
-
rl = readline.createInterface({
|
|
188
|
-
input: process.stdin,
|
|
189
|
-
output: process.stdout,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
const question = (prompt) => new Promise((resolve) => {
|
|
193
|
-
rl.question(prompt, resolve);
|
|
194
|
-
});
|
|
195
|
-
console.log("\nš Repository Setup");
|
|
196
|
-
console.log("ā".repeat(50));
|
|
197
|
-
try {
|
|
198
|
-
// Ask for repository details
|
|
199
|
-
const repositoryPath = (await question(`Repository path (default: ${process.cwd()}): `)) ||
|
|
200
|
-
process.cwd();
|
|
201
|
-
const repositoryName = (await question(`Repository name (default: ${basename(repositoryPath)}): `)) || basename(repositoryPath);
|
|
202
|
-
const baseBranch = (await question("Base branch (default: main): ")) || "main";
|
|
203
|
-
// Create a path-safe version of the repository name for namespacing
|
|
204
|
-
const repoNameSafe = repositoryName
|
|
205
|
-
.replace(/[^a-zA-Z0-9-_]/g, "-")
|
|
206
|
-
.toLowerCase();
|
|
207
|
-
const workspaceBaseDir = resolve(this.cyrusHome, "workspaces", repoNameSafe);
|
|
208
|
-
// Note: Prompt template is now hardcoded - no longer configurable
|
|
209
|
-
// Set reasonable defaults for configuration
|
|
210
|
-
// Allowed tools - default to all tools except Bash, plus Bash(git:*) and Bash(gh:*)
|
|
211
|
-
// Note: MCP tools (mcp__linear, mcp__cyrus-mcp-tools) are automatically added by EdgeWorker
|
|
212
|
-
const allowedTools = [
|
|
213
|
-
"Read(**)",
|
|
214
|
-
"Edit(**)",
|
|
215
|
-
"Bash(git:*)",
|
|
216
|
-
"Bash(gh:*)",
|
|
217
|
-
"Task",
|
|
218
|
-
"WebFetch",
|
|
219
|
-
"WebSearch",
|
|
220
|
-
"TodoRead",
|
|
221
|
-
"TodoWrite",
|
|
222
|
-
"NotebookRead",
|
|
223
|
-
"NotebookEdit",
|
|
224
|
-
"Batch",
|
|
225
|
-
];
|
|
226
|
-
// Label prompts - default to common label mappings
|
|
227
|
-
const labelPrompts = {
|
|
228
|
-
debugger: {
|
|
229
|
-
labels: ["Bug"],
|
|
230
|
-
},
|
|
231
|
-
builder: {
|
|
232
|
-
labels: ["Feature", "Improvement"],
|
|
233
|
-
},
|
|
234
|
-
scoper: {
|
|
235
|
-
labels: ["PRD"],
|
|
236
|
-
},
|
|
237
|
-
orchestrator: {
|
|
238
|
-
labels: ["Orchestrator"],
|
|
239
|
-
allowedTools: "coordinator", // Uses coordinator tools (all except file editing)
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
if (shouldCloseRl) {
|
|
243
|
-
rl.close();
|
|
244
|
-
}
|
|
245
|
-
// Create repository configuration
|
|
246
|
-
const repository = {
|
|
247
|
-
id: `${linearCredentials.linearWorkspaceId}-${Date.now()}`,
|
|
248
|
-
name: repositoryName,
|
|
249
|
-
repositoryPath: resolve(repositoryPath),
|
|
250
|
-
baseBranch,
|
|
251
|
-
linearWorkspaceId: linearCredentials.linearWorkspaceId,
|
|
252
|
-
linearToken: linearCredentials.linearToken,
|
|
253
|
-
workspaceBaseDir: resolve(workspaceBaseDir),
|
|
254
|
-
isActive: true,
|
|
255
|
-
allowedTools,
|
|
256
|
-
labelPrompts,
|
|
257
|
-
};
|
|
258
|
-
return repository;
|
|
259
|
-
}
|
|
260
|
-
catch (error) {
|
|
261
|
-
if (shouldCloseRl) {
|
|
262
|
-
rl.close();
|
|
263
|
-
}
|
|
264
|
-
throw error;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Start OAuth flow to get Linear token using EdgeWorker's shared server
|
|
269
|
-
*/
|
|
270
|
-
async startOAuthFlow(proxyUrl) {
|
|
271
|
-
if (this.edgeWorker) {
|
|
272
|
-
// Use existing EdgeWorker's OAuth flow
|
|
273
|
-
const port = this.edgeWorker.getServerPort();
|
|
274
|
-
const callbackBaseUrl = process.env.CYRUS_BASE_URL || `http://localhost:${port}`;
|
|
275
|
-
const authUrl = `${proxyUrl}/oauth/authorize?callback=${callbackBaseUrl}/callback`;
|
|
276
|
-
// Let SharedApplicationServer print the messages, but we handle browser opening
|
|
277
|
-
const resultPromise = this.edgeWorker.startOAuthFlow(proxyUrl);
|
|
278
|
-
// Open browser after SharedApplicationServer prints its messages
|
|
279
|
-
open(authUrl).catch(() => {
|
|
280
|
-
// Error is already communicated by SharedApplicationServer
|
|
281
|
-
});
|
|
282
|
-
return resultPromise;
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
// Create temporary SharedApplicationServer for OAuth flow during initial setup
|
|
286
|
-
const serverPort = process.env.CYRUS_SERVER_PORT
|
|
287
|
-
? parseInt(process.env.CYRUS_SERVER_PORT, 10)
|
|
288
|
-
: 3456;
|
|
289
|
-
const tempServer = new SharedApplicationServer(serverPort);
|
|
290
|
-
try {
|
|
291
|
-
// Start the server
|
|
292
|
-
await tempServer.start();
|
|
293
|
-
const port = tempServer.getPort();
|
|
294
|
-
const callbackBaseUrl = process.env.CYRUS_BASE_URL || `http://localhost:${port}`;
|
|
295
|
-
const authUrl = `${proxyUrl}/oauth/authorize?callback=${callbackBaseUrl}/callback`;
|
|
296
|
-
// Start OAuth flow (this prints the messages)
|
|
297
|
-
const resultPromise = tempServer.startOAuthFlow(proxyUrl);
|
|
298
|
-
// Open browser after SharedApplicationServer prints its messages
|
|
299
|
-
open(authUrl).catch(() => {
|
|
300
|
-
// Error is already communicated by SharedApplicationServer
|
|
301
|
-
});
|
|
302
|
-
// Wait for OAuth flow to complete
|
|
303
|
-
const result = await resultPromise;
|
|
304
|
-
return {
|
|
305
|
-
linearToken: result.linearToken,
|
|
306
|
-
linearWorkspaceId: result.linearWorkspaceId,
|
|
307
|
-
linearWorkspaceName: result.linearWorkspaceName,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
finally {
|
|
311
|
-
// Clean up temporary server
|
|
312
|
-
await tempServer.stop();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Get ngrok auth token from config or prompt user
|
|
318
|
-
*/
|
|
319
|
-
async getNgrokAuthToken(config) {
|
|
320
|
-
// Return existing token if available
|
|
321
|
-
if (config.ngrokAuthToken) {
|
|
322
|
-
return config.ngrokAuthToken;
|
|
323
|
-
}
|
|
324
|
-
// Skip ngrok setup if using external host
|
|
325
|
-
const isExternalHost = process.env.CYRUS_HOST_EXTERNAL?.toLowerCase().trim() === "true";
|
|
326
|
-
if (isExternalHost) {
|
|
327
|
-
console.log(`\nš” Using external host configuration (CYRUS_HOST_EXTERNAL=true)`);
|
|
328
|
-
console.log(` Skipping ngrok setup - using ${process.env.CYRUS_BASE_URL || "configured base URL"}`);
|
|
329
|
-
return undefined;
|
|
330
|
-
}
|
|
331
|
-
// Prompt user for ngrok auth token
|
|
332
|
-
console.log(`\nš Ngrok Setup Required`);
|
|
333
|
-
console.log(`ā`.repeat(50));
|
|
334
|
-
console.log(`Linear payloads need to reach your computer, so we use the secure technology ngrok for that.`);
|
|
335
|
-
console.log(`This requires a free ngrok account and auth token.`);
|
|
336
|
-
console.log(``);
|
|
337
|
-
console.log(`To get your ngrok auth token:`);
|
|
338
|
-
console.log(`1. Sign up at https://ngrok.com/ (free)`);
|
|
339
|
-
console.log(`2. Go to https://dashboard.ngrok.com/get-started/your-authtoken`);
|
|
340
|
-
console.log(`3. Copy your auth token`);
|
|
341
|
-
console.log(``);
|
|
342
|
-
console.log(`Alternatively, you can set CYRUS_HOST_EXTERNAL=true and CYRUS_BASE_URL`);
|
|
343
|
-
console.log(`to handle port forwarding or reverse proxy yourself.`);
|
|
344
|
-
console.log(``);
|
|
345
|
-
const rl = readline.createInterface({
|
|
346
|
-
input: process.stdin,
|
|
347
|
-
output: process.stdout,
|
|
348
|
-
});
|
|
349
|
-
return new Promise((resolve) => {
|
|
350
|
-
rl.question(`Enter your ngrok auth token (or press Enter to skip): `, async (token) => {
|
|
351
|
-
rl.close();
|
|
352
|
-
if (!token.trim()) {
|
|
353
|
-
console.log(`\nā ļø Skipping ngrok setup. You can set CYRUS_HOST_EXTERNAL=true and CYRUS_BASE_URL manually.`);
|
|
354
|
-
resolve(undefined);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
// Save token to config
|
|
358
|
-
config.ngrokAuthToken = token.trim();
|
|
359
|
-
try {
|
|
360
|
-
this.saveEdgeConfig(config);
|
|
361
|
-
console.log(`ā
Ngrok auth token saved to config`);
|
|
362
|
-
resolve(token.trim());
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
console.error(`ā Failed to save ngrok auth token:`, error);
|
|
366
|
-
resolve(token.trim()); // Still use the token for this session
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Start the EdgeWorker with given configuration
|
|
373
|
-
*/
|
|
374
|
-
async startEdgeWorker({ proxyUrl, repositories, }) {
|
|
375
|
-
// Get ngrok auth token (prompt if needed and not external host)
|
|
376
|
-
let ngrokAuthToken;
|
|
377
|
-
const isExternalHost = process.env.CYRUS_HOST_EXTERNAL?.toLowerCase().trim() === "true";
|
|
378
|
-
if (!isExternalHost) {
|
|
379
|
-
const config = this.loadEdgeConfig();
|
|
380
|
-
ngrokAuthToken = await this.getNgrokAuthToken(config);
|
|
381
|
-
}
|
|
382
|
-
// Create EdgeWorker configuration
|
|
383
|
-
const config = {
|
|
384
|
-
proxyUrl,
|
|
385
|
-
repositories,
|
|
386
|
-
cyrusHome: this.cyrusHome,
|
|
387
|
-
defaultAllowedTools: process.env.ALLOWED_TOOLS?.split(",").map((t) => t.trim()) || [],
|
|
388
|
-
defaultDisallowedTools: process.env.DISALLOWED_TOOLS?.split(",").map((t) => t.trim()) ||
|
|
389
|
-
undefined,
|
|
390
|
-
// Model configuration: environment variables take precedence over config file
|
|
391
|
-
defaultModel: process.env.CYRUS_DEFAULT_MODEL || this.loadEdgeConfig().defaultModel,
|
|
392
|
-
defaultFallbackModel: process.env.CYRUS_DEFAULT_FALLBACK_MODEL ||
|
|
393
|
-
this.loadEdgeConfig().defaultFallbackModel,
|
|
394
|
-
webhookBaseUrl: process.env.CYRUS_BASE_URL,
|
|
395
|
-
serverPort: process.env.CYRUS_SERVER_PORT
|
|
396
|
-
? parseInt(process.env.CYRUS_SERVER_PORT, 10)
|
|
397
|
-
: 3456,
|
|
398
|
-
serverHost: isExternalHost ? "0.0.0.0" : "localhost",
|
|
399
|
-
ngrokAuthToken,
|
|
400
|
-
features: {
|
|
401
|
-
enableContinuation: true,
|
|
402
|
-
},
|
|
403
|
-
handlers: {
|
|
404
|
-
createWorkspace: async (issue, repository) => {
|
|
405
|
-
return this.createGitWorktree(issue, repository);
|
|
406
|
-
},
|
|
407
|
-
onOAuthCallback: async (token, workspaceId, workspaceName) => {
|
|
408
|
-
const linearCredentials = {
|
|
409
|
-
linearToken: token,
|
|
410
|
-
linearWorkspaceId: workspaceId,
|
|
411
|
-
linearWorkspaceName: workspaceName,
|
|
412
|
-
};
|
|
413
|
-
// Handle OAuth completion for repository setup
|
|
414
|
-
if (this.edgeWorker) {
|
|
415
|
-
console.log("\nš Setting up new repository for workspace:", workspaceName);
|
|
416
|
-
console.log("ā".repeat(50));
|
|
417
|
-
try {
|
|
418
|
-
const newRepo = await this.setupRepositoryWizard(linearCredentials);
|
|
419
|
-
// Add to existing repositories
|
|
420
|
-
const edgeConfig = this.loadEdgeConfig();
|
|
421
|
-
console.log(`š Current config has ${edgeConfig.repositories?.length || 0} repositories`);
|
|
422
|
-
edgeConfig.repositories = [
|
|
423
|
-
...(edgeConfig.repositories || []),
|
|
424
|
-
newRepo,
|
|
425
|
-
];
|
|
426
|
-
console.log(`š Adding repository "${newRepo.name}", new total: ${edgeConfig.repositories.length}`);
|
|
427
|
-
this.saveEdgeConfig(edgeConfig);
|
|
428
|
-
console.log("\nā
Repository configured successfully!");
|
|
429
|
-
console.log("š ~/.cyrus/config.json file has been updated with your new repository configuration.");
|
|
430
|
-
console.log("š” You can edit this file and restart Cyrus at any time to modify settings.");
|
|
431
|
-
console.log("š Configuration docs: https://github.com/ceedaragents/cyrus#configuration");
|
|
432
|
-
// Restart edge worker with new config
|
|
433
|
-
await this.edgeWorker.stop();
|
|
434
|
-
this.edgeWorker = null;
|
|
435
|
-
// Give a small delay to ensure file is written
|
|
436
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
437
|
-
// Reload configuration and restart worker without going through setup
|
|
438
|
-
const updatedConfig = this.loadEdgeConfig();
|
|
439
|
-
console.log(`\nš Reloading with ${updatedConfig.repositories?.length || 0} repositories from config file`);
|
|
440
|
-
return this.startEdgeWorker({
|
|
441
|
-
proxyUrl,
|
|
442
|
-
repositories: updatedConfig.repositories || [],
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
catch (error) {
|
|
446
|
-
console.error("\nā Repository setup failed:", error.message);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
};
|
|
452
|
-
// Create and start EdgeWorker
|
|
453
|
-
this.edgeWorker = new EdgeWorker(config);
|
|
454
|
-
// Set config path for dynamic reloading
|
|
455
|
-
const configPath = this.getEdgeConfigPath();
|
|
456
|
-
this.edgeWorker.setConfigPath(configPath);
|
|
457
|
-
// Set up event handlers
|
|
458
|
-
this.setupEventHandlers();
|
|
459
|
-
// Start the worker
|
|
460
|
-
await this.edgeWorker.start();
|
|
461
|
-
console.log("\nā
Edge worker started successfully");
|
|
462
|
-
console.log(`Configured proxy URL: ${config.proxyUrl}`);
|
|
463
|
-
console.log(`Managing ${repositories.length} repositories:`);
|
|
464
|
-
repositories.forEach((repo) => {
|
|
465
|
-
console.log(` - ${repo.name} (${repo.repositoryPath})`);
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Start Cloudflare tunnel client (Pro plan only)
|
|
470
|
-
*/
|
|
471
|
-
async startCloudflareClient() {
|
|
472
|
-
// Validate required environment variables
|
|
473
|
-
const cloudflareToken = process.env.CLOUDFLARE_TOKEN;
|
|
474
|
-
const cyrusApiKey = process.env.CYRUS_API_KEY;
|
|
475
|
-
if (!cloudflareToken || !cyrusApiKey) {
|
|
476
|
-
console.error("\nā Missing required credentials");
|
|
477
|
-
console.log("ā".repeat(50));
|
|
478
|
-
console.log("Cloudflare tunnel mode requires authentication.");
|
|
479
|
-
console.log("\nRequired environment variables:");
|
|
480
|
-
console.log(` CLOUDFLARE_TOKEN: ${cloudflareToken ? "ā
Set" : "ā Missing"}`);
|
|
481
|
-
console.log(` CYRUS_API_KEY: ${cyrusApiKey ? "ā
Set" : "ā Missing"}`);
|
|
482
|
-
console.log("\nPlease run: cyrus auth <auth-key>");
|
|
483
|
-
console.log("Get your auth key from: https://www.atcyrus.com/onboarding/auth-cyrus");
|
|
484
|
-
process.exit(1);
|
|
485
|
-
}
|
|
486
|
-
console.log("\nš©ļø Starting Cloudflare Tunnel Client");
|
|
487
|
-
console.log("ā".repeat(50));
|
|
488
|
-
try {
|
|
489
|
-
const { CloudflareTunnelClient } = await import("cyrus-cloudflare-tunnel-client");
|
|
490
|
-
// Get auth key from config or environment
|
|
491
|
-
// For now, we'll use the API key as the auth key since it's what we validate against
|
|
492
|
-
const authKey = cyrusApiKey;
|
|
493
|
-
const client = new CloudflareTunnelClient({
|
|
494
|
-
authKey,
|
|
495
|
-
cyrusHome: this.cyrusHome,
|
|
496
|
-
onWebhook: (payload) => {
|
|
497
|
-
console.log("\nšØ Webhook received from Linear");
|
|
498
|
-
console.log(`Action: ${payload.action || "Unknown"}`);
|
|
499
|
-
console.log(`Type: ${payload.type || "Unknown"}`);
|
|
500
|
-
// TODO: Forward webhook to EdgeWorker or handle directly
|
|
501
|
-
},
|
|
502
|
-
onConfigUpdate: () => {
|
|
503
|
-
console.log("\nš Configuration updated from cyrus-hosted");
|
|
504
|
-
},
|
|
505
|
-
onError: (error) => {
|
|
506
|
-
console.error("\nā Cloudflare client error:", error.message);
|
|
507
|
-
},
|
|
508
|
-
onReady: (tunnelUrl) => {
|
|
509
|
-
console.log("\nā
Cloudflare tunnel established");
|
|
510
|
-
console.log(`š Tunnel URL: ${tunnelUrl}`);
|
|
511
|
-
console.log("ā".repeat(50));
|
|
512
|
-
console.log("\nš Pro Plan Active - Using Cloudflare Tunnel");
|
|
513
|
-
console.log("š Cyrus is now ready to receive webhooks and config updates");
|
|
514
|
-
console.log("ā".repeat(50));
|
|
515
|
-
},
|
|
516
|
-
});
|
|
517
|
-
// Authenticate and start the tunnel
|
|
518
|
-
await client.authenticate();
|
|
519
|
-
// Handle graceful shutdown
|
|
520
|
-
process.on("SIGINT", () => {
|
|
521
|
-
console.log("\n\nš Shutting down Cloudflare tunnel...");
|
|
522
|
-
client.disconnect();
|
|
523
|
-
process.exit(0);
|
|
524
|
-
});
|
|
525
|
-
process.on("SIGTERM", () => {
|
|
526
|
-
console.log("\n\nš Shutting down Cloudflare tunnel...");
|
|
527
|
-
client.disconnect();
|
|
528
|
-
process.exit(0);
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
console.error("\nā Failed to start Cloudflare tunnel:");
|
|
533
|
-
console.error(error.message);
|
|
534
|
-
console.log("\nIf you're having issues, try re-authenticating with: cyrus auth <auth-key>");
|
|
535
|
-
process.exit(1);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Check subscription status with the Cyrus API
|
|
540
|
-
*/
|
|
541
|
-
async checkSubscriptionStatus(customerId) {
|
|
542
|
-
const response = await fetch(`https://www.atcyrus.com/api/subscription-status?customerId=${encodeURIComponent(customerId)}`, {
|
|
543
|
-
method: "GET",
|
|
544
|
-
headers: {
|
|
545
|
-
"Content-Type": "application/json",
|
|
546
|
-
},
|
|
547
|
-
});
|
|
548
|
-
if (!response.ok) {
|
|
549
|
-
if (response.status === 400) {
|
|
550
|
-
const data = (await response.json());
|
|
551
|
-
throw new Error(data.error || "Invalid customer ID format");
|
|
552
|
-
}
|
|
553
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
554
|
-
}
|
|
555
|
-
const data = (await response.json());
|
|
556
|
-
return data;
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Validate customer ID format
|
|
560
|
-
*/
|
|
561
|
-
validateCustomerId(customerId) {
|
|
562
|
-
if (!customerId.startsWith("cus_")) {
|
|
563
|
-
console.error("\nā Invalid customer ID format");
|
|
564
|
-
console.log('Customer IDs should start with "cus_"');
|
|
565
|
-
process.exit(1);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Handle subscription validation failure
|
|
570
|
-
*/
|
|
571
|
-
handleSubscriptionFailure(subscriptionStatus) {
|
|
572
|
-
console.error("\nā Subscription Invalid");
|
|
573
|
-
console.log("ā".repeat(50));
|
|
574
|
-
if (subscriptionStatus.isReturningCustomer) {
|
|
575
|
-
console.log("Your subscription has expired or been cancelled.");
|
|
576
|
-
console.log(`Status: ${subscriptionStatus.status}`);
|
|
577
|
-
console.log("\nPlease visit https://www.atcyrus.com/pricing to reactivate your subscription.");
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
console.log("No active subscription found for this customer ID.");
|
|
581
|
-
console.log("\nPlease visit https://www.atcyrus.com/pricing to start a subscription.");
|
|
582
|
-
console.log("Once you obtain a valid customer ID,");
|
|
583
|
-
console.log("Run: cyrus set-customer-id cus_XXXXX");
|
|
584
|
-
}
|
|
585
|
-
process.exit(1);
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Validate subscription and handle failures
|
|
589
|
-
*/
|
|
590
|
-
async validateAndHandleSubscription(customerId) {
|
|
591
|
-
console.log("\nš Validating subscription...");
|
|
592
|
-
try {
|
|
593
|
-
const subscriptionStatus = await this.checkSubscriptionStatus(customerId);
|
|
594
|
-
if (subscriptionStatus.requiresPayment) {
|
|
595
|
-
this.handleSubscriptionFailure(subscriptionStatus);
|
|
596
|
-
}
|
|
597
|
-
console.log(`ā
Subscription active (${subscriptionStatus.status})`);
|
|
598
|
-
}
|
|
599
|
-
catch (error) {
|
|
600
|
-
console.error("\nā Failed to validate subscription");
|
|
601
|
-
console.log(`Error: ${error.message}`);
|
|
602
|
-
console.log('Run "cyrus set-customer-id cus_XXXXX" with a valid customer ID');
|
|
603
|
-
process.exit(1);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Create readline interface and ask question
|
|
608
|
-
*/
|
|
609
|
-
async askQuestion(prompt) {
|
|
610
|
-
const rl = readline.createInterface({
|
|
611
|
-
input: process.stdin,
|
|
612
|
-
output: process.stdout,
|
|
613
|
-
});
|
|
614
|
-
return new Promise((resolve) => {
|
|
615
|
-
rl.question(prompt, (answer) => {
|
|
616
|
-
rl.close();
|
|
617
|
-
resolve(answer.trim());
|
|
618
|
-
});
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Start the edge application
|
|
623
|
-
*/
|
|
624
|
-
async start() {
|
|
625
|
-
try {
|
|
626
|
-
// Set proxy URL with default
|
|
627
|
-
const proxyUrl = process.env.PROXY_URL || DEFAULT_PROXY_URL;
|
|
628
|
-
// No need to validate Claude CLI - using Claude TypeScript SDK now
|
|
629
|
-
// Load edge configuration
|
|
630
|
-
let edgeConfig = this.loadEdgeConfig();
|
|
631
|
-
let repositories = edgeConfig.repositories || [];
|
|
632
|
-
// Check if using default proxy URL without a customer ID
|
|
633
|
-
const isUsingDefaultProxy = proxyUrl === DEFAULT_PROXY_URL;
|
|
634
|
-
const hasCustomerId = !!edgeConfig.stripeCustomerId;
|
|
635
|
-
if (isUsingDefaultProxy && !hasCustomerId) {
|
|
636
|
-
console.log("\nšÆ Pro Plan Required");
|
|
637
|
-
console.log("ā".repeat(50));
|
|
638
|
-
console.log("You are using the default Cyrus proxy URL.");
|
|
639
|
-
console.log("\nWith Cyrus Pro you get:");
|
|
640
|
-
console.log("⢠No-hassle configuration");
|
|
641
|
-
console.log("⢠Priority support");
|
|
642
|
-
console.log("⢠Help fund product development");
|
|
643
|
-
console.log("\nChoose an option:");
|
|
644
|
-
console.log("1. Start a free trial");
|
|
645
|
-
console.log("2. I have a customer ID to enter");
|
|
646
|
-
console.log("3. Setup your own proxy (advanced)");
|
|
647
|
-
console.log("4. Exit");
|
|
648
|
-
const choice = await this.askQuestion("\nYour choice (1-4): ");
|
|
649
|
-
if (choice === "1") {
|
|
650
|
-
console.log("\nš Opening your browser to start a free trial...");
|
|
651
|
-
console.log("Visit: https://www.atcyrus.com/pricing");
|
|
652
|
-
await open("https://www.atcyrus.com/pricing");
|
|
653
|
-
process.exit(0);
|
|
654
|
-
}
|
|
655
|
-
else if (choice === "2") {
|
|
656
|
-
console.log("\nš After completing payment, you'll see your customer ID on the success page.");
|
|
657
|
-
console.log('It starts with "cus_" and can be copied from the website.');
|
|
658
|
-
const customerId = await this.askQuestion("\nPaste your customer ID here: ");
|
|
659
|
-
this.validateCustomerId(customerId);
|
|
660
|
-
edgeConfig.stripeCustomerId = customerId;
|
|
661
|
-
this.saveEdgeConfig(edgeConfig);
|
|
662
|
-
console.log("ā
Customer ID saved successfully!");
|
|
663
|
-
console.log("Continuing with startup...\n");
|
|
664
|
-
// Reload config to include the new customer ID
|
|
665
|
-
edgeConfig = this.loadEdgeConfig();
|
|
666
|
-
}
|
|
667
|
-
else if (choice === "3") {
|
|
668
|
-
console.log("\nš§ Self-Hosted Proxy Setup");
|
|
669
|
-
console.log("ā".repeat(50));
|
|
670
|
-
console.log("Configure your own Linear app and proxy to have full control over your stack.");
|
|
671
|
-
console.log("\nDocumentation:");
|
|
672
|
-
console.log("⢠Linear OAuth setup: https://linear.app/developers/agents");
|
|
673
|
-
console.log("⢠Proxy implementation: https://github.com/ceedaragents/cyrus/tree/main/apps/proxy-worker");
|
|
674
|
-
console.log("\nOnce deployed, set the PROXY_URL environment variable:");
|
|
675
|
-
console.log("export PROXY_URL=https://your-proxy-url.com");
|
|
676
|
-
process.exit(0);
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
console.log("\nExiting...");
|
|
680
|
-
process.exit(0);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
// If using default proxy and has customer ID, validate subscription
|
|
684
|
-
if (isUsingDefaultProxy && edgeConfig.stripeCustomerId) {
|
|
685
|
-
try {
|
|
686
|
-
await this.validateAndHandleSubscription(edgeConfig.stripeCustomerId);
|
|
687
|
-
}
|
|
688
|
-
catch (error) {
|
|
689
|
-
console.error("\nā ļø Warning: Could not validate subscription");
|
|
690
|
-
console.log("ā".repeat(50));
|
|
691
|
-
console.error("Unable to connect to subscription service:", error.message);
|
|
692
|
-
process.exit(1);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
// Check if we need to set up
|
|
696
|
-
const needsSetup = repositories.length === 0;
|
|
697
|
-
const hasLinearCredentials = repositories.some((r) => r.linearToken) ||
|
|
698
|
-
process.env.LINEAR_OAUTH_TOKEN;
|
|
699
|
-
if (needsSetup) {
|
|
700
|
-
console.log("š Welcome to Cyrus Edge Worker!");
|
|
701
|
-
// Check if they want to use existing credentials or add new workspace
|
|
702
|
-
let linearCredentials = null;
|
|
703
|
-
if (hasLinearCredentials) {
|
|
704
|
-
// Show available workspaces from existing repos
|
|
705
|
-
const workspaces = new Map();
|
|
706
|
-
for (const repo of edgeConfig.repositories || []) {
|
|
707
|
-
if (!workspaces.has(repo.linearWorkspaceId)) {
|
|
708
|
-
workspaces.set(repo.linearWorkspaceId, {
|
|
709
|
-
id: repo.linearWorkspaceId,
|
|
710
|
-
name: "Unknown Workspace",
|
|
711
|
-
token: repo.linearToken,
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
if (workspaces.size === 1) {
|
|
716
|
-
// Only one workspace, use it
|
|
717
|
-
const ws = Array.from(workspaces.values())[0];
|
|
718
|
-
if (ws) {
|
|
719
|
-
linearCredentials = {
|
|
720
|
-
linearToken: ws.token,
|
|
721
|
-
linearWorkspaceId: ws.id,
|
|
722
|
-
linearWorkspaceName: ws.name,
|
|
723
|
-
};
|
|
724
|
-
console.log(`\nš Using Linear workspace: ${linearCredentials.linearWorkspaceName}`);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
else if (workspaces.size > 1) {
|
|
728
|
-
// Multiple workspaces, let user choose
|
|
729
|
-
console.log("\nš Available Linear workspaces:");
|
|
730
|
-
const workspaceList = Array.from(workspaces.values());
|
|
731
|
-
workspaceList.forEach((ws, i) => {
|
|
732
|
-
console.log(`${i + 1}. ${ws.name}`);
|
|
733
|
-
});
|
|
734
|
-
const choice = await this.askQuestion("\nSelect workspace (number) or press Enter for new: ");
|
|
735
|
-
const index = parseInt(choice, 10) - 1;
|
|
736
|
-
if (index >= 0 && index < workspaceList.length) {
|
|
737
|
-
const ws = workspaceList[index];
|
|
738
|
-
if (ws) {
|
|
739
|
-
linearCredentials = {
|
|
740
|
-
linearToken: ws.token,
|
|
741
|
-
linearWorkspaceId: ws.id,
|
|
742
|
-
linearWorkspaceName: ws.name,
|
|
743
|
-
};
|
|
744
|
-
console.log(`Using workspace: ${linearCredentials.linearWorkspaceName}`);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
else {
|
|
748
|
-
// Get new credentials
|
|
749
|
-
linearCredentials = null;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
else if (process.env.LINEAR_OAUTH_TOKEN) {
|
|
753
|
-
// Use env vars
|
|
754
|
-
linearCredentials = {
|
|
755
|
-
linearToken: process.env.LINEAR_OAUTH_TOKEN,
|
|
756
|
-
linearWorkspaceId: process.env.LINEAR_WORKSPACE_ID || "unknown",
|
|
757
|
-
linearWorkspaceName: "Your Workspace",
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
if (linearCredentials) {
|
|
761
|
-
console.log("(OAuth server will start with EdgeWorker to connect additional workspaces)");
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
else {
|
|
765
|
-
// Get new Linear credentials
|
|
766
|
-
console.log("\nš Step 1: Connect to Linear");
|
|
767
|
-
console.log("ā".repeat(50));
|
|
768
|
-
try {
|
|
769
|
-
linearCredentials = await this.startOAuthFlow(proxyUrl);
|
|
770
|
-
console.log("\nā
Linear connected successfully!");
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
console.error("\nā OAuth flow failed:", error.message);
|
|
774
|
-
console.log("\nAlternatively, you can:");
|
|
775
|
-
console.log("1. Visit", `${proxyUrl}/oauth/authorize`, "in your browser");
|
|
776
|
-
console.log("2. Copy the token after authorization");
|
|
777
|
-
console.log("3. Add it to your .env.cyrus file as LINEAR_OAUTH_TOKEN");
|
|
778
|
-
process.exit(1);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if (!linearCredentials) {
|
|
782
|
-
console.error("ā No Linear credentials available");
|
|
783
|
-
process.exit(1);
|
|
784
|
-
}
|
|
785
|
-
// Now set up repository
|
|
786
|
-
console.log("\nš Step 2: Configure Repository");
|
|
787
|
-
console.log("ā".repeat(50));
|
|
788
|
-
// Create a single readline interface for the entire repository setup process
|
|
789
|
-
const rl = readline.createInterface({
|
|
790
|
-
input: process.stdin,
|
|
791
|
-
output: process.stdout,
|
|
792
|
-
});
|
|
793
|
-
try {
|
|
794
|
-
// Loop to allow adding multiple repositories
|
|
795
|
-
let continueAdding = true;
|
|
796
|
-
while (continueAdding) {
|
|
797
|
-
try {
|
|
798
|
-
const newRepo = await this.setupRepositoryWizard(linearCredentials, rl);
|
|
799
|
-
// Add to repositories
|
|
800
|
-
repositories = [...(edgeConfig.repositories || []), newRepo];
|
|
801
|
-
edgeConfig.repositories = repositories;
|
|
802
|
-
this.saveEdgeConfig(edgeConfig);
|
|
803
|
-
console.log("\nā
Repository configured successfully!");
|
|
804
|
-
console.log("š ~/.cyrus/config.json file has been updated with your repository configuration.");
|
|
805
|
-
console.log("š” You can edit this file and restart Cyrus at any time to modify settings.");
|
|
806
|
-
console.log("š Configuration docs: https://github.com/ceedaragents/cyrus#configuration");
|
|
807
|
-
// Ask if they want to add another
|
|
808
|
-
const addAnother = await new Promise((resolve) => {
|
|
809
|
-
rl.question("\nAdd another repository? (y/N): ", (answer) => {
|
|
810
|
-
resolve(answer.toLowerCase() === "y");
|
|
811
|
-
});
|
|
812
|
-
});
|
|
813
|
-
continueAdding = addAnother;
|
|
814
|
-
if (continueAdding) {
|
|
815
|
-
console.log("\nš Configure Additional Repository");
|
|
816
|
-
console.log("ā".repeat(50));
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
catch (error) {
|
|
820
|
-
console.error("\nā Repository setup failed:", error.message);
|
|
821
|
-
throw error;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
finally {
|
|
826
|
-
// Always close the readline interface when done
|
|
827
|
-
rl.close();
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
// Check if using Cloudflare tunnel mode (Pro plan)
|
|
831
|
-
const isLegacy = edgeConfig.isLegacy !== false; // Default to true if not set
|
|
832
|
-
if (!isLegacy) {
|
|
833
|
-
// Pro plan with Cloudflare tunnel
|
|
834
|
-
console.log("\nš Pro Plan Detected");
|
|
835
|
-
console.log("ā".repeat(50));
|
|
836
|
-
console.log("Using Cloudflare tunnel for secure connectivity");
|
|
837
|
-
// Start Cloudflare tunnel client (will validate credentials and start)
|
|
838
|
-
await this.startCloudflareClient();
|
|
839
|
-
return; // Exit early - Cloudflare client handles everything
|
|
840
|
-
}
|
|
841
|
-
// Legacy mode - validate we have repositories
|
|
842
|
-
if (repositories.length === 0) {
|
|
843
|
-
console.error("ā No repositories configured");
|
|
844
|
-
console.log("\nUse the authorization link above to configure your first repository.");
|
|
845
|
-
process.exit(1);
|
|
846
|
-
}
|
|
847
|
-
// Start the edge worker (legacy mode)
|
|
848
|
-
await this.startEdgeWorker({ proxyUrl, repositories });
|
|
849
|
-
// Display plan status
|
|
850
|
-
const isUsingDefaultProxyForStatus = proxyUrl === DEFAULT_PROXY_URL;
|
|
851
|
-
const hasCustomerIdForStatus = !!edgeConfig.stripeCustomerId;
|
|
852
|
-
console.log(`\n${"ā".repeat(70)}`);
|
|
853
|
-
if (isUsingDefaultProxyForStatus && hasCustomerIdForStatus) {
|
|
854
|
-
console.log("š Plan: Cyrus Pro");
|
|
855
|
-
console.log(`š Customer ID: ${edgeConfig.stripeCustomerId}`);
|
|
856
|
-
console.log('š³ Manage subscription: Run "cyrus billing"');
|
|
857
|
-
}
|
|
858
|
-
else if (!isUsingDefaultProxyForStatus) {
|
|
859
|
-
console.log("š ļø Plan: Community (Self-hosted proxy)");
|
|
860
|
-
console.log(`š Proxy URL: ${proxyUrl}`);
|
|
861
|
-
}
|
|
862
|
-
console.log("ā".repeat(70));
|
|
863
|
-
// Display OAuth information after EdgeWorker is started
|
|
864
|
-
const serverPort = this.edgeWorker?.getServerPort() || 3456;
|
|
865
|
-
const oauthCallbackBaseUrl = process.env.CYRUS_BASE_URL || `http://localhost:${serverPort}`;
|
|
866
|
-
console.log(`\nš OAuth server running on port ${serverPort}`);
|
|
867
|
-
console.log(`š To authorize Linear (new workspace or re-auth):`);
|
|
868
|
-
console.log(` ${proxyUrl}/oauth/authorize?callback=${oauthCallbackBaseUrl}/callback`);
|
|
869
|
-
console.log("ā".repeat(70));
|
|
870
|
-
// Handle graceful shutdown
|
|
871
|
-
process.on("SIGINT", () => this.shutdown());
|
|
872
|
-
process.on("SIGTERM", () => this.shutdown());
|
|
873
|
-
// Handle uncaught exceptions and unhandled promise rejections
|
|
874
|
-
process.on("uncaughtException", (error) => {
|
|
875
|
-
console.error("šØ Uncaught Exception:", error.message);
|
|
876
|
-
console.error("Error type:", error.constructor.name);
|
|
877
|
-
console.error("Stack:", error.stack);
|
|
878
|
-
console.error("This error was caught by the global handler, preventing application crash");
|
|
879
|
-
// Attempt graceful shutdown but don't wait indefinitely
|
|
880
|
-
this.shutdown().finally(() => {
|
|
881
|
-
console.error("Process exiting due to uncaught exception");
|
|
882
|
-
process.exit(1);
|
|
883
|
-
});
|
|
884
|
-
});
|
|
885
|
-
process.on("unhandledRejection", (reason, promise) => {
|
|
886
|
-
console.error("šØ Unhandled Promise Rejection at:", promise);
|
|
887
|
-
console.error("Reason:", reason);
|
|
888
|
-
console.error("This rejection was caught by the global handler, continuing operation");
|
|
889
|
-
// Log stack trace if reason is an Error
|
|
890
|
-
if (reason instanceof Error && reason.stack) {
|
|
891
|
-
console.error("Stack:", reason.stack);
|
|
892
|
-
}
|
|
893
|
-
// Log the error but don't exit the process for promise rejections
|
|
894
|
-
// as they might be recoverable
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
catch (error) {
|
|
898
|
-
console.error("\nā Failed to start edge application:", error.message);
|
|
899
|
-
// Provide more specific guidance for common errors
|
|
900
|
-
if (error.message?.includes("Failed to connect any repositories")) {
|
|
901
|
-
console.error("\nš” This usually happens when:");
|
|
902
|
-
console.error(" - All Linear OAuth tokens have expired");
|
|
903
|
-
console.error(" - The Linear API is temporarily unavailable");
|
|
904
|
-
console.error(" - Your network connection is having issues");
|
|
905
|
-
console.error("\nPlease check your edge configuration and try again.");
|
|
906
|
-
}
|
|
907
|
-
await this.shutdown();
|
|
908
|
-
process.exit(1);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Check if a branch exists locally or remotely
|
|
913
|
-
*/
|
|
914
|
-
async branchExists(branchName, repoPath) {
|
|
915
|
-
const { execSync } = await import("node:child_process");
|
|
916
|
-
try {
|
|
917
|
-
// Check if branch exists locally
|
|
918
|
-
execSync(`git rev-parse --verify "${branchName}"`, {
|
|
919
|
-
cwd: repoPath,
|
|
920
|
-
stdio: "pipe",
|
|
921
|
-
});
|
|
922
|
-
return true;
|
|
923
|
-
}
|
|
924
|
-
catch {
|
|
925
|
-
// Branch doesn't exist locally, check remote
|
|
926
|
-
try {
|
|
927
|
-
const remoteOutput = execSync(`git ls-remote --heads origin "${branchName}"`, {
|
|
928
|
-
cwd: repoPath,
|
|
929
|
-
stdio: "pipe",
|
|
930
|
-
});
|
|
931
|
-
// Check if output is non-empty (branch actually exists on remote)
|
|
932
|
-
return remoteOutput && remoteOutput.toString().trim().length > 0;
|
|
933
|
-
}
|
|
934
|
-
catch {
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
/**
|
|
940
|
-
* Set up event handlers for EdgeWorker
|
|
941
|
-
*/
|
|
942
|
-
setupEventHandlers() {
|
|
943
|
-
if (!this.edgeWorker)
|
|
944
|
-
return;
|
|
945
|
-
// Session events
|
|
946
|
-
this.edgeWorker.on("session:started", (issueId, _issue, repositoryId) => {
|
|
947
|
-
console.log(`Started session for issue ${issueId} in repository ${repositoryId}`);
|
|
948
|
-
});
|
|
949
|
-
this.edgeWorker.on("session:ended", (issueId, exitCode, repositoryId) => {
|
|
950
|
-
console.log(`Session for issue ${issueId} ended with exit code ${exitCode} in repository ${repositoryId}`);
|
|
951
|
-
});
|
|
952
|
-
// Connection events
|
|
953
|
-
this.edgeWorker.on("connected", (token) => {
|
|
954
|
-
console.log(`ā
Connected to proxy with token ending in ...${token.slice(-4)}`);
|
|
955
|
-
});
|
|
956
|
-
this.edgeWorker.on("disconnected", (token, reason) => {
|
|
957
|
-
console.error(`ā Disconnected from proxy (token ...${token.slice(-4)}): ${reason || "Unknown reason"}`);
|
|
958
|
-
});
|
|
959
|
-
// Error events
|
|
960
|
-
this.edgeWorker.on("error", (error) => {
|
|
961
|
-
console.error("EdgeWorker error:", error);
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
/**
|
|
965
|
-
* Run a setup script with proper error handling and logging
|
|
966
|
-
*/
|
|
967
|
-
async runSetupScript(scriptPath, scriptType, workspacePath, issue) {
|
|
968
|
-
const { execSync } = await import("node:child_process");
|
|
969
|
-
const { existsSync, statSync } = await import("node:fs");
|
|
970
|
-
const { basename } = await import("node:path");
|
|
971
|
-
const os = await import("node:os");
|
|
972
|
-
// Expand ~ to home directory
|
|
973
|
-
const expandedPath = scriptPath.replace(/^~/, os.homedir());
|
|
974
|
-
// Check if script exists
|
|
975
|
-
if (!existsSync(expandedPath)) {
|
|
976
|
-
console.warn(`ā ļø ${scriptType === "global" ? "Global" : "Repository"} setup script not found: ${scriptPath}`);
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
// Check if script is executable (Unix only)
|
|
980
|
-
if (process.platform !== "win32") {
|
|
981
|
-
try {
|
|
982
|
-
const stats = statSync(expandedPath);
|
|
983
|
-
// Check if file has execute permission for the owner
|
|
984
|
-
if (!(stats.mode & 0o100)) {
|
|
985
|
-
console.warn(`ā ļø ${scriptType === "global" ? "Global" : "Repository"} setup script is not executable: ${scriptPath}`);
|
|
986
|
-
console.warn(` Run: chmod +x "${expandedPath}"`);
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
catch (error) {
|
|
991
|
-
console.warn(`ā ļø Cannot check permissions for ${scriptType} setup script: ${error.message}`);
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
const scriptName = basename(expandedPath);
|
|
996
|
-
console.log(`ā¹ļø Running ${scriptType} setup script: ${scriptName}`);
|
|
997
|
-
try {
|
|
998
|
-
// Determine the command based on the script extension and platform
|
|
999
|
-
let command;
|
|
1000
|
-
const isWindows = process.platform === "win32";
|
|
1001
|
-
if (scriptPath.endsWith(".ps1")) {
|
|
1002
|
-
command = `powershell -ExecutionPolicy Bypass -File "${expandedPath}"`;
|
|
1003
|
-
}
|
|
1004
|
-
else if (scriptPath.endsWith(".cmd") || scriptPath.endsWith(".bat")) {
|
|
1005
|
-
command = `"${expandedPath}"`;
|
|
1006
|
-
}
|
|
1007
|
-
else if (isWindows) {
|
|
1008
|
-
// On Windows, try to run with bash if available (Git Bash/WSL)
|
|
1009
|
-
command = `bash "${expandedPath}"`;
|
|
1010
|
-
}
|
|
1011
|
-
else {
|
|
1012
|
-
// On Unix, run directly with bash
|
|
1013
|
-
command = `bash "${expandedPath}"`;
|
|
1014
|
-
}
|
|
1015
|
-
execSync(command, {
|
|
1016
|
-
cwd: workspacePath,
|
|
1017
|
-
stdio: "inherit",
|
|
1018
|
-
env: {
|
|
1019
|
-
...process.env,
|
|
1020
|
-
LINEAR_ISSUE_ID: issue.id,
|
|
1021
|
-
LINEAR_ISSUE_IDENTIFIER: issue.identifier,
|
|
1022
|
-
LINEAR_ISSUE_TITLE: issue.title || "",
|
|
1023
|
-
},
|
|
1024
|
-
timeout: 5 * 60 * 1000, // 5 minute timeout
|
|
1025
|
-
});
|
|
1026
|
-
console.log(`ā
${scriptType === "global" ? "Global" : "Repository"} setup script completed successfully`);
|
|
1027
|
-
}
|
|
1028
|
-
catch (error) {
|
|
1029
|
-
const errorMessage = error.signal === "SIGTERM"
|
|
1030
|
-
? "Script execution timed out (exceeded 5 minutes)"
|
|
1031
|
-
: error.message;
|
|
1032
|
-
console.error(`ā ${scriptType === "global" ? "Global" : "Repository"} setup script failed: ${errorMessage}`);
|
|
1033
|
-
// Log stderr if available
|
|
1034
|
-
if (error.stderr) {
|
|
1035
|
-
console.error(" stderr:", error.stderr.toString());
|
|
1036
|
-
}
|
|
1037
|
-
// Continue execution despite setup script failure
|
|
1038
|
-
console.log(` Continuing with worktree creation...`);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
/**
|
|
1042
|
-
* Create a git worktree for an issue
|
|
1043
|
-
*/
|
|
1044
|
-
async createGitWorktree(issue, repository) {
|
|
1045
|
-
const { execSync } = await import("node:child_process");
|
|
1046
|
-
const { existsSync } = await import("node:fs");
|
|
1047
|
-
const { join } = await import("node:path");
|
|
1048
|
-
try {
|
|
1049
|
-
// Verify this is a git repository
|
|
1050
|
-
try {
|
|
1051
|
-
execSync("git rev-parse --git-dir", {
|
|
1052
|
-
cwd: repository.repositoryPath,
|
|
1053
|
-
stdio: "pipe",
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
catch (_e) {
|
|
1057
|
-
console.error(`${repository.repositoryPath} is not a git repository`);
|
|
1058
|
-
throw new Error("Not a git repository");
|
|
1059
|
-
}
|
|
1060
|
-
// Sanitize branch name by removing backticks to prevent command injection
|
|
1061
|
-
const sanitizeBranchName = (name) => name ? name.replace(/`/g, "") : name;
|
|
1062
|
-
// Use Linear's preferred branch name, or generate one if not available
|
|
1063
|
-
const rawBranchName = issue.branchName ||
|
|
1064
|
-
`${issue.identifier}-${issue.title
|
|
1065
|
-
?.toLowerCase()
|
|
1066
|
-
.replace(/\s+/g, "-")
|
|
1067
|
-
.substring(0, 30)}`;
|
|
1068
|
-
const branchName = sanitizeBranchName(rawBranchName);
|
|
1069
|
-
const workspacePath = join(repository.workspaceBaseDir, issue.identifier);
|
|
1070
|
-
// Ensure workspace directory exists
|
|
1071
|
-
mkdirSync(repository.workspaceBaseDir, { recursive: true });
|
|
1072
|
-
// Check if worktree already exists
|
|
1073
|
-
try {
|
|
1074
|
-
const worktrees = execSync("git worktree list --porcelain", {
|
|
1075
|
-
cwd: repository.repositoryPath,
|
|
1076
|
-
encoding: "utf-8",
|
|
1077
|
-
});
|
|
1078
|
-
if (worktrees.includes(workspacePath)) {
|
|
1079
|
-
console.log(`Worktree already exists at ${workspacePath}, using existing`);
|
|
1080
|
-
return {
|
|
1081
|
-
path: workspacePath,
|
|
1082
|
-
isGitWorktree: true,
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
catch (_e) {
|
|
1087
|
-
// git worktree command failed, continue with creation
|
|
1088
|
-
}
|
|
1089
|
-
// Check if branch already exists
|
|
1090
|
-
let createBranch = true;
|
|
1091
|
-
try {
|
|
1092
|
-
execSync(`git rev-parse --verify "${branchName}"`, {
|
|
1093
|
-
cwd: repository.repositoryPath,
|
|
1094
|
-
stdio: "pipe",
|
|
1095
|
-
});
|
|
1096
|
-
createBranch = false;
|
|
1097
|
-
}
|
|
1098
|
-
catch (_e) {
|
|
1099
|
-
// Branch doesn't exist, we'll create it
|
|
1100
|
-
}
|
|
1101
|
-
// Determine base branch for this issue
|
|
1102
|
-
let baseBranch = repository.baseBranch;
|
|
1103
|
-
// Check if issue has a parent
|
|
1104
|
-
try {
|
|
1105
|
-
const parent = await issue.parent;
|
|
1106
|
-
if (parent) {
|
|
1107
|
-
console.log(`Issue ${issue.identifier} has parent: ${parent.identifier}`);
|
|
1108
|
-
// Get parent's branch name
|
|
1109
|
-
const parentRawBranchName = parent.branchName ||
|
|
1110
|
-
`${parent.identifier}-${parent.title
|
|
1111
|
-
?.toLowerCase()
|
|
1112
|
-
.replace(/\s+/g, "-")
|
|
1113
|
-
.substring(0, 30)}`;
|
|
1114
|
-
const parentBranchName = sanitizeBranchName(parentRawBranchName);
|
|
1115
|
-
// Check if parent branch exists
|
|
1116
|
-
const parentBranchExists = await this.branchExists(parentBranchName, repository.repositoryPath);
|
|
1117
|
-
if (parentBranchExists) {
|
|
1118
|
-
baseBranch = parentBranchName;
|
|
1119
|
-
console.log(`Using parent issue branch '${parentBranchName}' as base for sub-issue ${issue.identifier}`);
|
|
1120
|
-
}
|
|
1121
|
-
else {
|
|
1122
|
-
console.log(`Parent branch '${parentBranchName}' not found, using default base branch '${repository.baseBranch}'`);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
catch (_error) {
|
|
1127
|
-
// Parent field might not exist or couldn't be fetched, use default base branch
|
|
1128
|
-
console.log(`No parent issue found for ${issue.identifier}, using default base branch '${repository.baseBranch}'`);
|
|
1129
|
-
}
|
|
1130
|
-
// Fetch latest changes from remote
|
|
1131
|
-
console.log("Fetching latest changes from remote...");
|
|
1132
|
-
let hasRemote = true;
|
|
1133
|
-
try {
|
|
1134
|
-
execSync("git fetch origin", {
|
|
1135
|
-
cwd: repository.repositoryPath,
|
|
1136
|
-
stdio: "pipe",
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
catch (e) {
|
|
1140
|
-
console.warn("Warning: git fetch failed, proceeding with local branch:", e.message);
|
|
1141
|
-
hasRemote = false;
|
|
1142
|
-
}
|
|
1143
|
-
// Create the worktree - use determined base branch
|
|
1144
|
-
let worktreeCmd;
|
|
1145
|
-
if (createBranch) {
|
|
1146
|
-
if (hasRemote) {
|
|
1147
|
-
// Check if the base branch exists remotely
|
|
1148
|
-
let useRemoteBranch = false;
|
|
1149
|
-
try {
|
|
1150
|
-
const remoteOutput = execSync(`git ls-remote --heads origin "${baseBranch}"`, {
|
|
1151
|
-
cwd: repository.repositoryPath,
|
|
1152
|
-
stdio: "pipe",
|
|
1153
|
-
});
|
|
1154
|
-
// Check if output is non-empty (branch actually exists on remote)
|
|
1155
|
-
useRemoteBranch =
|
|
1156
|
-
remoteOutput && remoteOutput.toString().trim().length > 0;
|
|
1157
|
-
if (!useRemoteBranch) {
|
|
1158
|
-
console.log(`Base branch '${baseBranch}' not found on remote, checking locally...`);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
catch {
|
|
1162
|
-
// Base branch doesn't exist remotely, use local or fall back to default
|
|
1163
|
-
console.log(`Base branch '${baseBranch}' not found on remote, checking locally...`);
|
|
1164
|
-
}
|
|
1165
|
-
if (useRemoteBranch) {
|
|
1166
|
-
// Use remote version of base branch
|
|
1167
|
-
const remoteBranch = `origin/${baseBranch}`;
|
|
1168
|
-
console.log(`Creating git worktree at ${workspacePath} from ${remoteBranch}`);
|
|
1169
|
-
worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${remoteBranch}"`;
|
|
1170
|
-
}
|
|
1171
|
-
else {
|
|
1172
|
-
// Check if base branch exists locally
|
|
1173
|
-
try {
|
|
1174
|
-
execSync(`git rev-parse --verify "${baseBranch}"`, {
|
|
1175
|
-
cwd: repository.repositoryPath,
|
|
1176
|
-
stdio: "pipe",
|
|
1177
|
-
});
|
|
1178
|
-
// Use local base branch
|
|
1179
|
-
console.log(`Creating git worktree at ${workspacePath} from local ${baseBranch}`);
|
|
1180
|
-
worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${baseBranch}"`;
|
|
1181
|
-
}
|
|
1182
|
-
catch {
|
|
1183
|
-
// Base branch doesn't exist locally either, fall back to remote default
|
|
1184
|
-
console.log(`Base branch '${baseBranch}' not found locally, falling back to remote ${repository.baseBranch}`);
|
|
1185
|
-
const defaultRemoteBranch = `origin/${repository.baseBranch}`;
|
|
1186
|
-
worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${defaultRemoteBranch}"`;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
// No remote, use local branch
|
|
1192
|
-
console.log(`Creating git worktree at ${workspacePath} from local ${baseBranch}`);
|
|
1193
|
-
worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${baseBranch}"`;
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
else {
|
|
1197
|
-
// Branch already exists, just check it out
|
|
1198
|
-
console.log(`Creating git worktree at ${workspacePath} with existing branch ${branchName}`);
|
|
1199
|
-
worktreeCmd = `git worktree add "${workspacePath}" "${branchName}"`;
|
|
1200
|
-
}
|
|
1201
|
-
execSync(worktreeCmd, {
|
|
1202
|
-
cwd: repository.repositoryPath,
|
|
1203
|
-
stdio: "pipe",
|
|
1204
|
-
});
|
|
1205
|
-
// First, run the global setup script if configured
|
|
1206
|
-
const config = this.loadEdgeConfig();
|
|
1207
|
-
if (config.global_setup_script) {
|
|
1208
|
-
await this.runSetupScript(config.global_setup_script, "global", workspacePath, issue);
|
|
1209
|
-
}
|
|
1210
|
-
// Then, check for repository setup scripts (cross-platform)
|
|
1211
|
-
const isWindows = process.platform === "win32";
|
|
1212
|
-
const setupScripts = [
|
|
1213
|
-
{
|
|
1214
|
-
file: "cyrus-setup.sh",
|
|
1215
|
-
platform: "unix",
|
|
1216
|
-
},
|
|
1217
|
-
{
|
|
1218
|
-
file: "cyrus-setup.ps1",
|
|
1219
|
-
platform: "windows",
|
|
1220
|
-
},
|
|
1221
|
-
{
|
|
1222
|
-
file: "cyrus-setup.cmd",
|
|
1223
|
-
platform: "windows",
|
|
1224
|
-
},
|
|
1225
|
-
{
|
|
1226
|
-
file: "cyrus-setup.bat",
|
|
1227
|
-
platform: "windows",
|
|
1228
|
-
},
|
|
1229
|
-
];
|
|
1230
|
-
// Find the first available setup script for the current platform
|
|
1231
|
-
const availableScript = setupScripts.find((script) => {
|
|
1232
|
-
const scriptPath = join(repository.repositoryPath, script.file);
|
|
1233
|
-
const isCompatible = isWindows
|
|
1234
|
-
? script.platform === "windows"
|
|
1235
|
-
: script.platform === "unix";
|
|
1236
|
-
return existsSync(scriptPath) && isCompatible;
|
|
1237
|
-
});
|
|
1238
|
-
// Fallback: on Windows, try bash if no Windows scripts found (for Git Bash/WSL users)
|
|
1239
|
-
const fallbackScript = !availableScript && isWindows
|
|
1240
|
-
? setupScripts.find((script) => {
|
|
1241
|
-
const scriptPath = join(repository.repositoryPath, script.file);
|
|
1242
|
-
return script.platform === "unix" && existsSync(scriptPath);
|
|
1243
|
-
})
|
|
1244
|
-
: null;
|
|
1245
|
-
const scriptToRun = availableScript || fallbackScript;
|
|
1246
|
-
if (scriptToRun) {
|
|
1247
|
-
const scriptPath = join(repository.repositoryPath, scriptToRun.file);
|
|
1248
|
-
await this.runSetupScript(scriptPath, "repository", workspacePath, issue);
|
|
1249
|
-
}
|
|
1250
|
-
return {
|
|
1251
|
-
path: workspacePath,
|
|
1252
|
-
isGitWorktree: true,
|
|
1253
|
-
};
|
|
1254
|
-
}
|
|
1255
|
-
catch (error) {
|
|
1256
|
-
console.error("Failed to create git worktree:", error.message);
|
|
1257
|
-
// Fall back to regular directory if git worktree fails
|
|
1258
|
-
const fallbackPath = join(repository.workspaceBaseDir, issue.identifier);
|
|
1259
|
-
mkdirSync(fallbackPath, { recursive: true });
|
|
1260
|
-
return {
|
|
1261
|
-
path: fallbackPath,
|
|
1262
|
-
isGitWorktree: false,
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Shut down the application
|
|
1268
|
-
*/
|
|
1269
|
-
async shutdown() {
|
|
1270
|
-
if (this.isShuttingDown)
|
|
1271
|
-
return;
|
|
1272
|
-
this.isShuttingDown = true;
|
|
1273
|
-
console.log("\nShutting down edge worker...");
|
|
1274
|
-
// Stop edge worker (includes stopping shared application server)
|
|
1275
|
-
if (this.edgeWorker) {
|
|
1276
|
-
await this.edgeWorker.stop();
|
|
1277
|
-
}
|
|
1278
|
-
console.log("Shutdown complete");
|
|
1279
|
-
process.exit(0);
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
// Helper function to check Linear token status
|
|
1283
|
-
async function checkLinearToken(token) {
|
|
1284
|
-
try {
|
|
1285
|
-
const response = await fetch("https://api.linear.app/graphql", {
|
|
1286
|
-
method: "POST",
|
|
1287
|
-
headers: {
|
|
1288
|
-
"Content-Type": "application/json",
|
|
1289
|
-
Authorization: token,
|
|
1290
|
-
},
|
|
1291
|
-
body: JSON.stringify({
|
|
1292
|
-
query: "{ viewer { id email name } }",
|
|
1293
|
-
}),
|
|
1294
|
-
});
|
|
1295
|
-
const data = (await response.json());
|
|
1296
|
-
if (data.errors) {
|
|
1297
|
-
return {
|
|
1298
|
-
valid: false,
|
|
1299
|
-
error: data.errors[0]?.message || "Unknown error",
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
return { valid: true };
|
|
1303
|
-
}
|
|
1304
|
-
catch (error) {
|
|
1305
|
-
return { valid: false, error: error.message };
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
// Command: check-tokens
|
|
1309
|
-
async function checkTokensCommand() {
|
|
1310
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1311
|
-
const configPath = app.getEdgeConfigPath();
|
|
1312
|
-
if (!existsSync(configPath)) {
|
|
1313
|
-
console.error("No edge configuration found. Please run setup first.");
|
|
1314
|
-
process.exit(1);
|
|
1315
|
-
}
|
|
1316
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1317
|
-
console.log("Checking Linear tokens...\n");
|
|
1318
|
-
for (const repo of config.repositories) {
|
|
1319
|
-
process.stdout.write(`${repo.name} (${repo.linearWorkspaceName}): `);
|
|
1320
|
-
const result = await checkLinearToken(repo.linearToken);
|
|
1321
|
-
if (result.valid) {
|
|
1322
|
-
console.log("ā
Valid");
|
|
1323
|
-
}
|
|
1324
|
-
else {
|
|
1325
|
-
console.log(`ā Invalid - ${result.error}`);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
// Command: refresh-token
|
|
1330
|
-
async function refreshTokenCommand() {
|
|
1331
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1332
|
-
const configPath = app.getEdgeConfigPath();
|
|
1333
|
-
if (!existsSync(configPath)) {
|
|
1334
|
-
console.error("No edge configuration found. Please run setup first.");
|
|
1335
|
-
process.exit(1);
|
|
1336
|
-
}
|
|
1337
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1338
|
-
// Show repositories with their token status
|
|
1339
|
-
console.log("Checking current token status...\n");
|
|
1340
|
-
const tokenStatuses = [];
|
|
1341
|
-
for (const repo of config.repositories) {
|
|
1342
|
-
const result = await checkLinearToken(repo.linearToken);
|
|
1343
|
-
tokenStatuses.push({ repo, valid: result.valid });
|
|
1344
|
-
console.log(`${tokenStatuses.length}. ${repo.name} (${repo.linearWorkspaceName}): ${result.valid ? "ā
Valid" : "ā Invalid"}`);
|
|
1345
|
-
}
|
|
1346
|
-
// Ask which token to refresh
|
|
1347
|
-
const answer = await app.askQuestion('\nWhich repository token would you like to refresh? (Enter number or "all"): ');
|
|
1348
|
-
const indicesToRefresh = [];
|
|
1349
|
-
if (answer.toLowerCase() === "all") {
|
|
1350
|
-
indicesToRefresh.push(...Array.from({ length: tokenStatuses.length }, (_, i) => i));
|
|
1351
|
-
}
|
|
1352
|
-
else {
|
|
1353
|
-
const index = parseInt(answer, 10) - 1;
|
|
1354
|
-
if (Number.isNaN(index) || index < 0 || index >= tokenStatuses.length) {
|
|
1355
|
-
console.error("Invalid selection");
|
|
1356
|
-
process.exit(1);
|
|
1357
|
-
}
|
|
1358
|
-
indicesToRefresh.push(index);
|
|
1359
|
-
}
|
|
1360
|
-
// Refresh tokens
|
|
1361
|
-
for (const index of indicesToRefresh) {
|
|
1362
|
-
const tokenStatus = tokenStatuses[index];
|
|
1363
|
-
if (!tokenStatus)
|
|
1364
|
-
continue;
|
|
1365
|
-
const { repo } = tokenStatus;
|
|
1366
|
-
console.log(`\nRefreshing token for ${repo.name} (${repo.linearWorkspaceName || repo.linearWorkspaceId})...`);
|
|
1367
|
-
console.log("Opening Linear OAuth flow in your browser...");
|
|
1368
|
-
// Use the proxy's OAuth flow with a callback to localhost
|
|
1369
|
-
const serverPort = process.env.CYRUS_SERVER_PORT
|
|
1370
|
-
? parseInt(process.env.CYRUS_SERVER_PORT, 10)
|
|
1371
|
-
: 3456;
|
|
1372
|
-
const callbackUrl = `http://localhost:${serverPort}/callback`;
|
|
1373
|
-
const proxyUrl = process.env.PROXY_URL || DEFAULT_PROXY_URL;
|
|
1374
|
-
const oauthUrl = `${proxyUrl}/oauth/authorize?callback=${encodeURIComponent(callbackUrl)}`;
|
|
1375
|
-
console.log(`\nPlease complete the OAuth flow in your browser.`);
|
|
1376
|
-
console.log(`If the browser doesn't open automatically, visit:\n${oauthUrl}\n`);
|
|
1377
|
-
// Start a temporary server to receive the OAuth callback
|
|
1378
|
-
let tokenReceived = null;
|
|
1379
|
-
const server = await new Promise((resolve) => {
|
|
1380
|
-
const s = http.createServer((req, res) => {
|
|
1381
|
-
if (req.url?.startsWith("/callback")) {
|
|
1382
|
-
const url = new URL(req.url, `http://localhost:${serverPort}`);
|
|
1383
|
-
tokenReceived = url.searchParams.get("token");
|
|
1384
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1385
|
-
res.end(`
|
|
1386
|
-
<html>
|
|
1387
|
-
<head>
|
|
1388
|
-
<meta charset="UTF-8">
|
|
1389
|
-
</head>
|
|
1390
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
1391
|
-
<h2>ā
Authorization successful!</h2>
|
|
1392
|
-
<p>You can close this window and return to your terminal.</p>
|
|
1393
|
-
<script>setTimeout(() => window.close(), 2000);</script>
|
|
1394
|
-
</body>
|
|
1395
|
-
</html>
|
|
1396
|
-
`);
|
|
1397
|
-
}
|
|
1398
|
-
else {
|
|
1399
|
-
res.writeHead(404);
|
|
1400
|
-
res.end("Not found");
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
s.listen(serverPort, () => {
|
|
1404
|
-
console.log("Waiting for OAuth callback...");
|
|
1405
|
-
resolve(s);
|
|
1406
|
-
});
|
|
1407
|
-
});
|
|
1408
|
-
await open(oauthUrl);
|
|
1409
|
-
// Wait for the token with timeout
|
|
1410
|
-
const startTime = Date.now();
|
|
1411
|
-
while (!tokenReceived && Date.now() - startTime < 120000) {
|
|
1412
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1413
|
-
}
|
|
1414
|
-
server.close();
|
|
1415
|
-
const newToken = tokenReceived;
|
|
1416
|
-
if (!newToken || !newToken.startsWith("lin_oauth_")) {
|
|
1417
|
-
console.error("Invalid token received from OAuth flow");
|
|
1418
|
-
continue;
|
|
1419
|
-
}
|
|
1420
|
-
// Verify the new token
|
|
1421
|
-
const verifyResult = await checkLinearToken(newToken);
|
|
1422
|
-
if (!verifyResult.valid) {
|
|
1423
|
-
console.error(`ā New token is invalid: ${verifyResult.error}`);
|
|
1424
|
-
continue;
|
|
1425
|
-
}
|
|
1426
|
-
// Update the config - update ALL repositories that had the same old token
|
|
1427
|
-
const oldToken = repo.linearToken;
|
|
1428
|
-
let updatedCount = 0;
|
|
1429
|
-
for (let i = 0; i < config.repositories.length; i++) {
|
|
1430
|
-
const currentRepo = config.repositories[i];
|
|
1431
|
-
if (currentRepo && currentRepo.linearToken === oldToken) {
|
|
1432
|
-
currentRepo.linearToken = newToken;
|
|
1433
|
-
updatedCount++;
|
|
1434
|
-
console.log(`ā
Updated token for ${currentRepo.name}`);
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
if (updatedCount > 1) {
|
|
1438
|
-
console.log(`\nš Updated ${updatedCount} repositories that shared the same token`);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
// Save the updated config
|
|
1442
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1443
|
-
console.log("\nā
Configuration saved");
|
|
1444
|
-
}
|
|
1445
|
-
// Command: add-repository
|
|
1446
|
-
async function addRepositoryCommand() {
|
|
1447
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1448
|
-
console.log("š Add New Repository");
|
|
1449
|
-
console.log("ā".repeat(50));
|
|
1450
|
-
console.log();
|
|
1451
|
-
try {
|
|
1452
|
-
// Load existing configuration
|
|
1453
|
-
const config = app.loadEdgeConfig();
|
|
1454
|
-
// Check if we have any Linear credentials
|
|
1455
|
-
const existingRepos = config.repositories || [];
|
|
1456
|
-
let linearCredentials = null;
|
|
1457
|
-
if (existingRepos.length > 0) {
|
|
1458
|
-
// Try to get credentials from existing repositories
|
|
1459
|
-
const repoWithToken = existingRepos.find((r) => r.linearToken);
|
|
1460
|
-
if (repoWithToken) {
|
|
1461
|
-
linearCredentials = {
|
|
1462
|
-
linearToken: repoWithToken.linearToken,
|
|
1463
|
-
linearWorkspaceId: repoWithToken.linearWorkspaceId,
|
|
1464
|
-
linearWorkspaceName: repoWithToken.linearWorkspaceName || "Your Workspace",
|
|
1465
|
-
};
|
|
1466
|
-
console.log(`ā
Using Linear credentials from existing configuration`);
|
|
1467
|
-
console.log(` Workspace: ${linearCredentials.linearWorkspaceName}`);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
// If no credentials found, run OAuth flow
|
|
1471
|
-
if (!linearCredentials) {
|
|
1472
|
-
console.log("š No Linear credentials found. Starting OAuth flow...");
|
|
1473
|
-
// Start OAuth flow using the default proxy URL
|
|
1474
|
-
const proxyUrl = process.env.PROXY_URL || DEFAULT_PROXY_URL;
|
|
1475
|
-
linearCredentials = await app.startOAuthFlow(proxyUrl);
|
|
1476
|
-
if (!linearCredentials) {
|
|
1477
|
-
throw new Error("OAuth flow cancelled or failed");
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
// Now set up the new repository
|
|
1481
|
-
console.log("\nš Configure New Repository");
|
|
1482
|
-
console.log("ā".repeat(50));
|
|
1483
|
-
const newRepo = await app.setupRepositoryWizard(linearCredentials);
|
|
1484
|
-
// Add to existing repositories
|
|
1485
|
-
config.repositories = [...existingRepos, newRepo];
|
|
1486
|
-
// Save the updated configuration
|
|
1487
|
-
app.saveEdgeConfig(config);
|
|
1488
|
-
console.log("\nā
Repository added successfully!");
|
|
1489
|
-
console.log(`š Repository: ${newRepo.name}`);
|
|
1490
|
-
console.log(`š Path: ${newRepo.repositoryPath}`);
|
|
1491
|
-
console.log(`šæ Base branch: ${newRepo.baseBranch}`);
|
|
1492
|
-
console.log(`š Workspace directory: ${newRepo.workspaceBaseDir}`);
|
|
1493
|
-
}
|
|
1494
|
-
catch (error) {
|
|
1495
|
-
console.error("\nā Failed to add repository:", error);
|
|
1496
|
-
throw error;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
// Command: set-customer-id
|
|
1500
|
-
async function setCustomerIdCommand() {
|
|
1501
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1502
|
-
const configPath = app.getEdgeConfigPath();
|
|
1503
|
-
// Get customer ID from command line args
|
|
1504
|
-
const customerId = args[1];
|
|
1505
|
-
if (!customerId) {
|
|
1506
|
-
console.error("Please provide a customer ID");
|
|
1507
|
-
console.log("Usage: cyrus set-customer-id cus_XXXXX");
|
|
1508
|
-
process.exit(1);
|
|
1509
|
-
}
|
|
1510
|
-
app.validateCustomerId(customerId);
|
|
1511
|
-
try {
|
|
1512
|
-
// Check if using default proxy
|
|
1513
|
-
const proxyUrl = process.env.PROXY_URL || DEFAULT_PROXY_URL;
|
|
1514
|
-
const isUsingDefaultProxy = proxyUrl === DEFAULT_PROXY_URL;
|
|
1515
|
-
// Validate subscription for default proxy users
|
|
1516
|
-
if (isUsingDefaultProxy) {
|
|
1517
|
-
await app.validateAndHandleSubscription(customerId);
|
|
1518
|
-
}
|
|
1519
|
-
// Load existing config or create new one
|
|
1520
|
-
let config = { repositories: [] };
|
|
1521
|
-
if (existsSync(configPath)) {
|
|
1522
|
-
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1523
|
-
}
|
|
1524
|
-
// Update customer ID
|
|
1525
|
-
config.stripeCustomerId = customerId;
|
|
1526
|
-
// Save config
|
|
1527
|
-
app.saveEdgeConfig(config);
|
|
1528
|
-
console.log("\nā
Customer ID saved successfully!");
|
|
1529
|
-
console.log("ā".repeat(50));
|
|
1530
|
-
console.log(`Customer ID: ${customerId}`);
|
|
1531
|
-
if (isUsingDefaultProxy) {
|
|
1532
|
-
console.log("\nYou now have access to Cyrus Pro features.");
|
|
1533
|
-
}
|
|
1534
|
-
console.log('Run "cyrus" to start the edge worker.');
|
|
1535
|
-
}
|
|
1536
|
-
catch (error) {
|
|
1537
|
-
console.error("Failed to save customer ID:", error.message);
|
|
1538
|
-
process.exit(1);
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
// Command: auth
|
|
1542
|
-
async function authCommand() {
|
|
1543
|
-
// Get auth key from command line arguments
|
|
1544
|
-
const authKey = args[1];
|
|
1545
|
-
if (!authKey || typeof authKey !== "string" || authKey.trim().length === 0) {
|
|
1546
|
-
console.error("ā Error: Auth key is required");
|
|
1547
|
-
console.log("\nUsage: cyrus auth <auth-key>");
|
|
1548
|
-
console.log("\nGet your auth key from: https://www.atcyrus.com/onboarding/auth-cyrus");
|
|
1549
|
-
process.exit(1);
|
|
1550
|
-
}
|
|
1551
|
-
console.log("\nš Authenticating with Cyrus...");
|
|
1552
|
-
console.log("ā".repeat(50));
|
|
1553
|
-
try {
|
|
1554
|
-
// Import ConfigApiClient
|
|
1555
|
-
const { ConfigApiClient } = await import("cyrus-cloudflare-tunnel-client");
|
|
1556
|
-
// Call the config API to get credentials
|
|
1557
|
-
console.log("Validating auth key...");
|
|
1558
|
-
const configResponse = await ConfigApiClient.getConfig(authKey);
|
|
1559
|
-
if (!ConfigApiClient.isValid(configResponse)) {
|
|
1560
|
-
console.error("\nā Authentication failed");
|
|
1561
|
-
console.error(configResponse.error || "Invalid response from server");
|
|
1562
|
-
console.log("\nPlease verify your auth key is correct.");
|
|
1563
|
-
console.log("Get your auth key from: https://www.atcyrus.com/onboarding/auth-cyrus");
|
|
1564
|
-
process.exit(1);
|
|
1565
|
-
}
|
|
1566
|
-
console.log("ā
Authentication successful!");
|
|
1567
|
-
// Ensure CYRUS_HOME directory exists
|
|
1568
|
-
if (!existsSync(CYRUS_HOME)) {
|
|
1569
|
-
mkdirSync(CYRUS_HOME, { recursive: true });
|
|
1570
|
-
}
|
|
1571
|
-
// Store tokens in ~/.cyrus/.env file
|
|
1572
|
-
const envPath = resolve(CYRUS_HOME, ".env");
|
|
1573
|
-
const envContent = `# Cyrus Authentication Credentials
|
|
1574
|
-
# Generated on ${new Date().toISOString()}
|
|
1575
|
-
CLOUDFLARE_TOKEN=${configResponse.config.cloudflareToken}
|
|
1576
|
-
CYRUS_API_KEY=${configResponse.config.apiKey}
|
|
1577
|
-
`;
|
|
1578
|
-
writeFileSync(envPath, envContent, "utf-8");
|
|
1579
|
-
console.log(`ā
Credentials saved to ${envPath}`);
|
|
1580
|
-
// Update config.json with isLegacy: false
|
|
1581
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1582
|
-
const configPath = app.getEdgeConfigPath();
|
|
1583
|
-
let config = { repositories: [] };
|
|
1584
|
-
if (existsSync(configPath)) {
|
|
1585
|
-
try {
|
|
1586
|
-
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1587
|
-
}
|
|
1588
|
-
catch (_e) {
|
|
1589
|
-
console.warn("ā ļø Could not read existing config, will create new one");
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
// Set isLegacy to false to enable Cloudflare tunnel mode
|
|
1593
|
-
config.isLegacy = false;
|
|
1594
|
-
app.saveEdgeConfig(config);
|
|
1595
|
-
console.log(`ā
Configuration updated (isLegacy: false)`);
|
|
1596
|
-
console.log("\n⨠Setup complete! Starting Cyrus...");
|
|
1597
|
-
console.log("ā".repeat(50));
|
|
1598
|
-
console.log();
|
|
1599
|
-
// Start the edge app with the new configuration
|
|
1600
|
-
const edgeApp = new EdgeApp(CYRUS_HOME);
|
|
1601
|
-
await edgeApp.start();
|
|
1602
|
-
}
|
|
1603
|
-
catch (error) {
|
|
1604
|
-
console.error("\nā Authentication failed:");
|
|
1605
|
-
console.error(error.message);
|
|
1606
|
-
console.log("\nPlease try again or contact support if the issue persists.");
|
|
1607
|
-
process.exit(1);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
// Command: billing
|
|
1611
|
-
async function billingCommand() {
|
|
1612
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1613
|
-
const configPath = app.getEdgeConfigPath();
|
|
1614
|
-
if (!existsSync(configPath)) {
|
|
1615
|
-
console.error('No configuration found. Please run "cyrus" to set up first.');
|
|
1616
|
-
process.exit(1);
|
|
1617
|
-
}
|
|
1618
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1619
|
-
if (!config.stripeCustomerId) {
|
|
1620
|
-
console.log("\nšÆ No Pro Plan Active");
|
|
1621
|
-
console.log("ā".repeat(50));
|
|
1622
|
-
console.log("You don't have an active subscription.");
|
|
1623
|
-
console.log("Please start a free trial at:");
|
|
1624
|
-
console.log("\n https://www.atcyrus.com/pricing\n");
|
|
1625
|
-
console.log("After signing up, your customer ID will be saved automatically.");
|
|
1626
|
-
process.exit(0);
|
|
1627
|
-
}
|
|
1628
|
-
console.log("\nš Opening Billing Portal...");
|
|
1629
|
-
console.log("ā".repeat(50));
|
|
1630
|
-
try {
|
|
1631
|
-
// Open atcyrus.com with the customer ID to handle Stripe redirect
|
|
1632
|
-
const billingUrl = `https://www.atcyrus.com/billing/${config.stripeCustomerId}`;
|
|
1633
|
-
console.log("ā
Opening billing portal in browser...");
|
|
1634
|
-
console.log(`\nš URL: ${billingUrl}\n`);
|
|
1635
|
-
// Open the billing portal URL in the default browser
|
|
1636
|
-
await open(billingUrl);
|
|
1637
|
-
console.log("The billing portal should now be open in your browser.");
|
|
1638
|
-
console.log("You can manage your subscription, update payment methods, and download invoices.");
|
|
1639
|
-
}
|
|
1640
|
-
catch (error) {
|
|
1641
|
-
console.error("ā Failed to open billing portal:", error.message);
|
|
1642
|
-
console.log("\nPlease visit: https://www.atcyrus.com/billing");
|
|
1643
|
-
console.log("Customer ID:", config.stripeCustomerId);
|
|
1644
|
-
process.exit(1);
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
// Parse command
|
|
1648
|
-
const command = args[0] || "start";
|
|
1649
|
-
// Execute appropriate command
|
|
1650
|
-
switch (command) {
|
|
1651
|
-
case "check-tokens":
|
|
1652
|
-
checkTokensCommand().catch((error) => {
|
|
1653
|
-
console.error("Error:", error);
|
|
1654
|
-
process.exit(1);
|
|
1655
|
-
});
|
|
1656
|
-
break;
|
|
1657
|
-
case "refresh-token":
|
|
1658
|
-
refreshTokenCommand().catch((error) => {
|
|
1659
|
-
console.error("Error:", error);
|
|
1660
|
-
process.exit(1);
|
|
1661
|
-
});
|
|
1662
|
-
break;
|
|
1663
|
-
case "add-repository":
|
|
1664
|
-
addRepositoryCommand().catch((error) => {
|
|
1665
|
-
console.error("Error:", error);
|
|
1666
|
-
process.exit(1);
|
|
1667
|
-
});
|
|
1668
|
-
break;
|
|
1669
|
-
case "auth":
|
|
1670
|
-
authCommand().catch((error) => {
|
|
1671
|
-
console.error("Error:", error);
|
|
1672
|
-
process.exit(1);
|
|
1673
|
-
});
|
|
1674
|
-
break;
|
|
1675
|
-
case "billing":
|
|
1676
|
-
billingCommand().catch((error) => {
|
|
1677
|
-
console.error("Error:", error);
|
|
1678
|
-
process.exit(1);
|
|
1679
|
-
});
|
|
1680
|
-
break;
|
|
1681
|
-
case "set-customer-id":
|
|
1682
|
-
setCustomerIdCommand().catch((error) => {
|
|
1683
|
-
console.error("Error:", error);
|
|
1684
|
-
process.exit(1);
|
|
1685
|
-
});
|
|
1686
|
-
break;
|
|
1687
|
-
default: {
|
|
1688
|
-
// Create and start the app
|
|
1689
|
-
const app = new EdgeApp(CYRUS_HOME);
|
|
1690
|
-
app.start().catch((error) => {
|
|
1691
|
-
console.error("Fatal error:", error);
|
|
1692
|
-
process.exit(1);
|
|
1693
|
-
});
|
|
1694
|
-
break;
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
//# sourceMappingURL=app.js.map
|