openjob 1.0.3
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/bin/jobs +292 -0
- package/lib/daemon.d.ts +12 -0
- package/lib/daemon.d.ts.map +1 -0
- package/lib/daemon.js +172 -0
- package/lib/daemon.js.map +1 -0
- package/lib/dashboard.d.ts +3 -0
- package/lib/dashboard.d.ts.map +1 -0
- package/lib/dashboard.js +215 -0
- package/lib/dashboard.js.map +1 -0
- package/lib/executor.d.ts +7 -0
- package/lib/executor.d.ts.map +1 -0
- package/lib/executor.js +240 -0
- package/lib/executor.js.map +1 -0
- package/lib/parser.d.ts +3 -0
- package/lib/parser.d.ts.map +1 -0
- package/lib/parser.js +79 -0
- package/lib/parser.js.map +1 -0
- package/lib/prompt.d.ts +3 -0
- package/lib/prompt.d.ts.map +1 -0
- package/lib/prompt.js +15 -0
- package/lib/prompt.js.map +1 -0
- package/lib/registry.d.ts +20 -0
- package/lib/registry.d.ts.map +1 -0
- package/lib/registry.js +166 -0
- package/lib/registry.js.map +1 -0
- package/lib/state.d.ts +21 -0
- package/lib/state.d.ts.map +1 -0
- package/lib/state.js +214 -0
- package/lib/state.js.map +1 -0
- package/lib/sync.d.ts +8 -0
- package/lib/sync.d.ts.map +1 -0
- package/lib/sync.js +83 -0
- package/lib/sync.js.map +1 -0
- package/lib/types.d.ts +123 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +3 -0
- package/lib/types.js.map +1 -0
- package/package.json +35 -0
package/bin/jobs
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { version } = require('../package.json');
|
|
6
|
+
const { parseJob } = require('../lib/parser');
|
|
7
|
+
const { generatePrompt } = require('../lib/prompt');
|
|
8
|
+
const { initRegistry, addJob, removeJob, enableJob, disableJob, getJob, listJobs, readRegistry, REGISTRY_PATH, JOBS_DIR } = require('../lib/registry');
|
|
9
|
+
const { syncToClaude } = require('../lib/sync');
|
|
10
|
+
const { ensureRegistryState, summarizeJobs, readDaemonState, readRunLog } = require('../lib/state');
|
|
11
|
+
const { executeJob } = require('../lib/executor');
|
|
12
|
+
const { startDaemon, stopDaemon, runLoop, isProcessAlive } = require('../lib/daemon');
|
|
13
|
+
const { createServer } = require('../lib/dashboard');
|
|
14
|
+
|
|
15
|
+
const C = {
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
bold: '\x1b[1m',
|
|
18
|
+
dim: '\x1b[2m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
green: '\x1b[32m',
|
|
21
|
+
yellow: '\x1b[33m',
|
|
22
|
+
blue: '\x1b[34m',
|
|
23
|
+
cyan: '\x1b[36m',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const CLI_NAME = 'openjob';
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const cmd = args[0];
|
|
29
|
+
|
|
30
|
+
function printUsage() {
|
|
31
|
+
console.log(`
|
|
32
|
+
${C.bold}${CLI_NAME}${C.reset} - Scheduled task system for AI agents
|
|
33
|
+
|
|
34
|
+
${C.bold}USAGE${C.reset}
|
|
35
|
+
${CLI_NAME} <command> [options]
|
|
36
|
+
|
|
37
|
+
${C.bold}COMMANDS${C.reset}
|
|
38
|
+
${C.cyan}init${C.reset} Initialize the global jobs registry
|
|
39
|
+
${C.cyan}add${C.reset} <path> Install a job from a JOB.md file or directory
|
|
40
|
+
${C.cyan}remove${C.reset} <name> Remove a job by name
|
|
41
|
+
${C.cyan}list${C.reset} List all registered jobs
|
|
42
|
+
${C.cyan}status${C.reset} Show local runner status and job runtime info
|
|
43
|
+
${C.cyan}enable${C.reset} <name> Enable a job
|
|
44
|
+
${C.cyan}disable${C.reset} <name> Disable a job
|
|
45
|
+
${C.cyan}daemon start${C.reset} Start local jobs daemon
|
|
46
|
+
${C.cyan}daemon stop${C.reset} Stop local jobs daemon
|
|
47
|
+
${C.cyan}daemon status${C.reset} Show daemon status
|
|
48
|
+
${C.cyan}dashboard${C.reset} Open local web dashboard
|
|
49
|
+
${C.cyan}sync${C.reset} Sync enabled jobs to Claude scheduled_tasks.json
|
|
50
|
+
${C.cyan}run${C.reset} <name> Execute a job immediately
|
|
51
|
+
${C.cyan}--version${C.reset}, ${C.cyan}-v${C.reset} Show CLI version
|
|
52
|
+
${C.cyan}--help${C.reset} Show this help message
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatTable(jobs) {
|
|
57
|
+
if (jobs.length === 0) {
|
|
58
|
+
console.log(` ${C.dim}No jobs registered.${C.reset}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const nameWidth = Math.max(4, ...jobs.map(j => j.name.length));
|
|
63
|
+
const cronWidth = Math.max(4, ...jobs.map(j => j.cron.length));
|
|
64
|
+
const statusWidth = 6;
|
|
65
|
+
|
|
66
|
+
const header = `${'NAME'.padEnd(nameWidth)} ${'CRON'.padEnd(cronWidth)} ${'STATUS'.padEnd(statusWidth)} DESCRIPTION`;
|
|
67
|
+
const divider = '-'.repeat(header.length);
|
|
68
|
+
|
|
69
|
+
console.log(` ${C.bold}${header}${C.reset}`);
|
|
70
|
+
console.log(` ${C.dim}${divider}${C.reset}`);
|
|
71
|
+
|
|
72
|
+
for (const job of jobs) {
|
|
73
|
+
const statusColor = job.enabled ? C.green : C.red;
|
|
74
|
+
const status = job.enabled ? 'on' : 'off';
|
|
75
|
+
console.log(` ${job.name.padEnd(nameWidth)} ${job.cron.padEnd(cronWidth)} ${statusColor}${status.padEnd(statusWidth + C.green.length + C.reset.length - statusColor.length - C.reset.length)}${C.reset} ${job.description}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatStatusTable(jobs) {
|
|
80
|
+
if (jobs.length === 0) {
|
|
81
|
+
console.log(` ${C.dim}No jobs registered.${C.reset}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const nameWidth = Math.max(4, ...jobs.map(j => j.name.length));
|
|
86
|
+
const statusWidth = Math.max(6, ...jobs.map(j => (j.lastStatus || 'idle').length));
|
|
87
|
+
console.log(` ${C.bold}${'NAME'.padEnd(nameWidth)} ${'STATUS'.padEnd(statusWidth)} NEXT RUN LAST RUN ERROR${C.reset}`);
|
|
88
|
+
for (const job of jobs) {
|
|
89
|
+
const status = job.lastStatus || 'idle';
|
|
90
|
+
const color = status === 'success' ? C.green : status === 'failed' ? C.red : status === 'running' ? C.blue : C.yellow;
|
|
91
|
+
console.log(` ${job.name.padEnd(nameWidth)} ${color}${status.padEnd(statusWidth)}${C.reset} ${(job.nextRun || '-').padEnd(24)} ${(job.lastRun || '-').padEnd(24)} ${job.lastError || job.lastExitReason || '-'}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function main() {
|
|
96
|
+
switch (cmd) {
|
|
97
|
+
case 'init': {
|
|
98
|
+
const registry = initRegistry();
|
|
99
|
+
ensureRegistryState();
|
|
100
|
+
console.log(`${C.green}Initialized global jobs registry${C.reset} at ${REGISTRY_PATH}`);
|
|
101
|
+
console.log(` jobs dir: ${JOBS_DIR}`);
|
|
102
|
+
console.log(` ${registry.jobs.length} jobs`);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case 'add': {
|
|
107
|
+
let filePath = args[1];
|
|
108
|
+
if (!filePath) {
|
|
109
|
+
console.error(`${C.red}Error:${C.reset} Missing file path. Usage: ${CLI_NAME} add <path>`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
// If path is a directory, append JOB.md
|
|
113
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
|
114
|
+
filePath = path.join(filePath, 'JOB.md');
|
|
115
|
+
}
|
|
116
|
+
const job = parseJob(filePath);
|
|
117
|
+
const entry = addJob(job);
|
|
118
|
+
ensureRegistryState();
|
|
119
|
+
console.log(`${C.green}Added job:${C.reset} ${job.name}`);
|
|
120
|
+
console.log(` cron: ${job.cron}`);
|
|
121
|
+
console.log(` source: ${entry.source}`);
|
|
122
|
+
console.log(` original: ${entry.originalSource}`);
|
|
123
|
+
// Auto-sync after adding
|
|
124
|
+
const registry = readRegistry();
|
|
125
|
+
const result = syncToClaude(registry);
|
|
126
|
+
console.log(`${C.blue}Synced${C.reset} ${result.synced} jobs to Claude`);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case 'remove': {
|
|
131
|
+
const name = args[1];
|
|
132
|
+
if (!name) {
|
|
133
|
+
console.error(`${C.red}Error:${C.reset} Missing job name. Usage: ${CLI_NAME} remove <name>`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
removeJob(name);
|
|
137
|
+
console.log(`${C.green}Removed job:${C.reset} ${name}`);
|
|
138
|
+
// Auto-sync after removing
|
|
139
|
+
const registry = readRegistry();
|
|
140
|
+
const result = syncToClaude(registry);
|
|
141
|
+
console.log(`${C.blue}Synced${C.reset} ${result.synced} jobs to Claude`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'list': {
|
|
146
|
+
ensureRegistryState();
|
|
147
|
+
const jobs = listJobs();
|
|
148
|
+
console.log(`\n${C.bold}Registered Jobs${C.reset} (${jobs.length})\n`);
|
|
149
|
+
formatTable(jobs);
|
|
150
|
+
console.log();
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case 'status': {
|
|
155
|
+
ensureRegistryState();
|
|
156
|
+
const daemon = readDaemonState();
|
|
157
|
+
const jobs = summarizeJobs();
|
|
158
|
+
console.log(`\n${C.bold}Daemon${C.reset}`);
|
|
159
|
+
console.log(` status: ${daemon.status}`);
|
|
160
|
+
console.log(` pid: ${daemon.pid || '-'}`);
|
|
161
|
+
console.log(` heartbeat: ${daemon.heartbeatAt || '-'}`);
|
|
162
|
+
console.log(` wake gap: ${daemon.lastWakeGapMs || 0}ms`);
|
|
163
|
+
console.log(`\n${C.bold}Jobs Runtime${C.reset}\n`);
|
|
164
|
+
formatStatusTable(jobs);
|
|
165
|
+
console.log();
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'enable': {
|
|
170
|
+
const name = args[1];
|
|
171
|
+
if (!name) {
|
|
172
|
+
console.error(`${C.red}Error:${C.reset} Missing job name. Usage: ${CLI_NAME} enable <name>`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
enableJob(name);
|
|
176
|
+
console.log(`${C.green}Enabled job:${C.reset} ${name}`);
|
|
177
|
+
const registry = readRegistry();
|
|
178
|
+
syncToClaude(registry);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'disable': {
|
|
183
|
+
const name = args[1];
|
|
184
|
+
if (!name) {
|
|
185
|
+
console.error(`${C.red}Error:${C.reset} Missing job name. Usage: ${CLI_NAME} disable <name>`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
disableJob(name);
|
|
189
|
+
console.log(`${C.yellow}Disabled job:${C.reset} ${name}`);
|
|
190
|
+
const registry = readRegistry();
|
|
191
|
+
syncToClaude(registry);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'sync': {
|
|
196
|
+
const registry = readRegistry();
|
|
197
|
+
const result = syncToClaude(registry);
|
|
198
|
+
console.log(`${C.green}Synced${C.reset} ${result.synced} enabled jobs to Claude`);
|
|
199
|
+
if (result.removed > 0) {
|
|
200
|
+
console.log(` ${C.dim}Removed ${result.removed} stale job(s) from Claude${C.reset}`);
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case 'run': {
|
|
206
|
+
const name = args[1];
|
|
207
|
+
if (!name) {
|
|
208
|
+
console.error(`${C.red}Error:${C.reset} Missing job name. Usage: ${CLI_NAME} run <name>`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
const job = getJob(name);
|
|
212
|
+
if (!job) {
|
|
213
|
+
console.error(`${C.red}Error:${C.reset} Job "${name}" not found`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
ensureRegistryState();
|
|
217
|
+
const result = await executeJob(job, 'manual');
|
|
218
|
+
console.log(`${C.green}${result.status.toUpperCase()}${C.reset} ${name}`);
|
|
219
|
+
console.log(` started: ${result.startedAt}`);
|
|
220
|
+
console.log(` finished: ${result.finishedAt}`);
|
|
221
|
+
console.log(` exit: ${result.exitReason}`);
|
|
222
|
+
if (result.stderr) console.log(` stderr: ${result.stderr.trim().slice(0, 400)}`);
|
|
223
|
+
else if (result.stdout) console.log(` stdout: ${result.stdout.trim().slice(0, 400)}`);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
case 'daemon': {
|
|
228
|
+
const action = args[1];
|
|
229
|
+
ensureRegistryState();
|
|
230
|
+
if (action === 'start') {
|
|
231
|
+
const state = startDaemon();
|
|
232
|
+
console.log(`${C.green}Daemon${C.reset} ${state.status}`);
|
|
233
|
+
console.log(` pid: ${state.pid || '-'}`);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (action === 'stop') {
|
|
237
|
+
const state = stopDaemon();
|
|
238
|
+
console.log(`${C.yellow}Daemon${C.reset} ${state.status}`);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
if (action === 'status') {
|
|
242
|
+
const state = readDaemonState();
|
|
243
|
+
const alive = state.pid ? isProcessAlive(state.pid) : false;
|
|
244
|
+
console.log(`${C.bold}Daemon Status${C.reset}`);
|
|
245
|
+
console.log(` status: ${state.status}`);
|
|
246
|
+
console.log(` pid: ${state.pid || '-'}`);
|
|
247
|
+
console.log(` alive: ${alive ? 'yes' : 'no'}`);
|
|
248
|
+
console.log(` startedAt: ${state.startedAt || '-'}`);
|
|
249
|
+
console.log(` heartbeatAt: ${state.heartbeatAt || '-'}`);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
if (action === 'run') {
|
|
253
|
+
await runLoop();
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
console.error(`${C.red}Error:${C.reset} Usage: ${CLI_NAME} daemon <start|stop|status>`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case 'dashboard': {
|
|
261
|
+
ensureRegistryState();
|
|
262
|
+
const port = args[1] ? Number(args[1]) : 0;
|
|
263
|
+
createServer(Number.isFinite(port) ? port : 0, true);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case '--help':
|
|
268
|
+
case '-h':
|
|
269
|
+
case 'help':
|
|
270
|
+
printUsage();
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case '--version':
|
|
274
|
+
case '-v':
|
|
275
|
+
console.log(version);
|
|
276
|
+
break;
|
|
277
|
+
|
|
278
|
+
case undefined:
|
|
279
|
+
printUsage();
|
|
280
|
+
break;
|
|
281
|
+
|
|
282
|
+
default:
|
|
283
|
+
console.error(`${C.red}Unknown command:${C.reset} ${cmd}`);
|
|
284
|
+
console.error(`Run ${C.bold}${CLI_NAME} --help${C.reset} for usage.`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
main().catch((err) => {
|
|
290
|
+
console.error(`${C.red}Error:${C.reset} ${err.message}`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|
package/lib/daemon.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readDaemonState } from './state';
|
|
2
|
+
import { Job, DaemonState } from './types';
|
|
3
|
+
export declare function isProcessAlive(pid: number | null | undefined): boolean;
|
|
4
|
+
export declare function dueJobs(current?: Date): Job[];
|
|
5
|
+
export declare function collectMissedJobs(lastHeartbeat: Date, current: Date): Job[];
|
|
6
|
+
export declare function markSleepMissedJobs(lastHeartbeat: Date, current: Date): Promise<number>;
|
|
7
|
+
export declare function tick(): Promise<void>;
|
|
8
|
+
export declare function runLoop(): Promise<void>;
|
|
9
|
+
export declare function startDaemon(): DaemonState;
|
|
10
|
+
export declare function stopDaemon(): DaemonState;
|
|
11
|
+
export { readDaemonState };
|
|
12
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,eAAe,EAKhB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAK3C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAQtE;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,IAAiB,GAAG,GAAG,EAAE,CASzD;AAED,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,GAAG,EAAE,CAc3E;AAED,wBAAsB,mBAAmB,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAY7F;AAED,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B1C;AAED,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAmB7C;AAED,wBAAgB,WAAW,IAAI,WAAW,CAezC;AAED,wBAAgB,UAAU,IAAI,WAAW,CAMxC;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/lib/daemon.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readDaemonState = void 0;
|
|
37
|
+
exports.isProcessAlive = isProcessAlive;
|
|
38
|
+
exports.dueJobs = dueJobs;
|
|
39
|
+
exports.collectMissedJobs = collectMissedJobs;
|
|
40
|
+
exports.markSleepMissedJobs = markSleepMissedJobs;
|
|
41
|
+
exports.tick = tick;
|
|
42
|
+
exports.runLoop = runLoop;
|
|
43
|
+
exports.startDaemon = startDaemon;
|
|
44
|
+
exports.stopDaemon = stopDaemon;
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const cron_parser_1 = require("cron-parser");
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const state_1 = require("./state");
|
|
49
|
+
Object.defineProperty(exports, "readDaemonState", { enumerable: true, get: function () { return state_1.readDaemonState; } });
|
|
50
|
+
const executor_1 = require("./executor");
|
|
51
|
+
const HEARTBEAT_INTERVAL_MS = 15_000;
|
|
52
|
+
const SLEEP_GAP_THRESHOLD_MS = 90_000;
|
|
53
|
+
function isProcessAlive(pid) {
|
|
54
|
+
if (!pid)
|
|
55
|
+
return false;
|
|
56
|
+
try {
|
|
57
|
+
process.kill(pid, 0);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function dueJobs(current = new Date()) {
|
|
65
|
+
const registry = (0, state_1.ensureRegistryState)();
|
|
66
|
+
return registry.jobs.filter(job => {
|
|
67
|
+
if (!job.enabled)
|
|
68
|
+
return false;
|
|
69
|
+
if (job.lastStatus === 'running')
|
|
70
|
+
return false;
|
|
71
|
+
const nextRun = job.nextRun || (0, state_1.safeNextRun)(job.cron, current);
|
|
72
|
+
if (!nextRun)
|
|
73
|
+
return false;
|
|
74
|
+
return new Date(nextRun).getTime() <= current.getTime();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function collectMissedJobs(lastHeartbeat, current) {
|
|
78
|
+
const registry = (0, state_1.ensureRegistryState)();
|
|
79
|
+
const from = new Date(lastHeartbeat);
|
|
80
|
+
const to = new Date(current);
|
|
81
|
+
return registry.jobs.filter(job => {
|
|
82
|
+
if (!job.enabled)
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
const interval = (0, cron_parser_1.parseExpression)(job.cron, { currentDate: from, endDate: to });
|
|
86
|
+
interval.next();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function markSleepMissedJobs(lastHeartbeat, current) {
|
|
95
|
+
const jobs = collectMissedJobs(lastHeartbeat, current);
|
|
96
|
+
for (const job of jobs) {
|
|
97
|
+
(0, state_1.updateJob)(job.name, currentJob => ({
|
|
98
|
+
...currentJob,
|
|
99
|
+
lastStatus: 'missed',
|
|
100
|
+
lastError: 'device_sleep_suspected',
|
|
101
|
+
lastExitReason: 'sleep_missed',
|
|
102
|
+
nextRun: (0, state_1.safeNextRun)(currentJob.cron, current)
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
return jobs.length;
|
|
106
|
+
}
|
|
107
|
+
async function tick() {
|
|
108
|
+
const state = (0, state_1.readDaemonState)();
|
|
109
|
+
const now = new Date();
|
|
110
|
+
const lastHeartbeat = state.heartbeatAt ? new Date(state.heartbeatAt) : null;
|
|
111
|
+
const gapMs = lastHeartbeat ? now.getTime() - lastHeartbeat.getTime() : 0;
|
|
112
|
+
if (gapMs > SLEEP_GAP_THRESHOLD_MS && lastHeartbeat) {
|
|
113
|
+
await markSleepMissedJobs(lastHeartbeat, now);
|
|
114
|
+
(0, state_1.writeDaemonState)({ lastWakeGapMs: gapMs });
|
|
115
|
+
}
|
|
116
|
+
(0, state_1.writeDaemonState)({ status: 'running', pid: process.pid, machineId: state_1.MACHINE_ID, heartbeatAt: now.toISOString() });
|
|
117
|
+
const jobs = dueJobs(now);
|
|
118
|
+
for (const job of jobs) {
|
|
119
|
+
try {
|
|
120
|
+
await (0, executor_1.executeJob)(job, 'scheduled');
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
124
|
+
(0, state_1.updateJob)(job.name, currentJob => ({
|
|
125
|
+
...currentJob,
|
|
126
|
+
lastStatus: 'failed',
|
|
127
|
+
lastError: errorMsg,
|
|
128
|
+
lastExitReason: 'runner_exception',
|
|
129
|
+
nextRun: (0, state_1.safeNextRun)(currentJob.cron, new Date())
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function runLoop() {
|
|
135
|
+
(0, state_1.ensureRegistryState)();
|
|
136
|
+
(0, state_1.writeDaemonState)({ status: 'running', pid: process.pid, machineId: state_1.MACHINE_ID, startedAt: new Date().toISOString(), heartbeatAt: new Date().toISOString() });
|
|
137
|
+
const handleStop = () => {
|
|
138
|
+
(0, state_1.writeDaemonState)({ status: 'stopped', pid: null, machineId: state_1.MACHINE_ID, heartbeatAt: new Date().toISOString() });
|
|
139
|
+
process.exit(0);
|
|
140
|
+
};
|
|
141
|
+
process.on('SIGINT', handleStop);
|
|
142
|
+
process.on('SIGTERM', handleStop);
|
|
143
|
+
await tick();
|
|
144
|
+
setInterval(() => {
|
|
145
|
+
tick().catch(error => {
|
|
146
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
147
|
+
(0, state_1.writeDaemonState)({ status: 'degraded', pid: process.pid, machineId: state_1.MACHINE_ID, heartbeatAt: new Date().toISOString(), lastError: errorMsg });
|
|
148
|
+
});
|
|
149
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
150
|
+
}
|
|
151
|
+
function startDaemon() {
|
|
152
|
+
const state = (0, state_1.readDaemonState)();
|
|
153
|
+
if (state.pid && isProcessAlive(state.pid)) {
|
|
154
|
+
return state;
|
|
155
|
+
}
|
|
156
|
+
const entry = path.join(__dirname, '..', 'bin', 'jobs');
|
|
157
|
+
const child = (0, child_process_1.spawn)(process.execPath, [entry, 'daemon', 'run'], {
|
|
158
|
+
detached: true,
|
|
159
|
+
stdio: 'ignore',
|
|
160
|
+
env: process.env
|
|
161
|
+
});
|
|
162
|
+
child.unref();
|
|
163
|
+
return (0, state_1.writeDaemonState)({ status: 'starting', pid: child.pid, machineId: state_1.MACHINE_ID, startedAt: new Date().toISOString(), heartbeatAt: null });
|
|
164
|
+
}
|
|
165
|
+
function stopDaemon() {
|
|
166
|
+
const state = (0, state_1.readDaemonState)();
|
|
167
|
+
if (state.pid && isProcessAlive(state.pid)) {
|
|
168
|
+
process.kill(state.pid, 'SIGTERM');
|
|
169
|
+
}
|
|
170
|
+
return (0, state_1.writeDaemonState)({ status: 'stopped', pid: null, machineId: state_1.MACHINE_ID });
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAkI7B,wBAAgB,YAAY,CAAC,IAAI,SAAI,EAAE,QAAQ,UAAO,GAAG,IAAI,CAAC,MAAM,CAyDnE"}
|