UNPKG

cyrus-ai

Version:

AI-powered Linear issue automation using Claude

137 lines 6.21 kB
#!/usr/bin/env node import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { Command } from "commander"; import { setGlobalErrorReporter } from "cyrus-core"; import dotenv from "dotenv"; import { Application } from "./Application.js"; import { AuthCommand } from "./commands/AuthCommand.js"; import { CheckTokensCommand } from "./commands/CheckTokensCommand.js"; import { RefreshTokenCommand } from "./commands/RefreshTokenCommand.js"; import { SelfAddRepoCommand } from "./commands/SelfAddRepoCommand.js"; import { SelfAuthCommand } from "./commands/SelfAuthCommand.js"; import { StartCommand } from "./commands/StartCommand.js"; import { createErrorReporter } from "./services/createErrorReporter.js"; // Get the directory of the current module for reading package.json const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Read package.json to get the actual version // When compiled, this is in dist/src/, so we need to go up two levels const packageJsonPath = resolve(__dirname, "..", "..", "package.json"); const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); // Pre-load env vars from the resolved .env file before initialising Sentry, so // that CYRUS_SENTRY_DISABLED / CYRUS_SENTRY_DSN take effect on the first run. // We re-resolve the path inside Application using the same precedence (CLI // flag wins); this preliminary load only honours CYRUS_HOME and the default. preloadEnvForBootstrap(); // Initialise the error reporter as early as possible so that exceptions // thrown by subsequent imports/bootstrap are captured. Install it as the // process-wide reporter so that every Logger.error(...) call across the // codebase forwards to Sentry automatically. const errorReporter = createErrorReporter({ release: packageJson.version }); setGlobalErrorReporter(errorReporter); // Setup Commander program const program = new Command(); program .name("cyrus") .description("AI-powered Linear issue automation using Claude") .version(packageJson.version) .option("--cyrus-home <path>", "Specify custom Cyrus config directory", resolve(homedir(), ".cyrus")) .option("--env-file <path>", "Path to environment variables file"); // Start command (default) program .command("start", { isDefault: true }) .description("Start the edge worker") .action(async () => { const opts = program.opts(); const app = new Application(opts.cyrusHome, opts.envFile, packageJson.version, errorReporter); await new StartCommand(app).execute([]); }); // Auth command program .command("auth <auth-key>") .description("Authenticate with Cyrus using auth key") .action(async (authKey) => { const opts = program.opts(); const app = new Application(opts.cyrusHome, opts.envFile, packageJson.version, errorReporter); await new AuthCommand(app).execute([authKey]); }); // Check tokens command program .command("check-tokens") .description("Check the status of all Linear tokens") .action(async () => { const opts = program.opts(); const app = new Application(opts.cyrusHome, opts.envFile, packageJson.version, errorReporter); await new CheckTokensCommand(app).execute([]); }); // Refresh token command program .command("refresh-token") .description("Refresh a specific Linear token") .action(async () => { const opts = program.opts(); const app = new Application(opts.cyrusHome, opts.envFile, packageJson.version, errorReporter); await new RefreshTokenCommand(app).execute([]); }); // Self-auth-linear command - Linear OAuth directly from CLI program .command("self-auth-linear") .description("Authenticate with Linear OAuth directly") .action(async () => { const opts = program.opts(); const app = new Application(opts.cyrusHome, opts.envFile, packageJson.version, errorReporter); await new SelfAuthCommand(app).execute([]); }); // Self-add-repo command - Clone and add repository program .command("self-add-repo [url] [workspace]") .description('Clone a repo and add it to config. URL accepts any valid git clone address (e.g., "https://github.com/org/repo.git"). Workspace is the display name of the Linear workspace (e.g., "My Workspace"). If URL is omitted, prompts interactively.') .option("-l, --label <labels>", "Comma-separated routing labels (defaults to repo name)") .option("-b, --base-branch <branch>", "Base branch name (auto-detected from remote if not specified)") .action(async (url, workspace, cmdOpts) => { const opts = program.opts(); const app = new Application(opts.cyrusHome, opts.envFile, packageJson.version); const args = [url, workspace].filter(Boolean); if (cmdOpts.label) { args.push("-l", cmdOpts.label); } if (cmdOpts.baseBranch) { args.push("-b", cmdOpts.baseBranch); } await new SelfAddRepoCommand(app).execute(args); }); // Parse and execute (async () => { try { await program.parseAsync(process.argv); } catch (error) { errorReporter.captureException(error, { tags: { phase: "bootstrap" } }); await errorReporter.flush(2000).catch(() => false); console.error("Fatal error:", error); process.exit(1); } })(); /** * Best-effort env preload so the error reporter can read its config before the * full {@link Application} bootstrap. We honour `--env-file` only as a literal * argv lookup (Commander hasn't parsed yet) and otherwise fall back to the * default `<cyrus-home>/.env` path. */ function preloadEnvForBootstrap() { const argv = process.argv.slice(2); const flagIdx = argv.indexOf("--env-file"); const cyrusHomeIdx = argv.indexOf("--cyrus-home"); const envFile = flagIdx >= 0 ? argv[flagIdx + 1] : undefined; const cyrusHome = cyrusHomeIdx >= 0 && argv[cyrusHomeIdx + 1] ? argv[cyrusHomeIdx + 1] : resolve(homedir(), ".cyrus"); const path = envFile ?? join(cyrusHome, ".env"); if (existsSync(path)) { dotenv.config({ path, override: false }); } } //# sourceMappingURL=app.js.map