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.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/chunk-35FFLWP3.js +79 -0
- package/dist/chunk-35FFLWP3.js.map +1 -0
- package/dist/chunk-FMGZ3SSS.js +57 -0
- package/dist/chunk-FMGZ3SSS.js.map +1 -0
- package/dist/chunk-LPAFZNXK.js +214 -0
- package/dist/chunk-LPAFZNXK.js.map +1 -0
- package/dist/chunk-YMAT5MON.js +22 -0
- package/dist/chunk-YMAT5MON.js.map +1 -0
- package/dist/cli/index.cjs +879 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +554 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/daemon/index.cjs +1655 -0
- package/dist/daemon/index.cjs.map +1 -0
- package/dist/daemon/index.d.cts +2 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +1306 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/dashboard/.gitkeep +0 -0
- package/dist/dashboard/dashboard.css +192 -0
- package/dist/dashboard/dashboard.js +316 -0
- package/dist/dashboard/index.html +108 -0
- package/dist/index.cjs +180 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +121 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/job-JQGLDEJV.js +26 -0
- package/dist/job-JQGLDEJV.js.map +1 -0
- package/dist/mcp/index.cjs +1000 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +13 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +876 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +81 -0
- package/plugin/.gitkeep +0 -0
- package/plugin/README.md +50 -0
- package/plugin/install.mjs +155 -0
- package/plugin/plugin.json +11 -0
- package/plugin/uninstall.mjs +68 -0
- package/src/skill/SKILL.md +227 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createAutostart
|
|
4
|
+
} from "../chunk-LPAFZNXK.js";
|
|
5
|
+
import {
|
|
6
|
+
CrontickError
|
|
7
|
+
} from "../chunk-YMAT5MON.js";
|
|
8
|
+
import {
|
|
9
|
+
VERSION,
|
|
10
|
+
dataDir,
|
|
11
|
+
ensureDirs,
|
|
12
|
+
pidFilePath,
|
|
13
|
+
portFilePath
|
|
14
|
+
} from "../chunk-FMGZ3SSS.js";
|
|
15
|
+
import {
|
|
16
|
+
JobSchema
|
|
17
|
+
} from "../chunk-35FFLWP3.js";
|
|
18
|
+
|
|
19
|
+
// src/cli/index.ts
|
|
20
|
+
import { Command } from "commander";
|
|
21
|
+
import { spawn, spawnSync } from "child_process";
|
|
22
|
+
import { readFileSync, writeFileSync, existsSync, rmSync } from "fs";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
import { dirname, resolve } from "path";
|
|
25
|
+
import { createInterface } from "readline";
|
|
26
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
function getPort() {
|
|
28
|
+
const pf = portFilePath();
|
|
29
|
+
if (!existsSync(pf)) {
|
|
30
|
+
throw new CrontickError(
|
|
31
|
+
"DAEMON_NOT_RUNNING",
|
|
32
|
+
"Daemon is not running. Start it with: crontick daemon start"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const port = parseInt(readFileSync(pf, "utf-8").trim(), 10);
|
|
36
|
+
if (isNaN(port)) {
|
|
37
|
+
throw new CrontickError("DAEMON_NOT_RUNNING", "Could not read daemon port file");
|
|
38
|
+
}
|
|
39
|
+
return port;
|
|
40
|
+
}
|
|
41
|
+
async function api(method, path, body) {
|
|
42
|
+
const port = getPort();
|
|
43
|
+
const url = `http://127.0.0.1:${port}${path}`;
|
|
44
|
+
const options = {
|
|
45
|
+
method,
|
|
46
|
+
headers: { "Content-Type": "application/json" }
|
|
47
|
+
};
|
|
48
|
+
if (body !== void 0) options.body = JSON.stringify(body);
|
|
49
|
+
const res = await fetch(url, options);
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
let data;
|
|
52
|
+
try {
|
|
53
|
+
data = JSON.parse(text);
|
|
54
|
+
} catch {
|
|
55
|
+
throw new CrontickError("PARSE_ERROR", `Unexpected response: ${text.slice(0, 200)}`);
|
|
56
|
+
}
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
const err = data?.error;
|
|
59
|
+
throw new CrontickError(err?.code ?? "API_ERROR", err?.message ?? `HTTP ${res.status}`);
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
function print(data, useJson) {
|
|
64
|
+
if (useJson) {
|
|
65
|
+
console.log(JSON.stringify(data, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(data)) {
|
|
69
|
+
if (data.length === 0) {
|
|
70
|
+
console.log("(no items)");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const rows = data;
|
|
74
|
+
const keys = Object.keys(rows[0]);
|
|
75
|
+
console.log(keys.join(" "));
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
console.log(
|
|
78
|
+
keys.map((k) => {
|
|
79
|
+
const v = row[k];
|
|
80
|
+
return v !== null && typeof v === "object" ? JSON.stringify(v) : String(v ?? "");
|
|
81
|
+
}).join(" ")
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
} else if (data !== null && typeof data === "object") {
|
|
85
|
+
const obj = data;
|
|
86
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
87
|
+
const display = value !== null && typeof value === "object" ? JSON.stringify(value) : String(value ?? "");
|
|
88
|
+
console.log(`${key}: ${display}`);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
console.log(String(data ?? ""));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function handleError(err) {
|
|
95
|
+
if (err instanceof CrontickError) {
|
|
96
|
+
console.error(`Error [${err.code}]: ${err.message}`);
|
|
97
|
+
} else {
|
|
98
|
+
console.error(`Error: ${String(err)}`);
|
|
99
|
+
}
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
function daemonScript() {
|
|
103
|
+
return resolve(__dirname, "../daemon/index.js");
|
|
104
|
+
}
|
|
105
|
+
function waitForPort(timeout = 1e4) {
|
|
106
|
+
const start = Date.now();
|
|
107
|
+
return new Promise((resolve2, reject) => {
|
|
108
|
+
const check = () => {
|
|
109
|
+
try {
|
|
110
|
+
const port = getPort();
|
|
111
|
+
resolve2(port);
|
|
112
|
+
} catch {
|
|
113
|
+
if (Date.now() - start > timeout) {
|
|
114
|
+
reject(new CrontickError("DAEMON_TIMEOUT", "Timed out waiting for daemon to start"));
|
|
115
|
+
} else {
|
|
116
|
+
setTimeout(check, 200);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
check();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
var program = new Command();
|
|
124
|
+
program.name("crontick").description("A standalone cron daemon, CLI, and MCP server for local scheduled jobs.").version(VERSION).option("--json", "Output as JSON");
|
|
125
|
+
program.command("new <id>").description("Create a new job").option("--cron <expr>", 'Cron expression (e.g. "0 9 * * *")').option("--every <sec>", "Interval in seconds", parseInt).option("--at <iso>", "One-shot run-at ISO-8601 time").option("--tz <tz>", "Timezone for cron schedule").option("--script <body>", "Inline script body").option("--exec <cmd>", "Command to exec (use -- for args)").option("--file <path>", "Load full job from JSON file").option("--shell <shell>", "Shell: auto|bash|pwsh|cmd", "auto").option("--env-file <path>", "Load extra environment variables from a .env file").option("--timeout <sec>", "Timeout in seconds", parseInt).option("--overlap <policy>", "Overlap policy: skip|queue|cancel-previous", "skip").option("--retry <max>", "Retry count", parseInt).option("--desc <description>", "Job description").action(async (id, opts) => {
|
|
126
|
+
try {
|
|
127
|
+
let jobData;
|
|
128
|
+
if (opts.file) {
|
|
129
|
+
const raw = readFileSync(resolve(process.cwd(), opts.file), "utf-8");
|
|
130
|
+
jobData = JSON.parse(raw);
|
|
131
|
+
} else {
|
|
132
|
+
let schedule;
|
|
133
|
+
if (opts.cron) {
|
|
134
|
+
schedule = { kind: "cron", cron: opts.cron, tz: opts.tz };
|
|
135
|
+
} else if (opts.every) {
|
|
136
|
+
schedule = { kind: "interval", everySec: opts.every };
|
|
137
|
+
} else if (opts.at) {
|
|
138
|
+
schedule = { kind: "one-shot", runAt: opts.at };
|
|
139
|
+
} else {
|
|
140
|
+
throw new CrontickError("MISSING_ARG", "Provide --cron, --every <sec>, or --at <iso>");
|
|
141
|
+
}
|
|
142
|
+
let action;
|
|
143
|
+
if (opts.script) {
|
|
144
|
+
action = {
|
|
145
|
+
kind: "script",
|
|
146
|
+
script: opts.script,
|
|
147
|
+
shell: opts.shell,
|
|
148
|
+
envFile: opts.envFile,
|
|
149
|
+
timeoutSec: opts.timeout
|
|
150
|
+
};
|
|
151
|
+
} else if (opts.exec) {
|
|
152
|
+
const parts = opts.exec.split(/\s+/);
|
|
153
|
+
action = {
|
|
154
|
+
kind: "exec",
|
|
155
|
+
command: parts[0],
|
|
156
|
+
args: parts.slice(1),
|
|
157
|
+
envFile: opts.envFile,
|
|
158
|
+
timeoutSec: opts.timeout
|
|
159
|
+
};
|
|
160
|
+
} else {
|
|
161
|
+
throw new CrontickError("MISSING_ARG", "Provide --script or --exec");
|
|
162
|
+
}
|
|
163
|
+
jobData = {
|
|
164
|
+
id,
|
|
165
|
+
description: opts.desc,
|
|
166
|
+
schedule,
|
|
167
|
+
action,
|
|
168
|
+
overlap: opts.overlap,
|
|
169
|
+
retry: opts.retry !== void 0 ? { max: opts.retry, backoffSec: 30 } : void 0
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const parsed = JobSchema.safeParse(jobData);
|
|
173
|
+
if (!parsed.success) {
|
|
174
|
+
throw new CrontickError("VALIDATION_ERROR", "Invalid job", parsed.error.format());
|
|
175
|
+
}
|
|
176
|
+
const result = await api("POST", "/api/jobs", parsed.data);
|
|
177
|
+
print(result, !!program.opts().json);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
handleError(err);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
program.command("list").description("List all jobs").action(async () => {
|
|
183
|
+
try {
|
|
184
|
+
const jobs = await api("GET", "/api/jobs");
|
|
185
|
+
print(jobs, !!program.opts().json);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
handleError(err);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
program.command("get <id>").description("Get a job by ID").action(async (id) => {
|
|
191
|
+
try {
|
|
192
|
+
const job = await api("GET", `/api/jobs/${encodeURIComponent(id)}`);
|
|
193
|
+
print(job, !!program.opts().json);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
handleError(err);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
program.command("enable <id>").description("Enable a job").action(async (id) => {
|
|
199
|
+
try {
|
|
200
|
+
const result = await api("POST", `/api/jobs/${encodeURIComponent(id)}/enable`);
|
|
201
|
+
print(result, !!program.opts().json);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
handleError(err);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
program.command("disable <id>").description("Disable a job").action(async (id) => {
|
|
207
|
+
try {
|
|
208
|
+
const result = await api("POST", `/api/jobs/${encodeURIComponent(id)}/disable`);
|
|
209
|
+
print(result, !!program.opts().json);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
handleError(err);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
program.command("delete <id>").description("Delete a job").action(async (id) => {
|
|
215
|
+
try {
|
|
216
|
+
const result = await api("DELETE", `/api/jobs/${encodeURIComponent(id)}`);
|
|
217
|
+
print(result, !!program.opts().json);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
handleError(err);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
program.command("run-now <id>").description("Trigger an immediate run of a job").action(async (id) => {
|
|
223
|
+
try {
|
|
224
|
+
const result = await api("POST", `/api/jobs/${encodeURIComponent(id)}/run`);
|
|
225
|
+
print(result, !!program.opts().json);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
handleError(err);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
program.command("logs <runId>").description("Get logs for a run").option("--follow", "Follow (SSE stream) \u2014 not implemented in CLI yet; use --tail").option("--tail <n>", "Show last N lines", parseInt).action(async (runId, opts) => {
|
|
231
|
+
try {
|
|
232
|
+
const useJson = !!program.opts().json;
|
|
233
|
+
const logs = await api("GET", `/api/runs/${encodeURIComponent(runId)}/logs`);
|
|
234
|
+
const lines = Array.isArray(logs) ? logs : [];
|
|
235
|
+
const tail = opts.tail;
|
|
236
|
+
const display = tail ? lines.slice(-tail) : lines;
|
|
237
|
+
if (useJson) {
|
|
238
|
+
console.log(JSON.stringify(display, null, 2));
|
|
239
|
+
} else {
|
|
240
|
+
for (const entry of display) {
|
|
241
|
+
process.stdout.write(`[${entry.stream}] ${entry.data}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch (err) {
|
|
245
|
+
handleError(err);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
program.command("export").description("Export all jobs").option("--out <file>", "Output file (default: stdout)").action(async (opts) => {
|
|
249
|
+
try {
|
|
250
|
+
const data = await api("GET", "/api/export");
|
|
251
|
+
const json = JSON.stringify(data, null, 2);
|
|
252
|
+
if (opts.out) {
|
|
253
|
+
writeFileSync(resolve(process.cwd(), opts.out), json, "utf-8");
|
|
254
|
+
console.log(`Exported to ${opts.out}`);
|
|
255
|
+
} else {
|
|
256
|
+
console.log(json);
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
handleError(err);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
program.command("import <file>").description("Import jobs from a JSON file").action(async (file) => {
|
|
263
|
+
try {
|
|
264
|
+
const raw = readFileSync(resolve(process.cwd(), file), "utf-8");
|
|
265
|
+
const data = JSON.parse(raw);
|
|
266
|
+
const result = await api("POST", "/api/import", data);
|
|
267
|
+
print(result, !!program.opts().json);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
handleError(err);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
program.command("doctor").description("Check system health").action(async () => {
|
|
273
|
+
const checks = [];
|
|
274
|
+
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
275
|
+
checks.push({
|
|
276
|
+
name: "Node.js >= 22.5",
|
|
277
|
+
ok: major >= 22,
|
|
278
|
+
note: `v${process.versions.node}`
|
|
279
|
+
});
|
|
280
|
+
try {
|
|
281
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
282
|
+
new DatabaseSync(":memory:").close();
|
|
283
|
+
checks.push({ name: "node:sqlite", ok: true });
|
|
284
|
+
} catch (err) {
|
|
285
|
+
checks.push({ name: "node:sqlite", ok: false, note: String(err) });
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
ensureDirs();
|
|
289
|
+
checks.push({ name: "data dir writable", ok: true });
|
|
290
|
+
} catch (err) {
|
|
291
|
+
checks.push({ name: "data dir writable", ok: false, note: String(err) });
|
|
292
|
+
}
|
|
293
|
+
const portFile = portFilePath();
|
|
294
|
+
const portFileExists = existsSync(portFile);
|
|
295
|
+
checks.push({ name: "port file readable", ok: portFileExists, note: portFileExists ? portFile : "not found" });
|
|
296
|
+
try {
|
|
297
|
+
await api("GET", "/health");
|
|
298
|
+
checks.push({ name: "daemon reachable", ok: true });
|
|
299
|
+
} catch {
|
|
300
|
+
checks.push({ name: "daemon reachable", ok: false, note: "not running" });
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const port2 = getPort();
|
|
304
|
+
const dashRes = await fetch(`http://127.0.0.1:${port2}/dashboard`, {
|
|
305
|
+
signal: AbortSignal.timeout(2e3)
|
|
306
|
+
});
|
|
307
|
+
const text = await dashRes.text();
|
|
308
|
+
checks.push({
|
|
309
|
+
name: "dashboard reachable",
|
|
310
|
+
ok: dashRes.status === 200 && text.includes("crontick"),
|
|
311
|
+
note: dashRes.status === 200 ? "ok" : `HTTP ${dashRes.status}`
|
|
312
|
+
});
|
|
313
|
+
} catch {
|
|
314
|
+
checks.push({ name: "dashboard reachable", ok: false, note: "daemon not running or no dashboard" });
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const asResult = await api("GET", "/api/autostart/status");
|
|
318
|
+
const as = asResult;
|
|
319
|
+
checks.push({
|
|
320
|
+
name: "autostart",
|
|
321
|
+
ok: true,
|
|
322
|
+
note: `backend=${as.backend ?? "?"}, installed=${String(as.installed ?? false)}`
|
|
323
|
+
});
|
|
324
|
+
} catch {
|
|
325
|
+
checks.push({ name: "autostart", ok: false, note: "could not check (daemon not running)" });
|
|
326
|
+
}
|
|
327
|
+
const mcpScript = resolve(__dirname, "../mcp/index.js");
|
|
328
|
+
checks.push({
|
|
329
|
+
name: "MCP server binary",
|
|
330
|
+
ok: existsSync(mcpScript),
|
|
331
|
+
note: mcpScript
|
|
332
|
+
});
|
|
333
|
+
try {
|
|
334
|
+
const result = spawnSync(process.execPath, [mcpScript, "--help"], {
|
|
335
|
+
timeout: 5e3,
|
|
336
|
+
encoding: "utf-8",
|
|
337
|
+
env: { ...process.env, CRONTICK_MCP_NO_AUTOSTART: "1" }
|
|
338
|
+
});
|
|
339
|
+
const helpOk = result.status === 0 || (result.stdout ?? "").includes("stdio");
|
|
340
|
+
checks.push({ name: "MCP server --help", ok: helpOk });
|
|
341
|
+
} catch (err) {
|
|
342
|
+
checks.push({ name: "MCP server --help", ok: false, note: String(err) });
|
|
343
|
+
}
|
|
344
|
+
for (const c of checks) {
|
|
345
|
+
const icon = c.ok ? "\u2713" : "\u2717";
|
|
346
|
+
console.log(`${icon} ${c.name}${c.note ? ` (${c.note})` : ""}`);
|
|
347
|
+
}
|
|
348
|
+
const failed = checks.filter((c) => !c.ok);
|
|
349
|
+
if (failed.length > 0) process.exit(1);
|
|
350
|
+
});
|
|
351
|
+
var daemon = program.command("daemon").description("Manage the crontick daemon");
|
|
352
|
+
daemon.command("start").description("Start the daemon (detached by default)").option("--foreground", "Run in foreground (blocking)").action(async (opts) => {
|
|
353
|
+
try {
|
|
354
|
+
const script = daemonScript();
|
|
355
|
+
if (!existsSync(script)) {
|
|
356
|
+
throw new CrontickError("NOT_BUILT", `Daemon script not found: ${script}. Run: npm run build`);
|
|
357
|
+
}
|
|
358
|
+
if (opts.foreground) {
|
|
359
|
+
const result = spawnSync(process.execPath, [script], {
|
|
360
|
+
stdio: "inherit",
|
|
361
|
+
env: process.env
|
|
362
|
+
});
|
|
363
|
+
process.exit(result.status ?? 0);
|
|
364
|
+
} else {
|
|
365
|
+
const child = spawn(process.execPath, [script], {
|
|
366
|
+
detached: true,
|
|
367
|
+
stdio: "ignore",
|
|
368
|
+
env: process.env
|
|
369
|
+
});
|
|
370
|
+
child.unref();
|
|
371
|
+
console.log(`Starting daemon (pid will be written to port file)\u2026`);
|
|
372
|
+
const port = await waitForPort();
|
|
373
|
+
console.log(`Daemon started on port ${port}`);
|
|
374
|
+
}
|
|
375
|
+
} catch (err) {
|
|
376
|
+
handleError(err);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
daemon.command("stop").description("Stop the daemon").action(async () => {
|
|
380
|
+
try {
|
|
381
|
+
const pidFile = pidFilePath();
|
|
382
|
+
if (!existsSync(pidFile)) {
|
|
383
|
+
console.log("Daemon is not running");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
387
|
+
process.kill(pid, "SIGTERM");
|
|
388
|
+
console.log(`Sent SIGTERM to daemon (pid ${pid})`);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
handleError(err);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
daemon.command("status").description("Show daemon status").action(async () => {
|
|
394
|
+
try {
|
|
395
|
+
const status = await api("GET", "/api/daemon/status");
|
|
396
|
+
print(status, !!program.opts().json);
|
|
397
|
+
} catch {
|
|
398
|
+
console.log("Daemon is not running");
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
daemon.command("reload").description("Reload jobs from disk").action(async () => {
|
|
402
|
+
try {
|
|
403
|
+
const result = await api("POST", "/api/daemon/reload");
|
|
404
|
+
print(result, !!program.opts().json);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
handleError(err);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
daemon.command("restart").description("Restart the daemon").action(async () => {
|
|
410
|
+
try {
|
|
411
|
+
const pidFile = pidFilePath();
|
|
412
|
+
if (existsSync(pidFile)) {
|
|
413
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
414
|
+
try {
|
|
415
|
+
process.kill(pid, "SIGTERM");
|
|
416
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const script = daemonScript();
|
|
421
|
+
const child = spawn(process.execPath, [script], {
|
|
422
|
+
detached: true,
|
|
423
|
+
stdio: "ignore",
|
|
424
|
+
env: process.env
|
|
425
|
+
});
|
|
426
|
+
child.unref();
|
|
427
|
+
const port = await waitForPort();
|
|
428
|
+
console.log(`Daemon restarted on port ${port}`);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
handleError(err);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
var autostart = program.command("autostart").description("Manage daemon autostart at login");
|
|
434
|
+
autostart.command("install").description("Register the daemon to start automatically at login").option("--backend <backend>", "Backend: win32|darwin|linux|manual (default: auto-detect)").action(async (opts) => {
|
|
435
|
+
try {
|
|
436
|
+
const backend = opts.backend;
|
|
437
|
+
const as = createAutostart({ backend });
|
|
438
|
+
const result = await as.install();
|
|
439
|
+
print(result, !!program.opts().json);
|
|
440
|
+
if (process.platform !== "win32" && !backend) {
|
|
441
|
+
const statusResult = await as.status();
|
|
442
|
+
const details = statusResult.details;
|
|
443
|
+
const instr = details?.["instructions"];
|
|
444
|
+
if (instr) console.log("\nManual setup instructions:\n" + instr);
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
handleError(err);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
autostart.command("remove").description("Remove the daemon from automatic startup").option("--backend <backend>", "Backend: win32|darwin|linux|manual (default: auto-detect)").action(async (opts) => {
|
|
451
|
+
try {
|
|
452
|
+
const backend = opts.backend;
|
|
453
|
+
const as = createAutostart({ backend });
|
|
454
|
+
const result = await as.remove();
|
|
455
|
+
print(result, !!program.opts().json);
|
|
456
|
+
} catch (err) {
|
|
457
|
+
handleError(err);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
autostart.command("status").description("Check whether the daemon is registered for automatic startup").option("--backend <backend>", "Backend: win32|darwin|linux|manual (default: auto-detect)").action(async (opts) => {
|
|
461
|
+
try {
|
|
462
|
+
const backend = opts.backend;
|
|
463
|
+
const as = createAutostart({ backend });
|
|
464
|
+
const result = await as.status();
|
|
465
|
+
print(result, !!program.opts().json);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
handleError(err);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
program.command("uninstall").description("Remove autostart entry and optionally delete all crontick data").option("--purge", "Also delete the data directory (jobs, runs, config)").option("--yes", "Skip confirmation prompts").action(async (opts) => {
|
|
471
|
+
try {
|
|
472
|
+
const as = createAutostart();
|
|
473
|
+
await as.remove();
|
|
474
|
+
console.log("\u2713 Autostart entry removed.");
|
|
475
|
+
if (opts.purge) {
|
|
476
|
+
const dir = dataDir();
|
|
477
|
+
let confirmed = opts.yes;
|
|
478
|
+
if (!confirmed) {
|
|
479
|
+
confirmed = await new Promise((resolve2) => {
|
|
480
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
481
|
+
rl.question(`Delete all crontick data at ${dir}? [y/N] `, (answer) => {
|
|
482
|
+
rl.close();
|
|
483
|
+
resolve2(answer.trim().toLowerCase() === "y");
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (confirmed) {
|
|
488
|
+
rmSync(dir, { recursive: true, force: true });
|
|
489
|
+
console.log(`\u2713 Data directory deleted: ${dir}`);
|
|
490
|
+
} else {
|
|
491
|
+
console.log("Skipped data directory deletion.");
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
console.log(
|
|
495
|
+
"Data directory preserved. Run `crontick uninstall --purge` to also delete it."
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
} catch (err) {
|
|
499
|
+
handleError(err);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
program.command("dashboard").description("Open the crontick dashboard in a browser").option("--open", "Open in the default browser").action(async (opts) => {
|
|
503
|
+
try {
|
|
504
|
+
const port = getPort();
|
|
505
|
+
const url = `http://127.0.0.1:${port}/dashboard`;
|
|
506
|
+
if (opts.open) {
|
|
507
|
+
if (process.platform === "win32") {
|
|
508
|
+
spawn("cmd", ["/c", "start", url], { detached: true, stdio: "ignore" }).unref();
|
|
509
|
+
} else if (process.platform === "darwin") {
|
|
510
|
+
spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
511
|
+
} else {
|
|
512
|
+
try {
|
|
513
|
+
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
console.log(`Dashboard opened: ${url}`);
|
|
518
|
+
} else {
|
|
519
|
+
console.log(`Dashboard: ${url}`);
|
|
520
|
+
}
|
|
521
|
+
} catch (err) {
|
|
522
|
+
handleError(err);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
program.command("mcp").description("Start the crontick MCP server on stdio (for use with Claude Desktop, Copilot, Cursor, etc.)").option("--no-autostart", "Do not auto-start the daemon if it is not already running").option("--daemon-url <url>", "Override the daemon URL (default: resolved from port file)").addHelpText(
|
|
526
|
+
"after",
|
|
527
|
+
`
|
|
528
|
+
Transport: stdio (JSON-RPC 2.0 over stdin/stdout)
|
|
529
|
+
Tool prefix: crontick_
|
|
530
|
+
Autostart: Daemon is auto-started unless --no-autostart or CRONTICK_MCP_NO_AUTOSTART=1 is set
|
|
531
|
+
|
|
532
|
+
Example MCP host config (Claude Desktop):
|
|
533
|
+
{
|
|
534
|
+
"mcpServers": {
|
|
535
|
+
"crontick": { "command": "crontick", "args": ["mcp"] }
|
|
536
|
+
}
|
|
537
|
+
}`
|
|
538
|
+
).action((opts) => {
|
|
539
|
+
const mcpScript = resolve(__dirname, "../mcp/index.js");
|
|
540
|
+
if (!existsSync(mcpScript)) {
|
|
541
|
+
console.error(`MCP server script not found: ${mcpScript}. Run: npm run build`);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
const env = { ...process.env };
|
|
545
|
+
if (opts.noAutostart) env["CRONTICK_MCP_NO_AUTOSTART"] = "1";
|
|
546
|
+
if (opts.daemonUrl) env["CRONTICK_DAEMON_URL"] = opts.daemonUrl;
|
|
547
|
+
const result = spawnSync(process.execPath, [mcpScript], {
|
|
548
|
+
stdio: "inherit",
|
|
549
|
+
env
|
|
550
|
+
});
|
|
551
|
+
process.exit(result.status ?? 0);
|
|
552
|
+
});
|
|
553
|
+
program.parse();
|
|
554
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { spawn, spawnSync } from 'node:child_process';\nimport { readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { VERSION } from '../version.js';\nimport { portFilePath, pidFilePath, dataDir, ensureDirs } from '../paths.js';\nimport { JobSchema } from '../schemas/job.js';\nimport { CrontickError } from '../errors.js';\nimport { createAutostart } from '../autostart/index.js';\nimport type { AutostartBackend } from '../autostart/index.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// ── API client ────────────────────────────────────────────────────────────────\n\nfunction getPort(): number {\n const pf = portFilePath();\n if (!existsSync(pf)) {\n throw new CrontickError(\n 'DAEMON_NOT_RUNNING',\n 'Daemon is not running. Start it with: crontick daemon start',\n );\n }\n const port = parseInt(readFileSync(pf, 'utf-8').trim(), 10);\n if (isNaN(port)) {\n throw new CrontickError('DAEMON_NOT_RUNNING', 'Could not read daemon port file');\n }\n return port;\n}\n\nasync function api(\n method: string,\n path: string,\n body?: unknown,\n): Promise<unknown> {\n const port = getPort();\n const url = `http://127.0.0.1:${port}${path}`;\n const options: RequestInit = {\n method,\n headers: { 'Content-Type': 'application/json' },\n };\n if (body !== undefined) options.body = JSON.stringify(body);\n const res = await fetch(url, options);\n const text = await res.text();\n let data: unknown;\n try {\n data = JSON.parse(text);\n } catch {\n throw new CrontickError('PARSE_ERROR', `Unexpected response: ${text.slice(0, 200)}`);\n }\n if (!res.ok) {\n const err = (data as { error?: { code?: string; message?: string } })?.error;\n throw new CrontickError(err?.code ?? 'API_ERROR', err?.message ?? `HTTP ${res.status}`);\n }\n return data;\n}\n\n// ── Output helpers ────────────────────────────────────────────────────────────\n\nfunction print(data: unknown, useJson: boolean): void {\n if (useJson) {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n if (Array.isArray(data)) {\n if (data.length === 0) {\n console.log('(no items)');\n return;\n }\n const rows = data as Array<Record<string, unknown>>;\n const keys = Object.keys(rows[0]);\n console.log(keys.join('\\t'));\n for (const row of rows) {\n console.log(\n keys\n .map((k) => {\n const v = row[k];\n return v !== null && typeof v === 'object' ? JSON.stringify(v) : String(v ?? '');\n })\n .join('\\t'),\n );\n }\n } else if (data !== null && typeof data === 'object') {\n const obj = data as Record<string, unknown>;\n for (const [key, value] of Object.entries(obj)) {\n const display =\n value !== null && typeof value === 'object' ? JSON.stringify(value) : String(value ?? '');\n console.log(`${key}: ${display}`);\n }\n } else {\n console.log(String(data ?? ''));\n }\n}\n\nfunction handleError(err: unknown): never {\n if (err instanceof CrontickError) {\n console.error(`Error [${err.code}]: ${err.message}`);\n } else {\n console.error(`Error: ${String(err)}`);\n }\n process.exit(1);\n}\n\n// ── Daemon helpers ────────────────────────────────────────────────────────────\n\nfunction daemonScript(): string {\n // In dist: __dirname = dist/cli/, daemon is at dist/daemon/index.js\n // In source via ts-node/vitest: paths differ, but tests build first\n return resolve(__dirname, '../daemon/index.js');\n}\n\nfunction waitForPort(timeout = 10_000): Promise<number> {\n const start = Date.now();\n return new Promise((resolve, reject) => {\n const check = () => {\n try {\n const port = getPort();\n resolve(port);\n } catch {\n if (Date.now() - start > timeout) {\n reject(new CrontickError('DAEMON_TIMEOUT', 'Timed out waiting for daemon to start'));\n } else {\n setTimeout(check, 200);\n }\n }\n };\n check();\n });\n}\n\n// ── Program ───────────────────────────────────────────────────────────────────\n\nconst program = new Command();\n\nprogram\n .name('crontick')\n .description('A standalone cron daemon, CLI, and MCP server for local scheduled jobs.')\n .version(VERSION)\n .option('--json', 'Output as JSON');\n\n// ── new ───────────────────────────────────────────────────────────────────────\n\nprogram\n .command('new <id>')\n .description('Create a new job')\n .option('--cron <expr>', 'Cron expression (e.g. \"0 9 * * *\")')\n .option('--every <sec>', 'Interval in seconds', parseInt)\n .option('--at <iso>', 'One-shot run-at ISO-8601 time')\n .option('--tz <tz>', 'Timezone for cron schedule')\n .option('--script <body>', 'Inline script body')\n .option('--exec <cmd>', 'Command to exec (use -- for args)')\n .option('--file <path>', 'Load full job from JSON file')\n .option('--shell <shell>', 'Shell: auto|bash|pwsh|cmd', 'auto')\n .option('--env-file <path>', 'Load extra environment variables from a .env file')\n .option('--timeout <sec>', 'Timeout in seconds', parseInt)\n .option('--overlap <policy>', 'Overlap policy: skip|queue|cancel-previous', 'skip')\n .option('--retry <max>', 'Retry count', parseInt)\n .option('--desc <description>', 'Job description')\n .action(async (id: string, opts) => {\n try {\n let jobData: unknown;\n\n if (opts.file) {\n const raw = readFileSync(resolve(process.cwd(), opts.file as string), 'utf-8');\n jobData = JSON.parse(raw);\n } else {\n // Build schedule\n let schedule: unknown;\n if (opts.cron) {\n schedule = { kind: 'cron', cron: opts.cron as string, tz: opts.tz as string | undefined };\n } else if (opts.every) {\n schedule = { kind: 'interval', everySec: opts.every as number };\n } else if (opts.at) {\n schedule = { kind: 'one-shot', runAt: opts.at as string };\n } else {\n throw new CrontickError('MISSING_ARG', 'Provide --cron, --every <sec>, or --at <iso>');\n }\n\n // Build action\n let action: unknown;\n if (opts.script) {\n action = {\n kind: 'script',\n script: opts.script as string,\n shell: opts.shell as string,\n envFile: opts.envFile as string | undefined,\n timeoutSec: opts.timeout as number | undefined,\n };\n } else if (opts.exec) {\n const parts = (opts.exec as string).split(/\\s+/);\n action = {\n kind: 'exec',\n command: parts[0],\n args: parts.slice(1),\n envFile: opts.envFile as string | undefined,\n timeoutSec: opts.timeout as number | undefined,\n };\n } else {\n throw new CrontickError('MISSING_ARG', 'Provide --script or --exec');\n }\n\n jobData = {\n id,\n description: opts.desc as string | undefined,\n schedule,\n action,\n overlap: opts.overlap as string,\n retry: opts.retry !== undefined ? { max: opts.retry as number, backoffSec: 30 } : undefined,\n };\n }\n\n const parsed = JobSchema.safeParse(jobData);\n if (!parsed.success) {\n throw new CrontickError('VALIDATION_ERROR', 'Invalid job', parsed.error.format());\n }\n\n const result = await api('POST', '/api/jobs', parsed.data);\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── list ──────────────────────────────────────────────────────────────────────\n\nprogram\n .command('list')\n .description('List all jobs')\n .action(async () => {\n try {\n const jobs = await api('GET', '/api/jobs');\n print(jobs, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── get ───────────────────────────────────────────────────────────────────────\n\nprogram\n .command('get <id>')\n .description('Get a job by ID')\n .action(async (id: string) => {\n try {\n const job = await api('GET', `/api/jobs/${encodeURIComponent(id)}`);\n print(job, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── enable / disable / delete ─────────────────────────────────────────────────\n\nprogram\n .command('enable <id>')\n .description('Enable a job')\n .action(async (id: string) => {\n try {\n const result = await api('POST', `/api/jobs/${encodeURIComponent(id)}/enable`);\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\nprogram\n .command('disable <id>')\n .description('Disable a job')\n .action(async (id: string) => {\n try {\n const result = await api('POST', `/api/jobs/${encodeURIComponent(id)}/disable`);\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\nprogram\n .command('delete <id>')\n .description('Delete a job')\n .action(async (id: string) => {\n try {\n const result = await api('DELETE', `/api/jobs/${encodeURIComponent(id)}`);\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── run-now ───────────────────────────────────────────────────────────────────\n\nprogram\n .command('run-now <id>')\n .description('Trigger an immediate run of a job')\n .action(async (id: string) => {\n try {\n const result = await api('POST', `/api/jobs/${encodeURIComponent(id)}/run`);\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── logs ──────────────────────────────────────────────────────────────────────\n\nprogram\n .command('logs <runId>')\n .description('Get logs for a run')\n .option('--follow', 'Follow (SSE stream) — not implemented in CLI yet; use --tail')\n .option('--tail <n>', 'Show last N lines', parseInt)\n .action(async (runId: string, opts) => {\n try {\n const useJson = !!(program.opts() as { json?: boolean }).json;\n const logs = await api('GET', `/api/runs/${encodeURIComponent(runId)}/logs`) as Array<{\n stream: string;\n ts: number;\n data: string;\n }>;\n const lines = (Array.isArray(logs) ? logs : []) as Array<{ stream: string; ts: number; data: string }>;\n const tail = opts.tail as number | undefined;\n const display = tail ? lines.slice(-tail) : lines;\n if (useJson) {\n console.log(JSON.stringify(display, null, 2));\n } else {\n for (const entry of display) {\n process.stdout.write(`[${entry.stream}] ${entry.data}`);\n }\n }\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── export / import ───────────────────────────────────────────────────────────\n\nprogram\n .command('export')\n .description('Export all jobs')\n .option('--out <file>', 'Output file (default: stdout)')\n .action(async (opts) => {\n try {\n const data = await api('GET', '/api/export');\n const json = JSON.stringify(data, null, 2);\n if (opts.out) {\n writeFileSync(resolve(process.cwd(), opts.out as string), json, 'utf-8');\n console.log(`Exported to ${opts.out as string}`);\n } else {\n console.log(json);\n }\n } catch (err) {\n handleError(err);\n }\n });\n\nprogram\n .command('import <file>')\n .description('Import jobs from a JSON file')\n .action(async (file: string) => {\n try {\n const raw = readFileSync(resolve(process.cwd(), file), 'utf-8');\n const data = JSON.parse(raw);\n const result = await api('POST', '/api/import', data);\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── doctor ────────────────────────────────────────────────────────────────────\n\nprogram\n .command('doctor')\n .description('Check system health')\n .action(async () => {\n const checks: Array<{ name: string; ok: boolean; note?: string }> = [];\n\n // Node version\n const major = parseInt(process.versions.node.split('.')[0], 10);\n checks.push({\n name: 'Node.js >= 22.5',\n ok: major >= 22,\n note: `v${process.versions.node}`,\n });\n\n // SQLite availability\n try {\n const { DatabaseSync } = await import('node:sqlite');\n new DatabaseSync(':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\n const portFile = portFilePath();\n const portFileExists = existsSync(portFile);\n checks.push({ name: 'port file readable', ok: portFileExists, note: portFileExists ? portFile : 'not found' });\n\n // Daemon reachable\n try {\n await api('GET', '/health');\n checks.push({ name: 'daemon reachable', ok: true });\n } catch {\n checks.push({ name: 'daemon reachable', ok: false, note: 'not running' });\n }\n\n // Dashboard reachable\n try {\n const port2 = getPort();\n const dashRes = await fetch(`http://127.0.0.1:${port2}/dashboard`, {\n signal: AbortSignal.timeout(2000),\n });\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 try {\n const asResult = await api('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\n // MCP server binary\n const mcpScript = resolve(__dirname, '../mcp/index.js');\n checks.push({\n name: 'MCP server binary',\n ok: existsSync(mcpScript),\n note: mcpScript,\n });\n\n // MCP server --help smoke test\n try {\n const result = spawnSync(process.execPath, [mcpScript, '--help'], {\n timeout: 5000,\n encoding: 'utf-8',\n env: { ...process.env, CRONTICK_MCP_NO_AUTOSTART: '1' },\n });\n // --help exits 0 on commander; if exit code is 0 or it printed help text it's fine\n const helpOk = result.status === 0 || (result.stdout ?? '').includes('stdio');\n checks.push({ name: 'MCP server --help', ok: helpOk });\n } catch (err) {\n checks.push({ name: 'MCP server --help', ok: false, note: String(err) });\n }\n\n for (const c of checks) {\n const icon = c.ok ? '✓' : '✗';\n console.log(`${icon} ${c.name}${c.note ? ` (${c.note})` : ''}`);\n }\n\n const failed = checks.filter((c) => !c.ok);\n if (failed.length > 0) process.exit(1);\n });\n\n// ── daemon ────────────────────────────────────────────────────────────────────\n\nconst daemon = program.command('daemon').description('Manage the crontick daemon');\n\ndaemon\n .command('start')\n .description('Start the daemon (detached by default)')\n .option('--foreground', 'Run in foreground (blocking)')\n .action(async (opts) => {\n try {\n const script = daemonScript();\n if (!existsSync(script)) {\n throw new CrontickError('NOT_BUILT', `Daemon script not found: ${script}. Run: npm run build`);\n }\n\n if (opts.foreground as boolean) {\n // Run inline (blocking — for testing/debugging)\n const result = spawnSync(process.execPath, [script], {\n stdio: 'inherit',\n env: process.env,\n });\n process.exit(result.status ?? 0);\n } else {\n const child = spawn(process.execPath, [script], {\n detached: true,\n stdio: 'ignore',\n env: process.env,\n });\n child.unref();\n console.log(`Starting daemon (pid will be written to port file)…`);\n const port = await waitForPort();\n console.log(`Daemon started on port ${port}`);\n }\n } catch (err) {\n handleError(err);\n }\n });\n\ndaemon\n .command('stop')\n .description('Stop the daemon')\n .action(async () => {\n try {\n const pidFile = pidFilePath();\n if (!existsSync(pidFile)) {\n console.log('Daemon is not running');\n return;\n }\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n process.kill(pid, 'SIGTERM');\n console.log(`Sent SIGTERM to daemon (pid ${pid})`);\n } catch (err) {\n handleError(err);\n }\n });\n\ndaemon\n .command('status')\n .description('Show daemon status')\n .action(async () => {\n try {\n const status = await api('GET', '/api/daemon/status');\n print(status, !!(program.opts() as { json?: boolean }).json);\n } catch {\n console.log('Daemon is not running');\n }\n });\n\ndaemon\n .command('reload')\n .description('Reload jobs from disk')\n .action(async () => {\n try {\n const result = await api('POST', '/api/daemon/reload');\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\ndaemon\n .command('restart')\n .description('Restart the daemon')\n .action(async () => {\n try {\n // Stop if running\n const pidFile = pidFilePath();\n if (existsSync(pidFile)) {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n try {\n process.kill(pid, 'SIGTERM');\n await new Promise((r) => setTimeout(r, 1000));\n } catch {\n // ignore\n }\n }\n\n // Start\n const script = daemonScript();\n const child = spawn(process.execPath, [script], {\n detached: true,\n stdio: 'ignore',\n env: process.env,\n });\n child.unref();\n const port = await waitForPort();\n console.log(`Daemon restarted on port ${port}`);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── autostart ─────────────────────────────────────────────────────────────────\n\nconst autostart = program.command('autostart').description('Manage daemon autostart at login');\n\nautostart\n .command('install')\n .description('Register the daemon to start automatically at login')\n .option('--backend <backend>', 'Backend: win32|darwin|linux|manual (default: auto-detect)')\n .action(async (opts) => {\n try {\n const backend = opts.backend as AutostartBackend | undefined;\n const as = createAutostart({ backend });\n const result = await as.install();\n print(result, !!(program.opts() as { json?: boolean }).json);\n if (process.platform !== 'win32' && !backend) {\n const statusResult = await as.status();\n const details = (statusResult.details as Record<string, unknown> | undefined);\n const instr = details?.['instructions'] as string | undefined;\n if (instr) console.log('\\nManual setup instructions:\\n' + instr);\n }\n } catch (err) {\n handleError(err);\n }\n });\n\nautostart\n .command('remove')\n .description('Remove the daemon from automatic startup')\n .option('--backend <backend>', 'Backend: win32|darwin|linux|manual (default: auto-detect)')\n .action(async (opts) => {\n try {\n const backend = opts.backend as AutostartBackend | undefined;\n const as = createAutostart({ backend });\n const result = await as.remove();\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\nautostart\n .command('status')\n .description('Check whether the daemon is registered for automatic startup')\n .option('--backend <backend>', 'Backend: win32|darwin|linux|manual (default: auto-detect)')\n .action(async (opts) => {\n try {\n const backend = opts.backend as AutostartBackend | undefined;\n const as = createAutostart({ backend });\n const result = await as.status();\n print(result, !!(program.opts() as { json?: boolean }).json);\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── uninstall ─────────────────────────────────────────────────────────────────\n\nprogram\n .command('uninstall')\n .description('Remove autostart entry and optionally delete all crontick data')\n .option('--purge', 'Also delete the data directory (jobs, runs, config)')\n .option('--yes', 'Skip confirmation prompts')\n .action(async (opts) => {\n try {\n // Remove autostart\n const as = createAutostart();\n await as.remove();\n console.log('✓ Autostart entry removed.');\n\n if (opts.purge as boolean) {\n const dir = dataDir();\n let confirmed = opts.yes as boolean;\n if (!confirmed) {\n confirmed = await new Promise<boolean>((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`Delete all crontick data at ${dir}? [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n }\n if (confirmed) {\n rmSync(dir, { recursive: true, force: true });\n console.log(`✓ Data directory deleted: ${dir}`);\n } else {\n console.log('Skipped data directory deletion.');\n }\n } else {\n console.log(\n 'Data directory preserved. Run `crontick uninstall --purge` to also delete it.',\n );\n }\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── dashboard ─────────────────────────────────────────────────────────────────\n\nprogram\n .command('dashboard')\n .description('Open the crontick dashboard in a browser')\n .option('--open', 'Open in the default browser')\n .action(async (opts) => {\n try {\n const port = getPort();\n const url = `http://127.0.0.1:${port}/dashboard`;\n if (opts.open as boolean) {\n if (process.platform === 'win32') {\n spawn('cmd', ['/c', 'start', url], { detached: true, stdio: 'ignore' }).unref();\n } else if (process.platform === 'darwin') {\n spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();\n } else {\n try {\n spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();\n } catch {\n // xdg-open not available\n }\n }\n console.log(`Dashboard opened: ${url}`);\n } else {\n console.log(`Dashboard: ${url}`);\n }\n } catch (err) {\n handleError(err);\n }\n });\n\n// ── mcp ───────────────────────────────────────────────────────────────────────\n\nprogram\n .command('mcp')\n .description('Start the crontick MCP server on stdio (for use with Claude Desktop, Copilot, Cursor, etc.)')\n .option('--no-autostart', 'Do not auto-start the daemon if it is not already running')\n .option('--daemon-url <url>', 'Override the daemon URL (default: resolved from port file)')\n .addHelpText(\n 'after',\n `\nTransport: stdio (JSON-RPC 2.0 over stdin/stdout)\nTool prefix: crontick_\nAutostart: Daemon is auto-started unless --no-autostart or CRONTICK_MCP_NO_AUTOSTART=1 is set\n\nExample MCP host config (Claude Desktop):\n {\n \"mcpServers\": {\n \"crontick\": { \"command\": \"crontick\", \"args\": [\"mcp\"] }\n }\n }`,\n )\n .action((opts) => {\n const mcpScript = resolve(__dirname, '../mcp/index.js');\n if (!existsSync(mcpScript)) {\n console.error(`MCP server script not found: ${mcpScript}. Run: npm run build`);\n process.exit(1);\n }\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (opts.noAutostart) env['CRONTICK_MCP_NO_AUTOSTART'] = '1';\n if (opts.daemonUrl) env['CRONTICK_DAEMON_URL'] = opts.daemonUrl as string;\n const result = spawnSync(process.execPath, [mcpScript], {\n stdio: 'inherit',\n env,\n });\n process.exit(result.status ?? 0);\n });\n\n// ── parse ─────────────────────────────────────────────────────────────────────\n\nprogram.parse();\n"],"mappings":"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","names":["resolve"]}
|