@damian87/omp 0.4.1 → 0.6.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/.github/agents/researcher.md +7 -6
- package/.github/copilot-instructions.md +23 -0
- package/.github/skills/daily-log/SKILL.md +64 -0
- package/.github/skills/goal/SKILL.md +33 -0
- package/.github/skills/schedule/SKILL.md +71 -0
- package/README.md +19 -2
- package/dist/src/cli.js +272 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/comms/index.d.ts +116 -0
- package/dist/src/comms/index.js +258 -0
- package/dist/src/comms/index.js.map +1 -0
- package/dist/src/comms/resolve-session.d.ts +35 -0
- package/dist/src/comms/resolve-session.js +53 -0
- package/dist/src/comms/resolve-session.js.map +1 -0
- package/dist/src/daily-log.d.ts +18 -0
- package/dist/src/daily-log.js +138 -0
- package/dist/src/daily-log.js.map +1 -0
- package/dist/src/goal.d.ts +4 -0
- package/dist/src/goal.js +44 -0
- package/dist/src/goal.js.map +1 -0
- package/dist/src/instructions-memory.d.ts +9 -0
- package/dist/src/instructions-memory.js +72 -0
- package/dist/src/instructions-memory.js.map +1 -0
- package/dist/src/mcp/tools/daily-log.d.ts +2 -0
- package/dist/src/mcp/tools/daily-log.js +148 -0
- package/dist/src/mcp/tools/daily-log.js.map +1 -0
- package/dist/src/omp-root.d.ts +1 -0
- package/dist/src/omp-root.js +19 -0
- package/dist/src/omp-root.js.map +1 -0
- package/dist/src/project-memory.d.ts +13 -0
- package/dist/src/project-memory.js +105 -0
- package/dist/src/project-memory.js.map +1 -0
- package/dist/src/schedule/commands.d.ts +34 -0
- package/dist/src/schedule/commands.js +130 -0
- package/dist/src/schedule/commands.js.map +1 -0
- package/dist/src/schedule/installer.d.ts +20 -0
- package/dist/src/schedule/installer.js +76 -0
- package/dist/src/schedule/installer.js.map +1 -0
- package/dist/src/schedule/installers/crontab.d.ts +17 -0
- package/dist/src/schedule/installers/crontab.js +112 -0
- package/dist/src/schedule/installers/crontab.js.map +1 -0
- package/dist/src/schedule/installers/launchd.d.ts +22 -0
- package/dist/src/schedule/installers/launchd.js +125 -0
- package/dist/src/schedule/installers/launchd.js.map +1 -0
- package/dist/src/schedule/installers/systemd.d.ts +15 -0
- package/dist/src/schedule/installers/systemd.js +136 -0
- package/dist/src/schedule/installers/systemd.js.map +1 -0
- package/dist/src/schedule/job-store.d.ts +21 -0
- package/dist/src/schedule/job-store.js +102 -0
- package/dist/src/schedule/job-store.js.map +1 -0
- package/dist/src/schedule/lock.d.ts +9 -0
- package/dist/src/schedule/lock.js +83 -0
- package/dist/src/schedule/lock.js.map +1 -0
- package/dist/src/schedule/paths.d.ts +15 -0
- package/dist/src/schedule/paths.js +36 -0
- package/dist/src/schedule/paths.js.map +1 -0
- package/dist/src/schedule/runner.d.ts +8 -0
- package/dist/src/schedule/runner.js +151 -0
- package/dist/src/schedule/runner.js.map +1 -0
- package/dist/src/schedule/types.d.ts +60 -0
- package/dist/src/schedule/types.js +5 -0
- package/dist/src/schedule/types.js.map +1 -0
- package/dist/src/state.d.ts +17 -0
- package/dist/src/state.js +101 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/trace.d.ts +19 -0
- package/dist/src/trace.js +74 -0
- package/dist/src/trace.js.map +1 -0
- package/dist/test/catalog.test.d.ts +1 -0
- package/dist/test/catalog.test.js +21 -0
- package/dist/test/catalog.test.js.map +1 -0
- package/dist/test/jira.test.d.ts +1 -0
- package/dist/test/jira.test.js +26 -0
- package/dist/test/jira.test.js.map +1 -0
- package/dist/test/lint.test.d.ts +1 -0
- package/dist/test/lint.test.js +9 -0
- package/dist/test/lint.test.js.map +1 -0
- package/dist/test/sync.test.d.ts +1 -0
- package/dist/test/sync.test.js +15 -0
- package/dist/test/sync.test.js.map +1 -0
- package/docs/research/2026-06-01-schedule-cron-feature.md +346 -0
- package/package.json +1 -1
- package/scripts/lib/daily-log.mjs +155 -0
- package/scripts/lib/hook-output.mjs +2 -1
- package/scripts/lib/omp-root.mjs +15 -0
- package/scripts/lib/project-memory.mjs +21 -0
- package/scripts/lib/schedule-results.mjs +88 -0
- package/scripts/prompt-submit.mjs +14 -2
- package/scripts/session-end.mjs +6 -1
- package/scripts/session-start.mjs +60 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { ompRoot } from "./omp-root.js";
|
|
4
|
+
function memPath(cwd) {
|
|
5
|
+
return join(ompRoot(cwd), ".omp", "project-memory.json");
|
|
6
|
+
}
|
|
7
|
+
function notesDir(cwd) {
|
|
8
|
+
return join(ompRoot(cwd), ".omp", "memory", "notes");
|
|
9
|
+
}
|
|
10
|
+
// --- directives (rules, injected at session start) ---
|
|
11
|
+
function readMem(cwd) {
|
|
12
|
+
const p = memPath(cwd);
|
|
13
|
+
if (!existsSync(p))
|
|
14
|
+
return { directives: [], updatedAt: new Date(0).toISOString() };
|
|
15
|
+
try {
|
|
16
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
17
|
+
return {
|
|
18
|
+
directives: Array.isArray(data?.directives) ? data.directives : [],
|
|
19
|
+
updatedAt: typeof data?.updatedAt === "string" ? data.updatedAt : new Date(0).toISOString(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { directives: [], updatedAt: new Date(0).toISOString() };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeMem(cwd, mem) {
|
|
27
|
+
const p = memPath(cwd);
|
|
28
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
29
|
+
const tmp = `${p}.tmp.${process.pid}.${Date.now()}`;
|
|
30
|
+
writeFileSync(tmp, JSON.stringify({ directives: mem.directives, updatedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
31
|
+
renameSync(tmp, p);
|
|
32
|
+
}
|
|
33
|
+
export function readDirectives(cwd) {
|
|
34
|
+
return readMem(cwd).directives;
|
|
35
|
+
}
|
|
36
|
+
/** Append a must-follow directive; returns the new directive count. */
|
|
37
|
+
export function addDirective(cwd, directive) {
|
|
38
|
+
const mem = readMem(cwd);
|
|
39
|
+
mem.directives.push(String(directive).trim());
|
|
40
|
+
writeMem(cwd, mem);
|
|
41
|
+
return mem.directives.length;
|
|
42
|
+
}
|
|
43
|
+
function slugify(title) {
|
|
44
|
+
return (String(title)
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
47
|
+
.replace(/^-+|-+$/g, "")
|
|
48
|
+
.slice(0, 50) || "note");
|
|
49
|
+
}
|
|
50
|
+
/** Create a note (title + optional body); returns its id (slug, deduped). */
|
|
51
|
+
export function addNote(cwd, title, body) {
|
|
52
|
+
const dir = notesDir(cwd);
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
const base = slugify(title);
|
|
55
|
+
let id = base;
|
|
56
|
+
let n = 1;
|
|
57
|
+
while (existsSync(join(dir, `${id}.md`))) {
|
|
58
|
+
n += 1;
|
|
59
|
+
id = `${base}-${n}`;
|
|
60
|
+
}
|
|
61
|
+
const content = `# ${String(title).trim()}\n${body ? `\n${String(body).trim()}\n` : ""}`;
|
|
62
|
+
const p = join(dir, `${id}.md`);
|
|
63
|
+
const tmp = `${p}.tmp.${process.pid}.${Date.now()}`;
|
|
64
|
+
writeFileSync(tmp, content, "utf8");
|
|
65
|
+
renameSync(tmp, p);
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
/** Cheap index of (id, title) — the only thing surfaced; bodies stay on disk. */
|
|
69
|
+
export function noteIndex(cwd) {
|
|
70
|
+
const dir = notesDir(cwd);
|
|
71
|
+
if (!existsSync(dir))
|
|
72
|
+
return [];
|
|
73
|
+
return readdirSync(dir)
|
|
74
|
+
.filter((f) => f.endsWith(".md"))
|
|
75
|
+
.map((f) => {
|
|
76
|
+
const id = f.replace(/\.md$/, "");
|
|
77
|
+
let title = id;
|
|
78
|
+
try {
|
|
79
|
+
const first = readFileSync(join(dir, f), "utf8").split("\n")[0] ?? "";
|
|
80
|
+
title = first.replace(/^#\s*/, "").trim() || id;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// keep id as title
|
|
84
|
+
}
|
|
85
|
+
return { id, title };
|
|
86
|
+
})
|
|
87
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
88
|
+
}
|
|
89
|
+
/** Full note body by id, or null when missing. */
|
|
90
|
+
export function readNote(cwd, id) {
|
|
91
|
+
// Ids are slugs ([a-z0-9-]); reject anything else so a crafted id can't
|
|
92
|
+
// escape the notes dir via path traversal (e.g. "../../README").
|
|
93
|
+
if (!/^[a-z0-9-]+$/i.test(id))
|
|
94
|
+
return null;
|
|
95
|
+
const p = join(notesDir(cwd), `${id}.md`);
|
|
96
|
+
if (!existsSync(p))
|
|
97
|
+
return null;
|
|
98
|
+
try {
|
|
99
|
+
return readFileSync(p, "utf8").trim();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=project-memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-memory.js","sourceRoot":"","sources":["../../src/project-memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type OsBackend, type ScheduleAddOptions, type ScheduleJob } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the absolute path of the `omp` wrapper to write into OS entries.
|
|
4
|
+
* The dist `.js` (process.argv[1]) is a last resort because launchd/cron need
|
|
5
|
+
* the executable wrapper, not the script.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveOmpBinPath(): string;
|
|
8
|
+
export interface AddResult {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
job?: ScheduleJob;
|
|
11
|
+
backend?: OsBackend;
|
|
12
|
+
messages: string[];
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function addScheduleJob(stateCwd: string, opts: ScheduleAddOptions): AddResult;
|
|
16
|
+
export interface JobView extends ScheduleJob {
|
|
17
|
+
osInstalled: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function listScheduleJobs(stateCwd: string): JobView[];
|
|
20
|
+
export interface RemoveResult {
|
|
21
|
+
removed: boolean;
|
|
22
|
+
uninstalled: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function removeScheduleJob(stateCwd: string, id: string): RemoveResult;
|
|
25
|
+
export interface StatusView {
|
|
26
|
+
job?: ScheduleJob;
|
|
27
|
+
osInstalled: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function getScheduleStatus(stateCwd: string, id: string): StatusView;
|
|
30
|
+
/** Run handler entry used by `omp schedule run|run-now`. Missing job → clean no-op (exit 0). */
|
|
31
|
+
export declare function runScheduleById(stateCwd: string, id: string): Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
message: string;
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, statSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { getInstalledStatus, installJob, uninstallJob } from "./installer.js";
|
|
4
|
+
import { deleteJob, listJobs, readJob, writeJob } from "./job-store.js";
|
|
5
|
+
import { ensureScheduleDirs, jobFilePath, jobLockPath, resolveSchedulePaths, } from "./paths.js";
|
|
6
|
+
import { runScheduledJob } from "./runner.js";
|
|
7
|
+
import { DEFAULT_TIMEOUT_MS, DEFAULT_TTL_HOURS, } from "./types.js";
|
|
8
|
+
const ID_RE = /^[a-zA-Z0-9_-]+$/;
|
|
9
|
+
const CRON_RE = /^\S+\s+\S+\s+\S+\s+\S+\s+\S+$/; // exactly 5 whitespace-separated fields
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the absolute path of the `omp` wrapper to write into OS entries.
|
|
12
|
+
* The dist `.js` (process.argv[1]) is a last resort because launchd/cron need
|
|
13
|
+
* the executable wrapper, not the script.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveOmpBinPath() {
|
|
16
|
+
const fromEnv = process.env.OMP_BIN;
|
|
17
|
+
if (fromEnv)
|
|
18
|
+
return fromEnv;
|
|
19
|
+
try {
|
|
20
|
+
const which = execFileSync("which", ["omp"], { encoding: "utf8" }).trim();
|
|
21
|
+
if (which)
|
|
22
|
+
return which;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// not on PATH
|
|
26
|
+
}
|
|
27
|
+
return process.argv[1] ?? "omp";
|
|
28
|
+
}
|
|
29
|
+
export function addScheduleJob(stateCwd, opts) {
|
|
30
|
+
const messages = [];
|
|
31
|
+
if (!ID_RE.test(opts.id)) {
|
|
32
|
+
return { ok: false, messages, error: `invalid --id "${opts.id}" (use letters, digits, _ or -)` };
|
|
33
|
+
}
|
|
34
|
+
if (!CRON_RE.test(opts.cron)) {
|
|
35
|
+
return { ok: false, messages, error: `invalid --cron "${opts.cron}" (expected 5 fields)` };
|
|
36
|
+
}
|
|
37
|
+
const agentCwd = opts.cwd ?? stateCwd;
|
|
38
|
+
if (!existsSync(agentCwd) || !statSync(agentCwd).isDirectory()) {
|
|
39
|
+
return { ok: false, messages, error: `--cwd does not exist or is not a directory: ${agentCwd}` };
|
|
40
|
+
}
|
|
41
|
+
const paths = resolveSchedulePaths(stateCwd);
|
|
42
|
+
ensureScheduleDirs(paths);
|
|
43
|
+
const allowAllTools = opts.allowAllTools ?? false;
|
|
44
|
+
// TTL precedence: explicit --ttl-hours wins; else a --max-runs-only job has no
|
|
45
|
+
// TTL (run-count bounds it); else fall back to the default 72h TTL.
|
|
46
|
+
const expiresAt = opts.ttlHours !== undefined
|
|
47
|
+
? new Date(Date.now() + opts.ttlHours * 3_600_000).toISOString()
|
|
48
|
+
: opts.maxRuns !== undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: new Date(Date.now() + DEFAULT_TTL_HOURS * 3_600_000).toISOString();
|
|
51
|
+
const job = {
|
|
52
|
+
id: opts.id,
|
|
53
|
+
cron: opts.cron,
|
|
54
|
+
prompt: opts.prompt,
|
|
55
|
+
bin: opts.bin ?? "copilot",
|
|
56
|
+
model: opts.model,
|
|
57
|
+
cwd: agentCwd,
|
|
58
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
59
|
+
allowAllTools,
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
expiresAt,
|
|
62
|
+
maxRuns: opts.maxRuns,
|
|
63
|
+
runCount: 0,
|
|
64
|
+
backend: "crontab",
|
|
65
|
+
ompBinPath: resolveOmpBinPath(),
|
|
66
|
+
active: true,
|
|
67
|
+
};
|
|
68
|
+
if (opts.dryRun) {
|
|
69
|
+
messages.push(`[dry-run] would install job "${job.id}" (cron ${job.cron}) for agent ${job.bin} in ${job.cwd}`);
|
|
70
|
+
return { ok: true, job, messages };
|
|
71
|
+
}
|
|
72
|
+
// Clean replace: if a job with this id already exists, uninstall its OS entry by
|
|
73
|
+
// the RECORDED backend first. The new install's detected backend may differ (e.g.
|
|
74
|
+
// a prior crontab-fallback re-added with a simple cron resolves to launchd), which
|
|
75
|
+
// would otherwise orphan the old entry.
|
|
76
|
+
const existing = readJob(jobFilePath(paths.jobsDir, job.id));
|
|
77
|
+
if (existing)
|
|
78
|
+
uninstallJob(existing.id, existing.backend);
|
|
79
|
+
writeJob(jobFilePath(paths.jobsDir, job.id), job);
|
|
80
|
+
const result = installJob(job, paths.logsDir, paths.cwd);
|
|
81
|
+
job.backend = result.backend;
|
|
82
|
+
writeJob(jobFilePath(paths.jobsDir, job.id), job); // persist resolved backend
|
|
83
|
+
messages.push(`scheduled "${job.id}" via ${job.backend} (cron ${job.cron})`);
|
|
84
|
+
if (allowAllTools) {
|
|
85
|
+
messages.push(`⚠ WARNING: "${job.id}" runs UNATTENDED with full tool access (--allow-all-tools) in ${job.cwd}. Ensure the prompt is safe.`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
messages.push(`note: "${job.id}" runs without --allow-all-tools, so in unattended (-p, no TTY) mode the agent is limited to read-only/allowlisted tools. Re-add with --allow-all-tools if it must act.`);
|
|
89
|
+
}
|
|
90
|
+
return { ok: true, job, backend: job.backend, messages };
|
|
91
|
+
}
|
|
92
|
+
export function listScheduleJobs(stateCwd) {
|
|
93
|
+
const paths = resolveSchedulePaths(stateCwd);
|
|
94
|
+
return listJobs(paths.jobsDir).map((job) => ({ ...job, osInstalled: getInstalledStatus(job.id, job.backend) }));
|
|
95
|
+
}
|
|
96
|
+
export function removeScheduleJob(stateCwd, id) {
|
|
97
|
+
const paths = resolveSchedulePaths(stateCwd);
|
|
98
|
+
const jobPath = jobFilePath(paths.jobsDir, id);
|
|
99
|
+
const job = readJob(jobPath);
|
|
100
|
+
if (!job)
|
|
101
|
+
return { removed: false, uninstalled: false };
|
|
102
|
+
uninstallJob(id, job.backend);
|
|
103
|
+
deleteJob(jobPath);
|
|
104
|
+
try {
|
|
105
|
+
unlinkSync(jobLockPath(paths.jobsDir, id));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// no lock to clean
|
|
109
|
+
}
|
|
110
|
+
// results + logs are intentionally preserved for audit
|
|
111
|
+
return { removed: true, uninstalled: true };
|
|
112
|
+
}
|
|
113
|
+
export function getScheduleStatus(stateCwd, id) {
|
|
114
|
+
const paths = resolveSchedulePaths(stateCwd);
|
|
115
|
+
const job = readJob(jobFilePath(paths.jobsDir, id));
|
|
116
|
+
return { job, osInstalled: job ? getInstalledStatus(id, job.backend) : false };
|
|
117
|
+
}
|
|
118
|
+
/** Run handler entry used by `omp schedule run|run-now`. Missing job → clean no-op (exit 0). */
|
|
119
|
+
export async function runScheduleById(stateCwd, id) {
|
|
120
|
+
const paths = resolveSchedulePaths(stateCwd);
|
|
121
|
+
const job = readJob(jobFilePath(paths.jobsDir, id));
|
|
122
|
+
if (!job) {
|
|
123
|
+
return { ok: true, message: `schedule run: job "${id}" not found (orphan OS entry?); no-op` };
|
|
124
|
+
}
|
|
125
|
+
const result = await runScheduledJob(job, paths, {
|
|
126
|
+
onExpire: (j) => uninstallJob(j.id, j.backend),
|
|
127
|
+
});
|
|
128
|
+
return { ok: result.status !== "error", message: `run "${id}" → ${result.status}: ${result.summary}` };
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/schedule/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OsBackend, ScheduleJob } from "./types.js";
|
|
2
|
+
export interface DetectOptions {
|
|
3
|
+
platform?: NodeJS.Platform;
|
|
4
|
+
hasSystemctl?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** Pick the native backend for this host. Inputs are injectable for testing. */
|
|
7
|
+
export declare function detectOsBackend(opts?: DetectOptions): OsBackend;
|
|
8
|
+
export interface InstallResult {
|
|
9
|
+
backend: OsBackend;
|
|
10
|
+
installed: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Install the OS-scheduler entry for a job. On launchd, a cron expression that
|
|
14
|
+
* StartCalendarInterval cannot express falls back to crontab — the chosen
|
|
15
|
+
* backend is returned so the caller can persist it on the job.
|
|
16
|
+
*/
|
|
17
|
+
export declare function installJob(job: ScheduleJob, logsDir: string, stateRoot: string, opts?: DetectOptions): InstallResult;
|
|
18
|
+
/** Uninstall by the job's recorded backend. Idempotent — never throws if absent. */
|
|
19
|
+
export declare function uninstallJob(id: string, backend: OsBackend): void;
|
|
20
|
+
export declare function getInstalledStatus(id: string, backend: OsBackend): boolean;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { cronToLaunchdInterval, installLaunchd, statusLaunchd, uninstallLaunchd } from "./installers/launchd.js";
|
|
3
|
+
import { installCrontab, statusCrontab, uninstallCrontab } from "./installers/crontab.js";
|
|
4
|
+
import { installSystemd, statusSystemd, uninstallSystemd } from "./installers/systemd.js";
|
|
5
|
+
function systemctlAvailable() {
|
|
6
|
+
try {
|
|
7
|
+
execFileSync("systemctl", ["--user", "--version"], { stdio: "ignore" });
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Pick the native backend for this host. Inputs are injectable for testing. */
|
|
15
|
+
export function detectOsBackend(opts = {}) {
|
|
16
|
+
const platform = opts.platform ?? process.platform;
|
|
17
|
+
if (platform === "darwin")
|
|
18
|
+
return "launchd";
|
|
19
|
+
const hasSystemctl = opts.hasSystemctl ?? systemctlAvailable();
|
|
20
|
+
if (hasSystemctl)
|
|
21
|
+
return "systemd";
|
|
22
|
+
return "crontab";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Install the OS-scheduler entry for a job. On launchd, a cron expression that
|
|
26
|
+
* StartCalendarInterval cannot express falls back to crontab — the chosen
|
|
27
|
+
* backend is returned so the caller can persist it on the job.
|
|
28
|
+
*/
|
|
29
|
+
export function installJob(job, logsDir, stateRoot, opts = {}) {
|
|
30
|
+
const backend = detectOsBackend(opts);
|
|
31
|
+
// Replace semantics: clear any prior entry for this id first.
|
|
32
|
+
uninstallJob(job.id, backend);
|
|
33
|
+
if (backend === "launchd") {
|
|
34
|
+
const sched = cronToLaunchdInterval(job.cron);
|
|
35
|
+
if (sched === null) {
|
|
36
|
+
// launchd can't express this cron — fall back to crontab.
|
|
37
|
+
installCrontab(job, logsDir, stateRoot);
|
|
38
|
+
return { backend: "crontab", installed: true };
|
|
39
|
+
}
|
|
40
|
+
installLaunchd(job, sched, logsDir, stateRoot);
|
|
41
|
+
return { backend: "launchd", installed: true };
|
|
42
|
+
}
|
|
43
|
+
if (backend === "systemd") {
|
|
44
|
+
installSystemd(job, stateRoot);
|
|
45
|
+
return { backend: "systemd", installed: true };
|
|
46
|
+
}
|
|
47
|
+
installCrontab(job, logsDir, stateRoot);
|
|
48
|
+
return { backend: "crontab", installed: true };
|
|
49
|
+
}
|
|
50
|
+
/** Uninstall by the job's recorded backend. Idempotent — never throws if absent. */
|
|
51
|
+
export function uninstallJob(id, backend) {
|
|
52
|
+
try {
|
|
53
|
+
if (backend === "launchd")
|
|
54
|
+
uninstallLaunchd(id);
|
|
55
|
+
else if (backend === "systemd")
|
|
56
|
+
uninstallSystemd(id);
|
|
57
|
+
else
|
|
58
|
+
uninstallCrontab(id);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// best effort — entry may already be gone
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function getInstalledStatus(id, backend) {
|
|
65
|
+
try {
|
|
66
|
+
if (backend === "launchd")
|
|
67
|
+
return statusLaunchd(id);
|
|
68
|
+
if (backend === "systemd")
|
|
69
|
+
return statusSystemd(id);
|
|
70
|
+
return statusCrontab(id);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../../src/schedule/installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ScheduleJob } from "../types.js";
|
|
2
|
+
export declare const BLOCK_BEGIN = "# BEGIN omp-schedule";
|
|
3
|
+
export declare const BLOCK_END = "# END omp-schedule";
|
|
4
|
+
/** The crontab command line for a job (without the id marker). */
|
|
5
|
+
export declare function crontabEntryLine(job: ScheduleJob, logsDir: string, stateRoot: string): string;
|
|
6
|
+
/** Parse the managed block of an existing crontab into an ordered id→line map. */
|
|
7
|
+
export declare function parseManagedBlock(existing: string): Map<string, string>;
|
|
8
|
+
/** Pure: add or replace the entry for `id` in the managed block. */
|
|
9
|
+
export declare function applyCrontabBlock(existing: string, id: string, entryLine: string): string;
|
|
10
|
+
/** Pure: remove the entry for `id`; drops the block markers when it empties. */
|
|
11
|
+
export declare function removeCrontabEntry(existing: string, id: string): string;
|
|
12
|
+
export declare function hasCrontabEntry(existing: string, id: string): boolean;
|
|
13
|
+
export declare function readCrontab(): string;
|
|
14
|
+
export declare function writeCrontab(content: string): void;
|
|
15
|
+
export declare function installCrontab(job: ScheduleJob, logsDir: string, stateRoot: string): string;
|
|
16
|
+
export declare function uninstallCrontab(id: string): void;
|
|
17
|
+
export declare function statusCrontab(id: string): boolean;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const BLOCK_BEGIN = "# BEGIN omp-schedule";
|
|
4
|
+
export const BLOCK_END = "# END omp-schedule";
|
|
5
|
+
const ID_PREFIX = "# omp:";
|
|
6
|
+
/** Single-quote a value for safe shell interpolation. */
|
|
7
|
+
function shq(s) {
|
|
8
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
9
|
+
}
|
|
10
|
+
/** The crontab command line for a job (without the id marker). */
|
|
11
|
+
export function crontabEntryLine(job, logsDir, stateRoot) {
|
|
12
|
+
const logFile = join(logsDir, job.id, `${job.id}.cron.log`);
|
|
13
|
+
// `omp schedule run` resolves state from --root (independent of the agent cwd).
|
|
14
|
+
return `${job.cron} ${shq(job.ompBinPath)} schedule run --id ${job.id} --root ${shq(stateRoot)} >> ${shq(logFile)} 2>&1`;
|
|
15
|
+
}
|
|
16
|
+
/** Parse the managed block of an existing crontab into an ordered id→line map. */
|
|
17
|
+
export function parseManagedBlock(existing) {
|
|
18
|
+
const out = new Map();
|
|
19
|
+
const lines = existing.split("\n");
|
|
20
|
+
let inBlock = false;
|
|
21
|
+
let currentId;
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
if (line.trim() === BLOCK_BEGIN) {
|
|
24
|
+
inBlock = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (line.trim() === BLOCK_END) {
|
|
28
|
+
inBlock = false;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (!inBlock)
|
|
32
|
+
continue;
|
|
33
|
+
if (line.startsWith(ID_PREFIX)) {
|
|
34
|
+
currentId = line.slice(ID_PREFIX.length).trim();
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (currentId && line.trim()) {
|
|
38
|
+
out.set(currentId, line);
|
|
39
|
+
currentId = undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/** Strip the managed block, returning everything outside it (trimmed). */
|
|
45
|
+
function stripManagedBlock(existing) {
|
|
46
|
+
const lines = existing.split("\n");
|
|
47
|
+
const kept = [];
|
|
48
|
+
let inBlock = false;
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
if (line.trim() === BLOCK_BEGIN) {
|
|
51
|
+
inBlock = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (line.trim() === BLOCK_END) {
|
|
55
|
+
inBlock = false;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!inBlock)
|
|
59
|
+
kept.push(line);
|
|
60
|
+
}
|
|
61
|
+
return kept.join("\n").trim();
|
|
62
|
+
}
|
|
63
|
+
/** Render a fresh crontab from outside-block content + the managed entries. */
|
|
64
|
+
function render(outside, entries) {
|
|
65
|
+
if (entries.size === 0) {
|
|
66
|
+
return outside ? `${outside}\n` : "";
|
|
67
|
+
}
|
|
68
|
+
const block = [BLOCK_BEGIN];
|
|
69
|
+
for (const [id, line] of entries) {
|
|
70
|
+
block.push(`${ID_PREFIX}${id}`, line);
|
|
71
|
+
}
|
|
72
|
+
block.push(BLOCK_END);
|
|
73
|
+
return `${outside ? `${outside}\n\n` : ""}${block.join("\n")}\n`;
|
|
74
|
+
}
|
|
75
|
+
/** Pure: add or replace the entry for `id` in the managed block. */
|
|
76
|
+
export function applyCrontabBlock(existing, id, entryLine) {
|
|
77
|
+
const entries = parseManagedBlock(existing);
|
|
78
|
+
entries.set(id, entryLine);
|
|
79
|
+
return render(stripManagedBlock(existing), entries);
|
|
80
|
+
}
|
|
81
|
+
/** Pure: remove the entry for `id`; drops the block markers when it empties. */
|
|
82
|
+
export function removeCrontabEntry(existing, id) {
|
|
83
|
+
const entries = parseManagedBlock(existing);
|
|
84
|
+
entries.delete(id);
|
|
85
|
+
return render(stripManagedBlock(existing), entries);
|
|
86
|
+
}
|
|
87
|
+
export function hasCrontabEntry(existing, id) {
|
|
88
|
+
return parseManagedBlock(existing).has(id);
|
|
89
|
+
}
|
|
90
|
+
export function readCrontab() {
|
|
91
|
+
try {
|
|
92
|
+
return execSync("crontab -l 2>/dev/null", { encoding: "utf8" });
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return ""; // no crontab yet
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function writeCrontab(content) {
|
|
99
|
+
execSync("crontab -", { input: content });
|
|
100
|
+
}
|
|
101
|
+
export function installCrontab(job, logsDir, stateRoot) {
|
|
102
|
+
const next = applyCrontabBlock(readCrontab(), job.id, crontabEntryLine(job, logsDir, stateRoot));
|
|
103
|
+
writeCrontab(next);
|
|
104
|
+
return "crontab";
|
|
105
|
+
}
|
|
106
|
+
export function uninstallCrontab(id) {
|
|
107
|
+
writeCrontab(removeCrontabEntry(readCrontab(), id));
|
|
108
|
+
}
|
|
109
|
+
export function statusCrontab(id) {
|
|
110
|
+
return hasCrontabEntry(readCrontab(), id);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=crontab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crontab.js","sourceRoot":"","sources":["../../../../src/schedule/installers/crontab.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ScheduleJob } from "../types.js";
|
|
2
|
+
export declare function launchdLabel(id: string): string;
|
|
3
|
+
export declare function launchdPlistPath(id: string): string;
|
|
4
|
+
export interface LaunchdSchedule {
|
|
5
|
+
startInterval?: number;
|
|
6
|
+
startCalendarInterval?: {
|
|
7
|
+
Minute?: number;
|
|
8
|
+
Hour?: number;
|
|
9
|
+
Weekday?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Translate a simple 5-field cron to a launchd schedule. Returns null for
|
|
14
|
+
* patterns launchd's StartCalendarInterval cannot cleanly express (lists,
|
|
15
|
+
* ranges, multi-step) — the caller then falls back to crontab.
|
|
16
|
+
*/
|
|
17
|
+
export declare function cronToLaunchdInterval(cron: string): LaunchdSchedule | null;
|
|
18
|
+
/** Generate the LaunchAgent plist XML. Deliberately omits KeepAlive (timer jobs must not auto-restart). */
|
|
19
|
+
export declare function generatePlist(job: ScheduleJob, sched: LaunchdSchedule, logsDir: string, stateRoot: string): string;
|
|
20
|
+
export declare function installLaunchd(job: ScheduleJob, sched: LaunchdSchedule, logsDir: string, stateRoot: string): void;
|
|
21
|
+
export declare function uninstallLaunchd(id: string): void;
|
|
22
|
+
export declare function statusLaunchd(id: string): boolean;
|