crontick 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/chunk-35FFLWP3.js +79 -0
  4. package/dist/chunk-35FFLWP3.js.map +1 -0
  5. package/dist/chunk-FMGZ3SSS.js +57 -0
  6. package/dist/chunk-FMGZ3SSS.js.map +1 -0
  7. package/dist/chunk-LPAFZNXK.js +214 -0
  8. package/dist/chunk-LPAFZNXK.js.map +1 -0
  9. package/dist/chunk-YMAT5MON.js +22 -0
  10. package/dist/chunk-YMAT5MON.js.map +1 -0
  11. package/dist/cli/index.cjs +879 -0
  12. package/dist/cli/index.cjs.map +1 -0
  13. package/dist/cli/index.d.cts +2 -0
  14. package/dist/cli/index.d.ts +2 -0
  15. package/dist/cli/index.js +554 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/daemon/index.cjs +1655 -0
  18. package/dist/daemon/index.cjs.map +1 -0
  19. package/dist/daemon/index.d.cts +2 -0
  20. package/dist/daemon/index.d.ts +2 -0
  21. package/dist/daemon/index.js +1306 -0
  22. package/dist/daemon/index.js.map +1 -0
  23. package/dist/dashboard/.gitkeep +0 -0
  24. package/dist/dashboard/dashboard.css +192 -0
  25. package/dist/dashboard/dashboard.js +316 -0
  26. package/dist/dashboard/index.html +108 -0
  27. package/dist/index.cjs +180 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +121 -0
  30. package/dist/index.d.ts +121 -0
  31. package/dist/index.js +32 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/job-JQGLDEJV.js +26 -0
  34. package/dist/job-JQGLDEJV.js.map +1 -0
  35. package/dist/mcp/index.cjs +1000 -0
  36. package/dist/mcp/index.cjs.map +1 -0
  37. package/dist/mcp/index.d.cts +13 -0
  38. package/dist/mcp/index.d.ts +13 -0
  39. package/dist/mcp/index.js +876 -0
  40. package/dist/mcp/index.js.map +1 -0
  41. package/package.json +81 -0
  42. package/plugin/.gitkeep +0 -0
  43. package/plugin/README.md +50 -0
  44. package/plugin/install.mjs +155 -0
  45. package/plugin/plugin.json +11 -0
  46. package/plugin/uninstall.mjs +68 -0
  47. package/src/skill/SKILL.md +227 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/mcp/index.ts"],"sourcesContent":["/**\n * crontick MCP server — stdio transport.\n * Thin adapter over the local daemon HTTP API.\n */\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { readFileSync, existsSync, appendFileSync, statSync, mkdirSync } from 'node:fs';\nimport { spawn } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve as pathResolve, join as pathJoin } from 'node:path';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport { portFilePath, pidFilePath, logsDir, ensureDirs } from '../paths.js';\nimport { VERSION } from '../version.js';\nimport {\n JobSchema,\n ScheduleSchema,\n ActionSchema,\n} from '../schemas/job.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// ── Daemon URL resolution ─────────────────────────────────────────────────────\n\nfunction getDaemonBaseUrl(): string {\n const envUrl = process.env['CRONTICK_DAEMON_URL'];\n if (envUrl) return envUrl.replace(/\\/$/, '');\n const pf = portFilePath();\n if (!existsSync(pf)) throw new DaemonUnavailableError('Port file not found.');\n const port = parseInt(readFileSync(pf, 'utf-8').trim(), 10);\n if (isNaN(port) || port <= 0) throw new DaemonUnavailableError('Invalid port in port file.');\n return `http://127.0.0.1:${port}`;\n}\n\nclass DaemonUnavailableError extends Error {\n constructor(detail: string) {\n super(\n `Daemon is not running (${detail}) — run: crontick daemon start`,\n );\n this.name = 'DaemonUnavailableError';\n }\n}\n\nfunction daemonScript(): string {\n return pathResolve(__dirname, '../daemon/index.js');\n}\n\nfunction appendAutostartLog(text: string): void {\n try {\n mkdirSync(logsDir(), { recursive: true });\n const logPath = pathJoin(logsDir(), 'daemon.autostart.log');\n let size = 0;\n try { size = statSync(logPath).size; } catch { /* new file */ }\n if (size < 256 * 1024) {\n appendFileSync(logPath, `[${new Date().toISOString()}] ${text}\\n`);\n }\n } catch { /* ignore log errors */ }\n}\n\nfunction waitForPortFile(maxMs = 10_000, getStderr?: () => string): Promise<void> {\n const start = Date.now();\n return new Promise((resolve, reject) => {\n const check = () => {\n if (existsSync(portFilePath())) {\n // Verify it's a valid port\n try {\n const port = parseInt(readFileSync(portFilePath(), 'utf-8').trim(), 10);\n if (!isNaN(port) && port > 0) return resolve();\n } catch { /* retry */ }\n }\n if (Date.now() - start > maxMs) {\n const stderr = getStderr?.() ?? '';\n const hint = stderr ? `\\nAutostart stderr: ${stderr.slice(0, 500)}` : '';\n return reject(new DaemonUnavailableError(`Timed out waiting for daemon to start.${hint}`));\n }\n setTimeout(check, 200);\n };\n check();\n });\n}\n\nasync function ensureDaemon(): Promise<void> {\n // Check if already running\n try {\n const base = getDaemonBaseUrl();\n const res = await fetch(`${base}/health`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) return;\n } catch { /* not running or not responding */ }\n\n if (process.env['CRONTICK_MCP_NO_AUTOSTART'] === '1') {\n throw new DaemonUnavailableError(\n 'CRONTICK_MCP_NO_AUTOSTART=1 is set — start the daemon manually: crontick daemon start',\n );\n }\n\n // Auto-start\n ensureDirs();\n const script = daemonScript();\n if (!existsSync(script)) {\n throw new DaemonUnavailableError(\n `Daemon script not found. Run: npm run build`,\n );\n }\n const stderrChunks: Buffer[] = [];\n const child = spawn(process.execPath, [script], {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n env: process.env,\n });\n if (child.stderr) {\n child.stderr.on('data', (chunk: Buffer) => {\n if (Buffer.concat(stderrChunks).length < 4096) stderrChunks.push(chunk);\n });\n // unref so the MCP process can exit even if the daemon stderr pipe stays open\n (child.stderr as NodeJS.ReadableStream & { unref?(): void }).unref?.();\n }\n child.unref();\n\n const getStderr = (): string => {\n const raw = Buffer.concat(stderrChunks).toString('utf-8');\n return raw.length > 4096 ? raw.slice(0, 4096) + '…' : raw;\n };\n\n try {\n await waitForPortFile(10_000, getStderr);\n } catch (err) {\n const stderr = getStderr();\n if (stderr) appendAutostartLog(`ensureDaemon failed:\\n${stderr}`);\n throw err;\n }\n}\n\n// ── API client ────────────────────────────────────────────────────────────────\n\nasync function callDaemon(method: string, path: string, body?: unknown): Promise<unknown> {\n const base = getDaemonBaseUrl();\n const res = await fetch(`${base}${path}`, {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: AbortSignal.timeout(30_000),\n });\n const text = await res.text();\n let data: unknown;\n try {\n data = JSON.parse(text);\n } catch {\n throw new Error(`Daemon returned non-JSON: ${text.slice(0, 200)}`);\n }\n if (!res.ok) {\n const err = (data as { error?: { code?: string; message?: string } })?.error;\n throw new Error(`[${err?.code ?? 'API_ERROR'}] ${err?.message ?? `HTTP ${res.status}`}`);\n }\n return data;\n}\n\n// ── Tool result helpers ───────────────────────────────────────────────────────\n\ntype ToolResult = {\n content: Array<{ type: 'text'; text: string }>;\n isError?: boolean;\n};\n\nfunction okResult(data: unknown): ToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };\n}\n\n/** Redact sensitive details before returning errors to the LLM. Exported for testing. */\nexport function redactForLlm(msg: string): string {\n return msg\n // Loopback address:port\n .replace(/127\\.0\\.0\\.1:\\d+/g, '<daemon-addr>')\n // Windows absolute paths: C:\\foo\\bar (must have at least one separator)\n .replace(/[A-Za-z]:\\\\[^\\s\"']+/g, '<path>')\n // POSIX absolute paths: only when preceded by start-of-string, whitespace,\n // (, [, or a quote — to avoid matching /path inside http://host/path URLs.\n .replace(/(^|[\\s([\"'])\\/(?:[^\\s\"'/]+\\/)+[^\\s\"'/]+/g, '$1<path>');\n}\n\nfunction errResult(err: unknown): ToolResult {\n const msg = err instanceof Error ? err.message : String(err);\n const redacted = redactForLlm(msg);\n return {\n content: [{ type: 'text', text: JSON.stringify({ error: redacted }, null, 2) }],\n isError: true,\n };\n}\n\nasync function toolWrap(fn: () => Promise<unknown>): Promise<ToolResult> {\n try {\n await ensureDaemon();\n const result = await fn();\n return okResult(result);\n } catch (err) {\n return errResult(err);\n }\n}\n\n// ── MCP server setup ──────────────────────────────────────────────────────────\n\nconst kebabCase = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport function createMcpServer(): McpServer {\n const server = new McpServer({\n name: 'crontick',\n version: VERSION,\n });\n\n // ── Jobs ──────────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_job_create',\n {\n description:\n 'Create and schedule a new cron job. Provide the full job definition including id, schedule (kind: cron|interval|one-shot), and action (kind: script|exec). Validate the schedule first with crontick_schedule_validate.',\n inputSchema: {\n id: z.string().regex(kebabCase, 'Job ID must be kebab-case (e.g. \"my-job\")'),\n description: z.string().optional(),\n enabled: z.boolean().optional(),\n schedule: ScheduleSchema,\n action: ActionSchema,\n catchup: z.enum(['run-once', 'run-all', 'skip']).optional(),\n overlap: z.enum(['skip', 'queue', 'cancel-previous']).optional(),\n retry: z\n .object({ max: z.number().int().min(0), backoffSec: z.number().positive() })\n .optional(),\n budgets: z\n .object({\n maxRunsPerDay: z.number().int().positive().nullable(),\n maxTokensPerRun: z.number().int().positive().nullable(),\n })\n .optional(),\n },\n },\n async (args) => toolWrap(() => callDaemon('POST', '/api/jobs', args)),\n );\n\n server.registerTool(\n 'crontick_job_list',\n {\n description: 'List all scheduled jobs with their current status and next run time.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('GET', '/api/jobs')),\n );\n\n server.registerTool(\n 'crontick_job_get',\n {\n description: 'Get the full definition and status of a specific job by ID.',\n inputSchema: { id: z.string() },\n },\n async (args) =>\n toolWrap(() => callDaemon('GET', `/api/jobs/${encodeURIComponent(args.id)}`)),\n );\n\n server.registerTool(\n 'crontick_job_update',\n {\n description:\n 'Update an existing job. Provide the job ID and any fields to change (partial update is merged with existing definition).',\n inputSchema: {\n id: z.string(),\n description: z.string().optional(),\n enabled: z.boolean().optional(),\n schedule: ScheduleSchema.optional(),\n action: ActionSchema.optional(),\n catchup: z.enum(['run-once', 'run-all', 'skip']).optional(),\n overlap: z.enum(['skip', 'queue', 'cancel-previous']).optional(),\n retry: z\n .object({ max: z.number().int().min(0), backoffSec: z.number().positive() })\n .optional(),\n budgets: z\n .object({\n maxRunsPerDay: z.number().int().positive().nullable(),\n maxTokensPerRun: z.number().int().positive().nullable(),\n })\n .optional(),\n },\n },\n async (args) => {\n const { id, ...patch } = args;\n return toolWrap(() =>\n callDaemon('PUT', `/api/jobs/${encodeURIComponent(id)}`, patch),\n );\n },\n );\n\n server.registerTool(\n 'crontick_job_delete',\n {\n description:\n 'Permanently delete a job and all its run history. This cannot be undone — confirm with the user first.',\n inputSchema: { id: z.string() },\n },\n async (args) =>\n toolWrap(() => callDaemon('DELETE', `/api/jobs/${encodeURIComponent(args.id)}`)),\n );\n\n server.registerTool(\n 'crontick_job_enable',\n {\n description: 'Enable a disabled job so it will run on its next scheduled time.',\n inputSchema: { id: z.string() },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('POST', `/api/jobs/${encodeURIComponent(args.id)}/enable`),\n ),\n );\n\n server.registerTool(\n 'crontick_job_disable',\n {\n description: 'Disable a job so it will not run until re-enabled.',\n inputSchema: { id: z.string() },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('POST', `/api/jobs/${encodeURIComponent(args.id)}/disable`),\n ),\n );\n\n server.registerTool(\n 'crontick_job_run_now',\n {\n description:\n 'Trigger an immediate run of a job, bypassing its schedule. Returns a runId to track progress with crontick_run_get.',\n inputSchema: { id: z.string() },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('POST', `/api/jobs/${encodeURIComponent(args.id)}/run`),\n ),\n );\n\n server.registerTool(\n 'crontick_job_cancel_run',\n {\n description: 'Cancel an in-progress run by its run ID.',\n inputSchema: { runId: z.string() },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('POST', `/api/runs/${encodeURIComponent(args.runId)}/cancel`),\n ),\n );\n\n // ── Runs ───────────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_run_list',\n {\n description: 'List recent runs, optionally filtered by job ID.',\n inputSchema: {\n jobId: z.string().optional(),\n limit: z.number().int().positive().optional(),\n since: z.number().int().optional(),\n },\n },\n async (args) => {\n const params = new URLSearchParams();\n if (args.jobId) params.set('jobId', args.jobId);\n if (args.limit !== undefined) params.set('limit', String(args.limit));\n if (args.since !== undefined) params.set('since', String(args.since));\n const qs = params.toString();\n return toolWrap(() => callDaemon('GET', `/api/runs${qs ? `?${qs}` : ''}`));\n },\n );\n\n server.registerTool(\n 'crontick_run_get',\n {\n description: 'Get the details and current status of a specific run by run ID.',\n inputSchema: { runId: z.string() },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('GET', `/api/runs/${encodeURIComponent(args.runId)}`),\n ),\n );\n\n server.registerTool(\n 'crontick_run_logs_tail',\n {\n description:\n 'Get the last N lines of output for a run. Useful for diagnosing failures.',\n inputSchema: {\n runId: z.string(),\n lines: z.number().int().positive().default(50),\n },\n },\n async (args) =>\n toolWrap(async () => {\n const logs = (await callDaemon(\n 'GET',\n `/api/runs/${encodeURIComponent(args.runId)}/logs`,\n )) as Array<{ stream: string; ts: number; data: string }>;\n const all = Array.isArray(logs) ? logs : [];\n const tail = all.slice(-args.lines);\n return { runId: args.runId, lines: tail };\n }),\n );\n\n // ── Schedules ─────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_schedule_validate',\n {\n description:\n 'Validate a schedule definition. Returns ok:true and human-readable description on success, or an error message on failure. Always call this before creating a job.',\n inputSchema: {\n schedule: ScheduleSchema,\n },\n },\n async (args) =>\n toolWrap(() => callDaemon('POST', '/api/schedules/validate', args.schedule)),\n );\n\n server.registerTool(\n 'crontick_schedule_preview',\n {\n description:\n 'Preview the next N fire times for a schedule. Useful to confirm the schedule is what the user expects before creating the job.',\n inputSchema: {\n schedule: ScheduleSchema,\n n: z.number().int().positive().max(20).default(5),\n tz: z.string().optional(),\n },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('POST', '/api/schedules/preview', {\n schedule: args.schedule,\n n: args.n,\n tz: args.tz,\n }),\n ),\n );\n\n // ── Stats ──────────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_stats_summary',\n {\n description:\n 'Get an aggregate summary of all jobs: total count, enabled count, run history, success/failure counts, average duration.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('GET', '/api/stats/summary')),\n );\n\n server.registerTool(\n 'crontick_stats_job',\n {\n description: 'Get run statistics for a specific job: total runs, success/failure rates, last status.',\n inputSchema: { id: z.string() },\n },\n async (args) =>\n toolWrap(() =>\n callDaemon('GET', `/api/stats/jobs/${encodeURIComponent(args.id)}`),\n ),\n );\n\n // ── Daemon ─────────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_daemon_status',\n {\n description:\n 'Get the daemon process status: PID, version, uptime, job counts, run stats, Node version, and platform.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('GET', '/health')),\n );\n\n server.registerTool(\n 'crontick_daemon_reload',\n {\n description:\n 'Reload job definitions from disk without restarting the daemon. Use after manually editing job files.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('POST', '/api/daemon/reload')),\n );\n\n server.registerTool(\n 'crontick_daemon_restart',\n {\n description:\n 'Restart the crontick daemon (stop + start). Running jobs will be interrupted. Confirm with the user before calling.',\n inputSchema: {},\n },\n async () =>\n toolWrap(async () => {\n // Kill existing daemon\n const pidFile = pidFilePath();\n if (existsSync(pidFile)) {\n try {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n if (!isNaN(pid)) process.kill(pid, 'SIGTERM');\n } catch { /* already dead */ }\n }\n // Wait for port file to disappear (up to 3s)\n await new Promise<void>((res) => {\n const deadline = Date.now() + 3000;\n const poll = setInterval(() => {\n if (!existsSync(portFilePath()) || Date.now() > deadline) {\n clearInterval(poll);\n res();\n }\n }, 100);\n });\n // Start new daemon\n const script = daemonScript();\n const restartChunks: Buffer[] = [];\n const child = spawn(process.execPath, [script], {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n env: process.env,\n });\n if (child.stderr) {\n child.stderr.on('data', (chunk: Buffer) => {\n if (Buffer.concat(restartChunks).length < 4096) restartChunks.push(chunk);\n });\n (child.stderr as NodeJS.ReadableStream & { unref?(): void }).unref?.();\n }\n child.unref();\n const getRestartStderr = (): string => {\n const raw = Buffer.concat(restartChunks).toString('utf-8');\n return raw.length > 4096 ? raw.slice(0, 4096) + '…' : raw;\n };\n try {\n await waitForPortFile(10_000, getRestartStderr);\n } catch (err) {\n const stderr = getRestartStderr();\n if (stderr) appendAutostartLog(`daemon restart failed:\\n${stderr}`);\n throw err;\n }\n return { ok: true, message: 'Daemon restarted successfully.' };\n }),\n );\n\n // ── Autostart ─────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_autostart_status',\n {\n description:\n 'Check whether the crontick daemon is registered to start automatically at login. (v1: Windows uses HKCU Run; other platforms return manual instructions.)',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('GET', '/api/autostart/status')),\n );\n\n server.registerTool(\n 'crontick_autostart_install',\n {\n description:\n 'Register the crontick daemon to start automatically at login. (v1: Windows only via HKCU Run + VBS shim. On other platforms returns manual instructions.) On non-Windows, this returns a 501 with instructions to use manual autostart.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('POST', '/api/autostart/install', {})),\n );\n\n server.registerTool(\n 'crontick_autostart_remove',\n {\n description: 'Remove the crontick daemon from the automatic startup registry.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('POST', '/api/autostart/remove', {})),\n );\n\n // ── Admin ──────────────────────────────────────────────────────────────────\n\n server.registerTool(\n 'crontick_export',\n {\n description:\n 'Export all job definitions as a JSON object. Use this to back up or migrate jobs.',\n inputSchema: {},\n },\n async () => toolWrap(() => callDaemon('GET', '/api/export')),\n );\n\n server.registerTool(\n 'crontick_import',\n {\n description:\n 'Import job definitions from a JSON array. Jobs are upserted (existing jobs with the same ID are updated).',\n inputSchema: {\n jobs: z.array(z.unknown()),\n },\n },\n async (args) =>\n toolWrap(() => callDaemon('POST', '/api/import', { jobs: args.jobs })),\n );\n\n server.registerTool(\n 'crontick_dashboard_open',\n {\n description:\n 'Get the URL for the crontick dashboard web UI. Open it in a browser to view jobs and run history visually.',\n inputSchema: {},\n },\n async () =>\n toolWrap(async () => {\n const base = getDaemonBaseUrl();\n return {\n url: `${base}/dashboard`,\n message: `Dashboard available at: ${base}/dashboard — open in your browser.`,\n };\n }),\n );\n\n server.registerTool(\n 'crontick_doctor',\n {\n description:\n 'Run a suite of health checks: Node.js version, SQLite, data directory, daemon connectivity, dashboard reachability, autostart, and MCP server availability.',\n inputSchema: {},\n },\n async () => {\n const checks: Array<{ name: string; ok: boolean; note?: string }> = [];\n let daemonReachable = false;\n\n // Node version\n const major = parseInt(process.versions.node.split('.')[0], 10);\n checks.push({ name: 'Node.js >= 22.5', ok: major >= 22, note: `v${process.versions.node}` });\n\n // SQLite\n try {\n const { DatabaseSync } = await import('node:sqlite');\n new (DatabaseSync as new (path: string) => { close(): void })(':memory:').close();\n checks.push({ name: 'node:sqlite', ok: true });\n } catch (err) {\n checks.push({ name: 'node:sqlite', ok: false, note: String(err) });\n }\n\n // Data dir\n try {\n ensureDirs();\n checks.push({ name: 'data dir writable', ok: true });\n } catch (err) {\n checks.push({ name: 'data dir writable', ok: false, note: String(err) });\n }\n\n // Port file + daemon\n const portFile = portFilePath();\n const portFileExists = existsSync(portFile);\n checks.push({\n name: 'port file readable',\n ok: portFileExists,\n note: portFileExists ? portFile : 'not found',\n });\n\n try {\n const base = getDaemonBaseUrl();\n const res = await fetch(`${base}/health`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n daemonReachable = true;\n checks.push({ name: 'daemon reachable', ok: true });\n } else {\n checks.push({ name: 'daemon reachable', ok: false, note: `HTTP ${res.status}` });\n }\n } catch {\n checks.push({ name: 'daemon reachable', ok: false, note: 'not running' });\n }\n\n // Dashboard reachable\n try {\n const base = getDaemonBaseUrl();\n const dashRes = await fetch(`${base}/dashboard`, { signal: AbortSignal.timeout(2000) });\n const text = await dashRes.text();\n checks.push({\n name: 'dashboard reachable',\n ok: dashRes.status === 200 && text.includes('crontick'),\n note: dashRes.status === 200 ? 'ok' : `HTTP ${dashRes.status}`,\n });\n } catch {\n checks.push({ name: 'dashboard reachable', ok: false, note: 'daemon not running or no dashboard' });\n }\n\n // Autostart status\n if (daemonReachable) {\n try {\n const asResult = await callDaemon('GET', '/api/autostart/status');\n const as = asResult as { installed?: boolean; backend?: string };\n checks.push({\n name: 'autostart',\n ok: true,\n note: `backend=${as.backend ?? '?'}, installed=${String(as.installed ?? false)}`,\n });\n } catch {\n checks.push({ name: 'autostart', ok: false, note: 'could not check (daemon not running)' });\n }\n } else {\n checks.push({ name: 'autostart', ok: false, note: 'could not check (daemon not running)' });\n }\n\n // MCP server binary\n const mcpScript = pathResolve(__dirname, '../mcp/index.js');\n checks.push({\n name: 'MCP server binary',\n ok: existsSync(mcpScript),\n note: mcpScript,\n });\n\n const allOk = checks.every((c) => c.ok);\n return okResult({ ok: allOk, checks });\n },\n );\n\n // ── Resources ─────────────────────────────────────────────────────────────\n\n // crontick://jobs — list of job IDs\n server.resource(\n 'crontick-jobs-list',\n 'crontick://jobs',\n {\n description: 'List of all crontick job IDs',\n mimeType: 'application/json',\n },\n async () => {\n try {\n await ensureDaemon();\n const jobs = (await callDaemon('GET', '/api/jobs')) as Array<{ id: string }>;\n const ids = Array.isArray(jobs) ? jobs.map((j) => j.id) : [];\n return {\n contents: [\n {\n uri: 'crontick://jobs',\n mimeType: 'application/json',\n text: JSON.stringify({ jobIds: ids }, null, 2),\n },\n ],\n };\n } catch (err) {\n return {\n contents: [\n {\n uri: 'crontick://jobs',\n mimeType: 'application/json',\n text: JSON.stringify({ error: String(err) }, null, 2),\n },\n ],\n };\n }\n },\n );\n\n // crontick://jobs/{id} — single job JSON\n const jobTemplate = new ResourceTemplate('crontick://jobs/{id}', {\n list: async () => ({\n resources: [],\n }),\n });\n\n server.resource(\n 'crontick-job',\n jobTemplate,\n { description: 'Full job definition as JSON', mimeType: 'application/json' },\n async (uri, variables) => {\n const id = Array.isArray(variables.id) ? variables.id[0] : variables.id;\n try {\n await ensureDaemon();\n const job = await callDaemon('GET', `/api/jobs/${encodeURIComponent(String(id ?? ''))}`);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(job, null, 2),\n },\n ],\n };\n } catch (err) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify({ error: String(err) }, null, 2),\n },\n ],\n };\n }\n },\n );\n\n // crontick://runs/{id} — single run record\n const runTemplate = new ResourceTemplate('crontick://runs/{id}', {\n list: async () => ({ resources: [] }),\n });\n\n server.resource(\n 'crontick-run',\n runTemplate,\n { description: 'Run record as JSON', mimeType: 'application/json' },\n async (uri, variables) => {\n const id = Array.isArray(variables.id) ? variables.id[0] : variables.id;\n try {\n await ensureDaemon();\n const run = await callDaemon('GET', `/api/runs/${encodeURIComponent(String(id ?? ''))}`);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(run, null, 2),\n },\n ],\n };\n } catch (err) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify({ error: String(err) }, null, 2),\n },\n ],\n };\n }\n },\n );\n\n // crontick://runs/{id}/log — full log text\n const runLogTemplate = new ResourceTemplate('crontick://runs/{id}/log', {\n list: async () => ({ resources: [] }),\n });\n\n server.resource(\n 'crontick-run-log',\n runLogTemplate,\n { description: 'Full log output for a run as plain text', mimeType: 'text/plain' },\n async (uri, variables) => {\n const id = Array.isArray(variables.id) ? variables.id[0] : variables.id;\n try {\n await ensureDaemon();\n const logs = (await callDaemon(\n 'GET',\n `/api/runs/${encodeURIComponent(String(id ?? ''))}/logs`,\n )) as Array<{ stream: string; data: string; ts: number }>;\n const text = Array.isArray(logs)\n ? logs.map((l) => `[${l.stream}] ${l.data}`).join('')\n : '';\n return {\n contents: [{ uri: uri.href, mimeType: 'text/plain', text }],\n };\n } catch (err) {\n return {\n contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: ${String(err)}` }],\n };\n }\n },\n );\n\n // crontick://schemas/job — JSON schema for a job\n server.resource(\n 'crontick-schema-job',\n 'crontick://schemas/job',\n { description: 'JSON Schema for a crontick job definition', mimeType: 'application/json' },\n async () => {\n const schema = zodToJsonSchema(JobSchema as unknown as Parameters<typeof zodToJsonSchema>[0], { name: 'CrontickJob' });\n return {\n contents: [\n {\n uri: 'crontick://schemas/job',\n mimeType: 'application/json',\n text: JSON.stringify(schema, null, 2),\n },\n ],\n };\n },\n );\n\n // ── Prompts ────────────────────────────────────────────────────────────────\n\n server.prompt(\n 'create-scheduled-script',\n 'Guide the LLM through creating a new scheduled script job: understand intent, draft a self-contained shell script, validate/preview the schedule, then create the job.',\n {\n intent: z.string(),\n os: z.enum(['windows', 'unix']).optional(),\n },\n (args) => {\n const shell = args.os === 'windows' ? 'PowerShell' : 'bash';\n const shellHint = args.os === 'windows' ? 'pwsh' : 'bash';\n return {\n description: 'Create a new scheduled script job',\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: `You are helping me schedule an automated task with crontick.\n\nMy intent: ${args.intent}\n\nPlease follow these steps in order:\n\n**Step 1 — Understand the intent**\nClarify what the task does, when it should run, and any side effects or requirements.\n\n**Step 2 — Draft the script**\nWrite a self-contained ${shell} script that accomplishes the task idempotently.\n- The script must not rely on external state set up by other scripts.\n- If it calls an LLM CLI (e.g. \\`copilot\\`, \\`claude\\`), that command goes inside the script.\n- Use error handling: \\`set -euo pipefail\\` for bash, \\`$ErrorActionPreference = 'Stop'\\` for ${shell}.\n\n**Step 3 — Choose a schedule**\nDecide on the cron expression or interval. Then call:\n- \\`crontick_schedule_validate\\` with \\`schedule: { kind: \"cron\", cron: \"<expr>\", tz: \"<tz>\" }\\`\n- \\`crontick_schedule_preview\\` to show the next 5 fire times to the user for confirmation.\n\n**Step 4 — Create the job**\nOnce the user approves the schedule, call \\`crontick_job_create\\` with \\`action.kind: \"script\"\\`:\n\\`\\`\\`json\n{\n \"id\": \"<kebab-case-id>\",\n \"description\": \"<one-line description>\",\n \"schedule\": { \"kind\": \"cron\", \"cron\": \"<expr>\", \"tz\": \"<tz>\" },\n \"action\": {\n \"kind\": \"script\",\n \"script\": \"<full script body>\",\n \"shell\": \"${shellHint}\"\n }\n}\n\\`\\`\\`\n\n**Step 5 — Confirm**\nReport the returned job ID and next run time to the user.\nAlways confirm before calling crontick_job_delete or crontick_job_disable.`,\n },\n },\n ],\n };\n },\n );\n\n server.prompt(\n 'investigate-failed-run',\n 'Load a failed run record and its logs, then help diagnose the failure and propose a fix.',\n {\n runId: z.string(),\n },\n async (args) => {\n let runInfo = 'Run record unavailable.';\n let logInfo = 'Logs unavailable.';\n\n try {\n await ensureDaemon();\n const run = await callDaemon('GET', `/api/runs/${encodeURIComponent(args.runId)}`);\n runInfo = JSON.stringify(run, null, 2);\n } catch (err) {\n runInfo = `Error fetching run: ${String(err)}`;\n }\n\n try {\n const logs = (await callDaemon(\n 'GET',\n `/api/runs/${encodeURIComponent(args.runId)}/logs`,\n )) as Array<{ stream: string; data: string }>;\n const lines = Array.isArray(logs) ? logs.slice(-100) : [];\n logInfo =\n lines.length > 0\n ? lines.map((l) => `[${l.stream}] ${l.data}`).join('')\n : '(no log output)';\n } catch (err) {\n logInfo = `Error fetching logs: ${String(err)}`;\n }\n\n return {\n description: `Investigate failed run ${args.runId}`,\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: `Please investigate why run \\`${args.runId}\\` failed and propose a fix.\n\n## Run Record\n\\`\\`\\`json\n${runInfo}\n\\`\\`\\`\n\n## Last 100 Log Lines\n\\`\\`\\`\n${logInfo}\n\\`\\`\\`\n\n## What to diagnose\n1. What caused the failure? (exit code, timeout, budget exceeded, script error, etc.)\n2. Is this a one-time fluke or likely to recur?\n3. Proposed fix — choose the most appropriate:\n - **Script fix**: edit the script body via \\`crontick_job_update\\` with a corrected \\`action.script\\`\n - **Schedule change**: adjust timing/tz via \\`crontick_job_update\\` with a new \\`schedule\\`\n - **Retry policy**: increase retry max via \\`crontick_job_update\\` with \\`retry.max\\`\n - **Budget cap**: set \\`budgets.maxRunsPerDay\\` if it's running too often\n - **Timeout**: increase \\`action.timeoutSec\\` if the job was killed by timeout\n4. After proposing the fix, ask for user confirmation before applying it.`,\n },\n },\n ],\n };\n },\n );\n\n return server;\n}\n\n// ── Entry point ────────────────────────────────────────────────────────────────\n\nexport async function main(): Promise<void> {\n const server = createMcpServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n // Keep alive until transport closes\n}\n\nmain().catch((err) => {\n process.stderr.write(`[crontick-mcp] Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "crontick",
3
+ "version": "0.1.0",
4
+ "description": "A standalone cron daemon, CLI, and MCP server for local scheduled jobs.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "author": "crontick contributors",
9
+ "homepage": "https://github.com/tejitpabari99/crontick",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tejitpabari99/crontick.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/tejitpabari99/crontick/issues"
16
+ },
17
+ "engines": {
18
+ "node": ">=22.5"
19
+ },
20
+ "bin": {
21
+ "crontick": "./dist/cli/index.js",
22
+ "crontick-daemon": "./dist/daemon/index.js",
23
+ "crontick-mcp": "./dist/mcp/index.js"
24
+ },
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "plugin/**",
39
+ "src/skill/SKILL.md",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup && node scripts/fix-node-sqlite.mjs",
45
+ "typecheck": "tsc --noEmit",
46
+ "lint": "eslint .",
47
+ "format": "prettier --write .",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest",
50
+ "release": "changeset publish",
51
+ "prepublishOnly": "npm run build && npm test"
52
+ },
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.17.0",
55
+ "ajv": "^8.17.1",
56
+ "ajv-formats": "^3.0.1",
57
+ "commander": "^12.1.0",
58
+ "croner": "^9.0.0",
59
+ "env-paths": "^3.0.0",
60
+ "pino": "^9.4.0",
61
+ "zod": "^4.4.3",
62
+ "zod-to-json-schema": "^3.25.2"
63
+ },
64
+ "devDependencies": {
65
+ "@changesets/cli": "^2.27.9",
66
+ "@eslint/js": "^9.12.0",
67
+ "@types/node": "^22.7.5",
68
+ "@vitest/coverage-v8": "^2.1.2",
69
+ "eslint": "^9.12.0",
70
+ "fast-check": "^4.9.0",
71
+ "prettier": "^3.3.3",
72
+ "tsup": "^8.3.0",
73
+ "typescript": "^5.6.3",
74
+ "typescript-eslint": "^8.8.1",
75
+ "vitest": "^2.1.2"
76
+ },
77
+ "publishConfig": {
78
+ "access": "public",
79
+ "provenance": true
80
+ }
81
+ }
File without changes
@@ -0,0 +1,50 @@
1
+ # crontick — Copilot Marketplace Plugin
2
+
3
+ The `crontick` plugin installs the [crontick](https://www.npmjs.com/package/crontick) npm package and sets up your environment for LLM-assisted local job scheduling via MCP.
4
+
5
+ ## What It Does
6
+
7
+ 1. **Installs `crontick` globally** via `npm install -g crontick` (if not already present)
8
+ 2. **Runs `crontick doctor`** to verify Node.js, SQLite, and the data directory
9
+ 3. **Copies `SKILL.md`** to `~/.copilot/skills/crontick/` so Copilot learns to schedule jobs
10
+ 4. **Optionally installs autostart** (Windows: HKCU Run + VBS shim) so the daemon starts at login
11
+
12
+ ## Manual Installation
13
+
14
+ ```sh
15
+ npm install -g crontick
16
+ node /path/to/plugin/install.mjs
17
+ ```
18
+
19
+ Or non-interactively:
20
+
21
+ ```sh
22
+ CRONTICK_PLUGIN_NONINTERACTIVE=1 node plugin/install.mjs
23
+ ```
24
+
25
+ ## Manual Uninstallation
26
+
27
+ ```sh
28
+ node plugin/uninstall.mjs
29
+ # Also remove autostart:
30
+ CRONTICK_PLUGIN_UNINSTALL_AUTOSTART=1 node plugin/uninstall.mjs
31
+ # Also delete all data:
32
+ crontick uninstall --purge
33
+ ```
34
+
35
+ ## Environment Variables
36
+
37
+ | Variable | Effect |
38
+ |----------|--------|
39
+ | `CRONTICK_PLUGIN_NONINTERACTIVE=1` | Skip all prompts, accept defaults |
40
+ | `CRONTICK_PLUGIN_SKIP_NPM=1` | Skip `npm install -g crontick` (useful for testing) |
41
+ | `CRONTICK_PLUGIN_SKIP_AUTOSTART=1` | Skip autostart installation |
42
+ | `CRONTICK_PLUGIN_UNINSTALL_AUTOSTART=1` | Remove autostart entry on uninstall |
43
+
44
+ ## After Installation
45
+
46
+ 1. Start the daemon: `crontick daemon start`
47
+ 2. Schedule a job: `crontick new my-job --cron "0 9 * * *" --script "echo hello"`
48
+ 3. Configure your MCP host to use `crontick mcp` as an MCP server
49
+
50
+ See the [README](../README.md) for full documentation.
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * crontick Copilot marketplace plugin — install script.
4
+ *
5
+ * Steps:
6
+ * 1. Check/install the crontick npm package globally.
7
+ * 2. Run `crontick doctor` to verify the installation.
8
+ * 3. Copy the bundled SKILL.md to ~/.copilot/skills/crontick/.
9
+ * 4. Optionally install win32 autostart (skipped in non-interactive mode or non-win32).
10
+ *
11
+ * Environment flags:
12
+ * CRONTICK_PLUGIN_NONINTERACTIVE=1 — skip prompts, accept defaults
13
+ * CRONTICK_PLUGIN_SKIP_NPM=1 — skip `npm i -g crontick` step (for testing)
14
+ * CRONTICK_PLUGIN_SKIP_AUTOSTART=1 — skip autostart installation
15
+ */
16
+
17
+ import { spawnSync } from 'node:child_process';
18
+ import { existsSync, mkdirSync, copyFileSync } from 'node:fs';
19
+ import { join, dirname } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { createInterface } from 'node:readline';
23
+
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+ const nonInteractive = process.env['CRONTICK_PLUGIN_NONINTERACTIVE'] === '1';
26
+ const skipNpm = process.env['CRONTICK_PLUGIN_SKIP_NPM'] === '1';
27
+ const skipAutostart = process.env['CRONTICK_PLUGIN_SKIP_AUTOSTART'] === '1';
28
+
29
+ // ── Helpers ───────────────────────────────────────────────────────────────────
30
+
31
+ function run(cmd, args, opts = {}) {
32
+ const result = spawnSync(cmd, args, {
33
+ stdio: 'inherit',
34
+ encoding: 'utf-8',
35
+ timeout: 120_000,
36
+ ...opts,
37
+ });
38
+ return result.status ?? 1;
39
+ }
40
+
41
+ function runCapture(cmd, args, opts = {}) {
42
+ return spawnSync(cmd, args, {
43
+ encoding: 'utf-8',
44
+ timeout: 10_000,
45
+ windowsHide: true,
46
+ ...opts,
47
+ });
48
+ }
49
+
50
+ function binaryExists(name) {
51
+ const which = process.platform === 'win32' ? 'where.exe' : 'which';
52
+ const result = runCapture(which, [name]);
53
+ return result.status === 0 && result.stdout.trim().length > 0;
54
+ }
55
+
56
+ async function prompt(question) {
57
+ if (nonInteractive) return '';
58
+ return new Promise((resolve) => {
59
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
60
+ rl.question(question, (answer) => {
61
+ rl.close();
62
+ resolve(answer.trim());
63
+ });
64
+ });
65
+ }
66
+
67
+ // ── Step 1: Ensure crontick is installed ─────────────────────────────────────
68
+
69
+ const steps = [];
70
+
71
+ if (!skipNpm) {
72
+ if (!binaryExists('crontick')) {
73
+ console.log('\n[crontick-plugin] crontick not found on PATH — installing globally…');
74
+ const exitCode = run('npm', ['install', '-g', 'crontick']);
75
+ if (exitCode !== 0) {
76
+ console.error('[crontick-plugin] npm install failed. Try: npm install -g crontick');
77
+ process.exit(1);
78
+ }
79
+ steps.push('Installed crontick globally via npm');
80
+ console.log('[crontick-plugin] crontick installed.');
81
+ } else {
82
+ console.log('[crontick-plugin] crontick already installed — skipping npm install.');
83
+ steps.push('crontick already installed');
84
+ }
85
+ } else {
86
+ steps.push('npm install skipped (CRONTICK_PLUGIN_SKIP_NPM=1)');
87
+ }
88
+
89
+ // ── Step 2: Run crontick doctor ───────────────────────────────────────────────
90
+
91
+ console.log('\n[crontick-plugin] Running crontick doctor…');
92
+ run('crontick', ['doctor']);
93
+ steps.push('crontick doctor completed');
94
+
95
+ // ── Step 3: Copy SKILL.md ─────────────────────────────────────────────────────
96
+
97
+ // Locate the bundled SKILL.md relative to this script file
98
+ // This script is at <pkg>/plugin/install.mjs
99
+ // SKILL.md is at <pkg>/src/skill/SKILL.md
100
+ const skillSrc = join(__dirname, '..', 'src', 'skill', 'SKILL.md');
101
+
102
+ if (!existsSync(skillSrc)) {
103
+ console.warn(`[crontick-plugin] Warning: SKILL.md not found at ${skillSrc}`);
104
+ steps.push('SKILL.md copy SKIPPED (source not found)');
105
+ } else {
106
+ const home = process.env['USERPROFILE'] ?? process.env['HOME'] ?? homedir();
107
+ const skillsDir = join(home, '.copilot', 'skills', 'crontick');
108
+ mkdirSync(skillsDir, { recursive: true });
109
+ const skillDst = join(skillsDir, 'SKILL.md');
110
+ copyFileSync(skillSrc, skillDst);
111
+ steps.push(`SKILL.md copied to ${skillDst}`);
112
+ console.log(`[crontick-plugin] SKILL.md installed at: ${skillDst}`);
113
+ }
114
+
115
+ // ── Step 4: Optional autostart ────────────────────────────────────────────────
116
+
117
+ if (!skipAutostart) {
118
+ if (process.platform === 'win32') {
119
+ let doAutostart = nonInteractive;
120
+ if (!nonInteractive) {
121
+ const answer = await prompt(
122
+ '\n[crontick-plugin] Install autostart? Registers crontick-daemon to start at login. [Y/n] ',
123
+ );
124
+ doAutostart = answer === '' || answer.toLowerCase() === 'y';
125
+ }
126
+ if (doAutostart) {
127
+ console.log('[crontick-plugin] Installing autostart…');
128
+ const code = run('crontick', ['autostart', 'install']);
129
+ if (code === 0) {
130
+ steps.push('win32 autostart installed');
131
+ console.log('[crontick-plugin] Autostart installed.');
132
+ } else {
133
+ steps.push('autostart install FAILED (see above)');
134
+ console.warn('[crontick-plugin] Autostart install failed — you can retry later: crontick autostart install');
135
+ }
136
+ } else {
137
+ steps.push('autostart skipped by user');
138
+ console.log('[crontick-plugin] Autostart skipped. Run later: crontick autostart install');
139
+ }
140
+ } else {
141
+ console.log('[crontick-plugin] Autostart (non-Windows): run `crontick autostart status` for manual setup instructions.');
142
+ steps.push('autostart not applicable on this platform (non-win32)');
143
+ }
144
+ }
145
+
146
+ // ── Summary ───────────────────────────────────────────────────────────────────
147
+
148
+ console.log('\n[crontick-plugin] Installation complete!\n');
149
+ for (const step of steps) {
150
+ console.log(` ✓ ${step}`);
151
+ }
152
+ console.log('\nGet started:');
153
+ console.log(' crontick --help');
154
+ console.log(' crontick daemon start');
155
+ console.log(' crontick new my-job --cron "0 9 * * *" --script "echo hello"');
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "crontick",
3
+ "name": "crontick",
4
+ "description": "Local cron scheduler with MCP integration. Schedules shell scripts, provides a skill for LLM-assisted job creation, and manages daemon autostart.",
5
+ "version": "0.0.0",
6
+ "install": "plugin/install.mjs",
7
+ "postInstall": null,
8
+ "uninstall": "plugin/uninstall.mjs",
9
+ "homepage": "https://github.com/crontick/crontick",
10
+ "keywords": ["cron", "scheduler", "mcp", "daemon", "automation"]
11
+ }
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * crontick Copilot marketplace plugin — uninstall script.
4
+ *
5
+ * Steps:
6
+ * 1. Remove ~/.copilot/skills/crontick/SKILL.md (always).
7
+ * 2. Optionally remove win32 autostart (if CRONTICK_PLUGIN_UNINSTALL_AUTOSTART=1).
8
+ * 3. Data directory is preserved by default — hint at `crontick uninstall --purge`.
9
+ *
10
+ * Environment flags:
11
+ * CRONTICK_PLUGIN_UNINSTALL_AUTOSTART=1 — also remove autostart entry
12
+ */
13
+
14
+ import { spawnSync } from 'node:child_process';
15
+ import { existsSync, unlinkSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+
19
+ const removeAutostart = process.env['CRONTICK_PLUGIN_UNINSTALL_AUTOSTART'] === '1';
20
+
21
+ const steps = [];
22
+
23
+ // ── Step 1: Remove SKILL.md ───────────────────────────────────────────────────
24
+
25
+ const home = process.env['USERPROFILE'] ?? process.env['HOME'] ?? homedir();
26
+ const skillDst = join(home, '.copilot', 'skills', 'crontick', 'SKILL.md');
27
+
28
+ if (existsSync(skillDst)) {
29
+ try {
30
+ unlinkSync(skillDst);
31
+ steps.push(`SKILL.md removed from ${skillDst}`);
32
+ console.log(`[crontick-plugin] Removed SKILL.md: ${skillDst}`);
33
+ } catch (err) {
34
+ console.warn(`[crontick-plugin] Could not remove SKILL.md: ${err.message}`);
35
+ steps.push('SKILL.md removal FAILED');
36
+ }
37
+ } else {
38
+ steps.push('SKILL.md not found (already removed)');
39
+ console.log('[crontick-plugin] SKILL.md not found — already removed.');
40
+ }
41
+
42
+ // ── Step 2: Optional autostart removal ───────────────────────────────────────
43
+
44
+ if (removeAutostart) {
45
+ console.log('[crontick-plugin] Removing autostart…');
46
+ const result = spawnSync('crontick', ['autostart', 'remove'], {
47
+ stdio: 'inherit',
48
+ timeout: 10_000,
49
+ });
50
+ if ((result.status ?? 1) === 0) {
51
+ steps.push('autostart entry removed');
52
+ } else {
53
+ steps.push('autostart removal FAILED (see above)');
54
+ }
55
+ } else {
56
+ steps.push('autostart preserved (set CRONTICK_PLUGIN_UNINSTALL_AUTOSTART=1 to remove)');
57
+ }
58
+
59
+ // ── Summary ───────────────────────────────────────────────────────────────────
60
+
61
+ console.log('\n[crontick-plugin] Uninstall complete!\n');
62
+ for (const step of steps) {
63
+ console.log(` ✓ ${step}`);
64
+ }
65
+ console.log(
66
+ '\nData directory preserved. To delete ALL crontick data, run:\n' +
67
+ ' crontick uninstall --purge\n',
68
+ );