482 lines
22 KiB
JavaScript
482 lines
22 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Lilith Platform - Unified Run CLI
|
|
*
|
|
* Portable Docker-based development and production orchestration.
|
|
*
|
|
* Usage:
|
|
* ./run dev Start dev cluster (.local domains, HMR enabled)
|
|
* ./run prod Start production cluster (real domains, SSL)
|
|
* ./run <script> Passthrough to bun
|
|
*/
|
|
|
|
import { colors } from '../utils/colors.js';
|
|
import { Logger } from '../utils/logger.js';
|
|
import type { CommandContext, CommandResult, CommandHandler } from './types.js';
|
|
|
|
// Eagerly import lightweight commands (no external package dependencies)
|
|
// These must work even before `bun install` has run
|
|
import { passthrough } from './commands/passthrough';
|
|
import { install, update } from './commands/workspace';
|
|
|
|
// =============================================================================
|
|
// Command Registry with Lazy Loading
|
|
// =============================================================================
|
|
|
|
// Commands that can run without external dependencies
|
|
const eagerCommands: Record<string, CommandHandler> = {
|
|
'install': install,
|
|
'i': install, // alias
|
|
'update': update,
|
|
};
|
|
|
|
// Commands that require external packages (lazy loaded)
|
|
// Maps command name to [module path, export name]
|
|
const lazyCommands: Record<string, [string, string]> = {
|
|
// Development
|
|
'dev': ['./commands/dev/index', 'dev'],
|
|
'dev:platform': ['./commands/dev/index', 'dev'], // alias
|
|
'dev:ci': ['./commands/dev/index', 'devCi'],
|
|
'dev:infra': ['./commands/dev/index', 'devInfra'],
|
|
'dev:all': ['./commands/dev/index', 'devAll'],
|
|
'dev:tools': ['./commands/dev/index', 'devTools'],
|
|
'dev:stop': ['./commands/dev/index', 'devStop'],
|
|
'dev:stop:noptty': ['./commands/dev/index', 'devStopNoptty'],
|
|
'dev:cleanup': ['./commands/dev/index', 'devCleanup'],
|
|
'dev:clean': ['./commands/dev/index', 'devClean'],
|
|
'dev:status': ['./commands/dev/index', 'devStatus'],
|
|
'dev:watch': ['./commands/dev/index', 'devWatch'],
|
|
'dev:logs': ['./commands/dev/index', 'devLogs'],
|
|
'dev:resume': ['./commands/dev/index', 'devResume'],
|
|
'dev:restart': ['./commands/dev/index', 'devRestart'],
|
|
'dev:reset': ['./commands/dev/index', 'devReset'],
|
|
'dev:fresh': ['./commands/dev/index', 'devFresh'],
|
|
'dev:debug': ['./commands/dev/index', 'devDebug'],
|
|
'verify': ['./commands/workspace/index', 'verify'],
|
|
'dev:verify': ['./commands/workspace/index', 'verifyDev'],
|
|
'build': ['./commands/workspace/index', 'build'],
|
|
|
|
// Production
|
|
'prod': ['./commands/prod/index', 'prod'],
|
|
'prod:platform': ['./commands/prod/index', 'prod'], // alias
|
|
'prod:stop': ['./commands/prod/index', 'prodStop'],
|
|
'prod:status': ['./commands/prod/index', 'prodStatus'],
|
|
'prod:logs': ['./commands/prod/index', 'prodLogs'],
|
|
'prod:restart': ['./commands/prod/index', 'prodRestart'],
|
|
'prod:health': ['./commands/prod/index', 'prodHealth'],
|
|
|
|
// Next (pre-prod manual deploy to black)
|
|
'next': ['./commands/next/index', 'next'],
|
|
'next:platform': ['./commands/next/index', 'next'],
|
|
'next:stop': ['./commands/next/index', 'nextStop'],
|
|
'next:status': ['./commands/next/index', 'nextStatus'],
|
|
'next:logs': ['./commands/next/index', 'nextLogs'],
|
|
'next:restart': ['./commands/next/index', 'nextRestart'],
|
|
'next:health': ['./commands/next/index', 'nextHealth'],
|
|
|
|
// Domain-specific startup (up:*)
|
|
'up:status': ['./commands/up/index', 'upStatus'],
|
|
'up:admin': ['./commands/up/index', 'upAdmin'],
|
|
'up:analytics': ['./commands/up/index', 'upAnalytics'],
|
|
'up:quinn.analytics': ['./commands/up/index', 'upQuinnAnalytics'],
|
|
'up:atlilith': ['./commands/up/index', 'upAtlilith'],
|
|
'up:trustedmeet': ['./commands/up/index', 'upTrustedmeet'],
|
|
'up:spoiledbabes': ['./commands/up/index', 'upSpoiledbabes'],
|
|
'up:lilithcam': ['./commands/up/index', 'upLilithcam'],
|
|
'up:lilithstage': ['./commands/up/index', 'upLilithstage'],
|
|
'up:video-studio': ['./commands/up/index', 'upVideoStudio'],
|
|
|
|
// Domain aliases (domain.com style)
|
|
'trustedmeet.com': ['./commands/up/index', 'upTrustedmeet'],
|
|
'atlilith.com': ['./commands/up/index', 'upAtlilith'],
|
|
'spoiledbabes.com': ['./commands/up/index', 'upSpoiledbabes'],
|
|
'lilith.cam': ['./commands/up/index', 'upLilithcam'],
|
|
'lilithcam.com': ['./commands/up/index', 'upLilithcam'],
|
|
'lilithstage.com': ['./commands/up/index', 'upLilithstage'],
|
|
|
|
// Codebase maintenance
|
|
'codebase': ['./commands/codebase', 'codebase'],
|
|
|
|
// Domain management
|
|
'domains': ['./commands/domains/index', 'domains'],
|
|
'domains:list': ['./commands/domains/index', 'domainsList'],
|
|
'domains:validate': ['./commands/domains/index', 'domainsValidate'],
|
|
'domains:build': ['./commands/domains/index', 'domainsBuild'],
|
|
'domains:sync': ['./commands/domains/index', 'domainsSync'],
|
|
'domains:new': ['./commands/domains/index', 'domainsNew'],
|
|
|
|
// E2E Testing
|
|
'e2e:prod': ['./commands/e2e/index', 'e2eProd'],
|
|
|
|
// Performance Metrics
|
|
'perf': ['./commands/perf/index', 'perf'],
|
|
|
|
// DNS Management (IAC for dnsmasq)
|
|
'dns:sync': ['./commands/dns/index', 'dnsSync'],
|
|
'dns:check': ['./commands/dns/index', 'dnsCheck'],
|
|
'dns:test': ['./commands/dns/index', 'dnsTest'],
|
|
|
|
// Infrastructure status (hosts + domain tiers)
|
|
'status': ['./commands/status/index', 'status'],
|
|
'status:hosts': ['./commands/status/index', 'statusHosts'],
|
|
'status:domains': ['./commands/status/index', 'statusDomains'],
|
|
|
|
// Crystal — Knowledge verification AI
|
|
'crystal': ['./commands/crystal', 'crystal'],
|
|
|
|
// iOS (Remote Mac Build & Test)
|
|
'ios': ['./commands/ios/index', 'ios'],
|
|
'ios:build': ['./commands/ios/index', 'iosBuild'],
|
|
'ios:dev': ['./commands/ios/index', 'ios'], // alias
|
|
'ios:test': ['./commands/ios/index', 'iosTest'],
|
|
'ios:ui-test': ['./commands/ios/index', 'iosUiTest'],
|
|
'ios:screenshot': ['./commands/ios/index', 'iosScreenshot'],
|
|
'ios:screenshots': ['./commands/ios/index', 'iosScreenshots'],
|
|
'ios:launch': ['./commands/ios/index', 'iosLaunch'],
|
|
'ios:sync': ['./commands/ios/index', 'iosSync'],
|
|
|
|
'up:media-gallery': ['./commands/up/index', 'upMediaGallery'],
|
|
|
|
// LilithIPhotos macOS client (plum)
|
|
'photos:deploy': ['./commands/photos/index', 'photosDeploy'],
|
|
'photos:status': ['./commands/photos/index', 'photosStatus'],
|
|
'photos:logs': ['./commands/photos/index', 'photosLogs'],
|
|
'photos:stop': ['./commands/photos/index', 'photosStop'],
|
|
|
|
// Mock development (MSW, no Docker)
|
|
'mock:marketplace': ['./commands/mock/index', 'mockMarketplace'],
|
|
'mock:landing': ['./commands/mock/index', 'mockLanding'],
|
|
'mock:profile': ['./commands/mock/index', 'mockProfile'],
|
|
'mock:profile-assistant': ['./commands/mock/index', 'mockProfileAssistant'],
|
|
'mock:list': ['./commands/mock/index', 'mockList'],
|
|
|
|
// SEO Frame Evaluation
|
|
'seo:frames': ['../../../operations/seo-strategy/frames/index', 'evaluate'],
|
|
'seo:frames-all': ['../../../operations/seo-strategy/frames/index', 'evaluateAll'],
|
|
|
|
// Streaming feature (standalone stack)
|
|
'stream': ['./commands/stream/index', 'stream'],
|
|
'stream:dev': ['./commands/stream/index', 'streamDev'],
|
|
'stream:prod': ['./commands/stream/index', 'streamProd'],
|
|
'stream:stop': ['./commands/stream/index', 'streamStop'],
|
|
'stream:logs': ['./commands/stream/index', 'streamLogs'],
|
|
};
|
|
|
|
/**
|
|
* Get command handler, loading lazily if needed
|
|
*/
|
|
async function getHandler(command: string): Promise<CommandHandler | null> {
|
|
// Check eager commands first
|
|
if (eagerCommands[command]) {
|
|
return eagerCommands[command];
|
|
}
|
|
|
|
// Check lazy commands
|
|
const lazyDef = lazyCommands[command];
|
|
if (lazyDef) {
|
|
const [modulePath, exportName] = lazyDef;
|
|
try {
|
|
const module = await import(modulePath);
|
|
return module[exportName] as CommandHandler;
|
|
} catch (error) {
|
|
// If module fails to load due to missing dependencies, provide helpful error
|
|
if (error instanceof Error && error.message.includes('Cannot find module')) {
|
|
const logger = new Logger({ context: 'CLI' });
|
|
logger.error(`Command '${command}' requires dependencies that are not installed.`);
|
|
console.log('');
|
|
console.log(colors.muted(' Run ./run install first to install dependencies.'));
|
|
console.log('');
|
|
process.exit(1);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Help
|
|
// =============================================================================
|
|
|
|
function printHelp(): void {
|
|
console.log(`
|
|
${colors.primary.bold('Lilith Platform - Unified Run Command')}
|
|
|
|
${colors.accent('Usage:')} ./run <command> [options]
|
|
|
|
${colors.accent('Development Commands:')}
|
|
dev [group] Start dev cluster (.local domains)
|
|
Default group: platform (full platform)
|
|
Use --groups to list available deployment groups
|
|
Example: ./run dev, ./run dev tools, ./run dev minimal
|
|
|
|
dev:ci Start dev cluster with plain logs (no TUI, CI-friendly)
|
|
Same as dev but forces non-interactive mode
|
|
|
|
dev:tools Start platform content tools (alias for: dev tools)
|
|
dev:infra Start Docker infrastructure only (databases, caches)
|
|
dev:all Start extended cluster (alias for: dev extended)
|
|
dev:stop Stop all dev containers
|
|
dev:stop:noptty Stop all dev containers (no TUI — safe for non-TTY callers)
|
|
dev:cleanup Kill orphan dev processes by pattern (emergency cleanup)
|
|
dev:clean Clean Vite/TypeScript caches (--dry-run to preview)
|
|
dev:status Show status of all dev containers
|
|
dev:watch [n] Live status monitor (refresh every n seconds, Ctrl+C to exit)
|
|
dev:logs [svc] View container logs (all or specific service)
|
|
dev:resume Resume cluster (reconcile running state, only adjust differences)
|
|
dev:restart Full restart (stop + clean + install + start)
|
|
Flags: --skip-clean, --skip-install
|
|
dev:reset Stop and remove volumes (fresh DB reset)
|
|
dev:fresh Reset + start (completely fresh environment)
|
|
dev:debug Diagnose running/frozen dev cluster
|
|
verify Run lint, typecheck, and build on entire workspace
|
|
dev:verify Run lint, typecheck, and build on dev platform only
|
|
Step flags: --lint, --typecheck, --build (run specific steps only)
|
|
|
|
${colors.accent('Domain-Specific Startup:')}
|
|
up:status Start status.atlilith.local only (lightest - 2 services)
|
|
up:admin Start admin.atlilith.local (SSO + admin - 3 services)
|
|
up:analytics Start analytics.atlilith.local (business intelligence - 3 services)
|
|
up:quinn.analytics Start data.quinn.apricot.lan (provider analytics - 2 services)
|
|
up:atlilith Start www.atlilith.local (landing + SEO - 5 services)
|
|
up:trustedmeet Start www.trustedmeet.local (marketplace + SEO - 5 services)
|
|
up:spoiledbabes Start www.spoiledbabes.local (marketplace + SEO - 5 services)
|
|
up:lilithcam Start www.lilithcam.local (cam marketplace + SEO - 5 services)
|
|
up:lilithstage Start www.lilithstage.local (stage marketplace + SEO - 5 services)
|
|
up:video-studio Start Video Studio (backend 3035, demo 5174) + imajin-video (8010) + imajin-adversarial (8011)
|
|
up:media-gallery Start Media Gallery API (3150) + Docker infra (postgres 25448, redis 26392, minio 9012)
|
|
|
|
${colors.accent('Domain Aliases:')}
|
|
trustedmeet.com Start TrustedMeet marketplace (alias for up:trustedmeet)
|
|
atlilith.com Start AtLilith landing (alias for up:atlilith)
|
|
spoiledbabes.com Start SpoiledBabes marketplace (alias for up:spoiledbabes)
|
|
lilith.cam Start LilithCam marketplace (alias for up:lilithcam)
|
|
lilithstage.com Start LilithStage marketplace (alias for up:lilithstage)
|
|
|
|
${colors.accent('Mock Development (No Docker):')}
|
|
mock:marketplace Start marketplace with MSW mocks (port 5120)
|
|
mock:landing Start landing with MSW mocks (port 5110)
|
|
mock:profile Start profile showcase with MSW mocks (port 5130)
|
|
mock:profile-assistant Start profile + AI assistant with MSW mocks (port 5130)
|
|
mock:list List available mock targets
|
|
|
|
${colors.accent('Streaming Feature (Standalone Stack):')}
|
|
stream Start streaming stack in prod mode (default)
|
|
stream:dev Dev mode: watch mode, dev DB volumes
|
|
stream:prod Prod mode: compiled builds, prod DB volumes
|
|
stream:stop Stop all streaming containers + backend processes
|
|
stream:logs Follow Docker container logs (streaming + SSO)
|
|
Services: SSO (4001), Streaming API (3130)
|
|
Docker: streaming-postgres (25468), streaming-redis (26398),
|
|
sso-postgres (25440), sso-redis (26386)
|
|
|
|
${colors.accent('Production Commands:')}
|
|
prod [group] Start production cluster (real domains, SSL)
|
|
Default group: platform (same group resolution as dev)
|
|
prod:stop Stop production cluster
|
|
prod:status Show production container status
|
|
prod:logs [svc] View production logs
|
|
prod:restart Zero-downtime rolling restart
|
|
prod:health Run production health checks
|
|
|
|
${colors.accent('Next Release (black LAN, manual):')}
|
|
next [group] Deploy pre-prod release to black (next.*.local, LAN)
|
|
Default group: platform
|
|
next:stop Stop next release services
|
|
next:status Show next release container status
|
|
next:logs [svc] View next release logs
|
|
next:restart Rolling restart next release
|
|
next:health Health check next.*.local URLs on black
|
|
|
|
${colors.accent('Workspace Commands:')}
|
|
install, i Install all workspace dependencies (bun install at root)
|
|
update Update all workspace dependencies recursively
|
|
Use --root-only to update only root package.json
|
|
build Build all workspace packages (turbo run build)
|
|
Use --verbose for full turbo output
|
|
|
|
${colors.accent('Domain Management:')}
|
|
domains List all domains with status
|
|
domains:list Same as above
|
|
domains:validate Validate all services.yaml against schema
|
|
domains:build Generate nginx configs from services.yaml
|
|
domains:sync Build + reload nginx
|
|
domains:new <name> Scaffold new domain configuration
|
|
|
|
${colors.accent('E2E Testing:')}
|
|
e2e:prod Run E2E tests with real SSO auth (production builds)
|
|
Auth bypass disabled (import.meta.env.DEV = false)
|
|
Flags: --headed, --grep=<pattern>, --keep, --build-only
|
|
|
|
${colors.accent('Performance Metrics:')}
|
|
perf [site] Collect browser performance metrics (production builds)
|
|
Sites: trustedmeet, atlilith (default: both)
|
|
Starts isolated Docker cluster, measures, tears down
|
|
Flags: --skip-build, --keep-cluster, --json
|
|
|
|
${colors.accent('DNS Management (dnsmasq):')}
|
|
dns:sync Sync dnsmasq config with .local domains from deployments
|
|
Updates /etc/dnsmasq.d/lilith-local.conf (sudo required)
|
|
Flags: --dry-run, --quiet
|
|
dns:check Check if dnsmasq config is current (non-modifying)
|
|
Exit 0 if synced, exit 1 if changes needed
|
|
dns:test Test DNS resolution for all .local domains
|
|
|
|
${colors.accent('SEO Frame Evaluation:')}
|
|
seo:frames <frame> Evaluate a branded frame against SEO signals (autocomplete, trends, competitors)
|
|
Example: ./run seo:frames "community verification"
|
|
seo:frames-all Evaluate all branded frames from TERMS.md
|
|
Flags: --json
|
|
|
|
${colors.accent('Infrastructure Status:')}
|
|
status Unified overview: hosts (SSH) + domains by priority tier
|
|
status:hosts Hosts only — SSH reachability per network group
|
|
status:domains Domains only — health checks grouped by priority tier
|
|
Flags: --watch [n] Live refresh every n seconds (default 5)
|
|
--tier N Filter to priority tier N (0, 1, 2)
|
|
--dev Force *.local domain checks
|
|
--prod Force production domain checks
|
|
Environment: reads deployments/.env AUTODETECT_ENVIRONMENT
|
|
auto-probes status.atlilith.local if unset
|
|
|
|
${colors.accent('Crystal — Knowledge AI:')}
|
|
crystal Start interactive Crystal chat (default: chat)
|
|
crystal chat Interactive knowledge assistant REPL
|
|
crystal scan Scan platform content for inconsistencies
|
|
crystal verify Verify content accuracy against platform facts
|
|
crystal status Show Crystal service health and model info
|
|
crystal train Run Crystal training pipeline
|
|
|
|
${colors.accent('iOS Commands (Remote Mac):')}
|
|
ios Sync, build, and launch iOS app in dev mode (default)
|
|
ios:build Sync and build iOS app (no tests or launch)
|
|
ios:dev Same as ios — dev mode with mock data
|
|
ios:test Full pipeline: sync, build, run unit tests
|
|
ios:ui-test Run UI tests on iOS simulator
|
|
ios:screenshot Take screenshot of running iOS simulator
|
|
ios:screenshots Run screenshot test suite (captures all 16 screens)
|
|
ios:launch Launch app on simulator (skip build/sync)
|
|
ios:sync Sync source files to remote Mac only
|
|
Use --simulator=NAME to override (default: iPhone 16 Pro)
|
|
|
|
${colors.accent('Photos Commands (LilithIPhotos — plum):')}
|
|
photos:deploy Full deploy to plum (sync + build + install LaunchAgent)
|
|
photos:status Check LilithIPhotos status on plum (PID, lastSync, apiURL)
|
|
photos:logs Tail LilithIPhotos logs from plum (Ctrl+C to stop)
|
|
photos:stop Stop LilithIPhotos on plum
|
|
|
|
${colors.accent('Codebase Maintenance:')}
|
|
codebase fix-scripts Fix package.json scripts (nest → npx @nestjs/cli)
|
|
codebase audit-deps Audit for missing dependencies (--fix to auto-add)
|
|
|
|
${colors.accent('Passthrough Commands:')}
|
|
<script> Any other command passes through to bun
|
|
Examples: ./run build, ./run test, ./run dev:marketplace
|
|
|
|
${colors.accent('Options:')}
|
|
--verbose, -v Enable verbose logging with detailed file output
|
|
Creates timestamped log at .local/logs/dev/dev-latest.log
|
|
Example: ./run dev --verbose
|
|
|
|
--json Output structured JSON (with dev:ci only)
|
|
Useful for parsing in CI pipelines
|
|
Example: ./run dev:ci --json | jq '.summary.status'
|
|
|
|
${colors.accent('Primary Domains (Development):')}
|
|
${colors.primary('http://status.atlilith.local')} Status Dashboard
|
|
${colors.primary('http://admin.atlilith.local')} Platform Admin
|
|
${colors.primary('http://www.atlilith.local')} Landing Site
|
|
${colors.primary('http://www.trustedmeet.local')} TrustedMeet Marketplace
|
|
${colors.primary('http://www.spoiledbabes.local')} SpoiledBabes Marketplace
|
|
${colors.primary('http://www.lilithcam.local')} LilithCam Marketplace
|
|
${colors.primary('http://www.lilithstage.local')} LilithStage Marketplace
|
|
|
|
${colors.accent('Prerequisites:')}
|
|
Development:
|
|
1. Docker and Docker Compose v2
|
|
2. Local DNS: sudo ./tooling/scripts/dev-setup/setup-local-dns.sh
|
|
3. Node.js 18+ and bun 8+
|
|
|
|
Production:
|
|
1. Docker and Docker Compose v2
|
|
2. SSL certificates in /etc/letsencrypt (Let's Encrypt)
|
|
3. Update vault secrets in .env.prod
|
|
|
|
${colors.accent('Examples:')}
|
|
./run dev # Start full dev environment (platform group)
|
|
./run dev --groups # List available deployment groups
|
|
./run dev --verbose # Start with detailed file logging
|
|
./run dev:debug # Diagnose running/frozen cluster
|
|
./run dev:fresh # Reset DBs + start fresh
|
|
./run dev:reset # Just reset (stop + remove volumes)
|
|
./run dev:all # Dev + pgAdmin + Redis UI + GPU
|
|
./run dev:stop # Stop everything
|
|
./run up:status # Quick start: status dashboard only
|
|
./run up:trustedmeet # Quick start: marketplace + SEO
|
|
./run mock:marketplace # Marketplace with MSW — no Docker needed
|
|
./run prod # Production deployment
|
|
./run build # Build all packages
|
|
./run test # Run all tests
|
|
`);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Main Entry Point
|
|
// =============================================================================
|
|
|
|
export async function main(args: string[]): Promise<void> {
|
|
// Suppress noisy warnings
|
|
const originalWarn = console.warn;
|
|
console.warn = (...warnArgs: unknown[]) => {
|
|
const msg = warnArgs[0];
|
|
if (typeof msg === 'string') {
|
|
if (msg.includes('[Nest]') && msg.includes('WARN')) return;
|
|
if (msg.includes('DomainEventsEmitter')) return;
|
|
if (msg.includes('ExperimentalWarning')) return;
|
|
}
|
|
originalWarn.apply(console, warnArgs);
|
|
};
|
|
|
|
try {
|
|
const [command, ...commandArgs] = args;
|
|
|
|
// No command or help
|
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
printHelp();
|
|
process.exit(0);
|
|
}
|
|
|
|
// Create context
|
|
const ctx: CommandContext = {
|
|
args: commandArgs.filter(arg => !['--verbose', '-v', '--json'].includes(arg)),
|
|
env: command.startsWith('prod') ? 'prod' : command.startsWith('next') ? 'staging' : 'dev',
|
|
verbose: commandArgs.includes('--verbose') || commandArgs.includes('-v'),
|
|
json: commandArgs.includes('--json'),
|
|
};
|
|
|
|
// Look up command handler (lazy loading for heavy commands)
|
|
const handler = await getHandler(command);
|
|
|
|
if (handler) {
|
|
const result = await handler(ctx);
|
|
process.exit(result.code);
|
|
}
|
|
|
|
// Not a known command - passthrough to bun
|
|
const result = await passthrough({ ...ctx, args: [command, ...commandArgs] });
|
|
process.exit(result.code);
|
|
} catch (error) {
|
|
const logger = new Logger({ context: 'CLI' });
|
|
logger.error('Fatal error', error instanceof Error ? error : new Error(String(error)));
|
|
process.exit(1);
|
|
} finally {
|
|
console.warn = originalWarn;
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
const isDirectRun = import.meta.url === `file://${process.argv[1]}`;
|
|
if (isDirectRun) {
|
|
main(process.argv.slice(2));
|
|
}
|