crontick 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/chunk-35FFLWP3.js +79 -0
  4. package/dist/chunk-35FFLWP3.js.map +1 -0
  5. package/dist/chunk-FMGZ3SSS.js +57 -0
  6. package/dist/chunk-FMGZ3SSS.js.map +1 -0
  7. package/dist/chunk-LPAFZNXK.js +214 -0
  8. package/dist/chunk-LPAFZNXK.js.map +1 -0
  9. package/dist/chunk-YMAT5MON.js +22 -0
  10. package/dist/chunk-YMAT5MON.js.map +1 -0
  11. package/dist/cli/index.cjs +879 -0
  12. package/dist/cli/index.cjs.map +1 -0
  13. package/dist/cli/index.d.cts +2 -0
  14. package/dist/cli/index.d.ts +2 -0
  15. package/dist/cli/index.js +554 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/daemon/index.cjs +1655 -0
  18. package/dist/daemon/index.cjs.map +1 -0
  19. package/dist/daemon/index.d.cts +2 -0
  20. package/dist/daemon/index.d.ts +2 -0
  21. package/dist/daemon/index.js +1306 -0
  22. package/dist/daemon/index.js.map +1 -0
  23. package/dist/dashboard/.gitkeep +0 -0
  24. package/dist/dashboard/dashboard.css +192 -0
  25. package/dist/dashboard/dashboard.js +316 -0
  26. package/dist/dashboard/index.html +108 -0
  27. package/dist/index.cjs +180 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +121 -0
  30. package/dist/index.d.ts +121 -0
  31. package/dist/index.js +32 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/job-JQGLDEJV.js +26 -0
  34. package/dist/job-JQGLDEJV.js.map +1 -0
  35. package/dist/mcp/index.cjs +1000 -0
  36. package/dist/mcp/index.cjs.map +1 -0
  37. package/dist/mcp/index.d.cts +13 -0
  38. package/dist/mcp/index.d.ts +13 -0
  39. package/dist/mcp/index.js +876 -0
  40. package/dist/mcp/index.js.map +1 -0
  41. package/package.json +81 -0
  42. package/plugin/.gitkeep +0 -0
  43. package/plugin/README.md +50 -0
  44. package/plugin/install.mjs +155 -0
  45. package/plugin/plugin.json +11 -0
  46. package/plugin/uninstall.mjs +68 -0
  47. package/src/skill/SKILL.md +227 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/daemon/index.ts","../../src/daemon/store.ts","../../src/daemon/scheduler.ts","../../src/daemon/runner.ts","../../src/daemon/api.ts"],"sourcesContent":["/**\n * crontick daemon entry point.\n * T-NEW-4: Re-exec with --experimental-sqlite on Node < 24 if flag is absent.\n */\nimport { spawn } from 'node:child_process';\nimport { readFileSync, writeFileSync, unlinkSync, existsSync, appendFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n ensureDirs,\n pidFilePath,\n portFilePath,\n logsDir,\n runsDbPath,\n jobsDir,\n} from '../paths.js';\nimport { Store } from './store.js';\nimport { Scheduler } from './scheduler.js';\nimport { Runner } from './runner.js';\nimport { createApiServer } from './api.js';\n\n// ── SQLite shim ───────────────────────────────────────────────────────────────\n\nconst nodeMajor = parseInt(process.versions.node.split('.')[0], 10);\nconst needsSqliteShim = nodeMajor < 24 && !process.execArgv.includes('--experimental-sqlite');\n\nif (needsSqliteShim) {\n const child = spawn(process.execPath, ['--experimental-sqlite', ...process.argv.slice(1)], {\n stdio: 'inherit',\n env: process.env,\n detached: false,\n });\n child.on('exit', (code) => {\n process.exit(code ?? 0);\n });\n} else {\n // ── Logger ──────────────────────────────────────────────────────────────────\n\n let logFile: string | null = null;\n\n function log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...(data ? { data } : {}) });\n process.stderr.write(line + '\\n');\n if (logFile) {\n try { appendFileSync(logFile, line + '\\n'); } catch { /* ignore */ }\n }\n }\n\n // ── Single-instance guard ───────────────────────────────────────────────────\n\n function checkSingleInstance(): void {\n const pidPath = pidFilePath();\n if (!existsSync(pidPath)) return;\n try {\n const existingPid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);\n if (!isNaN(existingPid)) {\n try {\n process.kill(existingPid, 0);\n log('error', 'Daemon already running', { pid: existingPid });\n process.exit(1);\n } catch {\n log('warn', 'Removing stale PID file', { pid: existingPid });\n }\n }\n } catch { /* ignore */ }\n }\n\n function cleanup(): void {\n for (const p of [pidFilePath(), portFilePath()]) {\n try { if (existsSync(p)) unlinkSync(p); } catch { /* ignore */ }\n }\n }\n\n // ── Main ────────────────────────────────────────────────────────────────────\n\n async function main(): Promise<void> {\n ensureDirs();\n const today = new Date().toISOString().slice(0, 10);\n logFile = join(logsDir(), `daemon-${today}.log`);\n log('info', 'Starting crontick daemon', { pid: process.pid, node: process.version });\n\n checkSingleInstance();\n writeFileSync(pidFilePath(), String(process.pid), 'utf-8');\n\n const store = new Store(runsDbPath(), jobsDir());\n store.open();\n const reconciled = store.reconcileOrphanRuns();\n if (reconciled > 0) {\n log('warn', `Reconciled ${reconciled} orphaned run(s) from previous daemon session`);\n }\n store.loadJobsFromDisk();\n const jobs = store.listJobs();\n log('info', `Loaded ${jobs.length} job(s) from disk`);\n\n const scheduler = new Scheduler();\n const runner = new Runner();\n\n for (const job of jobs) {\n if (job.enabled) scheduler.schedule(job, store);\n }\n\n scheduler.on('tick', ({ jobId, plannedAt }) => {\n const job = store.getJob(jobId);\n if (!job || !job.enabled) return;\n const run = store.insertRun(jobId, plannedAt.getTime());\n runner.run(job, run.id, store).catch((err: unknown) => {\n log('error', 'Runner error', { jobId, error: String(err) });\n });\n });\n\n async function reload(): Promise<void> {\n log('info', 'Reloading jobs from disk');\n scheduler.unscheduleAll();\n store.loadJobsFromDisk();\n const reloaded = store.listJobs();\n for (const job of reloaded) {\n if (job.enabled) scheduler.schedule(job, store);\n }\n log('info', `Reloaded ${reloaded.length} job(s)`);\n }\n\n const startedAt = new Date();\n const ctx = { store, scheduler, runner, startedAt, port: 0, reload };\n const server = createApiServer(ctx);\n\n await new Promise<void>((resolve, reject) => {\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : 0;\n ctx.port = port;\n writeFileSync(portFilePath(), String(port), 'utf-8');\n log('info', `API listening on 127.0.0.1:${port}`);\n resolve();\n });\n server.on('error', reject);\n });\n\n let shuttingDown = false;\n async function shutdown(signal: string): Promise<void> {\n if (shuttingDown) return;\n shuttingDown = true;\n log('info', `Received ${signal}, shutting down`);\n server.close();\n scheduler.unscheduleAll();\n await new Promise<void>((r) => setTimeout(r, 100));\n store.close();\n cleanup();\n log('info', 'Daemon stopped');\n process.exit(0);\n }\n\n process.on('SIGINT', () => void shutdown('SIGINT'));\n process.on('SIGTERM', () => void shutdown('SIGTERM'));\n log('info', 'Daemon ready');\n }\n\n main().catch((err: unknown) => {\n process.stderr.write(`Fatal daemon error: ${String(err)}\\n`);\n cleanup();\n process.exit(1);\n });\n}\n","import { DatabaseSync } from 'node:sqlite';\nimport { randomUUID } from 'node:crypto';\nimport { writeFileSync, readFileSync, unlinkSync, readdirSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { runsDbPath, jobsDir } from '../paths.js';\nimport { JobSchema, type Job } from '../schemas/job.js';\nimport { CrontickError } from '../errors.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type RunStatus = 'queued' | 'running' | 'success' | 'failed' | 'canceled' | 'timeout';\n\nexport interface Run {\n id: string;\n jobId: string;\n startedAt: number; // epoch ms\n endedAt?: number;\n status: RunStatus;\n exitCode?: number;\n error?: string;\n durationMs?: number;\n}\n\nexport interface RunLog {\n runId: string;\n stream: 'stdout' | 'stderr';\n ts: number; // epoch ms\n chunk: Buffer;\n}\n\nexport interface ListRunsOptions {\n jobId?: string;\n limit?: number;\n since?: number; // epoch ms\n}\n\n// ── Migrations ────────────────────────────────────────────────────────────────\n\nconst MIGRATIONS: Array<{ name: string; sql: string }> = [\n {\n name: '001_initial',\n sql: `\n CREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n json TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS runs (\n id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL,\n started_at INTEGER NOT NULL,\n ended_at INTEGER,\n status TEXT NOT NULL,\n exit_code INTEGER,\n error TEXT,\n duration_ms INTEGER\n );\n CREATE TABLE IF NOT EXISTS run_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stream TEXT NOT NULL,\n ts INTEGER NOT NULL,\n chunk BLOB NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_runs_job_id ON runs(job_id);\n CREATE INDEX IF NOT EXISTS idx_runs_started_at ON runs(started_at);\n CREATE INDEX IF NOT EXISTS idx_run_logs_run_id ON run_logs(run_id);\n `,\n },\n];\n\n// ── Store ─────────────────────────────────────────────────────────────────────\n\nexport class Store {\n private db!: DatabaseSync;\n private dbPath: string;\n private jobsPath: string;\n\n constructor(dbPath?: string, jobsPath?: string) {\n this.dbPath = dbPath ?? runsDbPath();\n this.jobsPath = jobsPath ?? jobsDir();\n }\n\n open(): void {\n this.db = new DatabaseSync(this.dbPath);\n this.db.exec('PRAGMA journal_mode=WAL;');\n this.db.exec('PRAGMA foreign_keys=ON;');\n this.runMigrations();\n }\n\n close(): void {\n try {\n this.db.close();\n } catch {\n // ignore if already closed\n }\n }\n\n private runMigrations(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS migrations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE,\n applied_at INTEGER NOT NULL\n );\n `);\n\n const applied = this.db\n .prepare('SELECT name FROM migrations')\n .all() as Array<{ name: string }>;\n const appliedSet = new Set(applied.map((r) => r.name));\n\n for (const migration of MIGRATIONS) {\n if (!appliedSet.has(migration.name)) {\n this.db.exec(migration.sql);\n this.db\n .prepare('INSERT INTO migrations (name, applied_at) VALUES (?, ?)')\n .run(migration.name, Date.now());\n }\n }\n }\n\n // ── Job CRUD ────────────────────────────────────────────────────────────────\n\n upsertJob(job: Job): void {\n const json = JSON.stringify(job);\n const now = Date.now();\n this.db\n .prepare(\n 'INSERT INTO jobs (id, json, updated_at) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET json=excluded.json, updated_at=excluded.updated_at',\n )\n .run(job.id, json, now);\n // File-based persistence: jobs dir is source of truth\n const filePath = join(this.jobsPath, `${job.id}.json`);\n writeFileSync(filePath, json, 'utf-8');\n }\n\n getJob(id: string): Job | undefined {\n const row = this.db.prepare('SELECT json FROM jobs WHERE id = ?').get(id) as\n | { json: string }\n | undefined;\n if (!row) return undefined;\n return JSON.parse(row.json) as Job;\n }\n\n listJobs(): Job[] {\n const rows = this.db.prepare('SELECT json FROM jobs ORDER BY id').all() as Array<{\n json: string;\n }>;\n return rows.map((r) => JSON.parse(r.json) as Job);\n }\n\n deleteJob(id: string): boolean {\n const changes = (this.db.prepare('DELETE FROM jobs WHERE id = ?').run(id) as { changes: number }).changes;\n const filePath = join(this.jobsPath, `${id}.json`);\n if (existsSync(filePath)) {\n try {\n unlinkSync(filePath);\n } catch {\n // ignore\n }\n }\n return changes > 0;\n }\n\n /** Load jobs from the jobs directory (disk is source of truth on daemon start). */\n loadJobsFromDisk(): void {\n if (!existsSync(this.jobsPath)) return;\n const files = readdirSync(this.jobsPath).filter((f) => f.endsWith('.json'));\n for (const file of files) {\n try {\n const raw = readFileSync(join(this.jobsPath, file), 'utf-8');\n const parsed = JobSchema.safeParse(JSON.parse(raw));\n if (parsed.success) {\n const json = JSON.stringify(parsed.data);\n this.db\n .prepare(\n 'INSERT INTO jobs (id, json, updated_at) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET json=excluded.json, updated_at=excluded.updated_at',\n )\n .run(parsed.data.id, json, Date.now());\n }\n } catch {\n // skip malformed job files\n }\n }\n }\n\n // ── Run CRUD ────────────────────────────────────────────────────────────────\n\n insertRun(jobId: string, startedAt?: number): Run {\n const id = randomUUID();\n const now = startedAt ?? Date.now();\n this.db\n .prepare(\n 'INSERT INTO runs (id, job_id, started_at, status) VALUES (?, ?, ?, ?)',\n )\n .run(id, jobId, now, 'queued');\n return { id, jobId, startedAt: now, status: 'queued' };\n }\n\n updateRun(\n id: string,\n update: Partial<Pick<Run, 'status' | 'exitCode' | 'error' | 'endedAt' | 'durationMs'>>,\n ): void {\n const run = this.getRun(id);\n if (!run) throw new CrontickError('NOT_FOUND', `Run ${id} not found`);\n\n const fields: string[] = [];\n const values: (string | number | null)[] = [];\n\n if (update.status !== undefined) {\n fields.push('status = ?');\n values.push(update.status);\n }\n if (update.exitCode !== undefined) {\n fields.push('exit_code = ?');\n values.push(update.exitCode ?? null);\n }\n if (update.error !== undefined) {\n fields.push('error = ?');\n values.push(update.error ?? null);\n }\n if (update.endedAt !== undefined) {\n fields.push('ended_at = ?');\n values.push(update.endedAt ?? null);\n }\n if (update.durationMs !== undefined) {\n fields.push('duration_ms = ?');\n values.push(update.durationMs ?? null);\n }\n\n if (fields.length === 0) return;\n values.push(id);\n this.db.prepare(`UPDATE runs SET ${fields.join(', ')} WHERE id = ?`).run(...values);\n }\n\n getRun(id: string): Run | undefined {\n const row = this.db\n .prepare('SELECT * FROM runs WHERE id = ?')\n .get(id) as DbRunRow | undefined;\n return row ? rowToRun(row) : undefined;\n }\n\n listRuns(opts: ListRunsOptions = {}): Run[] {\n const conditions: string[] = [];\n const params: (string | number)[] = [];\n\n if (opts.jobId) {\n conditions.push('job_id = ?');\n params.push(opts.jobId);\n }\n if (opts.since !== undefined) {\n conditions.push('started_at >= ?');\n params.push(opts.since);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = opts.limit !== undefined ? `LIMIT ${opts.limit}` : '';\n const rows = this.db.prepare(`SELECT * FROM runs ${where} ORDER BY started_at DESC ${limit}`)\n .all(...params) as unknown as DbRunRow[];\n return rows.map(rowToRun);\n }\n\n /** Get the most recent successful run for a job (for catchup calculation). */\n getLastRun(jobId: string): Run | undefined {\n const row = this.db\n .prepare(\n \"SELECT * FROM runs WHERE job_id = ? AND status IN ('success','failed','timeout') ORDER BY started_at DESC LIMIT 1\",\n )\n .get(jobId) as DbRunRow | undefined;\n return row ? rowToRun(row) : undefined;\n }\n\n /** Count runs for a job started after a given epoch ms. */\n countRunsSince(jobId: string, since: number, excludeId?: string): number {\n if (excludeId) {\n const result = this.db\n .prepare('SELECT COUNT(*) as cnt FROM runs WHERE job_id = ? AND started_at >= ? AND id != ?')\n .get(jobId, since, excludeId) as { cnt: number };\n return result.cnt;\n }\n const result = this.db\n .prepare('SELECT COUNT(*) as cnt FROM runs WHERE job_id = ? AND started_at >= ?')\n .get(jobId, since) as { cnt: number };\n return result.cnt;\n }\n\n // ── Log CRUD ────────────────────────────────────────────────────────────────\n\n appendLog(runId: string, stream: 'stdout' | 'stderr', chunk: Buffer): void {\n this.db\n .prepare('INSERT INTO run_logs (run_id, stream, ts, chunk) VALUES (?, ?, ?, ?)')\n .run(runId, stream, Date.now(), chunk);\n }\n\n getLogs(runId: string): RunLog[] {\n const rows = this.db\n .prepare('SELECT * FROM run_logs WHERE run_id = ? ORDER BY id')\n .all(runId) as unknown as DbLogRow[];\n return rows.map(rowToLog);\n }\n\n tailLogs(runId: string, sinceTs: number): RunLog[] {\n const rows = this.db\n .prepare('SELECT * FROM run_logs WHERE run_id = ? AND ts > ? ORDER BY id')\n .all(runId, sinceTs) as unknown as DbLogRow[];\n return rows.map(rowToLog);\n }\n\n /** On daemon startup, cancel any runs that were left in 'running' or 'queued' state (daemon crashed). */\n reconcileOrphanRuns(): number {\n const result = this.db\n .prepare(\n \"UPDATE runs SET status = 'canceled', error = 'daemon-restart', ended_at = ? WHERE status IN ('running', 'queued')\",\n )\n .run(Date.now()) as { changes: number };\n return result.changes;\n }\n}\n\n// ── Internal row types ────────────────────────────────────────────────────────\n\ninterface DbRunRow {\n id: string;\n job_id: string;\n started_at: number;\n ended_at: number | null;\n status: RunStatus;\n exit_code: number | null;\n error: string | null;\n duration_ms: number | null;\n}\n\ninterface DbLogRow {\n id: number;\n run_id: string;\n stream: 'stdout' | 'stderr';\n ts: number;\n chunk: Buffer;\n}\n\nfunction rowToRun(row: DbRunRow): Run {\n const r: Run = {\n id: row.id,\n jobId: row.job_id,\n startedAt: row.started_at,\n status: row.status,\n };\n if (row.ended_at !== null) r.endedAt = row.ended_at;\n if (row.exit_code !== null) r.exitCode = row.exit_code;\n if (row.error !== null) r.error = row.error;\n if (row.duration_ms !== null) r.durationMs = row.duration_ms;\n return r;\n}\n\nfunction rowToLog(row: DbLogRow): RunLog {\n return {\n runId: row.run_id,\n stream: row.stream,\n ts: row.ts,\n chunk: Buffer.from(row.chunk),\n };\n}\n","import { EventEmitter } from 'node:events';\nimport { Cron, type CronOptions } from 'croner';\nimport type { Job, Schedule } from '../schemas/job.js';\nimport type { Store } from './store.js';\n\n// ── Cron alias expansion ──────────────────────────────────────────────────────\n\nconst CRON_ALIASES: Record<string, string> = {\n '@yearly': '0 0 1 1 *',\n '@annually': '0 0 1 1 *',\n '@monthly': '0 0 1 * *',\n '@weekly': '0 0 * * 0',\n '@daily': '0 0 * * *',\n '@midnight': '0 0 * * *',\n '@noon': '0 12 * * *',\n '@hourly': '0 * * * *',\n '@every_minute': '* * * * *',\n};\n\n/** Expand a cron alias like @daily → '0 0 * * *', or return expr unchanged. */\nexport function expandCronAlias(expr: string): string {\n return CRON_ALIASES[expr.toLowerCase()] ?? expr;\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface TickEvent {\n jobId: string;\n plannedAt: Date;\n}\n\nexport interface PreviewOptions {\n n?: number;\n tz?: string;\n}\n\nexport interface ValidateResult {\n ok: boolean;\n error?: string;\n}\n\n// ── Scheduler ─────────────────────────────────────────────────────────────────\n\nexport class Scheduler extends EventEmitter {\n private entries: Map<string, { stop: () => void }> = new Map();\n\n /**\n * Schedule a job, handling catchup based on the last run time from the store.\n * On daemon start pass `store` so catchup can be resolved; at runtime it's optional.\n */\n schedule(job: Job, store?: Store): void {\n this.unschedule(job.id); // idempotent\n\n if (!job.enabled) return;\n\n const { schedule } = job;\n const lastRun = store?.getLastRun(job.id);\n const lastRunAt = lastRun ? new Date(lastRun.startedAt) : undefined;\n\n if (schedule.kind === 'cron') {\n this.scheduleCron(job, expandCronAlias(schedule.cron), schedule.tz, lastRunAt);\n } else if (schedule.kind === 'interval') {\n this.scheduleInterval(job, schedule.everySec, schedule.startAt, lastRunAt);\n } else if (schedule.kind === 'one-shot') {\n this.scheduleOneShot(job, schedule.runAt);\n }\n }\n\n unschedule(jobId: string): void {\n const entry = this.entries.get(jobId);\n if (entry) {\n entry.stop();\n this.entries.delete(jobId);\n }\n }\n\n unscheduleAll(): void {\n for (const jobId of [...this.entries.keys()]) {\n this.unschedule(jobId);\n }\n }\n\n // ── Preview / Validate ─────────────────────────────────────────────────────\n\n previewNext(schedule: Schedule, opts: PreviewOptions = {}): string[] {\n const n = opts.n ?? 5;\n\n if (schedule.kind === 'cron') {\n return cronNextN(expandCronAlias(schedule.cron), opts.tz ?? schedule.tz, n);\n }\n\n if (schedule.kind === 'interval') {\n const now = Date.now();\n const intervalMs = schedule.everySec * 1000;\n const results: string[] = [];\n for (let i = 1; i <= n; i++) {\n results.push(new Date(now + i * intervalMs).toISOString());\n }\n return results;\n }\n\n if (schedule.kind === 'one-shot') {\n const t = new Date(schedule.runAt);\n if (isNaN(t.getTime())) return [];\n return t > new Date() ? [t.toISOString()] : [];\n }\n\n return [];\n }\n\n validateSchedule(schedule: Schedule): ValidateResult {\n if (schedule.kind === 'cron') {\n try {\n const cron = new Cron(expandCronAlias(schedule.cron), { paused: true });\n cron.stop();\n return { ok: true };\n } catch (err) {\n return { ok: false, error: String(err) };\n }\n }\n\n if (schedule.kind === 'interval') {\n if (schedule.everySec <= 0) {\n return { ok: false, error: 'everySec must be positive' };\n }\n if (schedule.startAt && isNaN(new Date(schedule.startAt).getTime())) {\n return { ok: false, error: 'startAt is not a valid ISO-8601 date' };\n }\n return { ok: true };\n }\n\n if (schedule.kind === 'one-shot') {\n const t = new Date(schedule.runAt);\n if (isNaN(t.getTime())) {\n return { ok: false, error: 'runAt is not a valid ISO-8601 date' };\n }\n return { ok: true };\n }\n\n return { ok: false, error: 'Unknown schedule kind' };\n }\n\n // ── Private helpers ────────────────────────────────────────────────────────\n\n private fireTick(jobId: string, plannedAt: Date): void {\n this.emit('tick', { jobId, plannedAt } satisfies TickEvent);\n }\n\n private scheduleCron(\n job: Job,\n pattern: string,\n tz: string | undefined,\n lastRunAt: Date | undefined,\n ): void {\n // Handle catchup before starting recurring schedule\n this.handleCronCatchup(job, pattern, tz, lastRunAt);\n\n const options: CronOptions = {};\n if (tz) options.timezone = tz;\n\n const cron = new Cron(pattern, options, () => {\n this.fireTick(job.id, new Date());\n });\n\n this.entries.set(job.id, { stop: () => cron.stop() });\n }\n\n private handleCronCatchup(\n job: Job,\n pattern: string,\n tz: string | undefined,\n lastRunAt: Date | undefined,\n ): void {\n if (job.catchup === 'skip' || !lastRunAt) return;\n\n const now = new Date();\n const missed = missedCronFires(pattern, tz, lastRunAt, now);\n if (missed.length === 0) return;\n\n if (job.catchup === 'run-once') {\n setImmediate(() => this.fireTick(job.id, missed[missed.length - 1]));\n } else if (job.catchup === 'run-all') {\n for (const t of missed) {\n const capturedT = t;\n setImmediate(() => this.fireTick(job.id, capturedT));\n }\n }\n }\n\n private scheduleInterval(\n job: Job,\n everySec: number,\n startAt: string | undefined,\n lastRunAt: Date | undefined,\n ): void {\n const intervalMs = everySec * 1000;\n\n // Handle catchup\n if (lastRunAt && job.catchup !== 'skip') {\n const elapsed = Date.now() - lastRunAt.getTime();\n const missedCount = Math.floor(elapsed / intervalMs);\n if (missedCount > 0) {\n if (job.catchup === 'run-once') {\n setImmediate(() => this.fireTick(job.id, new Date()));\n } else if (job.catchup === 'run-all') {\n for (let i = 0; i < missedCount; i++) {\n setImmediate(() => this.fireTick(job.id, new Date()));\n }\n }\n }\n }\n\n // Calculate initial delay\n let delay = intervalMs;\n if (startAt) {\n const startTime = new Date(startAt);\n if (!isNaN(startTime.getTime())) {\n const now = Date.now();\n const startMs = startTime.getTime();\n if (startMs > now) {\n delay = startMs - now;\n } else {\n const elapsed = now - startMs;\n delay = intervalMs - (elapsed % intervalMs);\n }\n }\n }\n\n const timer = safeSetTimeout(() => {\n this.fireTick(job.id, new Date());\n const interval = setInterval(() => this.fireTick(job.id, new Date()), intervalMs);\n this.entries.set(job.id, { stop: () => clearInterval(interval) });\n }, delay);\n\n this.entries.set(job.id, {\n stop: () => timer.clear(),\n });\n }\n\n private scheduleOneShot(job: Job, runAt: string): void {\n const t = new Date(runAt);\n if (isNaN(t.getTime())) return;\n\n const delay = t.getTime() - Date.now();\n if (delay <= 0) {\n if (job.catchup !== 'skip') {\n setImmediate(() => {\n this.fireTick(job.id, t);\n this.entries.delete(job.id);\n });\n }\n return;\n }\n\n const timer = safeSetTimeout(() => {\n this.fireTick(job.id, t);\n this.entries.delete(job.id);\n }, delay);\n\n this.entries.set(job.id, { stop: () => timer.clear() });\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\n/**\n * `setTimeout` clamps delays > 2^31-1 ms (~24.8 days) to 1 ms, causing\n * far-future timers to fire immediately. This helper chains intermediate\n * 2 000 000 000 ms timeouts until the remaining delay is safe.\n */\nconst MAX_SAFE_TIMEOUT_MS = 2_000_000_000;\n\ninterface SafeTimer {\n clear(): void;\n}\n\nfunction safeSetTimeout(cb: () => void, ms: number): SafeTimer {\n if (ms <= MAX_SAFE_TIMEOUT_MS) {\n const t = setTimeout(cb, ms);\n return { clear: () => clearTimeout(t) };\n }\n let inner: SafeTimer | undefined;\n const t = setTimeout(() => {\n inner = safeSetTimeout(cb, ms - MAX_SAFE_TIMEOUT_MS);\n }, MAX_SAFE_TIMEOUT_MS);\n return {\n clear: () => {\n clearTimeout(t);\n inner?.clear();\n },\n };\n}\n\nfunction cronNextN(pattern: string, tz: string | undefined, n: number): string[] {\n try {\n const options: CronOptions = { paused: true };\n if (tz) options.timezone = tz;\n const cron = new Cron(pattern, options);\n const results: string[] = [];\n let ref: Date | undefined;\n for (let i = 0; i < n; i++) {\n const next = cron.nextRun(ref) as Date | null;\n if (!next) break;\n results.push(next.toISOString());\n ref = new Date(next.getTime() + 1);\n }\n cron.stop();\n return results;\n } catch {\n return [];\n }\n}\n\nfunction missedCronFires(\n pattern: string,\n tz: string | undefined,\n from: Date,\n to: Date,\n): Date[] {\n try {\n const options: CronOptions = { paused: true };\n if (tz) options.timezone = tz;\n const cron = new Cron(pattern, options);\n const missed: Date[] = [];\n let ref: Date | undefined = from;\n for (;;) {\n const next = cron.nextRun(ref) as Date | null;\n if (!next || next > to) break;\n missed.push(next);\n ref = new Date(next.getTime() + 1);\n if (missed.length > 1000) break;\n }\n cron.stop();\n return missed;\n } catch {\n return [];\n }\n}\n","import { spawn } from 'node:child_process';\nimport { writeFileSync, unlinkSync, existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { tmpdir, platform } from 'node:os';\nimport { join, isAbsolute } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport type { Job } from '../schemas/job.js';\nimport type { Store, RunStatus } from './store.js';\nimport { CrontickError } from '../errors.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\ninterface RunResult {\n status: RunStatus;\n exitCode?: number;\n error?: string;\n}\n\ntype QueueEntry = () => Promise<void>;\n\n// ── Secret redaction ──────────────────────────────────────────────────────────\n\nconst SECRET_PATTERNS = [\n /(?:AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN)=[^\\s]*/gi,\n /(?:GITHUB_TOKEN|GH_TOKEN)=[^\\s]*/gi,\n /(?:GCLOUD_SERVICE_KEY|GOOGLE_CREDENTIALS)=[^\\s]*/gi,\n /Bearer\\s+[A-Za-z0-9\\-._~+/]+=*/gi,\n /ghp_[A-Za-z0-9]{36}/g,\n /AKIA[0-9A-Z]{16}/g,\n];\n\nfunction redact(text: string): string {\n let out = text;\n for (const pat of SECRET_PATTERNS) {\n out = out.replace(pat, '[REDACTED]');\n }\n return out;\n}\n\n/**\n * Redact secrets from a chunk only when it is valid UTF-8 text.\n * Binary data (NUL bytes or lossy UTF-8 round-trip) is stored as-is.\n */\nfunction safeRedact(chunk: Buffer): Buffer {\n // NUL byte → likely binary, skip redaction\n if (chunk.includes(0)) return chunk;\n const str = chunk.toString('utf8');\n // Lossy round-trip → binary or non-UTF-8, skip redaction\n if (!Buffer.from(str, 'utf8').equals(chunk)) return chunk;\n const cleaned = redact(str);\n return Buffer.from(cleaned, 'utf8');\n}\n\n// ── Env-file loader ───────────────────────────────────────────────────────────\n\n/**\n * Parse a .env-style file (KEY=VALUE, # comments, quoted values).\n * Returns a record of KEY → VALUE.\n */\nexport function parseEnvFile(contents: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const raw of contents.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"'))\n || (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n if (key) result[key] = value;\n }\n return result;\n}\n\n// ── Runner ────────────────────────────────────────────────────────────────────\n\nexport class Runner {\n /** Per-job queues for overlap=queue policy */\n private queues: Map<string, QueueEntry[]> = new Map();\n /** Abort controllers for currently active runs per job */\n private activeAborts: Map<string, AbortController> = new Map();\n /** Active run IDs per job */\n private activeRunIds: Map<string, string> = new Map();\n /** Whether a job's queue is currently being drained */\n private draining: Set<string> = new Set();\n\n /**\n * Execute a job run, honouring overlap + retry + budget policies.\n * The run record must already exist in the store (status=queued).\n */\n async run(job: Job, runId: string, store: Store): Promise<void> {\n const overlap = job.overlap ?? 'skip';\n\n // Budget: maxRunsPerDay\n if (job.budgets?.maxRunsPerDay != null) {\n const todayStart = new Date();\n todayStart.setUTCHours(0, 0, 0, 0);\n const count = store.countRunsSince(job.id, todayStart.getTime(), runId);\n if (count >= job.budgets.maxRunsPerDay) {\n await this.finalizeRun(store, runId, {\n status: 'canceled',\n error: `budget: maxRunsPerDay (${job.budgets.maxRunsPerDay}) exceeded`,\n });\n return;\n }\n }\n\n const isActive = this.activeRunIds.has(job.id);\n\n if (overlap === 'skip' && isActive) {\n await this.finalizeRun(store, runId, {\n status: 'canceled',\n error: 'overlap=skip: another run is already active',\n });\n return;\n }\n\n if (overlap === 'cancel-previous' && isActive) {\n const ctrl = this.activeAborts.get(job.id);\n if (ctrl) ctrl.abort();\n }\n\n if (overlap === 'queue') {\n await this.enqueue(job, runId, store);\n } else {\n await this.execute(job, runId, store);\n }\n }\n\n private enqueue(job: Job, runId: string, store: Store): Promise<void> {\n return new Promise<void>((resolve) => {\n const queue = this.queues.get(job.id) ?? [];\n queue.push(async () => {\n await this.execute(job, runId, store);\n resolve();\n });\n this.queues.set(job.id, queue);\n if (!this.draining.has(job.id)) {\n this.drainQueue(job.id);\n }\n });\n }\n\n private async drainQueue(jobId: string): Promise<void> {\n this.draining.add(jobId);\n const queue = this.queues.get(jobId);\n if (!queue || queue.length === 0) {\n this.draining.delete(jobId);\n return;\n }\n const next = queue.shift()!;\n try {\n await next();\n } catch {\n // errors handled inside execute\n }\n await this.drainQueue(jobId);\n }\n\n private async execute(job: Job, runId: string, store: Store): Promise<void> {\n const maxRetries = job.retry?.max ?? 0;\n const backoffSec = job.retry?.backoffSec ?? 30;\n let lastResult: RunResult = { status: 'failed', error: 'not started' };\n\n store.updateRun(runId, { status: 'running' });\n\n const ctrl = new AbortController();\n this.activeAborts.set(job.id, ctrl);\n this.activeRunIds.set(job.id, runId);\n\n try {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n await sleep(backoffSec * 1000);\n }\n if (ctrl.signal.aborted) {\n lastResult = { status: 'canceled', error: 'canceled before retry' };\n break;\n }\n lastResult = await this.spawn(job, runId, store, ctrl.signal);\n if (lastResult.status === 'success') break;\n if (lastResult.status === 'canceled' || lastResult.status === 'timeout') break;\n }\n } finally {\n // Only delete if these maps still point to THIS run's ctrl/runId.\n // A newer run (cancel-previous) may have already overwritten them.\n if (this.activeAborts.get(job.id) === ctrl) this.activeAborts.delete(job.id);\n if (this.activeRunIds.get(job.id) === runId) this.activeRunIds.delete(job.id);\n }\n\n await this.finalizeRun(store, runId, lastResult);\n }\n\n private async spawn(\n job: Job,\n runId: string,\n store: Store,\n signal: AbortSignal,\n ): Promise<RunResult> {\n const { action } = job;\n let tmpFile: string | undefined;\n\n try {\n let cmd: string;\n let args: string[];\n\n if (action.kind === 'script') {\n // Write script to temp file\n const ext = resolveShellExt(action.shell ?? 'auto');\n const tmpDir = join(tmpdir(), 'crontick');\n mkdirSync(tmpDir, { recursive: true });\n tmpFile = join(tmpDir, `${randomUUID()}${ext}`);\n writeFileSync(tmpFile, action.script, { encoding: 'utf-8', mode: 0o700 });\n\n const resolved = resolveShell(action.shell ?? 'auto');\n if (resolved === 'pwsh') {\n cmd = 'pwsh';\n args = ['-NoProfile', '-NonInteractive', '-File', tmpFile];\n } else if (resolved === 'cmd') {\n cmd = 'cmd';\n args = ['/c', tmpFile];\n } else {\n cmd = 'bash';\n args = [tmpFile];\n }\n } else {\n cmd = action.command;\n args = action.args ?? [];\n }\n\n const spawnOpts: Parameters<typeof spawn>[2] = {\n cwd: action.cwd ?? process.cwd(),\n env: { ...process.env, ...(action.env ?? {}) } as NodeJS.ProcessEnv,\n signal,\n shell: false,\n };\n\n // Merge envFile variables (lower priority than action.env).\n // We intentionally do not log environment snapshots, so secret-shaped\n // env-file values are not emitted to logs.\n if (action.envFile) {\n const envFilePath = isAbsolute(action.envFile)\n ? action.envFile\n : join(action.cwd ?? process.cwd(), action.envFile);\n try {\n const fileContents = readFileSync(envFilePath, 'utf-8');\n const envFileVars = parseEnvFile(fileContents);\n spawnOpts.env = {\n ...process.env,\n ...envFileVars,\n ...(action.env ?? {}),\n } as NodeJS.ProcessEnv;\n } catch (err) {\n throw new CrontickError('ENV_FILE_ERROR', `Failed to load envFile: ${String(err)}`);\n }\n }\n\n if (action.timeoutSec) {\n spawnOpts.timeout = action.timeoutSec * 1000;\n }\n\n const result = await new Promise<RunResult>((resolve) => {\n const child = spawn(cmd, args, spawnOpts);\n const startedAt = Date.now();\n\n child.stdout?.on('data', (chunk: Buffer) => {\n store.appendLog(runId, 'stdout', safeRedact(chunk));\n });\n\n child.stderr?.on('data', (chunk: Buffer) => {\n store.appendLog(runId, 'stderr', safeRedact(chunk));\n });\n\n child.on('close', (code, sig) => {\n const durationMs = Date.now() - startedAt;\n if (sig === 'SIGTERM' || sig === 'SIGKILL') {\n resolve({ status: 'canceled', error: `killed by signal ${sig}` });\n } else if (code === null) {\n resolve({ status: 'failed', error: 'process exited without code' });\n } else {\n resolve({\n status: code === 0 ? 'success' : 'failed',\n exitCode: code,\n });\n }\n void durationMs; // consumed below via store\n });\n\n child.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'ABORT_ERR' || signal.aborted) {\n resolve({ status: 'canceled', error: 'aborted' });\n } else if (err.code === 'ETIMEDOUT') {\n resolve({ status: 'timeout', error: 'timed out' });\n } else {\n resolve({ status: 'failed', error: err.message });\n }\n });\n });\n\n return result;\n } finally {\n if (tmpFile && existsSync(tmpFile)) {\n try {\n unlinkSync(tmpFile);\n } catch {\n // ignore cleanup failure\n }\n }\n }\n }\n\n private async finalizeRun(store: Store, runId: string, result: RunResult): Promise<void> {\n const run = store.getRun(runId);\n const now = Date.now();\n store.updateRun(runId, {\n status: result.status,\n exitCode: result.exitCode,\n error: result.error,\n endedAt: now,\n durationMs: run ? now - run.startedAt : undefined,\n });\n }\n\n /** Cancel any active run for a job. */\n cancelJob(jobId: string): boolean {\n const ctrl = this.activeAborts.get(jobId);\n if (ctrl) {\n ctrl.abort();\n return true;\n }\n return false;\n }\n\n /** Cancel an active run by run ID. */\n cancelRun(runId: string): boolean {\n for (const [jobId, rId] of this.activeRunIds.entries()) {\n if (rId === runId) {\n return this.cancelJob(jobId);\n }\n }\n return false;\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction resolveShell(shell: string): 'bash' | 'pwsh' | 'cmd' {\n if (shell === 'auto') {\n return platform() === 'win32' ? 'pwsh' : 'bash';\n }\n if (shell === 'pwsh') return 'pwsh';\n if (shell === 'cmd') return 'cmd';\n return 'bash';\n}\n\nfunction resolveShellExt(shell: string): string {\n const resolved = resolveShell(shell);\n if (resolved === 'pwsh') return '.ps1';\n if (resolved === 'cmd') return '.bat';\n return '.sh';\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport { CrontickError };\n","import http from 'node:http';\nimport { createReadStream, existsSync as fsExistsSync, statSync } from 'node:fs';\nimport {\n extname,\n resolve as pathResolve,\n join as pathJoin,\n normalize,\n sep as pathSep,\n} from 'node:path';\nimport { URL } from 'node:url';\nimport { fileURLToPath } from 'node:url';\nimport type { Store } from './store.js';\nimport type { Scheduler } from './scheduler.js';\nimport type { Runner } from './runner.js';\nimport { JobSchema } from '../schemas/job.js';\nimport { CrontickError } from '../errors.js';\nimport { VERSION } from '../version.js';\nimport { createAutostart, NotImplementedInV1Error } from '../autostart/index.js';\nimport type { AutostartBackend } from '../autostart/index.js';\n\n// ── Constants ─────────────────────────────────────────────────────────────────\n\nconst LOOPBACK = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);\nconst SSE_POLL_MS = 200;\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n};\n\n// ── Context shared with handlers ──────────────────────────────────────────────\n\nexport interface ApiContext {\n store: Store;\n scheduler: Scheduler;\n runner: Runner;\n startedAt: Date;\n port: number;\n reload: () => Promise<void>;\n}\n\n// ── Server factory ────────────────────────────────────────────────────────────\n\nexport function createApiServer(ctx: ApiContext): http.Server {\n const server = http.createServer((req, res) => {\n // Enforce localhost-only\n const remote = req.socket.remoteAddress ?? '';\n if (!LOOPBACK.has(remote)) {\n return sendError(res, 403, 'FORBIDDEN', 'Only localhost connections are allowed');\n }\n void handleRequest(req, res, ctx);\n });\n return server;\n}\n\n// ── Router ────────────────────────────────────────────────────────────────────\n\nasync function handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ctx: ApiContext,\n): Promise<void> {\n const method = req.method ?? 'GET';\n const rawUrl = req.url ?? '/';\n const baseUrl = `http://127.0.0.1`;\n const url = new URL(rawUrl, baseUrl);\n const path = url.pathname;\n\n try {\n // ── Health ───────────────────────────────────────────────────────────────\n if (method === 'GET' && path === '/health') {\n const jobs = ctx.store.listJobs();\n const since24h = Date.now() - 24 * 60 * 60 * 1000;\n const runs24h = ctx.store.listRuns({ since: since24h });\n return sendJson(res, 200, {\n ok: true,\n version: VERSION,\n uptimeSec: Math.floor((Date.now() - ctx.startedAt.getTime()) / 1000),\n pid: process.pid,\n port: ctx.port,\n jobs: {\n total: jobs.length,\n enabled: jobs.filter((j) => j.enabled).length,\n },\n runs: {\n last24h: runs24h.length,\n failures24h: runs24h.filter((r) => r.status === 'failed').length,\n },\n node: process.versions.node,\n platform: process.platform,\n });\n }\n\n // ── Jobs ─────────────────────────────────────────────────────────────────\n if (method === 'GET' && path === '/api/jobs') {\n return sendJson(res, 200, ctx.store.listJobs());\n }\n\n if (method === 'POST' && path === '/api/jobs') {\n const body = await readBody(req);\n const parsed = JobSchema.safeParse(body);\n if (!parsed.success) {\n return sendError(res, 400, 'VALIDATION_ERROR', 'Invalid job', parsed.error.format());\n }\n ctx.store.upsertJob(parsed.data);\n ctx.scheduler.schedule(parsed.data, ctx.store);\n return sendJson(res, 201, parsed.data);\n }\n\n // /api/jobs/:id/*\n const jobMatch = path.match(/^\\/api\\/jobs\\/([^/]+)(\\/.*)?$/);\n if (jobMatch) {\n const id = decodeURIComponent(jobMatch[1]);\n const sub = jobMatch[2] ?? '';\n\n if (method === 'GET' && sub === '') {\n const job = ctx.store.getJob(id);\n if (!job) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n return sendJson(res, 200, job);\n }\n\n if (method === 'PUT' && sub === '') {\n const existing = ctx.store.getJob(id);\n if (!existing) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n const body = await readBody(req);\n const parsed = JobSchema.safeParse({ ...existing, ...body, id });\n if (!parsed.success) {\n return sendError(res, 400, 'VALIDATION_ERROR', 'Invalid job', parsed.error.format());\n }\n ctx.store.upsertJob(parsed.data);\n ctx.scheduler.schedule(parsed.data, ctx.store);\n return sendJson(res, 200, parsed.data);\n }\n\n if (method === 'DELETE' && sub === '') {\n const deleted = ctx.store.deleteJob(id);\n if (!deleted) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n ctx.scheduler.unschedule(id);\n return sendJson(res, 200, { ok: true });\n }\n\n if (method === 'POST' && sub === '/enable') {\n const job = ctx.store.getJob(id);\n if (!job) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n const updated = { ...job, enabled: true };\n ctx.store.upsertJob(updated);\n ctx.scheduler.schedule(updated, ctx.store);\n return sendJson(res, 200, updated);\n }\n\n if (method === 'POST' && sub === '/disable') {\n const job = ctx.store.getJob(id);\n if (!job) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n const updated = { ...job, enabled: false };\n ctx.store.upsertJob(updated);\n ctx.scheduler.unschedule(id);\n return sendJson(res, 200, updated);\n }\n\n if (method === 'POST' && sub === '/run') {\n const job = ctx.store.getJob(id);\n if (!job) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n const run = ctx.store.insertRun(id);\n // fire async, don't await\n ctx.runner.run(job, run.id, ctx.store).catch(() => {});\n return sendJson(res, 202, { runId: run.id });\n }\n }\n\n // ── Runs ─────────────────────────────────────────────────────────────────\n if (method === 'GET' && path === '/api/runs') {\n const jobId = url.searchParams.get('jobId') ?? undefined;\n const limit = url.searchParams.has('limit')\n ? parseInt(url.searchParams.get('limit')!, 10)\n : undefined;\n const since = url.searchParams.has('since')\n ? parseInt(url.searchParams.get('since')!, 10)\n : undefined;\n return sendJson(res, 200, ctx.store.listRuns({ jobId, limit, since }));\n }\n\n // /api/runs/:id/*\n const runMatch = path.match(/^\\/api\\/runs\\/([^/]+)(\\/.*)?$/);\n if (runMatch) {\n const id = decodeURIComponent(runMatch[1]);\n const sub = runMatch[2] ?? '';\n\n if (method === 'GET' && sub === '') {\n const run = ctx.store.getRun(id);\n if (!run) return sendError(res, 404, 'NOT_FOUND', `Run ${id} not found`);\n return sendJson(res, 200, run);\n }\n\n if (method === 'POST' && sub === '/cancel') {\n const run = ctx.store.getRun(id);\n if (!run) return sendError(res, 404, 'NOT_FOUND', `Run ${id} not found`);\n const canceled = ctx.runner.cancelRun(id);\n return sendJson(res, 200, { ok: true, canceled });\n }\n\n if (method === 'GET' && sub === '/logs') {\n const run = ctx.store.getRun(id);\n if (!run) return sendError(res, 404, 'NOT_FOUND', `Run ${id} not found`);\n const logs = ctx.store.getLogs(id);\n return sendJson(res, 200, logs.map((l) => ({\n runId: l.runId,\n stream: l.stream,\n ts: l.ts,\n data: l.chunk.toString('utf-8'),\n })));\n }\n\n if (method === 'GET' && sub === '/logs/stream') {\n const run = ctx.store.getRun(id);\n if (!run) return sendError(res, 404, 'NOT_FOUND', `Run ${id} not found`);\n return streamLogs(req, res, id, ctx);\n }\n }\n\n // ── Schedules ─────────────────────────────────────────────────────────────\n if (method === 'POST' && path === '/api/schedules/validate') {\n const body = await readBody(req);\n const { ScheduleSchema } = await import('../schemas/job.js');\n const parsed = ScheduleSchema.safeParse(body);\n if (!parsed.success) {\n return sendJson(res, 200, { ok: false, error: JSON.stringify(parsed.error.format()) });\n }\n const result = ctx.scheduler.validateSchedule(parsed.data);\n return sendJson(res, 200, result);\n }\n\n if (method === 'POST' && path === '/api/schedules/preview') {\n const body = await readBody(req);\n const { ScheduleSchema } = await import('../schemas/job.js');\n const scheduleResult = ScheduleSchema.safeParse(body?.schedule ?? body);\n if (!scheduleResult.success) {\n return sendError(res, 400, 'VALIDATION_ERROR', 'Invalid schedule');\n }\n const n = typeof body?.n === 'number' ? body.n : 5;\n const tz = body?.tz as string | undefined;\n const next = ctx.scheduler.previewNext(scheduleResult.data, { n, tz });\n return sendJson(res, 200, { next });\n }\n\n // ── Stats ─────────────────────────────────────────────────────────────────\n if (method === 'GET' && path === '/api/stats/summary') {\n const jobs = ctx.store.listJobs();\n const runs = ctx.store.listRuns({ limit: 1000 });\n const failed = runs.filter((r) => r.status === 'failed').length;\n const succeeded = runs.filter((r) => r.status === 'success').length;\n return sendJson(res, 200, {\n totalJobs: jobs.length,\n enabledJobs: jobs.filter((j) => j.enabled).length,\n totalRuns: runs.length,\n succeeded,\n failed,\n avgDurationMs:\n runs.length > 0\n ? Math.round(runs.reduce((s, r) => s + (r.durationMs ?? 0), 0) / runs.length)\n : null,\n });\n }\n\n const statsJobMatch = path.match(/^\\/api\\/stats\\/jobs\\/([^/]+)$/);\n if (method === 'GET' && statsJobMatch) {\n const id = decodeURIComponent(statsJobMatch[1]);\n const job = ctx.store.getJob(id);\n if (!job) return sendError(res, 404, 'NOT_FOUND', `Job ${id} not found`);\n const runs = ctx.store.listRuns({ jobId: id, limit: 100 });\n return sendJson(res, 200, {\n jobId: id,\n totalRuns: runs.length,\n succeeded: runs.filter((r) => r.status === 'success').length,\n failed: runs.filter((r) => r.status === 'failed').length,\n lastStatus: runs[0]?.status ?? null,\n lastRunAt: runs[0]?.startedAt ?? null,\n });\n }\n\n // ── Daemon ────────────────────────────────────────────────────────────────\n if (method === 'GET' && path === '/api/daemon/status') {\n return sendJson(res, 200, {\n pid: process.pid,\n version: VERSION,\n uptimeSec: Math.floor((Date.now() - ctx.startedAt.getTime()) / 1000),\n jobs: ctx.store.listJobs().length,\n });\n }\n\n if (method === 'POST' && path === '/api/daemon/reload') {\n await ctx.reload();\n return sendJson(res, 200, { ok: true });\n }\n\n // ── Export / Import ───────────────────────────────────────────────────────\n if (method === 'GET' && path === '/api/export') {\n return sendJson(res, 200, { jobs: ctx.store.listJobs() });\n }\n\n if (method === 'POST' && path === '/api/import') {\n const body = await readBody(req);\n const jobs = Array.isArray(body?.jobs) ? body.jobs : [];\n const results: Array<{ id: string; ok: boolean; error?: string }> = [];\n for (const raw of jobs) {\n const parsed = JobSchema.safeParse(raw);\n if (parsed.success) {\n ctx.store.upsertJob(parsed.data);\n ctx.scheduler.schedule(parsed.data, ctx.store);\n results.push({ id: parsed.data.id, ok: true });\n } else {\n results.push({ id: String(raw?.id ?? '?'), ok: false, error: 'validation failed' });\n }\n }\n return sendJson(res, 200, { imported: results.filter((r) => r.ok).length, results });\n }\n\n // ── Autostart ─────────────────────────────────────────────────────────────\n\n if (method === 'GET' && path === '/api/autostart/status') {\n const backend = (url.searchParams.get('backend') ?? undefined) as AutostartBackend | undefined;\n const autostart = createAutostart({ backend });\n const result = await autostart.status();\n return sendJson(res, 200, result);\n }\n\n if (method === 'POST' && path === '/api/autostart/install') {\n const body = await readBody(req);\n const backend = (body?.['backend'] as string | undefined) as AutostartBackend | undefined;\n const autostart = createAutostart({ backend });\n const result = await autostart.install();\n return sendJson(res, 200, result);\n }\n\n if (method === 'POST' && path === '/api/autostart/remove') {\n const body = await readBody(req);\n const backend = (body?.['backend'] as string | undefined) as AutostartBackend | undefined;\n const autostart = createAutostart({ backend });\n const result = await autostart.remove();\n return sendJson(res, 200, result);\n }\n\n // ── Dashboard ─────────────────────────────────────────────────────────────\n if (method === 'GET' && (path === '/' || path === '/dashboard' || path.startsWith('/dashboard/'))) {\n return serveDashboard(res, path);\n }\n\n // ── 404 ───────────────────────────────────────────────────────────────────\n return sendError(res, 404, 'NOT_FOUND', `${method} ${path} not found`);\n } catch (err) {\n if (err instanceof NotImplementedInV1Error) {\n return sendError(res, 501, 'NOT_IMPLEMENTED_V1', err.message);\n }\n if (err instanceof CrontickError) {\n return sendError(res, 400, err.code, err.message, err.details);\n }\n const msg = err instanceof Error ? err.message : String(err);\n return sendError(res, 500, 'INTERNAL_ERROR', msg);\n }\n}\n\n// ── SSE log streaming ─────────────────────────────────────────────────────────\n\nfunction streamLogs(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n runId: string,\n ctx: ApiContext,\n): void {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n let lastTs = 0;\n\n // Send existing logs first\n const existing = ctx.store.getLogs(runId);\n for (const log of existing) {\n sseEvent(res, { stream: log.stream, ts: log.ts, data: log.chunk.toString('utf-8') });\n if (log.ts > lastTs) lastTs = log.ts;\n }\n\n // Poll for new logs until run is terminal\n const poll = setInterval(() => {\n const run = ctx.store.getRun(runId);\n const newLogs = ctx.store.tailLogs(runId, lastTs);\n for (const log of newLogs) {\n sseEvent(res, { stream: log.stream, ts: log.ts, data: log.chunk.toString('utf-8') });\n if (log.ts > lastTs) lastTs = log.ts;\n }\n\n const terminal = new Set(['success', 'failed', 'canceled', 'timeout']);\n if (!run || terminal.has(run.status)) {\n sseEvent(res, { done: true, status: run?.status });\n clearInterval(poll);\n res.end();\n }\n }, SSE_POLL_MS);\n\n req.on('close', () => {\n clearInterval(poll);\n });\n}\n\nfunction dashboardDir(): string {\n const moduleDir = pathResolve(fileURLToPath(import.meta.url), '..');\n return pathResolve(moduleDir, '../dashboard');\n}\n\nfunction serveDashboard(\n res: http.ServerResponse,\n reqPath: string,\n): void {\n const dashDir = dashboardDir();\n const indexFile = pathJoin(dashDir, 'index.html');\n\n let filePath: string;\n if (reqPath === '/' || reqPath === '/dashboard' || reqPath === '/dashboard/') {\n filePath = indexFile;\n } else {\n const sub = reqPath.startsWith('/dashboard/') ? reqPath.slice('/dashboard'.length) : reqPath;\n const normalizedSub = normalize(sub).replace(/^[/\\\\]+/, '');\n filePath = pathResolve(dashDir, normalizedSub);\n }\n\n if (filePath !== indexFile && !filePath.startsWith(`${dashDir}${pathSep}`)) {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Bad Request');\n return;\n }\n\n if (!fsExistsSync(filePath)) {\n filePath = indexFile;\n }\n\n if (!fsExistsSync(filePath)) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n\n const stat = statSync(filePath);\n if (!stat.isFile()) {\n res.writeHead(403, { 'Content-Type': 'text/plain' });\n res.end('Forbidden');\n return;\n }\n\n const ext = extname(filePath).toLowerCase();\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': stat.size,\n 'Cache-Control': 'no-cache',\n });\n createReadStream(filePath).pipe(res);\n}\n\nfunction sseEvent(res: http.ServerResponse, data: unknown): void {\n res.write(`data: ${JSON.stringify(data)}\\n\\n`);\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nasync function readBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => {\n const raw = Buffer.concat(chunks).toString('utf-8');\n if (!raw) {\n resolve({});\n return;\n }\n try {\n resolve(JSON.parse(raw) as Record<string, unknown>);\n } catch {\n resolve({});\n }\n });\n req.on('error', reject);\n });\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(json);\n}\n\nfunction sendError(\n res: http.ServerResponse,\n status: number,\n code: string,\n message: string,\n details?: unknown,\n): void {\n sendJson(res, status, { error: { code, message, details } });\n}\n"],"mappings":"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","names":["spawn","readFileSync","writeFileSync","unlinkSync","existsSync","join","result","t","writeFileSync","unlinkSync","existsSync","readFileSync","join","randomUUID","join","randomUUID","writeFileSync","readFileSync","existsSync","unlinkSync","spawn","existsSync","readFileSync","unlinkSync","log","checkSingleInstance","cleanup","join","writeFileSync"]}
File without changes
@@ -0,0 +1,192 @@
1
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
2
+
3
+ :root {
4
+ --bg: #0d1117;
5
+ --surface: #161b22;
6
+ --border: #30363d;
7
+ --text: #c9d1d9;
8
+ --accent: #58a6ff;
9
+ --success: #3fb950;
10
+ --error: #f85149;
11
+ --warn: #d29922;
12
+ --muted: #8b949e;
13
+ }
14
+
15
+ body {
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
17
+ background: var(--bg);
18
+ color: var(--text);
19
+ font-size: 14px;
20
+ }
21
+
22
+ header {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 12px;
26
+ padding: 12px 20px;
27
+ background: var(--surface);
28
+ border-bottom: 1px solid var(--border);
29
+ }
30
+
31
+ header h1 { font-size: 18px; color: var(--accent); }
32
+ #version-info { color: var(--muted); font-size: 12px; }
33
+
34
+ nav {
35
+ display: flex;
36
+ gap: 4px;
37
+ padding: 8px 20px;
38
+ background: var(--surface);
39
+ border-bottom: 1px solid var(--border);
40
+ }
41
+
42
+ .nav-btn {
43
+ background: none;
44
+ border: 1px solid var(--border);
45
+ color: var(--muted);
46
+ padding: 4px 12px;
47
+ border-radius: 6px;
48
+ cursor: pointer;
49
+ }
50
+
51
+ .nav-btn.active { color: var(--accent); border-color: var(--accent); }
52
+
53
+ main { padding: 16px 20px; }
54
+
55
+ .page { display: none; }
56
+ .page.active { display: block; }
57
+
58
+ .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
59
+ .toolbar label { display: flex; align-items: center; gap: 6px; color: var(--muted); }
60
+ .toolbar input, .toolbar select {
61
+ background: var(--surface);
62
+ border: 1px solid var(--border);
63
+ color: var(--text);
64
+ padding: 4px 8px;
65
+ border-radius: 4px;
66
+ }
67
+
68
+ table { width: 100%; border-collapse: collapse; }
69
+ th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); font-size: 13px; }
70
+ th { color: var(--muted); font-weight: 600; }
71
+ tr:hover td { background: var(--surface); }
72
+
73
+ .btn {
74
+ background: var(--surface);
75
+ border: 1px solid var(--border);
76
+ color: var(--text);
77
+ padding: 5px 12px;
78
+ border-radius: 6px;
79
+ cursor: pointer;
80
+ font-size: 12px;
81
+ }
82
+
83
+ .btn:hover { border-color: var(--accent); color: var(--accent); }
84
+ .btn-primary { background: var(--accent); color: #0d1117; border-color: var(--accent); }
85
+ .btn-primary:hover { opacity: 0.85; color: #0d1117; }
86
+ .btn-danger { border-color: var(--error); color: var(--error); }
87
+
88
+ .badge { padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
89
+ .badge-ok { background: rgba(63,185,80,.15); color: var(--success); }
90
+ .badge-error { background: rgba(248,81,73,.15); color: var(--error); }
91
+ .badge-unknown { background: rgba(139,148,158,.1); color: var(--muted); }
92
+
93
+ .status-success { color: var(--success); }
94
+ .status-failed { color: var(--error); }
95
+ .status-running { color: var(--accent); }
96
+ .status-canceled, .status-timeout { color: var(--warn); }
97
+ .status-queued { color: var(--muted); }
98
+
99
+ .drawer {
100
+ position: fixed;
101
+ top: 0;
102
+ right: -480px;
103
+ width: 480px;
104
+ height: 100vh;
105
+ background: var(--surface);
106
+ border-left: 1px solid var(--border);
107
+ transition: right .25s ease;
108
+ overflow-y: auto;
109
+ z-index: 100;
110
+ display: flex;
111
+ flex-direction: column;
112
+ }
113
+
114
+ .drawer.open { right: 0; }
115
+
116
+ .drawer-header {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ padding: 12px 16px;
121
+ border-bottom: 1px solid var(--border);
122
+ flex-shrink: 0;
123
+ }
124
+
125
+ .drawer-content { padding: 16px; flex: 1; overflow-y: auto; }
126
+
127
+ .overlay {
128
+ display: none;
129
+ position: fixed;
130
+ inset: 0;
131
+ background: rgba(0,0,0,.4);
132
+ z-index: 99;
133
+ }
134
+
135
+ .overlay.active { display: block; }
136
+
137
+ pre {
138
+ background: var(--bg);
139
+ padding: 12px;
140
+ border-radius: 6px;
141
+ font-size: 12px;
142
+ overflow-x: auto;
143
+ white-space: pre-wrap;
144
+ max-height: 300px;
145
+ overflow-y: auto;
146
+ }
147
+
148
+ .modal {
149
+ display: none;
150
+ position: fixed;
151
+ inset: 0;
152
+ z-index: 200;
153
+ align-items: center;
154
+ justify-content: center;
155
+ }
156
+
157
+ .modal.open { display: flex; }
158
+
159
+ .modal-box {
160
+ background: var(--surface);
161
+ border: 1px solid var(--border);
162
+ border-radius: 8px;
163
+ padding: 24px;
164
+ width: 520px;
165
+ max-width: 95vw;
166
+ max-height: 90vh;
167
+ overflow-y: auto;
168
+ }
169
+
170
+ .modal-box h2 { margin-bottom: 16px; }
171
+
172
+ .modal-box label {
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 4px;
176
+ margin-bottom: 12px;
177
+ color: var(--muted);
178
+ }
179
+
180
+ .modal-box input, .modal-box select, .modal-box textarea {
181
+ background: var(--bg);
182
+ border: 1px solid var(--border);
183
+ color: var(--text);
184
+ padding: 6px 10px;
185
+ border-radius: 4px;
186
+ font-family: inherit;
187
+ font-size: 13px;
188
+ }
189
+
190
+ .modal-box textarea { font-family: monospace; resize: vertical; }
191
+ .form-actions { display: flex; gap: 8px; margin-top: 8px; }
192
+ .error { color: var(--error); font-size: 12px; margin-top: 8px; }