@probelabs/visor 0.1.147 → 0.1.148-ee
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontends/github-frontend.d.ts +2 -1
- package/dist/frontends/github-frontend.d.ts.map +1 -1
- package/dist/index.js +2444 -134
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/scheduler/schedule-tool.d.ts.map +1 -1
- package/dist/scheduler/scheduler.d.ts +5 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/sdk/{check-provider-registry-CTZA3EVE.mjs → check-provider-registry-AMYY2ZJY.mjs} +5 -6
- package/dist/sdk/{check-provider-registry-SCPM6DIT.mjs → check-provider-registry-DVQDGTOE.mjs} +5 -6
- package/dist/sdk/{chunk-4F5UVWAN.mjs → chunk-62TNF5PJ.mjs} +2 -2
- package/dist/sdk/{chunk-4F5UVWAN.mjs.map → chunk-62TNF5PJ.mjs.map} +1 -1
- package/dist/sdk/{chunk-H23T7J6Y.mjs → chunk-75Q63UNX.mjs} +2743 -277
- package/dist/sdk/chunk-75Q63UNX.mjs.map +1 -0
- package/dist/sdk/{chunk-JKWLGLDR.mjs → chunk-CISJ6DJW.mjs} +3 -3
- package/dist/sdk/{chunk-EWGX7LI7.mjs → chunk-H4AYMOAT.mjs} +2742 -276
- package/dist/sdk/chunk-H4AYMOAT.mjs.map +1 -0
- package/dist/sdk/{chunk-2NFKN6CY.mjs → chunk-RJLJUTSU.mjs} +2 -2
- package/dist/sdk/{failure-condition-evaluator-FHNZL2US.mjs → failure-condition-evaluator-IVCTD4BZ.mjs} +3 -3
- package/dist/sdk/{github-frontend-V3WUHL6E.mjs → github-frontend-DFT5G32K.mjs} +16 -4
- package/dist/sdk/github-frontend-DFT5G32K.mjs.map +1 -0
- package/dist/sdk/{host-GVR4UGZ3.mjs → host-H7IX4GBK.mjs} +2 -2
- package/dist/sdk/{host-UQUQIYFG.mjs → host-NZXGBBJI.mjs} +2 -2
- package/dist/sdk/knex-store-HPXJILBL.mjs +411 -0
- package/dist/sdk/knex-store-HPXJILBL.mjs.map +1 -0
- package/dist/sdk/loader-YSRMVXC3.mjs +89 -0
- package/dist/sdk/loader-YSRMVXC3.mjs.map +1 -0
- package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs +655 -0
- package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs.map +1 -0
- package/dist/sdk/{routing-CZ36LVVS.mjs → routing-LU5PAREW.mjs} +4 -4
- package/dist/sdk/{check-provider-registry-CDL5AJSI.mjs → schedule-tool-4JMWZCCK.mjs} +15 -10
- package/dist/sdk/{workflow-check-provider-3K7732MW.mjs → schedule-tool-CONR4VW3.mjs} +15 -10
- package/dist/sdk/{schedule-tool-handler-KFYNV7HL.mjs → schedule-tool-handler-AXMR7NBI.mjs} +5 -6
- package/dist/sdk/{schedule-tool-handler-QUMAF2DJ.mjs → schedule-tool-handler-YUC6CAXX.mjs} +5 -6
- package/dist/sdk/sdk.js +2831 -371
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +4 -5
- package/dist/sdk/sdk.mjs.map +1 -1
- package/dist/sdk/{trace-helpers-W7TF5ZKF.mjs → trace-helpers-6ROJR7N3.mjs} +2 -2
- package/dist/sdk/validator-XTZJZZJH.mjs +134 -0
- package/dist/sdk/validator-XTZJZZJH.mjs.map +1 -0
- package/dist/sdk/{workflow-check-provider-5453TW65.mjs → workflow-check-provider-DYSO3PML.mjs} +5 -6
- package/dist/sdk/{workflow-check-provider-HMABCGB5.mjs → workflow-check-provider-MMB7L3YG.mjs} +5 -6
- package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
- package/dist/utils/tool-resolver.d.ts.map +1 -1
- package/dist/utils/workspace-manager.d.ts +31 -8
- package/dist/utils/workspace-manager.d.ts.map +1 -1
- package/dist/utils/worktree-manager.d.ts +6 -0
- package/dist/utils/worktree-manager.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/output/traces/run-2026-02-27T11-27-22-261Z.ndjson +0 -138
- package/dist/output/traces/run-2026-02-27T11-28-08-546Z.ndjson +0 -1442
- package/dist/sdk/chunk-EWGX7LI7.mjs.map +0 -1
- package/dist/sdk/chunk-FBJ7MC7R.mjs +0 -1502
- package/dist/sdk/chunk-H23T7J6Y.mjs.map +0 -1
- package/dist/sdk/chunk-JKWLGLDR.mjs.map +0 -1
- package/dist/sdk/chunk-R77LN3OE.mjs +0 -40693
- package/dist/sdk/chunk-R77LN3OE.mjs.map +0 -1
- package/dist/sdk/chunk-V2QW6ECX.mjs +0 -739
- package/dist/sdk/chunk-V2QW6ECX.mjs.map +0 -1
- package/dist/sdk/chunk-XKCER23W.mjs +0 -1490
- package/dist/sdk/chunk-XKCER23W.mjs.map +0 -1
- package/dist/sdk/chunk-YQZW3D2V.mjs +0 -443
- package/dist/sdk/chunk-YQZW3D2V.mjs.map +0 -1
- package/dist/sdk/failure-condition-evaluator-2B5WY7QN.mjs +0 -17
- package/dist/sdk/github-frontend-47EU2HBY.mjs +0 -1356
- package/dist/sdk/github-frontend-47EU2HBY.mjs.map +0 -1
- package/dist/sdk/github-frontend-V3WUHL6E.mjs.map +0 -1
- package/dist/sdk/routing-THIWDEYY.mjs +0 -25
- package/dist/sdk/schedule-tool-2COUUTF7.mjs +0 -18
- package/dist/sdk/schedule-tool-handler-GEH62OUM.mjs +0 -40
- package/dist/sdk/trace-helpers-EHDZ42HH.mjs +0 -25
- package/dist/sdk/trace-helpers-EHDZ42HH.mjs.map +0 -1
- package/dist/sdk/trace-helpers-W7TF5ZKF.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-3K7732MW.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-5453TW65.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-HMABCGB5.mjs.map +0 -1
- package/dist/traces/run-2026-02-27T11-27-22-261Z.ndjson +0 -138
- package/dist/traces/run-2026-02-27T11-28-08-546Z.ndjson +0 -1442
- /package/dist/sdk/{check-provider-registry-CDL5AJSI.mjs.map → check-provider-registry-AMYY2ZJY.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-CTZA3EVE.mjs.map → check-provider-registry-DVQDGTOE.mjs.map} +0 -0
- /package/dist/sdk/{chunk-FBJ7MC7R.mjs.map → chunk-CISJ6DJW.mjs.map} +0 -0
- /package/dist/sdk/{chunk-2NFKN6CY.mjs.map → chunk-RJLJUTSU.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-SCPM6DIT.mjs.map → failure-condition-evaluator-IVCTD4BZ.mjs.map} +0 -0
- /package/dist/sdk/{host-GVR4UGZ3.mjs.map → host-H7IX4GBK.mjs.map} +0 -0
- /package/dist/sdk/{host-UQUQIYFG.mjs.map → host-NZXGBBJI.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-2B5WY7QN.mjs.map → routing-LU5PAREW.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-FHNZL2US.mjs.map → schedule-tool-4JMWZCCK.mjs.map} +0 -0
- /package/dist/sdk/{routing-CZ36LVVS.mjs.map → schedule-tool-CONR4VW3.mjs.map} +0 -0
- /package/dist/sdk/{routing-THIWDEYY.mjs.map → schedule-tool-handler-AXMR7NBI.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-2COUUTF7.mjs.map → schedule-tool-handler-YUC6CAXX.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-GEH62OUM.mjs.map → trace-helpers-6ROJR7N3.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-KFYNV7HL.mjs.map → workflow-check-provider-DYSO3PML.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-QUMAF2DJ.mjs.map → workflow-check-provider-MMB7L3YG.mjs.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/scheduler/store/sqlite-store.ts","../../src/scheduler/store/index.ts","../../src/scheduler/store/json-migrator.ts","../../src/scheduler/schedule-store.ts","../../src/scheduler/schedule-parser.ts","../../src/scheduler/schedule-tool.ts"],"sourcesContent":["/**\n * SQLite-backed schedule store (OSS default)\n *\n * Uses better-sqlite3 loaded via createRequire to avoid ncc bundling the native addon.\n * Schema migration is inline (no external migration files — ncc-safe).\n * WAL mode is enabled for better concurrent read performance.\n *\n * HA locking is in-memory (Map-based) since SQLite is single-node only.\n */\nimport path from 'path';\nimport fs from 'fs';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../../logger';\nimport type { Schedule, ScheduleLimits } from '../schedule-store';\nimport type { ScheduleStoreBackend, ScheduleStoreStats } from './types';\n\n// Type declarations for better-sqlite3 (avoid importing the module at compile time)\ninterface BetterSqliteDatabase {\n pragma(source: string): unknown;\n exec(source: string): void;\n prepare(source: string): BetterSqliteStatement;\n close(): void;\n}\n\ninterface BetterSqliteStatement {\n run(...params: unknown[]): { changes: number };\n get(...params: unknown[]): unknown;\n all(...params: unknown[]): unknown[];\n}\n\n/**\n * Database row shape (snake_case)\n */\ninterface ScheduleRow {\n id: string;\n creator_id: string;\n creator_context: string | null;\n creator_name: string | null;\n timezone: string;\n schedule_expr: string;\n run_at: number | null;\n is_recurring: number; // SQLite boolean: 0/1\n original_expression: string;\n workflow: string | null;\n workflow_inputs: string | null; // JSON\n output_context: string | null; // JSON\n status: string;\n created_at: number;\n last_run_at: number | null;\n next_run_at: number | null;\n run_count: number;\n failure_count: number;\n last_error: string | null;\n previous_response: string | null;\n}\n\n/**\n * Convert a Schedule object to a database row\n */\nfunction toDbRow(schedule: Schedule): ScheduleRow {\n return {\n id: schedule.id,\n creator_id: schedule.creatorId,\n creator_context: schedule.creatorContext ?? null,\n creator_name: schedule.creatorName ?? null,\n timezone: schedule.timezone,\n schedule_expr: schedule.schedule,\n run_at: schedule.runAt ?? null,\n is_recurring: schedule.isRecurring ? 1 : 0,\n original_expression: schedule.originalExpression,\n workflow: schedule.workflow ?? null,\n workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,\n output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,\n status: schedule.status,\n created_at: schedule.createdAt,\n last_run_at: schedule.lastRunAt ?? null,\n next_run_at: schedule.nextRunAt ?? null,\n run_count: schedule.runCount,\n failure_count: schedule.failureCount,\n last_error: schedule.lastError ?? null,\n previous_response: schedule.previousResponse ?? null,\n };\n}\n\nfunction safeJsonParse<T = unknown>(value: string | null): T | undefined {\n if (!value) return undefined;\n try {\n return JSON.parse(value) as T;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Convert a database row to a Schedule object\n */\nfunction fromDbRow(row: ScheduleRow): Schedule {\n return {\n id: row.id,\n creatorId: row.creator_id,\n creatorContext: row.creator_context ?? undefined,\n creatorName: row.creator_name ?? undefined,\n timezone: row.timezone,\n schedule: row.schedule_expr,\n runAt: row.run_at ?? undefined,\n isRecurring: row.is_recurring === 1,\n originalExpression: row.original_expression,\n workflow: row.workflow ?? undefined,\n workflowInputs: safeJsonParse(row.workflow_inputs),\n outputContext: safeJsonParse(row.output_context),\n status: row.status as Schedule['status'],\n createdAt: row.created_at,\n lastRunAt: row.last_run_at ?? undefined,\n nextRunAt: row.next_run_at ?? undefined,\n runCount: row.run_count,\n failureCount: row.failure_count,\n lastError: row.last_error ?? undefined,\n previousResponse: row.previous_response ?? undefined,\n };\n}\n\n/**\n * SQLite implementation of ScheduleStoreBackend\n */\nexport class SqliteStoreBackend implements ScheduleStoreBackend {\n private db: BetterSqliteDatabase | null = null;\n private dbPath: string;\n\n // In-memory locks (single-node only; SQLite doesn't support distributed locking)\n private locks = new Map<string, { nodeId: string; token: string; expiresAt: number }>();\n\n constructor(filename?: string) {\n this.dbPath = filename || '.visor/schedules.db';\n }\n\n async initialize(): Promise<void> {\n // Resolve and ensure directory exists\n const resolvedPath = path.resolve(process.cwd(), this.dbPath);\n const dir = path.dirname(resolvedPath);\n fs.mkdirSync(dir, { recursive: true });\n\n // Load better-sqlite3 via createRequire (ncc-safe)\n const { createRequire } = require('module') as typeof import('module');\n const runtimeRequire = createRequire(__filename);\n let Database: new (filename: string) => BetterSqliteDatabase;\n try {\n Database = runtimeRequire('better-sqlite3');\n } catch (err: unknown) {\n const code = (err as { code?: string })?.code;\n if (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND') {\n throw new Error(\n 'better-sqlite3 is required for SQLite schedule storage. ' +\n 'Install it with: npm install better-sqlite3'\n );\n }\n throw err;\n }\n\n this.db = new Database(resolvedPath);\n\n // Enable WAL mode for better concurrent reads\n this.db.pragma('journal_mode = WAL');\n\n // Run schema migration (idempotent)\n this.migrateSchema();\n\n logger.info(`[SqliteStore] Initialized at ${this.dbPath}`);\n }\n\n async shutdown(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n this.locks.clear();\n }\n\n // --- Schema Migration ---\n\n private migrateSchema(): void {\n const db = this.getDb();\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS schedules (\n id VARCHAR(36) PRIMARY KEY,\n creator_id VARCHAR(255) NOT NULL,\n creator_context VARCHAR(255),\n creator_name VARCHAR(255),\n timezone VARCHAR(64) NOT NULL DEFAULT 'UTC',\n schedule_expr VARCHAR(255),\n run_at BIGINT,\n is_recurring BOOLEAN NOT NULL,\n original_expression TEXT,\n workflow VARCHAR(255),\n workflow_inputs TEXT,\n output_context TEXT,\n status VARCHAR(20) NOT NULL,\n created_at BIGINT NOT NULL,\n last_run_at BIGINT,\n next_run_at BIGINT,\n run_count INTEGER NOT NULL DEFAULT 0,\n failure_count INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n previous_response TEXT,\n claimed_by VARCHAR(255),\n claimed_at BIGINT,\n lock_token VARCHAR(36)\n );\n\n CREATE INDEX IF NOT EXISTS idx_schedules_creator_id\n ON schedules(creator_id);\n\n CREATE INDEX IF NOT EXISTS idx_schedules_status\n ON schedules(status);\n\n CREATE INDEX IF NOT EXISTS idx_schedules_status_next_run\n ON schedules(status, next_run_at);\n\n CREATE TABLE IF NOT EXISTS scheduler_locks (\n lock_id VARCHAR(255) PRIMARY KEY,\n node_id VARCHAR(255) NOT NULL,\n lock_token VARCHAR(36) NOT NULL,\n acquired_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL\n );\n `);\n }\n\n // --- Helpers ---\n\n private getDb(): BetterSqliteDatabase {\n if (!this.db) {\n throw new Error('[SqliteStore] Database not initialized. Call initialize() first.');\n }\n return this.db;\n }\n\n // --- CRUD ---\n\n async create(\n schedule: Omit<Schedule, 'id' | 'createdAt' | 'runCount' | 'failureCount' | 'status'>\n ): Promise<Schedule> {\n const db = this.getDb();\n\n const newSchedule: Schedule = {\n ...schedule,\n id: uuidv4(),\n createdAt: Date.now(),\n runCount: 0,\n failureCount: 0,\n status: 'active',\n };\n\n const row = toDbRow(newSchedule);\n\n db.prepare(\n `\n INSERT INTO schedules (\n id, creator_id, creator_context, creator_name, timezone,\n schedule_expr, run_at, is_recurring, original_expression,\n workflow, workflow_inputs, output_context,\n status, created_at, last_run_at, next_run_at,\n run_count, failure_count, last_error, previous_response\n ) VALUES (\n ?, ?, ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?, ?\n )\n `\n ).run(\n row.id,\n row.creator_id,\n row.creator_context,\n row.creator_name,\n row.timezone,\n row.schedule_expr,\n row.run_at,\n row.is_recurring,\n row.original_expression,\n row.workflow,\n row.workflow_inputs,\n row.output_context,\n row.status,\n row.created_at,\n row.last_run_at,\n row.next_run_at,\n row.run_count,\n row.failure_count,\n row.last_error,\n row.previous_response\n );\n\n logger.info(\n `[SqliteStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`\n );\n\n return newSchedule;\n }\n\n async importSchedule(schedule: Schedule): Promise<void> {\n const db = this.getDb();\n const row = toDbRow(schedule);\n\n db.prepare(\n `\n INSERT OR IGNORE INTO schedules (\n id, creator_id, creator_context, creator_name, timezone,\n schedule_expr, run_at, is_recurring, original_expression,\n workflow, workflow_inputs, output_context,\n status, created_at, last_run_at, next_run_at,\n run_count, failure_count, last_error, previous_response\n ) VALUES (\n ?, ?, ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?, ?\n )\n `\n ).run(\n row.id,\n row.creator_id,\n row.creator_context,\n row.creator_name,\n row.timezone,\n row.schedule_expr,\n row.run_at,\n row.is_recurring,\n row.original_expression,\n row.workflow,\n row.workflow_inputs,\n row.output_context,\n row.status,\n row.created_at,\n row.last_run_at,\n row.next_run_at,\n row.run_count,\n row.failure_count,\n row.last_error,\n row.previous_response\n );\n }\n\n async get(id: string): Promise<Schedule | undefined> {\n const db = this.getDb();\n const row = db.prepare('SELECT * FROM schedules WHERE id = ?').get(id) as\n | ScheduleRow\n | undefined;\n return row ? fromDbRow(row) : undefined;\n }\n\n async update(id: string, patch: Partial<Schedule>): Promise<Schedule | undefined> {\n const db = this.getDb();\n\n // Get current row\n const existing = db.prepare('SELECT * FROM schedules WHERE id = ?').get(id) as\n | ScheduleRow\n | undefined;\n if (!existing) return undefined;\n\n const current = fromDbRow(existing);\n const updated: Schedule = { ...current, ...patch, id: current.id }; // ID cannot change\n const row = toDbRow(updated);\n\n db.prepare(\n `\n UPDATE schedules SET\n creator_id = ?, creator_context = ?, creator_name = ?, timezone = ?,\n schedule_expr = ?, run_at = ?, is_recurring = ?, original_expression = ?,\n workflow = ?, workflow_inputs = ?, output_context = ?,\n status = ?, last_run_at = ?, next_run_at = ?,\n run_count = ?, failure_count = ?, last_error = ?, previous_response = ?\n WHERE id = ?\n `\n ).run(\n row.creator_id,\n row.creator_context,\n row.creator_name,\n row.timezone,\n row.schedule_expr,\n row.run_at,\n row.is_recurring,\n row.original_expression,\n row.workflow,\n row.workflow_inputs,\n row.output_context,\n row.status,\n row.last_run_at,\n row.next_run_at,\n row.run_count,\n row.failure_count,\n row.last_error,\n row.previous_response,\n row.id\n );\n\n return updated;\n }\n\n async delete(id: string): Promise<boolean> {\n const db = this.getDb();\n const result = db.prepare('DELETE FROM schedules WHERE id = ?').run(id);\n if (result.changes > 0) {\n logger.info(`[SqliteStore] Deleted schedule ${id}`);\n return true;\n }\n return false;\n }\n\n // --- Queries ---\n\n async getByCreator(creatorId: string): Promise<Schedule[]> {\n const db = this.getDb();\n const rows = db\n .prepare('SELECT * FROM schedules WHERE creator_id = ?')\n .all(creatorId) as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getActiveSchedules(): Promise<Schedule[]> {\n const db = this.getDb();\n const rows = db\n .prepare(\"SELECT * FROM schedules WHERE status = 'active'\")\n .all() as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getDueSchedules(now?: number): Promise<Schedule[]> {\n const ts = now ?? Date.now();\n const db = this.getDb();\n const rows = db\n .prepare(\n `SELECT * FROM schedules\n WHERE status = 'active'\n AND (\n (is_recurring = 0 AND run_at IS NOT NULL AND run_at <= ?)\n OR\n (is_recurring = 1 AND next_run_at IS NOT NULL AND next_run_at <= ?)\n )`\n )\n .all(ts, ts) as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async findByWorkflow(creatorId: string, workflowName: string): Promise<Schedule[]> {\n const db = this.getDb();\n const escaped = workflowName.toLowerCase().replace(/[%_\\\\]/g, '\\\\$&');\n const pattern = `%${escaped}%`;\n const rows = db\n .prepare(\n `SELECT * FROM schedules\n WHERE creator_id = ? AND status = 'active'\n AND LOWER(workflow) LIKE ? ESCAPE '\\\\'`\n )\n .all(creatorId, pattern) as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getAll(): Promise<Schedule[]> {\n const db = this.getDb();\n const rows = db.prepare('SELECT * FROM schedules').all() as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getStats(): Promise<ScheduleStoreStats> {\n const db = this.getDb();\n\n const row = db\n .prepare(\n `SELECT\n COUNT(*) as total,\n SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,\n SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused,\n SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,\n SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,\n SUM(CASE WHEN is_recurring = 1 THEN 1 ELSE 0 END) as recurring,\n SUM(CASE WHEN is_recurring = 0 THEN 1 ELSE 0 END) as one_time\n FROM schedules`\n )\n .get() as {\n total: number;\n active: number;\n paused: number;\n completed: number;\n failed: number;\n recurring: number;\n one_time: number;\n };\n\n return {\n total: row.total,\n active: row.active,\n paused: row.paused,\n completed: row.completed,\n failed: row.failed,\n recurring: row.recurring,\n oneTime: row.one_time,\n };\n }\n\n async validateLimits(\n creatorId: string,\n isRecurring: boolean,\n limits: ScheduleLimits\n ): Promise<void> {\n const db = this.getDb();\n\n // Global limit\n if (limits.maxGlobal) {\n const row = db.prepare('SELECT COUNT(*) as cnt FROM schedules').get() as { cnt: number };\n if (row.cnt >= limits.maxGlobal) {\n throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);\n }\n }\n\n // Per-user limit\n if (limits.maxPerUser) {\n const row = db\n .prepare('SELECT COUNT(*) as cnt FROM schedules WHERE creator_id = ?')\n .get(creatorId) as { cnt: number };\n if (row.cnt >= limits.maxPerUser) {\n throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);\n }\n }\n\n // Per-user recurring limit\n if (isRecurring && limits.maxRecurringPerUser) {\n const row = db\n .prepare('SELECT COUNT(*) as cnt FROM schedules WHERE creator_id = ? AND is_recurring = 1')\n .get(creatorId) as { cnt: number };\n if (row.cnt >= limits.maxRecurringPerUser) {\n throw new Error(\n `You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`\n );\n }\n }\n }\n\n // --- HA Locking (in-memory for SQLite — single-node only) ---\n\n async tryAcquireLock(\n scheduleId: string,\n nodeId: string,\n ttlSeconds: number\n ): Promise<string | null> {\n const now = Date.now();\n const existing = this.locks.get(scheduleId);\n\n // Check if an unexpired lock exists\n if (existing && existing.expiresAt > now) {\n if (existing.nodeId === nodeId) {\n // Same node: return existing token\n return existing.token;\n }\n // Another node holds the lock\n return null;\n }\n\n // Acquire lock\n const token = uuidv4();\n this.locks.set(scheduleId, {\n nodeId,\n token,\n expiresAt: now + ttlSeconds * 1000,\n });\n return token;\n }\n\n async releaseLock(scheduleId: string, lockToken: string): Promise<void> {\n const existing = this.locks.get(scheduleId);\n if (existing && existing.token === lockToken) {\n this.locks.delete(scheduleId);\n }\n }\n\n async renewLock(scheduleId: string, lockToken: string, ttlSeconds: number): Promise<boolean> {\n const existing = this.locks.get(scheduleId);\n if (!existing || existing.token !== lockToken) {\n return false;\n }\n existing.expiresAt = Date.now() + ttlSeconds * 1000;\n return true;\n }\n\n async flush(): Promise<void> {\n // No-op for SQLite — writes are synchronous\n }\n}\n","/**\n * Store backend factory\n *\n * Creates the appropriate ScheduleStoreBackend based on configuration:\n * - 'sqlite' (default) → SqliteStoreBackend (OSS, zero-config)\n * - 'postgresql' / 'mysql' → Enterprise KnexStoreBackend (requires license)\n */\nimport { logger } from '../../logger';\nimport type {\n ScheduleStoreBackend,\n StorageConfig,\n HAConfig,\n SqliteConnectionConfig,\n} from './types';\nimport { SqliteStoreBackend } from './sqlite-store';\n\nexport type { ScheduleStoreBackend, ScheduleStoreStats, StorageConfig, HAConfig } from './types';\n\n/**\n * Create a store backend based on configuration\n */\nexport async function createStoreBackend(\n storageConfig?: StorageConfig,\n haConfig?: HAConfig\n): Promise<ScheduleStoreBackend> {\n const driver = storageConfig?.driver || 'sqlite';\n\n switch (driver) {\n case 'sqlite': {\n const conn = storageConfig?.connection as SqliteConnectionConfig | undefined;\n return new SqliteStoreBackend(conn?.filename);\n }\n\n case 'postgresql':\n case 'mysql':\n case 'mssql': {\n // Enterprise-only: dynamic import to keep OSS code clean\n try {\n // Variable path prevents ncc from tracing into enterprise/ (stashed during OSS builds)\n const loaderPath = '../../enterprise/loader';\n // @ts-ignore — enterprise/ may not exist in OSS builds (caught at runtime)\n const { loadEnterpriseStoreBackend } = await import(loaderPath);\n return await loadEnterpriseStoreBackend(driver, storageConfig!, haConfig);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(`[StoreFactory] Failed to load enterprise ${driver} backend: ${msg}`);\n throw new Error(\n `The ${driver} schedule storage driver requires a Visor Enterprise license. ` +\n `Install the enterprise package or use driver: 'sqlite' (default). ` +\n `Original error: ${msg}`\n );\n }\n }\n\n default:\n throw new Error(`Unknown schedule storage driver: ${driver}`);\n }\n}\n","/**\n * One-time migration from .visor/schedules.json to a SQL backend\n *\n * - Reads the JSON file, inserts each schedule into the backend\n * - Skips schedules whose ID already exists (idempotent)\n * - Renames the JSON file to .json.migrated as a backup\n * - Called automatically during ScheduleStore.initialize() when the JSON file exists\n */\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { logger } from '../../logger';\nimport type { Schedule } from '../schedule-store';\nimport type { ScheduleStoreBackend } from './types';\n\n/**\n * Migrate schedules from a JSON file into a ScheduleStoreBackend.\n *\n * @param jsonPath Path to the JSON file (absolute or relative to cwd)\n * @param backend Already-initialized store backend to migrate into\n * @returns Number of schedules migrated (0 if file doesn't exist or already migrated)\n */\nexport async function migrateJsonToBackend(\n jsonPath: string,\n backend: ScheduleStoreBackend\n): Promise<number> {\n const resolvedPath = path.resolve(process.cwd(), jsonPath);\n\n // Check if JSON file exists\n let content: string;\n try {\n content = await fs.readFile(resolvedPath, 'utf-8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n // No file to migrate — nothing to do\n return 0;\n }\n throw err;\n }\n\n // Parse the JSON\n let data: { schedules?: Schedule[] };\n try {\n data = JSON.parse(content);\n } catch {\n logger.warn(`[JsonMigrator] Failed to parse ${jsonPath}, skipping migration`);\n return 0;\n }\n\n const schedules = data.schedules;\n if (!Array.isArray(schedules) || schedules.length === 0) {\n logger.debug('[JsonMigrator] No schedules to migrate');\n // Still rename to mark as processed\n await renameToMigrated(resolvedPath);\n return 0;\n }\n\n let migrated = 0;\n\n for (const schedule of schedules) {\n if (!schedule.id) {\n logger.warn('[JsonMigrator] Skipping schedule without ID');\n continue;\n }\n\n // Check if already migrated (idempotent)\n const existing = await backend.get(schedule.id);\n if (existing) {\n logger.debug(`[JsonMigrator] Schedule ${schedule.id} already exists, skipping`);\n continue;\n }\n\n try {\n // Import preserving original ID (idempotent — skips if ID already exists)\n await backend.importSchedule(schedule);\n migrated++;\n } catch (err) {\n logger.warn(\n `[JsonMigrator] Failed to migrate schedule ${schedule.id}: ${\n err instanceof Error ? err.message : err\n }`\n );\n }\n }\n\n // Rename original file as backup\n await renameToMigrated(resolvedPath);\n\n logger.info(`[JsonMigrator] Migrated ${migrated}/${schedules.length} schedules from ${jsonPath}`);\n return migrated;\n}\n\n/**\n * Rename the JSON file to .migrated as a backup\n */\nasync function renameToMigrated(resolvedPath: string): Promise<void> {\n const migratedPath = `${resolvedPath}.migrated`;\n try {\n await fs.rename(resolvedPath, migratedPath);\n logger.info(`[JsonMigrator] Backed up ${resolvedPath} → ${migratedPath}`);\n } catch (err) {\n logger.warn(\n `[JsonMigrator] Failed to rename ${resolvedPath}: ${err instanceof Error ? err.message : err}`\n );\n }\n}\n","/**\n * Generic persistent storage for scheduled workflows\n *\n * Delegates all persistence to a ScheduleStoreBackend (default: SQLite).\n * The in-memory Map and JSON file I/O have been replaced by the backend abstraction\n * to support SQLite (OSS) and PostgreSQL/MySQL (Enterprise) storage.\n */\nimport { logger } from '../logger';\nimport type {\n ScheduleStoreBackend,\n StorageConfig,\n HAConfig,\n ScheduleStoreStats,\n} from './store/types';\nimport { createStoreBackend } from './store/index';\nimport { migrateJsonToBackend } from './store/json-migrator';\n\n/**\n * Output context for schedule execution results\n */\nexport interface ScheduleOutputContext {\n /** Output destination type */\n type: 'slack' | 'github' | 'webhook' | 'none';\n /** Target identifier (channel ID, repo name, webhook URL, etc.) */\n target?: string;\n /** Thread/issue/PR identifier for threaded outputs */\n threadId?: string;\n /** Additional destination-specific metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Represents a scheduled workflow execution\n */\nexport interface Schedule {\n id: string; // UUID v4\n\n // Creator\n creatorId: string; // Generic user ID (e.g., Slack user ID, GitHub user, CLI user)\n creatorContext?: string; // Context identifier: \"slack:U123\", \"github:user\", \"cli\"\n creatorName?: string; // Display name (cached)\n timezone: string; // IANA timezone\n\n // Scheduling\n schedule: string; // Cron expression (for recurring)\n runAt?: number; // Unix timestamp (for one-time)\n isRecurring: boolean;\n originalExpression: string; // User's natural language input\n\n // What to execute\n workflow?: string; // Workflow/check ID to run (undefined for simple text reminders)\n workflowInputs?: Record<string, unknown>; // Input parameters for the workflow\n\n // Output routing (optional - workflows can handle their own output)\n outputContext?: ScheduleOutputContext;\n\n // State\n status: 'active' | 'paused' | 'completed' | 'failed';\n createdAt: number;\n lastRunAt?: number;\n nextRunAt?: number; // Computed next execution time\n runCount: number;\n failureCount: number;\n lastError?: string;\n\n // Previous execution response (for recurring reminders)\n previousResponse?: string; // The AI response from the last execution\n}\n\n/**\n * Configuration for the schedule store\n */\nexport interface ScheduleStoreConfig {\n /** Path to the JSON file for persistence (legacy, triggers auto-migration) */\n path?: string;\n /** Auto-save after changes (default: true) — ignored for SQL backends */\n autoSave?: boolean;\n /** Debounce save operations in ms (default: 1000) — ignored for SQL backends */\n saveDebounceMs?: number;\n /** SQL storage configuration */\n storage?: StorageConfig;\n /** High-availability configuration */\n ha?: HAConfig;\n}\n\n/**\n * Limits configuration for schedules\n */\nexport interface ScheduleLimits {\n maxPerUser?: number; // Default: 25\n maxRecurringPerUser?: number; // Default: 10\n maxGlobal?: number; // Default: 1000\n}\n\n/**\n * Storage class for scheduled workflows with pluggable backend persistence\n */\nexport class ScheduleStore {\n private static instance: ScheduleStore | undefined;\n private backend: ScheduleStoreBackend | null = null;\n private initialized = false;\n private limits: ScheduleLimits;\n private config: ScheduleStoreConfig;\n private externalBackend: ScheduleStoreBackend | null = null;\n\n private constructor(\n config?: ScheduleStoreConfig,\n limits?: ScheduleLimits,\n backend?: ScheduleStoreBackend\n ) {\n this.config = config || {};\n this.limits = {\n maxPerUser: limits?.maxPerUser ?? 25,\n maxRecurringPerUser: limits?.maxRecurringPerUser ?? 10,\n maxGlobal: limits?.maxGlobal ?? 1000,\n };\n if (backend) {\n this.externalBackend = backend;\n }\n }\n\n /**\n * Get singleton instance\n *\n * Note: Config and limits are only applied on first call. Subsequent calls\n * with different parameters will log a warning and return the existing instance.\n * Use createIsolated() for testing with different configurations.\n */\n static getInstance(config?: ScheduleStoreConfig, limits?: ScheduleLimits): ScheduleStore {\n if (!ScheduleStore.instance) {\n ScheduleStore.instance = new ScheduleStore(config, limits);\n } else if (config || limits) {\n logger.warn(\n '[ScheduleStore] getInstance() called with config/limits but instance already exists. ' +\n 'Parameters ignored. Use createIsolated() for testing or resetInstance() first.'\n );\n }\n return ScheduleStore.instance;\n }\n\n /**\n * Create a new isolated instance (for testing)\n */\n static createIsolated(\n config?: ScheduleStoreConfig,\n limits?: ScheduleLimits,\n backend?: ScheduleStoreBackend\n ): ScheduleStore {\n return new ScheduleStore(config, limits, backend);\n }\n\n /**\n * Reset singleton instance (for testing)\n */\n static resetInstance(): void {\n if (ScheduleStore.instance) {\n // Best-effort shutdown\n if (ScheduleStore.instance.backend) {\n ScheduleStore.instance.backend.shutdown().catch(() => {});\n }\n }\n ScheduleStore.instance = undefined;\n }\n\n /**\n * Initialize the store - creates backend and runs migrations\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n // Create backend (or use the externally provided one)\n if (this.externalBackend) {\n this.backend = this.externalBackend;\n } else {\n this.backend = await createStoreBackend(this.config.storage, this.config.ha);\n }\n\n await this.backend.initialize();\n\n // Auto-migrate from JSON if a legacy path is configured or default JSON file exists\n const jsonPath = this.config.path || '.visor/schedules.json';\n try {\n await migrateJsonToBackend(jsonPath, this.backend);\n } catch (err) {\n logger.warn(\n `[ScheduleStore] JSON migration failed (non-fatal): ${\n err instanceof Error ? err.message : err\n }`\n );\n }\n\n this.initialized = true;\n }\n\n /**\n * Create a new schedule (async, persists immediately)\n */\n async createAsync(\n schedule: Omit<Schedule, 'id' | 'createdAt' | 'runCount' | 'failureCount' | 'status'>\n ): Promise<Schedule> {\n const backend = this.getBackend();\n await backend.validateLimits(schedule.creatorId, schedule.isRecurring, this.limits);\n return backend.create(schedule);\n }\n\n /**\n * Get a schedule by ID\n */\n async getAsync(id: string): Promise<Schedule | undefined> {\n return this.getBackend().get(id);\n }\n\n /**\n * Update a schedule\n */\n async updateAsync(id: string, patch: Partial<Schedule>): Promise<Schedule | undefined> {\n return this.getBackend().update(id, patch);\n }\n\n /**\n * Delete a schedule\n */\n async deleteAsync(id: string): Promise<boolean> {\n return this.getBackend().delete(id);\n }\n\n /**\n * Get all schedules for a specific creator\n */\n async getByCreatorAsync(creatorId: string): Promise<Schedule[]> {\n return this.getBackend().getByCreator(creatorId);\n }\n\n /**\n * Get all active schedules\n */\n async getActiveSchedulesAsync(): Promise<Schedule[]> {\n return this.getBackend().getActiveSchedules();\n }\n\n /**\n * Get all schedules due for execution\n * @param now Current timestamp in milliseconds\n */\n async getDueSchedulesAsync(now: number = Date.now()): Promise<Schedule[]> {\n return this.getBackend().getDueSchedules(now);\n }\n\n /**\n * Find schedules by workflow name\n */\n async findByWorkflowAsync(creatorId: string, workflowName: string): Promise<Schedule[]> {\n return this.getBackend().findByWorkflow(creatorId, workflowName);\n }\n\n /**\n * Get schedule count statistics\n */\n async getStatsAsync(): Promise<ScheduleStoreStats> {\n return this.getBackend().getStats();\n }\n\n /**\n * Force immediate save (useful for shutdown)\n */\n async flush(): Promise<void> {\n if (this.backend) {\n await this.backend.flush();\n }\n }\n\n /**\n * Check if initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Check if there are unsaved changes\n */\n hasPendingChanges(): boolean {\n return false; // SQL backends persist immediately\n }\n\n /**\n * Get all schedules\n */\n async getAllAsync(): Promise<Schedule[]> {\n return this.getBackend().getAll();\n }\n\n /**\n * Get the underlying backend (for HA lock operations)\n */\n getBackend(): ScheduleStoreBackend {\n if (!this.backend) {\n throw new Error('[ScheduleStore] Not initialized. Call initialize() first.');\n }\n return this.backend;\n }\n\n /**\n * Shut down the backend cleanly\n */\n async shutdown(): Promise<void> {\n if (this.backend) {\n await this.backend.shutdown();\n this.backend = null;\n }\n this.initialized = false;\n }\n}\n","/**\n * Schedule parsing utilities\n * Note: Natural language parsing is handled by the AI which generates\n * cron expressions or ISO timestamps directly. This module provides\n * validation and helper functions.\n */\n\n/**\n * Parsed schedule result\n */\nexport interface ParsedSchedule {\n type: 'one-time' | 'recurring';\n cronExpression?: string; // For recurring schedules\n runAt?: Date; // For one-time schedules\n timezone: string;\n description: string; // Human-readable description\n}\n\n/**\n * Day of week mapping for cron expressions\n */\nconst DAYS_OF_WEEK: Record<string, number> = {\n sunday: 0,\n sun: 0,\n monday: 1,\n mon: 1,\n tuesday: 2,\n tue: 2,\n wednesday: 3,\n wed: 3,\n thursday: 4,\n thu: 4,\n friday: 5,\n fri: 5,\n saturday: 6,\n sat: 6,\n};\n\n/**\n * Recurring pattern detection\n */\ninterface RecurringPattern {\n frequency:\n | 'minute'\n | 'hourly'\n | 'daily'\n | 'weekly'\n | 'monthly'\n | 'yearly'\n | 'weekday'\n | 'weekend';\n daysOfWeek?: number[];\n dayOfMonth?: number;\n hour?: number;\n minute?: number;\n interval?: number;\n}\n\n/**\n * Parse a natural language schedule expression\n *\n * @param expression Natural language time expression (e.g., \"every Monday at 9am\", \"in 2 hours\")\n * @param userTimezone IANA timezone string (e.g., \"America/New_York\")\n * @param referenceDate Optional reference date for parsing (defaults to now)\n * @returns Parsed schedule information\n */\nexport function parseScheduleExpression(\n expression: string,\n userTimezone: string = 'UTC',\n _referenceDate?: Date\n): ParsedSchedule {\n const trimmedExpr = expression.trim();\n const normalizedExpr = trimmedExpr.toLowerCase();\n\n // First check if it's a standard cron expression (5 space-separated parts)\n if (isValidCronExpression(trimmedExpr)) {\n return {\n type: 'recurring',\n cronExpression: trimmedExpr,\n timezone: userTimezone,\n description: describeCronExpression(trimmedExpr),\n };\n }\n\n // Check for natural language recurring patterns\n const recurring = detectRecurringPattern(normalizedExpr);\n if (recurring) {\n const cronExpr = buildCronExpression(recurring);\n return {\n type: 'recurring',\n cronExpression: cronExpr,\n timezone: userTimezone,\n description: buildRecurringDescription(recurring),\n };\n }\n\n // Try to parse as ISO 8601 timestamp for one-time schedules\n const isoDate = new Date(expression);\n if (!isNaN(isoDate.getTime())) {\n // Valid ISO date\n if (isoDate.getTime() <= Date.now()) {\n throw new Error(`Schedule time must be in the future: \"${expression}\"`);\n }\n return {\n type: 'one-time',\n runAt: isoDate,\n timezone: userTimezone,\n description: formatOneTimeDescription(isoDate),\n };\n }\n\n // Could not parse - provide helpful error\n throw new Error(\n `Could not parse schedule expression: \"${expression}\". ` +\n `Use a cron expression (e.g., \"0 9 * * 1\" for Monday 9am) or ISO timestamp (e.g., \"2026-02-08T15:00:00Z\").`\n );\n}\n\n/**\n * Detect recurring patterns in the expression\n */\nfunction detectRecurringPattern(expr: string): RecurringPattern | null {\n // Check for keywords that indicate recurring schedules\n if (\n !expr.includes('every') &&\n !expr.includes('daily') &&\n !expr.includes('weekly') &&\n !expr.includes('monthly')\n ) {\n return null;\n }\n\n // Extract time component\n const timeMatch = expr.match(/at\\s+(\\d{1,2})(?::(\\d{2}))?\\s*(am|pm)?/i);\n let hour: number | undefined;\n let minute: number | undefined;\n\n if (timeMatch) {\n hour = parseInt(timeMatch[1], 10);\n minute = timeMatch[2] ? parseInt(timeMatch[2], 10) : 0;\n\n // Convert to 24-hour format\n const ampm = timeMatch[3]?.toLowerCase();\n if (ampm === 'pm' && hour !== 12) {\n hour += 12;\n } else if (ampm === 'am' && hour === 12) {\n hour = 0;\n }\n } else {\n // Check for special times\n if (expr.includes('noon')) {\n hour = 12;\n minute = 0;\n } else if (expr.includes('midnight')) {\n hour = 0;\n minute = 0;\n } else if (expr.includes('morning')) {\n hour = 9;\n minute = 0;\n } else if (expr.includes('evening')) {\n hour = 18;\n minute = 0;\n }\n }\n\n // Every minute (singular, no number)\n if (expr.match(/every\\s+minute\\b/i) && !expr.match(/every\\s+\\d+\\s+minute/i)) {\n return {\n frequency: 'minute',\n interval: 1,\n minute: 0,\n };\n }\n\n // Every hour (singular, no number)\n if (expr.match(/every\\s+hour\\b/i) && !expr.match(/every\\s+\\d+\\s+hour/i)) {\n return {\n frequency: 'hourly',\n interval: 1,\n minute: minute ?? 0,\n };\n }\n\n // Every N minutes/hours\n const intervalMatch = expr.match(/every\\s+(\\d+)\\s+(minute|hour)s?/i);\n if (intervalMatch) {\n const interval = parseInt(intervalMatch[1], 10);\n const unit = intervalMatch[2].toLowerCase();\n\n if (unit === 'minute') {\n return {\n frequency: 'minute',\n interval,\n minute: 0,\n };\n } else {\n return {\n frequency: 'hourly',\n interval,\n minute: minute ?? 0,\n };\n }\n }\n\n // Every day / daily\n if (expr.includes('every day') || expr.includes('daily')) {\n return {\n frequency: 'daily',\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Every weekday\n if (\n expr.includes('weekday') ||\n (expr.includes('monday') && expr.includes('friday') && expr.includes('through'))\n ) {\n return {\n frequency: 'weekday',\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Every weekend\n if (expr.includes('weekend')) {\n return {\n frequency: 'weekend',\n hour: hour ?? 10,\n minute: minute ?? 0,\n };\n }\n\n // Monthly - check before day of week to avoid false matches\n if (expr.includes('monthly') || expr.includes('every month')) {\n const dayOfMonthMatch = expr.match(/(?:on the\\s+)?(\\d{1,2})(?:st|nd|rd|th)?/);\n const dayOfMonth = dayOfMonthMatch ? parseInt(dayOfMonthMatch[1], 10) : 1;\n return {\n frequency: 'monthly',\n dayOfMonth,\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Weekly on specific day(s)\n if (expr.includes('weekly') || expr.includes('every week')) {\n // Default to Monday if no day specified\n const daysOfWeek = extractDaysOfWeek(expr);\n return {\n frequency: 'weekly',\n daysOfWeek: daysOfWeek.length > 0 ? daysOfWeek : [1],\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Every [day of week]\n const daysOfWeek = extractDaysOfWeek(expr);\n if (daysOfWeek.length > 0) {\n return {\n frequency: 'weekly',\n daysOfWeek,\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // If we detected \"every\" but couldn't parse the pattern, default to daily\n if (expr.includes('every')) {\n return {\n frequency: 'daily',\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n return null;\n}\n\n/**\n * Extract days of week from expression\n */\nfunction extractDaysOfWeek(expr: string): number[] {\n const days: Set<number> = new Set();\n\n for (const [dayName, dayNum] of Object.entries(DAYS_OF_WEEK)) {\n if (expr.includes(dayName)) {\n days.add(dayNum);\n }\n }\n\n return Array.from(days).sort((a, b) => a - b);\n}\n\n/**\n * Build a cron expression from a recurring pattern\n *\n * Cron format: minute hour day-of-month month day-of-week\n */\nfunction buildCronExpression(pattern: RecurringPattern): string {\n const minute = pattern.minute ?? 0;\n const hour = pattern.hour ?? 9;\n\n switch (pattern.frequency) {\n case 'minute':\n // Every N minutes\n if (pattern.interval && pattern.interval > 1) {\n return `*/${pattern.interval} * * * *`;\n }\n return '* * * * *';\n\n case 'hourly':\n // Every N hours\n if (pattern.interval && pattern.interval > 1) {\n return `${minute} */${pattern.interval} * * *`;\n }\n return `${minute} * * * *`;\n\n case 'daily':\n return `${minute} ${hour} * * *`;\n\n case 'weekday':\n return `${minute} ${hour} * * 1-5`;\n\n case 'weekend':\n return `${minute} ${hour} * * 0,6`;\n\n case 'weekly':\n const days = pattern.daysOfWeek?.join(',') ?? '1';\n return `${minute} ${hour} * * ${days}`;\n\n case 'monthly':\n const dayOfMonth = pattern.dayOfMonth ?? 1;\n return `${minute} ${hour} ${dayOfMonth} * *`;\n\n case 'yearly':\n return `${minute} ${hour} 1 1 *`;\n\n default:\n return `${minute} ${hour} * * *`;\n }\n}\n\n/**\n * Build a human-readable description for recurring schedules\n */\nfunction buildRecurringDescription(pattern: RecurringPattern): string {\n const timeStr = formatTime(pattern.hour ?? 9, pattern.minute ?? 0);\n\n switch (pattern.frequency) {\n case 'minute':\n if (pattern.interval && pattern.interval > 1) {\n return `Every ${pattern.interval} minutes`;\n }\n return 'Every minute';\n\n case 'hourly':\n if (pattern.interval && pattern.interval > 1) {\n return `Every ${pattern.interval} hours at :${String(pattern.minute ?? 0).padStart(2, '0')}`;\n }\n return `Every hour at :${String(pattern.minute ?? 0).padStart(2, '0')}`;\n\n case 'daily':\n return `Daily at ${timeStr}`;\n\n case 'weekday':\n return `Weekdays at ${timeStr}`;\n\n case 'weekend':\n return `Weekends at ${timeStr}`;\n\n case 'weekly':\n const dayNames = (pattern.daysOfWeek ?? [1])\n .map(d => Object.entries(DAYS_OF_WEEK).find(([, v]) => v === d)?.[0] ?? String(d))\n .map(n => n.charAt(0).toUpperCase() + n.slice(1, 3));\n return `Every ${dayNames.join(', ')} at ${timeStr}`;\n\n case 'monthly':\n return `Monthly on the ${ordinal(pattern.dayOfMonth ?? 1)} at ${timeStr}`;\n\n case 'yearly':\n return `Yearly on January 1st at ${timeStr}`;\n\n default:\n return `At ${timeStr}`;\n }\n}\n\n/**\n * Format time in 12-hour format\n */\nfunction formatTime(hour: number, minute: number): string {\n const h = hour % 12 || 12;\n const m = String(minute).padStart(2, '0');\n const ampm = hour >= 12 ? 'PM' : 'AM';\n return `${h}:${m} ${ampm}`;\n}\n\n/**\n * Format a one-time schedule description\n */\nfunction formatOneTimeDescription(date: Date): string {\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n const diffMins = Math.round(diffMs / 60000);\n const diffHours = Math.round(diffMs / 3600000);\n const diffDays = Math.round(diffMs / 86400000);\n\n // Format the actual time\n const timeStr = date.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n });\n\n // Format the date\n const dateOptions: Intl.DateTimeFormatOptions = {\n weekday: 'long',\n month: 'short',\n day: 'numeric',\n };\n\n if (date.getFullYear() !== now.getFullYear()) {\n dateOptions.year = 'numeric';\n }\n\n const dateStr = date.toLocaleDateString('en-US', dateOptions);\n\n // Add relative time if within 24 hours\n if (diffMins < 60) {\n return `In ${diffMins} minute${diffMins !== 1 ? 's' : ''} (${dateStr} at ${timeStr})`;\n } else if (diffHours < 24) {\n return `In ${diffHours} hour${diffHours !== 1 ? 's' : ''} (${dateStr} at ${timeStr})`;\n } else if (diffDays === 1) {\n return `Tomorrow at ${timeStr}`;\n } else if (diffDays < 7) {\n return `${dateStr} at ${timeStr}`;\n }\n\n return `${dateStr} at ${timeStr}`;\n}\n\n/**\n * Calculate the next run time for a cron expression\n */\nexport function getNextRunTime(cronExpression: string, _timezone: string = 'UTC'): Date {\n // Note: Full timezone support would require a library like luxon or date-fns-tz\n // For now, calculations are done in local time\n // Simple cron parser for next run calculation\n // For production, consider using a library like 'cron-parser'\n const parts = cronExpression.split(' ');\n if (parts.length !== 5) {\n throw new Error(`Invalid cron expression: ${cronExpression}`);\n }\n\n const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;\n const now = new Date();\n\n // Start from the next minute\n const next = new Date(now);\n next.setSeconds(0, 0);\n next.setMinutes(next.getMinutes() + 1);\n\n // Try to find the next matching time (within 1 year)\n const maxAttempts = 365 * 24 * 60; // 1 year of minutes\n for (let i = 0; i < maxAttempts; i++) {\n if (\n matchesCronPart(next.getMinutes(), minute) &&\n matchesCronPart(next.getHours(), hour) &&\n matchesCronPart(next.getDate(), dayOfMonth) &&\n matchesCronPart(next.getMonth() + 1, month) &&\n matchesCronPart(next.getDay(), dayOfWeek)\n ) {\n return next;\n }\n next.setMinutes(next.getMinutes() + 1);\n }\n\n // Fallback to tomorrow at the specified time\n const fallback = new Date(now);\n fallback.setDate(fallback.getDate() + 1);\n fallback.setHours(parseInt(hour, 10) || 9);\n fallback.setMinutes(parseInt(minute, 10) || 0);\n fallback.setSeconds(0, 0);\n return fallback;\n}\n\n/**\n * Check if a value matches a cron part\n */\nfunction matchesCronPart(value: number, cronPart: string): boolean {\n if (cronPart === '*') return true;\n\n // Handle step values (*/n)\n if (cronPart.startsWith('*/')) {\n const step = parseInt(cronPart.slice(2), 10);\n return value % step === 0;\n }\n\n // Handle ranges (n-m)\n if (cronPart.includes('-')) {\n const [start, end] = cronPart.split('-').map(n => parseInt(n, 10));\n return value >= start && value <= end;\n }\n\n // Handle lists (n,m,o)\n if (cronPart.includes(',')) {\n return cronPart\n .split(',')\n .map(n => parseInt(n, 10))\n .includes(value);\n }\n\n // Handle exact value\n return parseInt(cronPart, 10) === value;\n}\n\n/**\n * Get ordinal suffix for a number\n */\nfunction ordinal(n: number): string {\n const s = ['th', 'st', 'nd', 'rd'];\n const v = n % 100;\n return n + (s[(v - 20) % 10] || s[v] || s[0]);\n}\n\n/**\n * Validate a cron expression\n */\nexport function isValidCronExpression(expr: string): boolean {\n if (!expr || typeof expr !== 'string') return false;\n\n const parts = expr.trim().split(/\\s+/);\n if (parts.length !== 5) return false;\n\n const ranges: [number, number][] = [\n [0, 59], // minute\n [0, 23], // hour\n [1, 31], // day of month\n [1, 12], // month\n [0, 7], // day of week (0 and 7 are Sunday)\n ];\n\n return parts.every((part, i) => {\n if (part === '*') return true;\n if (part.startsWith('*/')) {\n const step = parseInt(part.slice(2), 10);\n return !isNaN(step) && step > 0;\n }\n if (part.includes('-')) {\n const [start, end] = part.split('-').map(n => parseInt(n, 10));\n return !isNaN(start) && !isNaN(end) && start >= ranges[i][0] && end <= ranges[i][1];\n }\n if (part.includes(',')) {\n return part.split(',').every(n => {\n const val = parseInt(n, 10);\n return !isNaN(val) && val >= ranges[i][0] && val <= ranges[i][1];\n });\n }\n const val = parseInt(part, 10);\n return !isNaN(val) && val >= ranges[i][0] && val <= ranges[i][1];\n });\n}\n\n/**\n * Generate a human-readable description for a cron expression\n */\nfunction describeCronExpression(cronExpr: string): string {\n const parts = cronExpr.trim().split(/\\s+/);\n if (parts.length !== 5) return `Cron: ${cronExpr}`;\n\n const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;\n const descriptions: string[] = [];\n\n // Time description\n if (minute === '*' && hour === '*') {\n descriptions.push('Every minute');\n } else if (minute.startsWith('*/')) {\n const interval = parseInt(minute.slice(2), 10);\n descriptions.push(`Every ${interval} minute${interval > 1 ? 's' : ''}`);\n } else if (hour.startsWith('*/')) {\n const interval = parseInt(hour.slice(2), 10);\n const m = minute === '*' ? 0 : parseInt(minute, 10);\n descriptions.push(\n `Every ${interval} hour${interval > 1 ? 's' : ''} at :${String(m).padStart(2, '0')}`\n );\n } else {\n const h = hour === '*' ? null : parseInt(hour, 10);\n const m = minute === '*' ? 0 : parseInt(minute, 10);\n if (h !== null) {\n descriptions.push(`At ${formatTime(h, m)}`);\n } else {\n descriptions.push(`At :${String(m).padStart(2, '0')}`);\n }\n }\n\n // Day of week description\n if (dayOfWeek !== '*') {\n const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n if (dayOfWeek === '1-5') {\n descriptions.push('on weekdays');\n } else if (dayOfWeek === '0,6' || dayOfWeek === '6,0') {\n descriptions.push('on weekends');\n } else if (dayOfWeek.includes(',')) {\n const days = dayOfWeek.split(',').map(d => dayNames[parseInt(d, 10) % 7]);\n descriptions.push(`on ${days.join(', ')}`);\n } else if (dayOfWeek.includes('-')) {\n const [start, end] = dayOfWeek.split('-').map(d => parseInt(d, 10) % 7);\n descriptions.push(`on ${dayNames[start]}-${dayNames[end]}`);\n } else {\n const dayNum = parseInt(dayOfWeek, 10) % 7;\n descriptions.push(`on ${dayNames[dayNum]}`);\n }\n }\n\n // Day of month description\n if (dayOfMonth !== '*' && dayOfWeek === '*') {\n if (dayOfMonth.includes(',')) {\n const days = dayOfMonth.split(',');\n descriptions.push(`on day${days.length > 1 ? 's' : ''} ${days.join(', ')} of the month`);\n } else {\n descriptions.push(`on day ${dayOfMonth} of the month`);\n }\n }\n\n // Month description\n if (month !== '*') {\n const monthNames = [\n '',\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n ];\n if (month.includes(',')) {\n const months = month.split(',').map(m => monthNames[parseInt(m, 10)]);\n descriptions.push(`in ${months.join(', ')}`);\n } else {\n descriptions.push(`in ${monthNames[parseInt(month, 10)]}`);\n }\n }\n\n return descriptions.join(' ');\n}\n","/**\n * Generic AI Tool for scheduling and managing workflow executions\n * This tool is frontend-agnostic and can work with any context (Slack, CLI, GitHub, etc.)\n */\nimport { CustomToolDefinition } from '../types/config';\nimport { ScheduleStore, Schedule, ScheduleOutputContext } from './schedule-store';\nimport { isValidCronExpression, getNextRunTime } from './schedule-parser';\nimport { logger } from '../logger';\n\n/**\n * Simple glob-style pattern matching for workflow names\n * Supports * (any characters) and ? (single character)\n */\nfunction matchGlobPattern(pattern: string, value: string): boolean {\n // Escape regex special chars except * and ?\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n return new RegExp(`^${regexPattern}$`).test(value);\n}\n\n/**\n * Check if a workflow is allowed by patterns\n */\nfunction isWorkflowAllowedByPatterns(\n workflow: string,\n allowedPatterns?: string[],\n deniedPatterns?: string[]\n): { allowed: boolean; reason?: string } {\n // If denied patterns exist and workflow matches any, deny\n if (deniedPatterns && deniedPatterns.length > 0) {\n for (const pattern of deniedPatterns) {\n if (matchGlobPattern(pattern, workflow)) {\n return {\n allowed: false,\n reason: `Workflow \"${workflow}\" matches denied pattern \"${pattern}\"`,\n };\n }\n }\n }\n\n // If allowed patterns exist, workflow must match at least one\n if (allowedPatterns && allowedPatterns.length > 0) {\n for (const pattern of allowedPatterns) {\n if (matchGlobPattern(pattern, workflow)) {\n return { allowed: true };\n }\n }\n return {\n allowed: false,\n reason: `Workflow \"${workflow}\" does not match any allowed patterns: ${allowedPatterns.join(', ')}`,\n };\n }\n\n // No patterns defined - allow by default\n return { allowed: true };\n}\n\n/**\n * Check permissions for creating a schedule\n */\nfunction checkSchedulePermissions(\n context: ScheduleToolContext,\n workflow: string,\n requestedScheduleType?: ScheduleType\n): { allowed: boolean; reason?: string } {\n const permissions = context.permissions;\n const scheduleType = requestedScheduleType || context.scheduleType || 'personal';\n\n // Enforce context-based restrictions first\n // From a DM, can only create personal schedules\n // From a channel, can only create channel schedules\n // From a group DM, can only create dm/group schedules\n if (context.allowedScheduleType && scheduleType !== context.allowedScheduleType) {\n const contextNames: Record<ScheduleType, string> = {\n personal: 'a direct message (DM)',\n channel: 'a channel',\n dm: 'a group DM',\n };\n const targetNames: Record<ScheduleType, string> = {\n personal: 'personal',\n channel: 'channel',\n dm: 'group',\n };\n return {\n allowed: false,\n reason: `From ${contextNames[context.allowedScheduleType]}, you can only create ${targetNames[context.allowedScheduleType]} schedules. To create a ${targetNames[scheduleType]} schedule, please use the appropriate context.`,\n };\n }\n\n // No permissions configured - allow everything (backwards compatible)\n if (!permissions) {\n return { allowed: true };\n }\n\n // Check schedule type permission\n switch (scheduleType) {\n case 'personal':\n if (permissions.allowPersonal === false) {\n return {\n allowed: false,\n reason: 'Personal schedules are not allowed in this configuration',\n };\n }\n break;\n case 'channel':\n if (permissions.allowChannel === false) {\n return {\n allowed: false,\n reason: 'Channel schedules are not allowed in this configuration',\n };\n }\n break;\n case 'dm':\n if (permissions.allowDm === false) {\n return {\n allowed: false,\n reason: 'DM schedules are not allowed in this configuration',\n };\n }\n break;\n }\n\n // Check workflow patterns\n return isWorkflowAllowedByPatterns(\n workflow,\n permissions.allowedWorkflows,\n permissions.deniedWorkflows\n );\n}\n\n/**\n * Tool action types\n */\nexport type ScheduleAction = 'create' | 'list' | 'cancel' | 'pause' | 'resume';\n\n/**\n * Target type for where to send the reminder/output\n */\nexport type TargetType = 'channel' | 'dm' | 'thread' | 'user';\n\n/**\n * Tool input arguments - AI provides structured data, no parsing needed\n */\nexport interface ScheduleToolArgs {\n action: ScheduleAction;\n\n // For create action - AI extracts and structures all of this:\n /** What to say/do when the schedule fires */\n reminder_text?: string;\n /** Where to send: channel, dm (to self), thread, or user (DM to specific user) */\n target_type?: TargetType;\n /** The Slack channel ID (C... for channels, D... for DMs) */\n target_id?: string;\n /** For thread replies: the thread_ts to reply to */\n thread_ts?: string;\n /** Is this a recurring schedule? */\n is_recurring?: boolean;\n /** For recurring: cron expression (AI generates this, e.g., \"* * * * *\" for every minute) */\n cron?: string;\n /** For one-time: ISO 8601 timestamp when to run */\n run_at?: string;\n /** Original natural language expression (for display only) */\n original_expression?: string;\n /** Optional workflow to run instead of just sending reminder_text */\n workflow?: string;\n /** Optional workflow inputs */\n workflow_inputs?: Record<string, unknown>;\n\n // For cancel/pause/resume actions:\n schedule_id?: string;\n}\n\n/**\n * Schedule type for permission checking\n */\nexport type ScheduleType = 'personal' | 'channel' | 'dm';\n\n/**\n * Permissions configuration for dynamic schedules\n */\nexport interface SchedulePermissions {\n /** Allow personal schedules (via DM or CLI) */\n allowPersonal?: boolean;\n /** Allow channel schedules (in Slack channels) */\n allowChannel?: boolean;\n /** Allow DM schedules (to specific users) */\n allowDm?: boolean;\n /** List of allowed workflow patterns (glob-style, e.g., \"report-*\") */\n allowedWorkflows?: string[];\n /** List of denied workflow patterns */\n deniedWorkflows?: string[];\n}\n\n/**\n * Context passed to the tool handler\n * This is generic and works with any frontend\n */\nexport interface ScheduleToolContext {\n /** Generic user ID (can be Slack user, GitHub user, CLI user, etc.) */\n userId: string;\n /** User display name (optional) */\n userName?: string;\n /** Context identifier: \"slack:U123\", \"github:user\", \"cli\", etc. */\n contextType: string;\n /** User's timezone (IANA format) */\n timezone?: string;\n /** Available workflows in the current config */\n availableWorkflows?: string[];\n /** Schedule type being created (for permission checking) */\n scheduleType?: ScheduleType;\n /** Permissions for dynamic schedule creation */\n permissions?: SchedulePermissions;\n /**\n * Allowed schedule type based on originating context.\n * When set, only schedules of this type can be created/managed.\n * - From DM: only 'personal' allowed\n * - From channel: only 'channel' allowed\n * - From group DM: only 'dm' allowed (targeting the group)\n */\n allowedScheduleType?: ScheduleType;\n}\n\n/**\n * Tool execution result\n */\nexport interface ScheduleToolResult {\n success: boolean;\n message: string;\n schedule?: Schedule;\n schedules?: Schedule[];\n error?: string;\n}\n\n/**\n * Format a schedule for display\n */\nfunction formatSchedule(schedule: Schedule): string {\n const time = schedule.isRecurring\n ? schedule.originalExpression\n : new Date(schedule.runAt!).toLocaleString();\n const status = schedule.status !== 'active' ? ` (${schedule.status})` : '';\n // For simple reminders, show the reminder text; for workflows, show the workflow name\n const displayName =\n schedule.workflow || (schedule.workflowInputs?.text as string) || 'scheduled message';\n const truncatedName =\n displayName.length > 30 ? displayName.substring(0, 27) + '...' : displayName;\n const output = schedule.outputContext?.type || 'none';\n\n return `\\`${schedule.id.substring(0, 8)}\\` - \"${truncatedName}\" - ${time} (→ ${output})${status}`;\n}\n\n/**\n * Format confirmation message for a new schedule\n */\nfunction formatCreateConfirmation(schedule: Schedule): string {\n const outputDesc = schedule.outputContext?.type\n ? `${schedule.outputContext.type}${schedule.outputContext.target ? `:${schedule.outputContext.target}` : ''}`\n : 'none';\n\n // For simple reminders, show the reminder text; for workflows, show the workflow name\n const displayName =\n schedule.workflow || (schedule.workflowInputs?.text as string) || 'scheduled message';\n\n if (schedule.isRecurring) {\n const nextRun = schedule.nextRunAt\n ? new Date(schedule.nextRunAt).toLocaleString('en-US', {\n weekday: 'long',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n })\n : 'calculating...';\n\n return `**Schedule created!**\n\n**${schedule.workflow ? 'Workflow' : 'Reminder'}**: ${displayName}\n**When**: ${schedule.originalExpression}\n**Output**: ${outputDesc}\n**Next run**: ${nextRun}\n\nID: \\`${schedule.id.substring(0, 8)}\\``;\n } else {\n const when = new Date(schedule.runAt!).toLocaleString('en-US', {\n weekday: 'long',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n });\n\n return `**Schedule created!**\n\n**${schedule.workflow ? 'Workflow' : 'Reminder'}**: ${displayName}\n**When**: ${when}\n**Output**: ${outputDesc}\n\nID: \\`${schedule.id.substring(0, 8)}\\``;\n }\n}\n\n/**\n * Format the list of schedules\n */\nfunction formatScheduleList(schedules: Schedule[]): string {\n if (schedules.length === 0) {\n return `You don't have any active schedules.\n\nTo create one: \"remind me every Monday at 9am to check PRs\" or \"schedule %daily-report every Monday at 9am\"`;\n }\n\n const lines = schedules.map((s, i) => `${i + 1}. ${formatSchedule(s)}`);\n\n return `**Your active schedules:**\n\n${lines.join('\\n')}\n\nTo cancel: \"cancel schedule <id>\"\nTo pause: \"pause schedule <id>\"`;\n}\n\n/**\n * Handle schedule tool actions\n */\nexport async function handleScheduleAction(\n args: ScheduleToolArgs,\n context: ScheduleToolContext\n): Promise<ScheduleToolResult> {\n const store = ScheduleStore.getInstance();\n\n // Ensure store is initialized\n if (!store.isInitialized()) {\n await store.initialize();\n }\n\n switch (args.action) {\n case 'create':\n return handleCreate(args, context, store);\n\n case 'list':\n return handleList(context, store);\n\n case 'cancel':\n return handleCancel(args, context, store);\n\n case 'pause':\n return handlePauseResume(args, context, store, 'paused');\n\n case 'resume':\n return handlePauseResume(args, context, store, 'active');\n\n default:\n return {\n success: false,\n message: `Unknown action: ${args.action}`,\n error: `Supported actions: create, list, cancel, pause, resume`,\n };\n }\n}\n\n/**\n * Handle create action - AI provides structured data, minimal parsing needed\n */\nasync function handleCreate(\n args: ScheduleToolArgs,\n context: ScheduleToolContext,\n store: ScheduleStore\n): Promise<ScheduleToolResult> {\n // Validate: need either reminder_text or workflow\n if (!args.reminder_text && !args.workflow) {\n return {\n success: false,\n message: 'Missing reminder content',\n error: 'Please specify either reminder_text (what to say) or workflow (what to run)',\n };\n }\n\n // Validate: need either cron (recurring) or run_at (one-time)\n if (!args.cron && !args.run_at) {\n return {\n success: false,\n message: 'Missing schedule timing',\n error:\n 'Please specify either cron (for recurring, e.g., \"* * * * *\") or run_at (ISO timestamp for one-time)',\n };\n }\n\n // Validate cron format if provided\n if (args.cron && !isValidCronExpression(args.cron)) {\n return {\n success: false,\n message: 'Invalid cron expression',\n error: `\"${args.cron}\" is not a valid cron expression. Format: \"minute hour day-of-month month day-of-week\"`,\n };\n }\n\n // Validate run_at format if provided\n let runAtTimestamp: number | undefined;\n if (args.run_at) {\n const parsed = new Date(args.run_at);\n if (isNaN(parsed.getTime())) {\n return {\n success: false,\n message: 'Invalid run_at timestamp',\n error: `\"${args.run_at}\" is not a valid ISO 8601 timestamp`,\n };\n }\n if (parsed.getTime() <= Date.now()) {\n return {\n success: false,\n message: 'run_at must be in the future',\n error: 'Cannot schedule a reminder in the past',\n };\n }\n runAtTimestamp = parsed.getTime();\n }\n\n // Validate target_id is provided when target_type is specified\n if (args.target_type && !args.target_id) {\n return {\n success: false,\n message: 'Missing target_id',\n error: `target_type \"${args.target_type}\" requires a target_id (channel ID, user ID, or thread_ts)`,\n };\n }\n\n // Determine schedule type from target\n // 'channel' -> channel schedule, 'user' -> dm schedule, 'dm' or 'thread' -> personal\n let scheduleType: ScheduleType = 'personal';\n if (args.target_type === 'channel') {\n scheduleType = 'channel';\n } else if (args.target_type === 'user') {\n scheduleType = 'dm'; // Sending to a specific user is a DM schedule\n }\n\n // Check permissions\n const workflowName = args.workflow || 'reminder';\n const permissionCheck = checkSchedulePermissions(context, workflowName, scheduleType);\n if (!permissionCheck.allowed) {\n logger.warn(\n `[ScheduleTool] Permission denied for user ${context.userId}: ${permissionCheck.reason}`\n );\n return {\n success: false,\n message: 'Permission denied',\n error: permissionCheck.reason || 'You do not have permission to create this schedule',\n };\n }\n\n // Validate workflow exists if specified and we have available workflows\n if (\n args.workflow &&\n context.availableWorkflows &&\n !context.availableWorkflows.includes(args.workflow)\n ) {\n return {\n success: false,\n message: `Workflow \"${args.workflow}\" not found`,\n error: `Available workflows: ${context.availableWorkflows.slice(0, 5).join(', ')}${context.availableWorkflows.length > 5 ? '...' : ''}`,\n };\n }\n\n try {\n const timezone = context.timezone || 'UTC';\n const isRecurring = args.is_recurring === true || !!args.cron;\n\n // Build output context from AI-provided target info\n let outputContext: ScheduleOutputContext | undefined;\n if (args.target_type && args.target_id) {\n outputContext = {\n type: 'slack', // Currently only Slack supported\n target: args.target_id, // Channel ID (C... or D...)\n threadId: args.thread_ts, // Thread timestamp for replies\n metadata: {\n targetType: args.target_type,\n reminderText: args.reminder_text,\n },\n };\n }\n\n // Calculate next run time\n let nextRunAt: number | undefined;\n if (isRecurring && args.cron) {\n nextRunAt = getNextRunTime(args.cron, timezone).getTime();\n } else if (runAtTimestamp) {\n nextRunAt = runAtTimestamp;\n }\n\n // Create the schedule - AI has done all the parsing\n // workflow is only set when explicitly provided (e.g., from YAML cron jobs)\n // For simple reminders, workflow is undefined and scheduler posts text directly\n const schedule = await store.createAsync({\n creatorId: context.userId,\n creatorContext: context.contextType,\n creatorName: context.userName,\n timezone,\n schedule: args.cron || '',\n runAt: runAtTimestamp,\n isRecurring,\n originalExpression: args.original_expression || args.cron || args.run_at || '',\n workflow: args.workflow, // Only set if explicitly provided\n workflowInputs:\n args.workflow_inputs || (args.reminder_text ? { text: args.reminder_text } : undefined),\n outputContext,\n nextRunAt,\n });\n\n const displayText = args.reminder_text || args.workflow || 'scheduled task';\n logger.info(\n `[ScheduleTool] Created schedule ${schedule.id} for user ${context.userId}: \"${displayText}\"`\n );\n\n return {\n success: true,\n message: formatCreateConfirmation(schedule),\n schedule,\n };\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n logger.warn(`[ScheduleTool] Failed to create schedule: ${errorMsg}`);\n\n return {\n success: false,\n message: `Failed to create schedule: ${errorMsg}`,\n error: errorMsg,\n };\n }\n}\n\n/**\n * Handle list action\n * Users can only see their own schedules to protect privacy\n */\nasync function handleList(\n context: ScheduleToolContext,\n store: ScheduleStore\n): Promise<ScheduleToolResult> {\n // Only show schedules created by this user - protects privacy of personal schedules\n const allUserSchedules = await store.getByCreatorAsync(context.userId);\n const schedules = allUserSchedules.filter(s => s.status !== 'completed');\n\n // If in a specific context, optionally filter to show only relevant schedules\n let filteredSchedules = schedules;\n if (context.allowedScheduleType) {\n // Map schedule output context to schedule type for filtering\n filteredSchedules = schedules.filter(s => {\n const scheduleOutputType = s.outputContext?.type;\n if (!scheduleOutputType || scheduleOutputType === 'none') {\n return context.allowedScheduleType === 'personal';\n }\n if (scheduleOutputType === 'slack') {\n const target = s.outputContext?.target || '';\n if (target.startsWith('#') || target.match(/^C[A-Z0-9]+$/)) {\n return context.allowedScheduleType === 'channel';\n }\n if (target.startsWith('@') || target.match(/^U[A-Z0-9]+$/)) {\n return context.allowedScheduleType === 'dm';\n }\n }\n return context.allowedScheduleType === 'personal';\n });\n }\n\n return {\n success: true,\n message: formatScheduleList(filteredSchedules),\n schedules: filteredSchedules,\n };\n}\n\n/**\n * Handle cancel action\n * Only the creator can cancel their own schedules\n */\nasync function handleCancel(\n args: ScheduleToolArgs,\n context: ScheduleToolContext,\n store: ScheduleStore\n): Promise<ScheduleToolResult> {\n // Try to find by ID - only search in user's own schedules\n let schedule: Schedule | undefined;\n\n if (args.schedule_id) {\n // Only search in schedules created by this user\n const userSchedules = await store.getByCreatorAsync(context.userId);\n\n // Try exact match first\n schedule = userSchedules.find(s => s.id === args.schedule_id);\n\n // Try partial ID match (first 8 chars)\n if (!schedule) {\n schedule = userSchedules.find(s => s.id.startsWith(args.schedule_id!));\n }\n }\n\n if (!schedule) {\n return {\n success: false,\n message: 'Schedule not found',\n error: `Could not find schedule with ID \"${args.schedule_id}\" in your schedules. Use \"list my schedules\" to see your schedules.`,\n };\n }\n\n // Double-check ownership (defensive - already filtered above)\n if (schedule.creatorId !== context.userId) {\n logger.warn(\n `[ScheduleTool] Attempted cross-user schedule cancellation: ${context.userId} tried to cancel ${schedule.id} owned by ${schedule.creatorId}`\n );\n return {\n success: false,\n message: 'Not your schedule',\n error: 'You can only cancel your own schedules.',\n };\n }\n\n // Delete the schedule\n await store.deleteAsync(schedule.id);\n\n logger.info(`[ScheduleTool] Cancelled schedule ${schedule.id} for user ${context.userId}`);\n\n return {\n success: true,\n message: `**Schedule cancelled!**\n\nWas: \"${schedule.workflow}\" scheduled for ${schedule.originalExpression}`,\n };\n}\n\n/**\n * Handle pause/resume actions\n * Only the creator can pause/resume their own schedules\n */\nasync function handlePauseResume(\n args: ScheduleToolArgs,\n context: ScheduleToolContext,\n store: ScheduleStore,\n newStatus: 'active' | 'paused'\n): Promise<ScheduleToolResult> {\n if (!args.schedule_id) {\n return {\n success: false,\n message: 'Missing schedule ID',\n error: 'Please specify which schedule to pause/resume.',\n };\n }\n\n // Only search in schedules created by this user\n const userSchedules = await store.getByCreatorAsync(context.userId);\n\n // Try exact match first\n let schedule = userSchedules.find(s => s.id === args.schedule_id);\n\n // Try partial ID match (first 8 chars)\n if (!schedule) {\n schedule = userSchedules.find(s => s.id.startsWith(args.schedule_id!));\n }\n\n if (!schedule) {\n return {\n success: false,\n message: 'Schedule not found',\n error: `Could not find schedule with ID \"${args.schedule_id}\" in your schedules.`,\n };\n }\n\n // Double-check ownership (defensive - already filtered above)\n if (schedule.creatorId !== context.userId) {\n logger.warn(\n `[ScheduleTool] Attempted cross-user schedule modification: ${context.userId} tried to modify ${schedule.id} owned by ${schedule.creatorId}`\n );\n return {\n success: false,\n message: 'Not your schedule',\n error: 'You can only modify your own schedules.',\n };\n }\n\n // Update status\n const updated = await store.updateAsync(schedule.id, { status: newStatus });\n\n const action = newStatus === 'paused' ? 'paused' : 'resumed';\n logger.info(`[ScheduleTool] ${action} schedule ${schedule.id} for user ${context.userId}`);\n\n return {\n success: true,\n message: `**Schedule ${action}!**\n\n\"${schedule.workflow}\" - ${schedule.originalExpression}`,\n schedule: updated,\n };\n}\n\n/**\n * Get the schedule tool definition for registration with AI providers\n *\n * The AI is responsible for:\n * 1. Extracting the target (channel ID, user ID, thread_ts) from the conversation context\n * 2. Determining if the schedule is recurring or one-time\n * 3. Generating the cron expression OR ISO timestamp\n * 4. Extracting the reminder text or workflow name\n */\nexport function getScheduleToolDefinition(): CustomToolDefinition {\n return {\n name: 'schedule',\n description: `Schedule, list, and manage reminders or workflow executions.\n\nYOU (the AI) must extract and structure all scheduling parameters. Do NOT pass natural language time expressions - convert them to cron or ISO timestamps.\n\nCRITICAL WORKFLOW RULE:\n- To schedule a WORKFLOW, the user MUST use a '%' prefix (e.g., \"schedule %my-workflow daily\").\n- If the '%' prefix is present, extract the word following it as the 'workflow' parameter (without the '%').\n- If the '%' prefix is NOT present, the request is a simple text reminder. The ENTIRE user request (excluding the schedule expression) MUST be placed in the 'reminder_text' parameter.\n- DO NOT guess or infer a workflow name from a user's request without the '%' prefix.\n\nACTIONS:\n- create: Schedule a new reminder or workflow\n- list: Show user's active schedules\n- cancel: Remove a schedule by ID\n- pause/resume: Temporarily disable/enable a schedule\n\nFOR CREATE ACTION - Extract these from user's request:\n1. WHAT:\n - If user says \"schedule %some-workflow ...\", populate 'workflow' with \"some-workflow\".\n - Otherwise, populate 'reminder_text' with the user's full request text.\n2. WHERE: Use the CURRENT channel from context\n - target_id: The channel ID from context (C... for channels, D... for DMs)\n - target_type: \"channel\" for public/private channels, \"dm\" for direct messages\n - ONLY use target_type=\"thread\" with thread_ts if user is INSIDE a thread\n - When NOT in a thread, reminders post as NEW messages (not thread replies)\n3. WHEN: Either cron (for recurring) OR run_at (ISO 8601 for one-time)\n - Recurring: Generate cron expression (minute hour day-of-month month day-of-week)\n - One-time: Generate ISO 8601 timestamp\n\nCRON EXAMPLES:\n- \"every minute\" → cron: \"* * * * *\"\n- \"every hour\" → cron: \"0 * * * *\"\n- \"every day at 9am\" → cron: \"0 9 * * *\"\n- \"every Monday at 9am\" → cron: \"0 9 * * 1\"\n- \"weekdays at 8:30am\" → cron: \"30 8 * * 1-5\"\n- \"every 5 minutes\" → cron: \"*/5 * * * *\"\n\nONE-TIME EXAMPLES:\n- \"in 2 hours\" → run_at: \"<ISO timestamp 2 hours from now>\"\n- \"tomorrow at 3pm\" → run_at: \"2026-02-08T15:00:00Z\"\n\nUSAGE EXAMPLES:\n\nUser in DM: \"remind me to check builds every day at 9am\"\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"check builds\",\n \"is_recurring\": true,\n \"cron\": \"0 9 * * *\",\n \"target_type\": \"dm\",\n \"target_id\": \"<DM channel ID from context, e.g., D09SZABNLG3>\",\n \"original_expression\": \"every day at 9am\"\n }\n\nUser in #security channel: \"schedule %security-scan every Monday at 10am\"\n→ {\n \"action\": \"create\",\n \"workflow\": \"security-scan\",\n \"is_recurring\": true,\n \"cron\": \"0 10 * * 1\",\n \"target_type\": \"channel\",\n \"target_id\": \"<channel ID from context, e.g., C05ABC123>\",\n \"original_expression\": \"every Monday at 10am\"\n }\n\nUser in #security channel: \"run security-scan every Monday at 10am\" (NO % prefix!)\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"run security-scan every Monday at 10am\",\n \"is_recurring\": true,\n \"cron\": \"0 10 * * 1\",\n \"target_type\": \"channel\",\n \"target_id\": \"<channel ID from context, e.g., C05ABC123>\",\n \"original_expression\": \"every Monday at 10am\"\n }\n\nUser in DM: \"remind me in 2 hours to review the PR\"\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"review the PR\",\n \"is_recurring\": false,\n \"run_at\": \"2026-02-07T18:00:00Z\",\n \"target_type\": \"dm\",\n \"target_id\": \"<DM channel ID from context>\",\n \"original_expression\": \"in 2 hours\"\n }\n\nUser inside a thread: \"remind me about this tomorrow\"\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"Check this thread\",\n \"is_recurring\": false,\n \"run_at\": \"2026-02-08T09:00:00Z\",\n \"target_type\": \"thread\",\n \"target_id\": \"<channel ID>\",\n \"thread_ts\": \"<thread_ts from context>\",\n \"original_expression\": \"tomorrow\"\n }\n\nUser: \"list my schedules\"\n→ { \"action\": \"list\" }\n\nUser: \"cancel schedule abc123\"\n→ { \"action\": \"cancel\", \"schedule_id\": \"abc123\" }`,\n inputSchema: {\n type: 'object',\n properties: {\n action: {\n type: 'string',\n enum: ['create', 'list', 'cancel', 'pause', 'resume'],\n description: 'What to do: create new, list existing, cancel/pause/resume by ID',\n },\n // WHAT to do\n reminder_text: {\n type: 'string',\n description: 'For create: the message/reminder text to send when triggered',\n },\n workflow: {\n type: 'string',\n description:\n 'For create: workflow ID to run. ONLY populate this if the user used the % prefix (e.g., \"%my-workflow\"). Extract the name without the % symbol. If no % prefix, use reminder_text instead.',\n },\n workflow_inputs: {\n type: 'object',\n description: 'For create: optional inputs to pass to the workflow',\n },\n // WHERE to send\n target_type: {\n type: 'string',\n enum: ['channel', 'dm', 'thread', 'user'],\n description:\n 'For create: where to send output. channel=public/private channel, dm=DM to self (current DM channel), user=DM to specific user, thread=reply in current thread',\n },\n target_id: {\n type: 'string',\n description:\n 'For create: Slack channel ID. Channels start with C, DMs start with D. Always use the channel ID from the current context.',\n },\n thread_ts: {\n type: 'string',\n description:\n 'For create with target_type=thread: the thread timestamp to reply to. Get this from the current thread context.',\n },\n // WHEN to run\n is_recurring: {\n type: 'boolean',\n description:\n 'For create: true for recurring schedules (cron), false for one-time (run_at)',\n },\n cron: {\n type: 'string',\n description:\n 'For create recurring: cron expression (minute hour day-of-month month day-of-week). Examples: \"0 9 * * *\" (daily 9am), \"* * * * *\" (every minute), \"0 9 * * 1\" (Mondays 9am)',\n },\n run_at: {\n type: 'string',\n description:\n 'For create one-time: ISO 8601 timestamp when to run (e.g., \"2026-02-07T15:00:00Z\")',\n },\n original_expression: {\n type: 'string',\n description:\n 'For create: the original natural language expression from user (for display only)',\n },\n // For cancel/pause/resume\n schedule_id: {\n type: 'string',\n description:\n 'For cancel/pause/resume: the schedule ID to act on (first 8 chars is enough)',\n },\n },\n required: ['action'],\n },\n exec: '', // Not used - this tool has a custom handler\n };\n}\n\n/**\n * Check if this is the schedule tool and should be handled specially\n */\nexport function isScheduleTool(toolName: string): boolean {\n return toolName === 'schedule';\n}\n\n/**\n * Determine schedule type from context\n */\nfunction determineScheduleType(\n contextType: string,\n outputType?: 'slack' | 'github' | 'webhook' | 'none',\n outputTarget?: string\n): ScheduleType {\n // If output is to a Slack channel (starts with # or C), it's a channel schedule\n if (outputType === 'slack' && outputTarget) {\n if (outputTarget.startsWith('#') || outputTarget.match(/^C[A-Z0-9]+$/)) {\n return 'channel';\n }\n // If output is to a Slack user (starts with @ or U), it's a DM schedule\n if (outputTarget.startsWith('@') || outputTarget.match(/^U[A-Z0-9]+$/)) {\n return 'dm';\n }\n }\n\n // CLI and GitHub are personal schedules\n if (contextType === 'cli' || contextType.startsWith('github:')) {\n return 'personal';\n }\n\n // Default to personal for Slack DM context\n return 'personal';\n}\n\n/**\n * Map Slack channel type to allowed schedule type\n */\nfunction slackChannelTypeToScheduleType(channelType: 'channel' | 'dm' | 'group'): ScheduleType {\n switch (channelType) {\n case 'channel':\n return 'channel';\n case 'group':\n return 'dm'; // Group DMs map to 'dm' schedule type\n case 'dm':\n default:\n return 'personal';\n }\n}\n\n/**\n * Build schedule tool context from various sources\n */\nexport function buildScheduleToolContext(\n sources: {\n slackContext?: {\n userId: string;\n userName?: string;\n timezone?: string;\n channelType?: 'channel' | 'dm' | 'group';\n };\n cliContext?: { userId?: string };\n githubContext?: { login: string };\n },\n availableWorkflows?: string[],\n permissions?: SchedulePermissions,\n outputInfo?: { outputType?: 'slack' | 'github' | 'webhook' | 'none'; outputTarget?: string }\n): ScheduleToolContext {\n // Prefer Slack context, then GitHub, then CLI\n if (sources.slackContext) {\n const contextType = `slack:${sources.slackContext.userId}`;\n const scheduleType = determineScheduleType(\n contextType,\n outputInfo?.outputType,\n outputInfo?.outputTarget\n );\n\n // Determine allowed schedule type based on originating Slack context\n // This enforces that from a DM you can only create personal schedules, etc.\n let allowedScheduleType: ScheduleType | undefined;\n if (sources.slackContext.channelType) {\n allowedScheduleType = slackChannelTypeToScheduleType(sources.slackContext.channelType);\n }\n\n // Override schedule type based on Slack channel type if no explicit output\n let finalScheduleType = scheduleType;\n if (!outputInfo?.outputType && sources.slackContext.channelType) {\n finalScheduleType = slackChannelTypeToScheduleType(sources.slackContext.channelType);\n }\n\n return {\n userId: sources.slackContext.userId,\n userName: sources.slackContext.userName,\n contextType,\n timezone: sources.slackContext.timezone,\n availableWorkflows,\n scheduleType: finalScheduleType,\n permissions,\n allowedScheduleType,\n };\n }\n\n if (sources.githubContext) {\n return {\n userId: sources.githubContext.login,\n contextType: `github:${sources.githubContext.login}`,\n timezone: 'UTC', // GitHub doesn't provide timezone\n availableWorkflows,\n scheduleType: 'personal',\n permissions,\n allowedScheduleType: 'personal', // GitHub context only allows personal schedules\n };\n }\n\n // CLI/default context\n return {\n userId: sources.cliContext?.userId || process.env.USER || 'cli-user',\n contextType: 'cli',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',\n availableWorkflows,\n scheduleType: 'personal',\n permissions,\n allowedScheduleType: 'personal', // CLI context only allows personal schedules\n };\n}\n"],"mappings":";;;;;;;;;;AASA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,MAAM,cAAc;AAgD7B,SAAS,QAAQ,UAAiC;AAChD,SAAO;AAAA,IACL,IAAI,SAAS;AAAA,IACb,YAAY,SAAS;AAAA,IACrB,iBAAiB,SAAS,kBAAkB;AAAA,IAC5C,cAAc,SAAS,eAAe;AAAA,IACtC,UAAU,SAAS;AAAA,IACnB,eAAe,SAAS;AAAA,IACxB,QAAQ,SAAS,SAAS;AAAA,IAC1B,cAAc,SAAS,cAAc,IAAI;AAAA,IACzC,qBAAqB,SAAS;AAAA,IAC9B,UAAU,SAAS,YAAY;AAAA,IAC/B,iBAAiB,SAAS,iBAAiB,KAAK,UAAU,SAAS,cAAc,IAAI;AAAA,IACrF,gBAAgB,SAAS,gBAAgB,KAAK,UAAU,SAAS,aAAa,IAAI;AAAA,IAClF,QAAQ,SAAS;AAAA,IACjB,YAAY,SAAS;AAAA,IACrB,aAAa,SAAS,aAAa;AAAA,IACnC,aAAa,SAAS,aAAa;AAAA,IACnC,WAAW,SAAS;AAAA,IACpB,eAAe,SAAS;AAAA,IACxB,YAAY,SAAS,aAAa;AAAA,IAClC,mBAAmB,SAAS,oBAAoB;AAAA,EAClD;AACF;AAEA,SAAS,cAA2B,OAAqC;AACvE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,UAAU,KAA4B;AAC7C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,gBAAgB,IAAI,mBAAmB;AAAA,IACvC,aAAa,IAAI,gBAAgB;AAAA,IACjC,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,OAAO,IAAI,UAAU;AAAA,IACrB,aAAa,IAAI,iBAAiB;AAAA,IAClC,oBAAoB,IAAI;AAAA,IACxB,UAAU,IAAI,YAAY;AAAA,IAC1B,gBAAgB,cAAc,IAAI,eAAe;AAAA,IACjD,eAAe,cAAc,IAAI,cAAc;AAAA,IAC/C,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,WAAW,IAAI,eAAe;AAAA,IAC9B,WAAW,IAAI,eAAe;AAAA,IAC9B,UAAU,IAAI;AAAA,IACd,cAAc,IAAI;AAAA,IAClB,WAAW,IAAI,cAAc;AAAA,IAC7B,kBAAkB,IAAI,qBAAqB;AAAA,EAC7C;AACF;AAvHA,IA4Ha;AA5Hb;AAAA;AAAA;AAYA;AAgHO,IAAM,qBAAN,MAAyD;AAAA,MACtD,KAAkC;AAAA,MAClC;AAAA;AAAA,MAGA,QAAQ,oBAAI,IAAkE;AAAA,MAEtF,YAAY,UAAmB;AAC7B,aAAK,SAAS,YAAY;AAAA,MAC5B;AAAA,MAEA,MAAM,aAA4B;AAEhC,cAAM,eAAe,KAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,MAAM;AAC5D,cAAM,MAAM,KAAK,QAAQ,YAAY;AACrC,WAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAGrC,cAAM,EAAE,cAAc,IAAI,UAAQ,QAAQ;AAC1C,cAAM,iBAAiB,cAAc,UAAU;AAC/C,YAAI;AACJ,YAAI;AACF,qBAAW,eAAe,gBAAgB;AAAA,QAC5C,SAAS,KAAc;AACrB,gBAAM,OAAQ,KAA2B;AACzC,cAAI,SAAS,sBAAsB,SAAS,wBAAwB;AAClE,kBAAM,IAAI;AAAA,cACR;AAAA,YAEF;AAAA,UACF;AACA,gBAAM;AAAA,QACR;AAEA,aAAK,KAAK,IAAI,SAAS,YAAY;AAGnC,aAAK,GAAG,OAAO,oBAAoB;AAGnC,aAAK,cAAc;AAEnB,eAAO,KAAK,gCAAgC,KAAK,MAAM,EAAE;AAAA,MAC3D;AAAA,MAEA,MAAM,WAA0B;AAC9B,YAAI,KAAK,IAAI;AACX,eAAK,GAAG,MAAM;AACd,eAAK,KAAK;AAAA,QACZ;AACA,aAAK,MAAM,MAAM;AAAA,MACnB;AAAA;AAAA,MAIQ,gBAAsB;AAC5B,cAAM,KAAK,KAAK,MAAM;AAEtB,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA2CP;AAAA,MACH;AAAA;AAAA,MAIQ,QAA8B;AACpC,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,IAAI,MAAM,kEAAkE;AAAA,QACpF;AACA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA,MAIA,MAAM,OACJ,UACmB;AACnB,cAAM,KAAK,KAAK,MAAM;AAEtB,cAAM,cAAwB;AAAA,UAC5B,GAAG;AAAA,UACH,IAAI,OAAO;AAAA,UACX,WAAW,KAAK,IAAI;AAAA,UACpB,UAAU;AAAA,UACV,cAAc;AAAA,UACd,QAAQ;AAAA,QACV;AAEA,cAAM,MAAM,QAAQ,WAAW;AAE/B,WAAG;AAAA,UACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAeF,EAAE;AAAA,UACA,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AAEA,eAAO;AAAA,UACL,kCAAkC,YAAY,EAAE,aAAa,YAAY,SAAS;AAAA,QACpF;AAEA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,eAAe,UAAmC;AACtD,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,MAAM,QAAQ,QAAQ;AAE5B,WAAG;AAAA,UACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAeF,EAAE;AAAA,UACA,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AAAA,MACF;AAAA,MAEA,MAAM,IAAI,IAA2C;AACnD,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,MAAM,GAAG,QAAQ,sCAAsC,EAAE,IAAI,EAAE;AAGrE,eAAO,MAAM,UAAU,GAAG,IAAI;AAAA,MAChC;AAAA,MAEA,MAAM,OAAO,IAAY,OAAyD;AAChF,cAAM,KAAK,KAAK,MAAM;AAGtB,cAAM,WAAW,GAAG,QAAQ,sCAAsC,EAAE,IAAI,EAAE;AAG1E,YAAI,CAAC,SAAU,QAAO;AAEtB,cAAM,UAAU,UAAU,QAAQ;AAClC,cAAM,UAAoB,EAAE,GAAG,SAAS,GAAG,OAAO,IAAI,QAAQ,GAAG;AACjE,cAAM,MAAM,QAAQ,OAAO;AAE3B,WAAG;AAAA,UACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASF,EAAE;AAAA,UACA,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AAEA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,OAAO,IAA8B;AACzC,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,SAAS,GAAG,QAAQ,oCAAoC,EAAE,IAAI,EAAE;AACtE,YAAI,OAAO,UAAU,GAAG;AACtB,iBAAO,KAAK,kCAAkC,EAAE,EAAE;AAClD,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT;AAAA;AAAA,MAIA,MAAM,aAAa,WAAwC;AACzD,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,OAAO,GACV,QAAQ,8CAA8C,EACtD,IAAI,SAAS;AAChB,eAAO,KAAK,IAAI,SAAS;AAAA,MAC3B;AAAA,MAEA,MAAM,qBAA0C;AAC9C,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,OAAO,GACV,QAAQ,iDAAiD,EACzD,IAAI;AACP,eAAO,KAAK,IAAI,SAAS;AAAA,MAC3B;AAAA,MAEA,MAAM,gBAAgB,KAAmC;AACvD,cAAM,KAAK,OAAO,KAAK,IAAI;AAC3B,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,OAAO,GACV;AAAA,UACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOF,EACC,IAAI,IAAI,EAAE;AACb,eAAO,KAAK,IAAI,SAAS;AAAA,MAC3B;AAAA,MAEA,MAAM,eAAe,WAAmB,cAA2C;AACjF,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,UAAU,aAAa,YAAY,EAAE,QAAQ,WAAW,MAAM;AACpE,cAAM,UAAU,IAAI,OAAO;AAC3B,cAAM,OAAO,GACV;AAAA,UACC;AAAA;AAAA;AAAA,QAGF,EACC,IAAI,WAAW,OAAO;AACzB,eAAO,KAAK,IAAI,SAAS;AAAA,MAC3B;AAAA,MAEA,MAAM,SAA8B;AAClC,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,OAAO,GAAG,QAAQ,yBAAyB,EAAE,IAAI;AACvD,eAAO,KAAK,IAAI,SAAS;AAAA,MAC3B;AAAA,MAEA,MAAM,WAAwC;AAC5C,cAAM,KAAK,KAAK,MAAM;AAEtB,cAAM,MAAM,GACT;AAAA,UACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASF,EACC,IAAI;AAUP,eAAO;AAAA,UACL,OAAO,IAAI;AAAA,UACX,QAAQ,IAAI;AAAA,UACZ,QAAQ,IAAI;AAAA,UACZ,WAAW,IAAI;AAAA,UACf,QAAQ,IAAI;AAAA,UACZ,WAAW,IAAI;AAAA,UACf,SAAS,IAAI;AAAA,QACf;AAAA,MACF;AAAA,MAEA,MAAM,eACJ,WACA,aACA,QACe;AACf,cAAM,KAAK,KAAK,MAAM;AAGtB,YAAI,OAAO,WAAW;AACpB,gBAAM,MAAM,GAAG,QAAQ,uCAAuC,EAAE,IAAI;AACpE,cAAI,IAAI,OAAO,OAAO,WAAW;AAC/B,kBAAM,IAAI,MAAM,kCAAkC,OAAO,SAAS,GAAG;AAAA,UACvE;AAAA,QACF;AAGA,YAAI,OAAO,YAAY;AACrB,gBAAM,MAAM,GACT,QAAQ,4DAA4D,EACpE,IAAI,SAAS;AAChB,cAAI,IAAI,OAAO,OAAO,YAAY;AAChC,kBAAM,IAAI,MAAM,qDAAqD,OAAO,UAAU,GAAG;AAAA,UAC3F;AAAA,QACF;AAGA,YAAI,eAAe,OAAO,qBAAqB;AAC7C,gBAAM,MAAM,GACT,QAAQ,iFAAiF,EACzF,IAAI,SAAS;AAChB,cAAI,IAAI,OAAO,OAAO,qBAAqB;AACzC,kBAAM,IAAI;AAAA,cACR,+DAA+D,OAAO,mBAAmB;AAAA,YAC3F;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA;AAAA,MAIA,MAAM,eACJ,YACA,QACA,YACwB;AACxB,cAAM,MAAM,KAAK,IAAI;AACrB,cAAM,WAAW,KAAK,MAAM,IAAI,UAAU;AAG1C,YAAI,YAAY,SAAS,YAAY,KAAK;AACxC,cAAI,SAAS,WAAW,QAAQ;AAE9B,mBAAO,SAAS;AAAA,UAClB;AAEA,iBAAO;AAAA,QACT;AAGA,cAAM,QAAQ,OAAO;AACrB,aAAK,MAAM,IAAI,YAAY;AAAA,UACzB;AAAA,UACA;AAAA,UACA,WAAW,MAAM,aAAa;AAAA,QAChC,CAAC;AACD,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,YAAY,YAAoB,WAAkC;AACtE,cAAM,WAAW,KAAK,MAAM,IAAI,UAAU;AAC1C,YAAI,YAAY,SAAS,UAAU,WAAW;AAC5C,eAAK,MAAM,OAAO,UAAU;AAAA,QAC9B;AAAA,MACF;AAAA,MAEA,MAAM,UAAU,YAAoB,WAAmB,YAAsC;AAC3F,cAAM,WAAW,KAAK,MAAM,IAAI,UAAU;AAC1C,YAAI,CAAC,YAAY,SAAS,UAAU,WAAW;AAC7C,iBAAO;AAAA,QACT;AACA,iBAAS,YAAY,KAAK,IAAI,IAAI,aAAa;AAC/C,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,QAAuB;AAAA,MAE7B;AAAA,IACF;AAAA;AAAA;;;ACxjBA,eAAsB,mBACpB,eACA,UAC+B;AAC/B,QAAM,SAAS,eAAe,UAAU;AAExC,UAAQ,QAAQ;AAAA,IACd,KAAK,UAAU;AACb,YAAM,OAAO,eAAe;AAC5B,aAAO,IAAI,mBAAmB,MAAM,QAAQ;AAAA,IAC9C;AAAA,IAEA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,SAAS;AAEZ,UAAI;AAEF,cAAM,aAAa;AAEnB,cAAM,EAAE,2BAA2B,IAAI,MAAM,OAAO;AACpD,eAAO,MAAM,2BAA2B,QAAQ,eAAgB,QAAQ;AAAA,MAC1E,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAO,MAAM,4CAA4C,MAAM,aAAa,GAAG,EAAE;AACjF,cAAM,IAAI;AAAA,UACR,OAAO,MAAM,mJAEQ,GAAG;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,IAEA;AACE,YAAM,IAAI,MAAM,oCAAoC,MAAM,EAAE;AAAA,EAChE;AACF;AAzDA;AAAA;AAAA;AAOA;AAOA;AAAA;AAAA;;;ACNA,OAAOA,SAAQ;AACf,OAAOC,WAAU;AAYjB,eAAsB,qBACpB,UACA,SACiB;AACjB,QAAM,eAAeA,MAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ;AAGzD,MAAI;AACJ,MAAI;AACF,cAAU,MAAMD,IAAG,SAAS,cAAc,OAAO;AAAA,EACnD,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AAEpD,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AAGA,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO,KAAK,kCAAkC,QAAQ,sBAAsB;AAC5E,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,KAAK;AACvB,MAAI,CAAC,MAAM,QAAQ,SAAS,KAAK,UAAU,WAAW,GAAG;AACvD,WAAO,MAAM,wCAAwC;AAErD,UAAM,iBAAiB,YAAY;AACnC,WAAO;AAAA,EACT;AAEA,MAAI,WAAW;AAEf,aAAW,YAAY,WAAW;AAChC,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,KAAK,6CAA6C;AACzD;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,QAAQ,IAAI,SAAS,EAAE;AAC9C,QAAI,UAAU;AACZ,aAAO,MAAM,2BAA2B,SAAS,EAAE,2BAA2B;AAC9E;AAAA,IACF;AAEA,QAAI;AAEF,YAAM,QAAQ,eAAe,QAAQ;AACrC;AAAA,IACF,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,6CAA6C,SAAS,EAAE,KACtD,eAAe,QAAQ,IAAI,UAAU,GACvC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAAiB,YAAY;AAEnC,SAAO,KAAK,2BAA2B,QAAQ,IAAI,UAAU,MAAM,mBAAmB,QAAQ,EAAE;AAChG,SAAO;AACT;AAKA,eAAe,iBAAiB,cAAqC;AACnE,QAAM,eAAe,GAAG,YAAY;AACpC,MAAI;AACF,UAAMA,IAAG,OAAO,cAAc,YAAY;AAC1C,WAAO,KAAK,4BAA4B,YAAY,WAAM,YAAY,EAAE;AAAA,EAC1E,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,mCAAmC,YAAY,KAAK,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,IAC9F;AAAA,EACF;AACF;AAxGA;AAAA;AAAA;AAUA;AAAA;AAAA;;;ACVA,IAiGa;AAjGb;AAAA;AAAA;AAOA;AAOA;AACA;AAkFO,IAAM,gBAAN,MAAM,eAAc;AAAA,MACzB,OAAe;AAAA,MACP,UAAuC;AAAA,MACvC,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,kBAA+C;AAAA,MAE/C,YACN,QACA,QACA,SACA;AACA,aAAK,SAAS,UAAU,CAAC;AACzB,aAAK,SAAS;AAAA,UACZ,YAAY,QAAQ,cAAc;AAAA,UAClC,qBAAqB,QAAQ,uBAAuB;AAAA,UACpD,WAAW,QAAQ,aAAa;AAAA,QAClC;AACA,YAAI,SAAS;AACX,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASA,OAAO,YAAY,QAA8B,QAAwC;AACvF,YAAI,CAAC,eAAc,UAAU;AAC3B,yBAAc,WAAW,IAAI,eAAc,QAAQ,MAAM;AAAA,QAC3D,WAAW,UAAU,QAAQ;AAC3B,iBAAO;AAAA,YACL;AAAA,UAEF;AAAA,QACF;AACA,eAAO,eAAc;AAAA,MACvB;AAAA;AAAA;AAAA;AAAA,MAKA,OAAO,eACL,QACA,QACA,SACe;AACf,eAAO,IAAI,eAAc,QAAQ,QAAQ,OAAO;AAAA,MAClD;AAAA;AAAA;AAAA;AAAA,MAKA,OAAO,gBAAsB;AAC3B,YAAI,eAAc,UAAU;AAE1B,cAAI,eAAc,SAAS,SAAS;AAClC,2BAAc,SAAS,QAAQ,SAAS,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UAC1D;AAAA,QACF;AACA,uBAAc,WAAW;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,aAA4B;AAChC,YAAI,KAAK,aAAa;AACpB;AAAA,QACF;AAGA,YAAI,KAAK,iBAAiB;AACxB,eAAK,UAAU,KAAK;AAAA,QACtB,OAAO;AACL,eAAK,UAAU,MAAM,mBAAmB,KAAK,OAAO,SAAS,KAAK,OAAO,EAAE;AAAA,QAC7E;AAEA,cAAM,KAAK,QAAQ,WAAW;AAG9B,cAAM,WAAW,KAAK,OAAO,QAAQ;AACrC,YAAI;AACF,gBAAM,qBAAqB,UAAU,KAAK,OAAO;AAAA,QACnD,SAAS,KAAK;AACZ,iBAAO;AAAA,YACL,sDACE,eAAe,QAAQ,IAAI,UAAU,GACvC;AAAA,UACF;AAAA,QACF;AAEA,aAAK,cAAc;AAAA,MACrB;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,YACJ,UACmB;AACnB,cAAM,UAAU,KAAK,WAAW;AAChC,cAAM,QAAQ,eAAe,SAAS,WAAW,SAAS,aAAa,KAAK,MAAM;AAClF,eAAO,QAAQ,OAAO,QAAQ;AAAA,MAChC;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,SAAS,IAA2C;AACxD,eAAO,KAAK,WAAW,EAAE,IAAI,EAAE;AAAA,MACjC;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,YAAY,IAAY,OAAyD;AACrF,eAAO,KAAK,WAAW,EAAE,OAAO,IAAI,KAAK;AAAA,MAC3C;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,YAAY,IAA8B;AAC9C,eAAO,KAAK,WAAW,EAAE,OAAO,EAAE;AAAA,MACpC;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,kBAAkB,WAAwC;AAC9D,eAAO,KAAK,WAAW,EAAE,aAAa,SAAS;AAAA,MACjD;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,0BAA+C;AACnD,eAAO,KAAK,WAAW,EAAE,mBAAmB;AAAA,MAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,MAAM,qBAAqB,MAAc,KAAK,IAAI,GAAwB;AACxE,eAAO,KAAK,WAAW,EAAE,gBAAgB,GAAG;AAAA,MAC9C;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,oBAAoB,WAAmB,cAA2C;AACtF,eAAO,KAAK,WAAW,EAAE,eAAe,WAAW,YAAY;AAAA,MACjE;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,gBAA6C;AACjD,eAAO,KAAK,WAAW,EAAE,SAAS;AAAA,MACpC;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,QAAuB;AAC3B,YAAI,KAAK,SAAS;AAChB,gBAAM,KAAK,QAAQ,MAAM;AAAA,QAC3B;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,gBAAyB;AACvB,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKA,oBAA6B;AAC3B,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,cAAmC;AACvC,eAAO,KAAK,WAAW,EAAE,OAAO;AAAA,MAClC;AAAA;AAAA;AAAA;AAAA,MAKA,aAAmC;AACjC,YAAI,CAAC,KAAK,SAAS;AACjB,gBAAM,IAAI,MAAM,2DAA2D;AAAA,QAC7E;AACA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,WAA0B;AAC9B,YAAI,KAAK,SAAS;AAChB,gBAAM,KAAK,QAAQ,SAAS;AAC5B,eAAK,UAAU;AAAA,QACjB;AACA,aAAK,cAAc;AAAA,MACrB;AAAA,IACF;AAAA;AAAA;;;ACqIO,SAAS,eAAe,gBAAwB,YAAoB,OAAa;AAKtF,QAAM,QAAQ,eAAe,MAAM,GAAG;AACtC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,4BAA4B,cAAc,EAAE;AAAA,EAC9D;AAEA,QAAM,CAAC,QAAQ,MAAM,YAAY,OAAO,SAAS,IAAI;AACrD,QAAM,MAAM,oBAAI,KAAK;AAGrB,QAAM,OAAO,IAAI,KAAK,GAAG;AACzB,OAAK,WAAW,GAAG,CAAC;AACpB,OAAK,WAAW,KAAK,WAAW,IAAI,CAAC;AAGrC,QAAM,cAAc,MAAM,KAAK;AAC/B,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,QACE,gBAAgB,KAAK,WAAW,GAAG,MAAM,KACzC,gBAAgB,KAAK,SAAS,GAAG,IAAI,KACrC,gBAAgB,KAAK,QAAQ,GAAG,UAAU,KAC1C,gBAAgB,KAAK,SAAS,IAAI,GAAG,KAAK,KAC1C,gBAAgB,KAAK,OAAO,GAAG,SAAS,GACxC;AACA,aAAO;AAAA,IACT;AACA,SAAK,WAAW,KAAK,WAAW,IAAI,CAAC;AAAA,EACvC;AAGA,QAAM,WAAW,IAAI,KAAK,GAAG;AAC7B,WAAS,QAAQ,SAAS,QAAQ,IAAI,CAAC;AACvC,WAAS,SAAS,SAAS,MAAM,EAAE,KAAK,CAAC;AACzC,WAAS,WAAW,SAAS,QAAQ,EAAE,KAAK,CAAC;AAC7C,WAAS,WAAW,GAAG,CAAC;AACxB,SAAO;AACT;AAKA,SAAS,gBAAgB,OAAe,UAA2B;AACjE,MAAI,aAAa,IAAK,QAAO;AAG7B,MAAI,SAAS,WAAW,IAAI,GAAG;AAC7B,UAAM,OAAO,SAAS,SAAS,MAAM,CAAC,GAAG,EAAE;AAC3C,WAAO,QAAQ,SAAS;AAAA,EAC1B;AAGA,MAAI,SAAS,SAAS,GAAG,GAAG;AAC1B,UAAM,CAAC,OAAO,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,OAAK,SAAS,GAAG,EAAE,CAAC;AACjE,WAAO,SAAS,SAAS,SAAS;AAAA,EACpC;AAGA,MAAI,SAAS,SAAS,GAAG,GAAG;AAC1B,WAAO,SACJ,MAAM,GAAG,EACT,IAAI,OAAK,SAAS,GAAG,EAAE,CAAC,EACxB,SAAS,KAAK;AAAA,EACnB;AAGA,SAAO,SAAS,UAAU,EAAE,MAAM;AACpC;AAcO,SAAS,sBAAsB,MAAuB;AAC3D,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,SAA6B;AAAA,IACjC,CAAC,GAAG,EAAE;AAAA;AAAA,IACN,CAAC,GAAG,EAAE;AAAA;AAAA,IACN,CAAC,GAAG,EAAE;AAAA;AAAA,IACN,CAAC,GAAG,EAAE;AAAA;AAAA,IACN,CAAC,GAAG,CAAC;AAAA;AAAA,EACP;AAEA,SAAO,MAAM,MAAM,CAAC,MAAM,MAAM;AAC9B,QAAI,SAAS,IAAK,QAAO;AACzB,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,YAAM,OAAO,SAAS,KAAK,MAAM,CAAC,GAAG,EAAE;AACvC,aAAO,CAAC,MAAM,IAAI,KAAK,OAAO;AAAA,IAChC;AACA,QAAI,KAAK,SAAS,GAAG,GAAG;AACtB,YAAM,CAAC,OAAO,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,OAAK,SAAS,GAAG,EAAE,CAAC;AAC7D,aAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,GAAG,KAAK,SAAS,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,OAAO,CAAC,EAAE,CAAC;AAAA,IACpF;AACA,QAAI,KAAK,SAAS,GAAG,GAAG;AACtB,aAAO,KAAK,MAAM,GAAG,EAAE,MAAM,OAAK;AAChC,cAAME,OAAM,SAAS,GAAG,EAAE;AAC1B,eAAO,CAAC,MAAMA,IAAG,KAAKA,QAAO,OAAO,CAAC,EAAE,CAAC,KAAKA,QAAO,OAAO,CAAC,EAAE,CAAC;AAAA,MACjE,CAAC;AAAA,IACH;AACA,UAAM,MAAM,SAAS,MAAM,EAAE;AAC7B,WAAO,CAAC,MAAM,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,OAAO,CAAC,EAAE,CAAC;AAAA,EACjE,CAAC;AACH;AApjBA;AAAA;AAAA;AAAA;AAAA;;;ACaA,SAAS,iBAAiB,SAAiB,OAAwB;AAEjE,QAAM,eAAe,QAClB,QAAQ,qBAAqB,MAAM,EACnC,QAAQ,OAAO,IAAI,EACnB,QAAQ,OAAO,GAAG;AACrB,SAAO,IAAI,OAAO,IAAI,YAAY,GAAG,EAAE,KAAK,KAAK;AACnD;AAKA,SAAS,4BACP,UACA,iBACA,gBACuC;AAEvC,MAAI,kBAAkB,eAAe,SAAS,GAAG;AAC/C,eAAW,WAAW,gBAAgB;AACpC,UAAI,iBAAiB,SAAS,QAAQ,GAAG;AACvC,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ,aAAa,QAAQ,6BAA6B,OAAO;AAAA,QACnE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AACjD,eAAW,WAAW,iBAAiB;AACrC,UAAI,iBAAiB,SAAS,QAAQ,GAAG;AACvC,eAAO,EAAE,SAAS,KAAK;AAAA,MACzB;AAAA,IACF;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,aAAa,QAAQ,0CAA0C,gBAAgB,KAAK,IAAI,CAAC;AAAA,IACnG;AAAA,EACF;AAGA,SAAO,EAAE,SAAS,KAAK;AACzB;AAKA,SAAS,yBACP,SACA,UACA,uBACuC;AACvC,QAAM,cAAc,QAAQ;AAC5B,QAAM,eAAe,yBAAyB,QAAQ,gBAAgB;AAMtE,MAAI,QAAQ,uBAAuB,iBAAiB,QAAQ,qBAAqB;AAC/E,UAAM,eAA6C;AAAA,MACjD,UAAU;AAAA,MACV,SAAS;AAAA,MACT,IAAI;AAAA,IACN;AACA,UAAM,cAA4C;AAAA,MAChD,UAAU;AAAA,MACV,SAAS;AAAA,MACT,IAAI;AAAA,IACN;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,QAAQ,aAAa,QAAQ,mBAAmB,CAAC,yBAAyB,YAAY,QAAQ,mBAAmB,CAAC,2BAA2B,YAAY,YAAY,CAAC;AAAA,IAChL;AAAA,EACF;AAGA,MAAI,CAAC,aAAa;AAChB,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAGA,UAAQ,cAAc;AAAA,IACpB,KAAK;AACH,UAAI,YAAY,kBAAkB,OAAO;AACvC,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AACA;AAAA,IACF,KAAK;AACH,UAAI,YAAY,iBAAiB,OAAO;AACtC,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AACA;AAAA,IACF,KAAK;AACH,UAAI,YAAY,YAAY,OAAO;AACjC,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AACA;AAAA,EACJ;AAGA,SAAO;AAAA,IACL;AAAA,IACA,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AACF;AA4GA,SAAS,eAAe,UAA4B;AAClD,QAAM,OAAO,SAAS,cAClB,SAAS,qBACT,IAAI,KAAK,SAAS,KAAM,EAAE,eAAe;AAC7C,QAAM,SAAS,SAAS,WAAW,WAAW,KAAK,SAAS,MAAM,MAAM;AAExE,QAAM,cACJ,SAAS,YAAa,SAAS,gBAAgB,QAAmB;AACpE,QAAM,gBACJ,YAAY,SAAS,KAAK,YAAY,UAAU,GAAG,EAAE,IAAI,QAAQ;AACnE,QAAM,SAAS,SAAS,eAAe,QAAQ;AAE/C,SAAO,KAAK,SAAS,GAAG,UAAU,GAAG,CAAC,CAAC,SAAS,aAAa,OAAO,IAAI,YAAO,MAAM,IAAI,MAAM;AACjG;AAKA,SAAS,yBAAyB,UAA4B;AAC5D,QAAM,aAAa,SAAS,eAAe,OACvC,GAAG,SAAS,cAAc,IAAI,GAAG,SAAS,cAAc,SAAS,IAAI,SAAS,cAAc,MAAM,KAAK,EAAE,KACzG;AAGJ,QAAM,cACJ,SAAS,YAAa,SAAS,gBAAgB,QAAmB;AAEpE,MAAI,SAAS,aAAa;AACxB,UAAM,UAAU,SAAS,YACrB,IAAI,KAAK,SAAS,SAAS,EAAE,eAAe,SAAS;AAAA,MACnD,SAAS;AAAA,MACT,OAAO;AAAA,MACP,KAAK;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC,IACD;AAEJ,WAAO;AAAA;AAAA,IAEP,SAAS,WAAW,aAAa,UAAU,OAAO,WAAW;AAAA,YACrD,SAAS,kBAAkB;AAAA,cACzB,UAAU;AAAA,gBACR,OAAO;AAAA;AAAA,QAEf,SAAS,GAAG,UAAU,GAAG,CAAC,CAAC;AAAA,EACjC,OAAO;AACL,UAAM,OAAO,IAAI,KAAK,SAAS,KAAM,EAAE,eAAe,SAAS;AAAA,MAC7D,SAAS;AAAA,MACT,OAAO;AAAA,MACP,KAAK;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AAED,WAAO;AAAA;AAAA,IAEP,SAAS,WAAW,aAAa,UAAU,OAAO,WAAW;AAAA,YACrD,IAAI;AAAA,cACF,UAAU;AAAA;AAAA,QAEhB,SAAS,GAAG,UAAU,GAAG,CAAC,CAAC;AAAA,EACjC;AACF;AAKA,SAAS,mBAAmB,WAA+B;AACzD,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO;AAAA;AAAA;AAAA,EAGT;AAEA,QAAM,QAAQ,UAAU,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,eAAe,CAAC,CAAC,EAAE;AAEtE,SAAO;AAAA;AAAA,EAEP,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAIlB;AAKA,eAAsB,qBACpB,MACA,SAC6B;AAC7B,QAAM,QAAQ,cAAc,YAAY;AAGxC,MAAI,CAAC,MAAM,cAAc,GAAG;AAC1B,UAAM,MAAM,WAAW;AAAA,EACzB;AAEA,UAAQ,KAAK,QAAQ;AAAA,IACnB,KAAK;AACH,aAAO,aAAa,MAAM,SAAS,KAAK;AAAA,IAE1C,KAAK;AACH,aAAO,WAAW,SAAS,KAAK;AAAA,IAElC,KAAK;AACH,aAAO,aAAa,MAAM,SAAS,KAAK;AAAA,IAE1C,KAAK;AACH,aAAO,kBAAkB,MAAM,SAAS,OAAO,QAAQ;AAAA,IAEzD,KAAK;AACH,aAAO,kBAAkB,MAAM,SAAS,OAAO,QAAQ;AAAA,IAEzD;AACE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,mBAAmB,KAAK,MAAM;AAAA,QACvC,OAAO;AAAA,MACT;AAAA,EACJ;AACF;AAKA,eAAe,aACb,MACA,SACA,OAC6B;AAE7B,MAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,UAAU;AACzC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,QAAQ;AAC9B,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OACE;AAAA,IACJ;AAAA,EACF;AAGA,MAAI,KAAK,QAAQ,CAAC,sBAAsB,KAAK,IAAI,GAAG;AAClD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO,IAAI,KAAK,IAAI;AAAA,IACtB;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,KAAK,QAAQ;AACf,UAAM,SAAS,IAAI,KAAK,KAAK,MAAM;AACnC,QAAI,MAAM,OAAO,QAAQ,CAAC,GAAG;AAC3B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO,IAAI,KAAK,MAAM;AAAA,MACxB;AAAA,IACF;AACA,QAAI,OAAO,QAAQ,KAAK,KAAK,IAAI,GAAG;AAClC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AACA,qBAAiB,OAAO,QAAQ;AAAA,EAClC;AAGA,MAAI,KAAK,eAAe,CAAC,KAAK,WAAW;AACvC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO,gBAAgB,KAAK,WAAW;AAAA,IACzC;AAAA,EACF;AAIA,MAAI,eAA6B;AACjC,MAAI,KAAK,gBAAgB,WAAW;AAClC,mBAAe;AAAA,EACjB,WAAW,KAAK,gBAAgB,QAAQ;AACtC,mBAAe;AAAA,EACjB;AAGA,QAAM,eAAe,KAAK,YAAY;AACtC,QAAM,kBAAkB,yBAAyB,SAAS,cAAc,YAAY;AACpF,MAAI,CAAC,gBAAgB,SAAS;AAC5B,WAAO;AAAA,MACL,6CAA6C,QAAQ,MAAM,KAAK,gBAAgB,MAAM;AAAA,IACxF;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO,gBAAgB,UAAU;AAAA,IACnC;AAAA,EACF;AAGA,MACE,KAAK,YACL,QAAQ,sBACR,CAAC,QAAQ,mBAAmB,SAAS,KAAK,QAAQ,GAClD;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,aAAa,KAAK,QAAQ;AAAA,MACnC,OAAO,wBAAwB,QAAQ,mBAAmB,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,QAAQ,mBAAmB,SAAS,IAAI,QAAQ,EAAE;AAAA,IACvI;AAAA,EACF;AAEA,MAAI;AACF,UAAM,WAAW,QAAQ,YAAY;AACrC,UAAM,cAAc,KAAK,iBAAiB,QAAQ,CAAC,CAAC,KAAK;AAGzD,QAAI;AACJ,QAAI,KAAK,eAAe,KAAK,WAAW;AACtC,sBAAgB;AAAA,QACd,MAAM;AAAA;AAAA,QACN,QAAQ,KAAK;AAAA;AAAA,QACb,UAAU,KAAK;AAAA;AAAA,QACf,UAAU;AAAA,UACR,YAAY,KAAK;AAAA,UACjB,cAAc,KAAK;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,eAAe,KAAK,MAAM;AAC5B,kBAAY,eAAe,KAAK,MAAM,QAAQ,EAAE,QAAQ;AAAA,IAC1D,WAAW,gBAAgB;AACzB,kBAAY;AAAA,IACd;AAKA,UAAM,WAAW,MAAM,MAAM,YAAY;AAAA,MACvC,WAAW,QAAQ;AAAA,MACnB,gBAAgB,QAAQ;AAAA,MACxB,aAAa,QAAQ;AAAA,MACrB;AAAA,MACA,UAAU,KAAK,QAAQ;AAAA,MACvB,OAAO;AAAA,MACP;AAAA,MACA,oBAAoB,KAAK,uBAAuB,KAAK,QAAQ,KAAK,UAAU;AAAA,MAC5E,UAAU,KAAK;AAAA;AAAA,MACf,gBACE,KAAK,oBAAoB,KAAK,gBAAgB,EAAE,MAAM,KAAK,cAAc,IAAI;AAAA,MAC/E;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,cAAc,KAAK,iBAAiB,KAAK,YAAY;AAC3D,WAAO;AAAA,MACL,mCAAmC,SAAS,EAAE,aAAa,QAAQ,MAAM,MAAM,WAAW;AAAA,IAC5F;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,yBAAyB,QAAQ;AAAA,MAC1C;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU;AAC1D,WAAO,KAAK,6CAA6C,QAAQ,EAAE;AAEnE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,8BAA8B,QAAQ;AAAA,MAC/C,OAAO;AAAA,IACT;AAAA,EACF;AACF;AAMA,eAAe,WACb,SACA,OAC6B;AAE7B,QAAM,mBAAmB,MAAM,MAAM,kBAAkB,QAAQ,MAAM;AACrE,QAAM,YAAY,iBAAiB,OAAO,OAAK,EAAE,WAAW,WAAW;AAGvE,MAAI,oBAAoB;AACxB,MAAI,QAAQ,qBAAqB;AAE/B,wBAAoB,UAAU,OAAO,OAAK;AACxC,YAAM,qBAAqB,EAAE,eAAe;AAC5C,UAAI,CAAC,sBAAsB,uBAAuB,QAAQ;AACxD,eAAO,QAAQ,wBAAwB;AAAA,MACzC;AACA,UAAI,uBAAuB,SAAS;AAClC,cAAM,SAAS,EAAE,eAAe,UAAU;AAC1C,YAAI,OAAO,WAAW,GAAG,KAAK,OAAO,MAAM,cAAc,GAAG;AAC1D,iBAAO,QAAQ,wBAAwB;AAAA,QACzC;AACA,YAAI,OAAO,WAAW,GAAG,KAAK,OAAO,MAAM,cAAc,GAAG;AAC1D,iBAAO,QAAQ,wBAAwB;AAAA,QACzC;AAAA,MACF;AACA,aAAO,QAAQ,wBAAwB;AAAA,IACzC,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,mBAAmB,iBAAiB;AAAA,IAC7C,WAAW;AAAA,EACb;AACF;AAMA,eAAe,aACb,MACA,SACA,OAC6B;AAE7B,MAAI;AAEJ,MAAI,KAAK,aAAa;AAEpB,UAAM,gBAAgB,MAAM,MAAM,kBAAkB,QAAQ,MAAM;AAGlE,eAAW,cAAc,KAAK,OAAK,EAAE,OAAO,KAAK,WAAW;AAG5D,QAAI,CAAC,UAAU;AACb,iBAAW,cAAc,KAAK,OAAK,EAAE,GAAG,WAAW,KAAK,WAAY,CAAC;AAAA,IACvE;AAAA,EACF;AAEA,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO,oCAAoC,KAAK,WAAW;AAAA,IAC7D;AAAA,EACF;AAGA,MAAI,SAAS,cAAc,QAAQ,QAAQ;AACzC,WAAO;AAAA,MACL,8DAA8D,QAAQ,MAAM,oBAAoB,SAAS,EAAE,aAAa,SAAS,SAAS;AAAA,IAC5I;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,MAAM,YAAY,SAAS,EAAE;AAEnC,SAAO,KAAK,qCAAqC,SAAS,EAAE,aAAa,QAAQ,MAAM,EAAE;AAEzF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA;AAAA,QAEL,SAAS,QAAQ,mBAAmB,SAAS,kBAAkB;AAAA,EACrE;AACF;AAMA,eAAe,kBACb,MACA,SACA,OACA,WAC6B;AAC7B,MAAI,CAAC,KAAK,aAAa;AACrB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,MAAM,kBAAkB,QAAQ,MAAM;AAGlE,MAAI,WAAW,cAAc,KAAK,OAAK,EAAE,OAAO,KAAK,WAAW;AAGhE,MAAI,CAAC,UAAU;AACb,eAAW,cAAc,KAAK,OAAK,EAAE,GAAG,WAAW,KAAK,WAAY,CAAC;AAAA,EACvE;AAEA,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO,oCAAoC,KAAK,WAAW;AAAA,IAC7D;AAAA,EACF;AAGA,MAAI,SAAS,cAAc,QAAQ,QAAQ;AACzC,WAAO;AAAA,MACL,8DAA8D,QAAQ,MAAM,oBAAoB,SAAS,EAAE,aAAa,SAAS,SAAS;AAAA,IAC5I;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,UAAU,MAAM,MAAM,YAAY,SAAS,IAAI,EAAE,QAAQ,UAAU,CAAC;AAE1E,QAAM,SAAS,cAAc,WAAW,WAAW;AACnD,SAAO,KAAK,kBAAkB,MAAM,aAAa,SAAS,EAAE,aAAa,QAAQ,MAAM,EAAE;AAEzF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,cAAc,MAAM;AAAA;AAAA,GAE9B,SAAS,QAAQ,OAAO,SAAS,kBAAkB;AAAA,IAClD,UAAU;AAAA,EACZ;AACF;AAWO,SAAS,4BAAkD;AAChE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAwGb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM,CAAC,UAAU,QAAQ,UAAU,SAAS,QAAQ;AAAA,UACpD,aAAa;AAAA,QACf;AAAA;AAAA,QAEA,eAAe;AAAA,UACb,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,UAAU;AAAA,UACR,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,QACA,iBAAiB;AAAA,UACf,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA;AAAA,QAEA,aAAa;AAAA,UACX,MAAM;AAAA,UACN,MAAM,CAAC,WAAW,MAAM,UAAU,MAAM;AAAA,UACxC,aACE;AAAA,QACJ;AAAA,QACA,WAAW;AAAA,UACT,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,QACA,WAAW;AAAA,UACT,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA;AAAA,QAEA,cAAc;AAAA,UACZ,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,QACA,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,QACA,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,QACA,qBAAqB;AAAA,UACnB,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA;AAAA,QAEA,aAAa;AAAA,UACX,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,UAAU,CAAC,QAAQ;AAAA,IACrB;AAAA,IACA,MAAM;AAAA;AAAA,EACR;AACF;AAKO,SAAS,eAAe,UAA2B;AACxD,SAAO,aAAa;AACtB;AAKA,SAAS,sBACP,aACA,YACA,cACc;AAEd,MAAI,eAAe,WAAW,cAAc;AAC1C,QAAI,aAAa,WAAW,GAAG,KAAK,aAAa,MAAM,cAAc,GAAG;AACtE,aAAO;AAAA,IACT;AAEA,QAAI,aAAa,WAAW,GAAG,KAAK,aAAa,MAAM,cAAc,GAAG;AACtE,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,gBAAgB,SAAS,YAAY,WAAW,SAAS,GAAG;AAC9D,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAKA,SAAS,+BAA+B,aAAuD;AAC7F,UAAQ,aAAa;AAAA,IACnB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAKO,SAAS,yBACd,SAUA,oBACA,aACA,YACqB;AAErB,MAAI,QAAQ,cAAc;AACxB,UAAM,cAAc,SAAS,QAAQ,aAAa,MAAM;AACxD,UAAM,eAAe;AAAA,MACnB;AAAA,MACA,YAAY;AAAA,MACZ,YAAY;AAAA,IACd;AAIA,QAAI;AACJ,QAAI,QAAQ,aAAa,aAAa;AACpC,4BAAsB,+BAA+B,QAAQ,aAAa,WAAW;AAAA,IACvF;AAGA,QAAI,oBAAoB;AACxB,QAAI,CAAC,YAAY,cAAc,QAAQ,aAAa,aAAa;AAC/D,0BAAoB,+BAA+B,QAAQ,aAAa,WAAW;AAAA,IACrF;AAEA,WAAO;AAAA,MACL,QAAQ,QAAQ,aAAa;AAAA,MAC7B,UAAU,QAAQ,aAAa;AAAA,MAC/B;AAAA,MACA,UAAU,QAAQ,aAAa;AAAA,MAC/B;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,eAAe;AACzB,WAAO;AAAA,MACL,QAAQ,QAAQ,cAAc;AAAA,MAC9B,aAAa,UAAU,QAAQ,cAAc,KAAK;AAAA,MAClD,UAAU;AAAA;AAAA,MACV;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA,qBAAqB;AAAA;AAAA,IACvB;AAAA,EACF;AAGA,SAAO;AAAA,IACL,QAAQ,QAAQ,YAAY,UAAU,QAAQ,IAAI,QAAQ;AAAA,IAC1D,aAAa;AAAA,IACb,UAAU,KAAK,eAAe,EAAE,gBAAgB,EAAE,YAAY;AAAA,IAC9D;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA,qBAAqB;AAAA;AAAA,EACvB;AACF;AA/+BA;AAAA;AAKA;AACA;AACA;AAAA;AAAA;","names":["fs","path","val"]}
|