UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

140 lines (122 loc) 5.07 kB
#!/usr/bin/env node import { execFileSync } from 'child_process'; import { fileURLToPath } from 'url'; import { join, dirname } from 'path'; import { existsSync, mkdirSync, appendFileSync, statSync, readFileSync, writeFileSync } from 'fs'; import { homedir } from 'os'; import { createRequire } from 'module'; // --- Phase 0: silent-exit visibility ----------------------------------------- // Boot banner, duration footer, and boot-trace log. Best-effort — never throw // from the bin wrapper, even if the log directory is unwritable. const require = createRequire(import.meta.url); // eslint-disable-next-line @typescript-eslint/no-require-imports const pkg = require('../package.json'); const VERSION = pkg.version || 'unknown'; function resolveConsortiumHomeDir() { // Priority: env CONSORTIUM_HOME_DIR > dev-variant heuristic > default. // Mirrors src/configuration.ts + bin/consortium-dev.mjs. const envDir = process.env.CONSORTIUM_HOME_DIR; if (envDir) { return envDir.replace(/^~/, homedir()); } // Dev variant (published as dev-hummingbird-sneakers) keys off package name. const name = typeof pkg.name === 'string' ? pkg.name : ''; if (name === 'dev-hummingbird-sneakers' || process.env.CONSORTIUM_VARIANT === 'dev') { return join(homedir(), '.consortium-dev'); } return join(homedir(), '.consortium'); } const BOOT_TRACE_MAX_BYTES = 1024 * 1024; // 1 MB function writeBootTrace() { try { const homeDir = resolveConsortiumHomeDir(); const logsDir = join(homeDir, 'logs'); if (!existsSync(logsDir)) { mkdirSync(logsDir, { recursive: true, mode: 0o700 }); } const tracePath = join(logsDir, 'boot-trace.log'); const entry = JSON.stringify({ ts: new Date().toISOString(), pid: process.pid, version: VERSION, args: process.argv.slice(2), node: process.versions.node, platform: `${process.platform}-${process.arch}`, }) + '\n'; // Rotate by truncating from the front when > 1 MB. if (existsSync(tracePath)) { try { const st = statSync(tracePath); if (st.size + Buffer.byteLength(entry) > BOOT_TRACE_MAX_BYTES) { const raw = readFileSync(tracePath, 'utf8'); // Keep the last ~half of the file so we retain recent history. const half = Math.floor(BOOT_TRACE_MAX_BYTES / 2); const sliced = raw.length > half ? raw.slice(raw.length - half) : raw; // Drop any leading partial line from the front-truncation. const firstNewline = sliced.indexOf('\n'); const trimmed = firstNewline >= 0 ? sliced.slice(firstNewline + 1) : sliced; writeFileSync(tracePath, trimmed, { mode: 0o600 }); } } catch { /* best-effort rotation */ } } appendFileSync(tracePath, entry, { mode: 0o600 }); } catch { /* best-effort — never throw from the bin wrapper */ } } function writeStderr(msg) { try { process.stderr.write(msg); } catch { /* ignore */ } } // Boot banner — last-resort signal that the CLI started. writeStderr(`[consortium] starting v${VERSION} pid=${process.pid}\n`); writeBootTrace(); const bootStart = process.hrtime.bigint(); function emitExitFooter(code) { const ms = Number((process.hrtime.bigint() - bootStart) / 1_000_000n); writeStderr(`[consortium] exited code=${code} duration=${ms}ms\n`); } // --- end Phase 0 -------------------------------------------------------------- // Dispatcher note: this wrapper just forwards to dist/index.mjs and lets the // CLI entrypoint decide what to do. When `process.argv.length <= 2` (i.e. the // user ran `consortium` with no subcommand) the entrypoint renders the ink // TUI menu; otherwise the existing subcommand dispatcher handles things. // Keeping the branch in TypeScript means the TUI imports (ink, react) live // in the bundled dist and don't need to be duplicated at the bin layer. // Check if we're already running with the flags const hasNoWarnings = process.execArgv.includes('--no-warnings'); const hasNoDeprecation = process.execArgv.includes('--no-deprecation'); if (!hasNoWarnings || !hasNoDeprecation) { // Get path to the actual CLI entrypoint const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))); const entrypoint = join(projectRoot, 'dist', 'index.mjs'); // Execute the actual CLI directly with the correct flags let exitCode = 0; try { execFileSync(process.execPath, [ '--no-warnings', '--no-deprecation', entrypoint, ...process.argv.slice(2) ], { stdio: 'inherit', env: process.env }); } catch (error) { // execFileSync throws if the process exits with non-zero exitCode = error.status || 1; emitExitFooter(exitCode); process.exit(exitCode); } emitExitFooter(exitCode); } else { // We're running Node with the flags we wanted, import the CLI entrypoint // module to avoid creating a new process. Best-effort footer on exit. process.on('exit', (code) => emitExitFooter(code)); import("../dist/index.mjs"); }