453 lines
23 KiB
JavaScript
453 lines
23 KiB
JavaScript
import { D as colorize, O as isRich, k as theme, p as defaultRuntime, v as danger } from "./entry.js";
|
|
import "./auth-profiles-CfFGCDJa.js";
|
|
import { L as sanitizeAgentId } from "./agent-scope-jm0ZdXwM.js";
|
|
import "./utils-PmTbZoD1.js";
|
|
import "./exec-BIMFe4XS.js";
|
|
import "./github-copilot-token-rP-6QdKv.js";
|
|
import "./config-DxJyvt4o.js";
|
|
import "./manifest-registry-tuAcHxrV.js";
|
|
import "./client-BYx0ZoAv.js";
|
|
import "./call-BDMBg5WA.js";
|
|
import "./message-channel-Csfn34Sf.js";
|
|
import { t as formatDocsLink } from "./links-jGisPfXW.js";
|
|
import { n as listChannelPlugins } from "./plugins-TrKFfrLt.js";
|
|
import "./logging-fywhKCmE.js";
|
|
import "./accounts-B5QZU96b.js";
|
|
import "./progress-Dn3kWpaL.js";
|
|
import { t as parseAbsoluteTimeMs } from "./parse-BB0Cqon8.js";
|
|
import { n as parsePositiveIntOrUndefined } from "./helpers-uGhPZ_kK.js";
|
|
import { n as callGatewayFromCli, t as addGatewayClientOptions } from "./gateway-rpc-DNKo4-h7.js";
|
|
|
|
//#region src/cli/cron-cli/shared.ts
|
|
const getCronChannelOptions = () => ["last", ...listChannelPlugins().map((plugin) => plugin.id)].join("|");
|
|
async function warnIfCronSchedulerDisabled(opts) {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.status", opts, {});
|
|
if (res?.enabled === true) return;
|
|
const store = typeof res?.storePath === "string" ? res.storePath : "";
|
|
defaultRuntime.error([
|
|
"warning: cron scheduler is disabled in the Gateway; jobs are saved but will not run automatically.",
|
|
"Re-enable with `cron.enabled: true` (or remove `cron.enabled: false`) and restart the Gateway.",
|
|
store ? `store: ${store}` : ""
|
|
].filter(Boolean).join("\n"));
|
|
} catch {}
|
|
}
|
|
function parseDurationMs(input) {
|
|
const raw = input.trim();
|
|
if (!raw) return null;
|
|
const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i);
|
|
if (!match) return null;
|
|
const n = Number.parseFloat(match[1] ?? "");
|
|
if (!Number.isFinite(n) || n <= 0) return null;
|
|
const unit = (match[2] ?? "").toLowerCase();
|
|
const factor = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
return Math.floor(n * factor);
|
|
}
|
|
function parseAt(input) {
|
|
const raw = input.trim();
|
|
if (!raw) return null;
|
|
const absolute = parseAbsoluteTimeMs(raw);
|
|
if (absolute !== null) return new Date(absolute).toISOString();
|
|
const dur = parseDurationMs(raw);
|
|
if (dur !== null) return new Date(Date.now() + dur).toISOString();
|
|
return null;
|
|
}
|
|
const CRON_ID_PAD = 36;
|
|
const CRON_NAME_PAD = 24;
|
|
const CRON_SCHEDULE_PAD = 32;
|
|
const CRON_NEXT_PAD = 10;
|
|
const CRON_LAST_PAD = 10;
|
|
const CRON_STATUS_PAD = 9;
|
|
const CRON_TARGET_PAD = 9;
|
|
const CRON_AGENT_PAD = 10;
|
|
const pad = (value, width) => value.padEnd(width);
|
|
const truncate = (value, width) => {
|
|
if (value.length <= width) return value;
|
|
if (width <= 3) return value.slice(0, width);
|
|
return `${value.slice(0, width - 3)}...`;
|
|
};
|
|
const formatIsoMinute = (iso) => {
|
|
const parsed = parseAbsoluteTimeMs(iso);
|
|
const d = new Date(parsed ?? NaN);
|
|
if (Number.isNaN(d.getTime())) return "-";
|
|
const isoStr = d.toISOString();
|
|
return `${isoStr.slice(0, 10)} ${isoStr.slice(11, 16)}Z`;
|
|
};
|
|
const formatDuration = (ms) => {
|
|
if (ms < 6e4) return `${Math.max(1, Math.round(ms / 1e3))}s`;
|
|
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
|
|
return `${Math.round(ms / 864e5)}d`;
|
|
};
|
|
const formatSpan = (ms) => {
|
|
if (ms < 6e4) return "<1m";
|
|
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
|
|
return `${Math.round(ms / 864e5)}d`;
|
|
};
|
|
const formatRelative = (ms, nowMs) => {
|
|
if (!ms) return "-";
|
|
const delta = ms - nowMs;
|
|
const label = formatSpan(Math.abs(delta));
|
|
return delta >= 0 ? `in ${label}` : `${label} ago`;
|
|
};
|
|
const formatSchedule = (schedule) => {
|
|
if (schedule.kind === "at") return `at ${formatIsoMinute(schedule.at)}`;
|
|
if (schedule.kind === "every") return `every ${formatDuration(schedule.everyMs)}`;
|
|
return schedule.tz ? `cron ${schedule.expr} @ ${schedule.tz}` : `cron ${schedule.expr}`;
|
|
};
|
|
const formatStatus = (job) => {
|
|
if (!job.enabled) return "disabled";
|
|
if (job.state.runningAtMs) return "running";
|
|
return job.state.lastStatus ?? "idle";
|
|
};
|
|
function printCronList(jobs, runtime = defaultRuntime) {
|
|
if (jobs.length === 0) {
|
|
runtime.log("No cron jobs.");
|
|
return;
|
|
}
|
|
const rich = isRich();
|
|
const header = [
|
|
pad("ID", CRON_ID_PAD),
|
|
pad("Name", CRON_NAME_PAD),
|
|
pad("Schedule", CRON_SCHEDULE_PAD),
|
|
pad("Next", CRON_NEXT_PAD),
|
|
pad("Last", CRON_LAST_PAD),
|
|
pad("Status", CRON_STATUS_PAD),
|
|
pad("Target", CRON_TARGET_PAD),
|
|
pad("Agent", CRON_AGENT_PAD)
|
|
].join(" ");
|
|
runtime.log(rich ? theme.heading(header) : header);
|
|
const now = Date.now();
|
|
for (const job of jobs) {
|
|
const idLabel = pad(job.id, CRON_ID_PAD);
|
|
const nameLabel = pad(truncate(job.name, CRON_NAME_PAD), CRON_NAME_PAD);
|
|
const scheduleLabel = pad(truncate(formatSchedule(job.schedule), CRON_SCHEDULE_PAD), CRON_SCHEDULE_PAD);
|
|
const nextLabel = pad(job.enabled ? formatRelative(job.state.nextRunAtMs, now) : "-", CRON_NEXT_PAD);
|
|
const lastLabel = pad(formatRelative(job.state.lastRunAtMs, now), CRON_LAST_PAD);
|
|
const statusRaw = formatStatus(job);
|
|
const statusLabel = pad(statusRaw, CRON_STATUS_PAD);
|
|
const targetLabel = pad(job.sessionTarget, CRON_TARGET_PAD);
|
|
const agentLabel = pad(truncate(job.agentId ?? "default", CRON_AGENT_PAD), CRON_AGENT_PAD);
|
|
const coloredStatus = (() => {
|
|
if (statusRaw === "ok") return colorize(rich, theme.success, statusLabel);
|
|
if (statusRaw === "error") return colorize(rich, theme.error, statusLabel);
|
|
if (statusRaw === "running") return colorize(rich, theme.warn, statusLabel);
|
|
if (statusRaw === "skipped") return colorize(rich, theme.muted, statusLabel);
|
|
return colorize(rich, theme.muted, statusLabel);
|
|
})();
|
|
const coloredTarget = job.sessionTarget === "isolated" ? colorize(rich, theme.accentBright, targetLabel) : colorize(rich, theme.accent, targetLabel);
|
|
const coloredAgent = job.agentId ? colorize(rich, theme.info, agentLabel) : colorize(rich, theme.muted, agentLabel);
|
|
const line = [
|
|
colorize(rich, theme.accent, idLabel),
|
|
colorize(rich, theme.info, nameLabel),
|
|
colorize(rich, theme.info, scheduleLabel),
|
|
colorize(rich, theme.muted, nextLabel),
|
|
colorize(rich, theme.muted, lastLabel),
|
|
coloredStatus,
|
|
coloredTarget,
|
|
coloredAgent
|
|
].join(" ");
|
|
runtime.log(line.trimEnd());
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/cli/cron-cli/register.cron-add.ts
|
|
function registerCronStatusCommand(cron) {
|
|
addGatewayClientOptions(cron.command("status").description("Show cron scheduler status").option("--json", "Output JSON", false).action(async (opts) => {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.status", opts, {});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
}
|
|
function registerCronListCommand(cron) {
|
|
addGatewayClientOptions(cron.command("list").description("List cron jobs").option("--all", "Include disabled jobs", false).option("--json", "Output JSON", false).action(async (opts) => {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.list", opts, { includeDisabled: Boolean(opts.all) });
|
|
if (opts.json) {
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
return;
|
|
}
|
|
printCronList(res?.jobs ?? [], defaultRuntime);
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
}
|
|
function registerCronAddCommand(cron) {
|
|
addGatewayClientOptions(cron.command("add").alias("create").description("Add a cron job").requiredOption("--name <name>", "Job name").option("--description <text>", "Optional description").option("--disabled", "Create job disabled", false).option("--delete-after-run", "Delete one-shot job after it succeeds", false).option("--keep-after-run", "Keep one-shot job after it succeeds", false).option("--agent <id>", "Agent id for this job").option("--session <target>", "Session target (main|isolated)").option("--wake <mode>", "Wake mode (now|next-heartbeat)", "next-heartbeat").option("--at <when>", "Run once at time (ISO) or +duration (e.g. 20m)").option("--every <duration>", "Run every duration (e.g. 10m, 1h)").option("--cron <expr>", "Cron expression (5-field)").option("--tz <iana>", "Timezone for cron expressions (IANA)", "").option("--system-event <text>", "System event payload (main session)").option("--message <text>", "Agent message payload").option("--thinking <level>", "Thinking level for agent jobs (off|minimal|low|medium|high)").option("--model <model>", "Model override for agent jobs (provider/model or alias)").option("--timeout-seconds <n>", "Timeout seconds for agent jobs").option("--announce", "Announce summary to a chat (subagent-style)", false).option("--deliver", "Deprecated (use --announce). Announces a summary to a chat.").option("--no-deliver", "Disable announce delivery and skip main-session summary").option("--channel <channel>", `Delivery channel (${getCronChannelOptions()})`, "last").option("--to <dest>", "Delivery destination (E.164, Telegram chatId, or Discord channel/user)").option("--best-effort-deliver", "Do not fail the job if delivery fails", false).option("--json", "Output JSON", false).action(async (opts, cmd) => {
|
|
try {
|
|
const schedule = (() => {
|
|
const at = typeof opts.at === "string" ? opts.at : "";
|
|
const every = typeof opts.every === "string" ? opts.every : "";
|
|
const cronExpr = typeof opts.cron === "string" ? opts.cron : "";
|
|
if ([
|
|
Boolean(at),
|
|
Boolean(every),
|
|
Boolean(cronExpr)
|
|
].filter(Boolean).length !== 1) throw new Error("Choose exactly one schedule: --at, --every, or --cron");
|
|
if (at) {
|
|
const atIso = parseAt(at);
|
|
if (!atIso) throw new Error("Invalid --at; use ISO time or duration like 20m");
|
|
return {
|
|
kind: "at",
|
|
at: atIso
|
|
};
|
|
}
|
|
if (every) {
|
|
const everyMs = parseDurationMs(every);
|
|
if (!everyMs) throw new Error("Invalid --every; use e.g. 10m, 1h, 1d");
|
|
return {
|
|
kind: "every",
|
|
everyMs
|
|
};
|
|
}
|
|
return {
|
|
kind: "cron",
|
|
expr: cronExpr,
|
|
tz: typeof opts.tz === "string" && opts.tz.trim() ? opts.tz.trim() : void 0
|
|
};
|
|
})();
|
|
const wakeMode = (typeof opts.wake === "string" ? opts.wake : "next-heartbeat").trim() || "next-heartbeat";
|
|
if (wakeMode !== "now" && wakeMode !== "next-heartbeat") throw new Error("--wake must be now or next-heartbeat");
|
|
const agentId = typeof opts.agent === "string" && opts.agent.trim() ? sanitizeAgentId(opts.agent.trim()) : void 0;
|
|
const hasAnnounce = Boolean(opts.announce) || opts.deliver === true;
|
|
const hasNoDeliver = opts.deliver === false;
|
|
if ([hasAnnounce, hasNoDeliver].filter(Boolean).length > 1) throw new Error("Choose at most one of --announce or --no-deliver");
|
|
const payload = (() => {
|
|
const systemEvent = typeof opts.systemEvent === "string" ? opts.systemEvent.trim() : "";
|
|
const message = typeof opts.message === "string" ? opts.message.trim() : "";
|
|
if ([Boolean(systemEvent), Boolean(message)].filter(Boolean).length !== 1) throw new Error("Choose exactly one payload: --system-event or --message");
|
|
if (systemEvent) return {
|
|
kind: "systemEvent",
|
|
text: systemEvent
|
|
};
|
|
const timeoutSeconds = parsePositiveIntOrUndefined(opts.timeoutSeconds);
|
|
return {
|
|
kind: "agentTurn",
|
|
message,
|
|
model: typeof opts.model === "string" && opts.model.trim() ? opts.model.trim() : void 0,
|
|
thinking: typeof opts.thinking === "string" && opts.thinking.trim() ? opts.thinking.trim() : void 0,
|
|
timeoutSeconds: timeoutSeconds && Number.isFinite(timeoutSeconds) ? timeoutSeconds : void 0
|
|
};
|
|
})();
|
|
const sessionSource = (typeof cmd?.getOptionValueSource === "function" ? (name) => cmd.getOptionValueSource(name) : () => void 0)("session");
|
|
const sessionTargetRaw = typeof opts.session === "string" ? opts.session.trim() : "";
|
|
const inferredSessionTarget = payload.kind === "agentTurn" ? "isolated" : "main";
|
|
const sessionTarget = sessionSource === "cli" ? sessionTargetRaw || "" : inferredSessionTarget;
|
|
if (sessionTarget !== "main" && sessionTarget !== "isolated") throw new Error("--session must be main or isolated");
|
|
if (opts.deleteAfterRun && opts.keepAfterRun) throw new Error("Choose --delete-after-run or --keep-after-run, not both");
|
|
if (sessionTarget === "main" && payload.kind !== "systemEvent") throw new Error("Main jobs require --system-event (systemEvent).");
|
|
if (sessionTarget === "isolated" && payload.kind !== "agentTurn") throw new Error("Isolated jobs require --message (agentTurn).");
|
|
if ((opts.announce || typeof opts.deliver === "boolean") && (sessionTarget !== "isolated" || payload.kind !== "agentTurn")) throw new Error("--announce/--no-deliver require --session isolated.");
|
|
const deliveryMode = sessionTarget === "isolated" && payload.kind === "agentTurn" ? hasAnnounce ? "announce" : hasNoDeliver ? "none" : "announce" : void 0;
|
|
const name = (typeof opts.name === "string" ? opts.name : "").trim();
|
|
if (!name) throw new Error("--name is required");
|
|
const res = await callGatewayFromCli("cron.add", opts, {
|
|
name,
|
|
description: typeof opts.description === "string" && opts.description.trim() ? opts.description.trim() : void 0,
|
|
enabled: !opts.disabled,
|
|
deleteAfterRun: opts.deleteAfterRun ? true : opts.keepAfterRun ? false : void 0,
|
|
agentId,
|
|
schedule,
|
|
sessionTarget,
|
|
wakeMode,
|
|
payload,
|
|
delivery: deliveryMode ? {
|
|
mode: deliveryMode,
|
|
channel: typeof opts.channel === "string" && opts.channel.trim() ? opts.channel.trim() : void 0,
|
|
to: typeof opts.to === "string" && opts.to.trim() ? opts.to.trim() : void 0,
|
|
bestEffort: opts.bestEffortDeliver ? true : void 0
|
|
} : void 0
|
|
});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
await warnIfCronSchedulerDisabled(opts);
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/cli/cron-cli/register.cron-edit.ts
|
|
const assignIf = (target, key, value, shouldAssign) => {
|
|
if (shouldAssign) target[key] = value;
|
|
};
|
|
function registerCronEditCommand(cron) {
|
|
addGatewayClientOptions(cron.command("edit").description("Edit a cron job (patch fields)").argument("<id>", "Job id").option("--name <name>", "Set name").option("--description <text>", "Set description").option("--enable", "Enable job", false).option("--disable", "Disable job", false).option("--delete-after-run", "Delete one-shot job after it succeeds", false).option("--keep-after-run", "Keep one-shot job after it succeeds", false).option("--session <target>", "Session target (main|isolated)").option("--agent <id>", "Set agent id").option("--clear-agent", "Unset agent and use default", false).option("--wake <mode>", "Wake mode (now|next-heartbeat)").option("--at <when>", "Set one-shot time (ISO) or duration like 20m").option("--every <duration>", "Set interval duration like 10m").option("--cron <expr>", "Set cron expression").option("--tz <iana>", "Timezone for cron expressions (IANA)").option("--system-event <text>", "Set systemEvent payload").option("--message <text>", "Set agentTurn payload message").option("--thinking <level>", "Thinking level for agent jobs").option("--model <model>", "Model override for agent jobs").option("--timeout-seconds <n>", "Timeout seconds for agent jobs").option("--announce", "Announce summary to a chat (subagent-style)").option("--deliver", "Deprecated (use --announce). Announces a summary to a chat.").option("--no-deliver", "Disable announce delivery").option("--channel <channel>", `Delivery channel (${getCronChannelOptions()})`).option("--to <dest>", "Delivery destination (E.164, Telegram chatId, or Discord channel/user)").option("--best-effort-deliver", "Do not fail job if delivery fails").option("--no-best-effort-deliver", "Fail job when delivery fails").action(async (id, opts) => {
|
|
try {
|
|
if (opts.session === "main" && opts.message) throw new Error("Main jobs cannot use --message; use --system-event or --session isolated.");
|
|
if (opts.session === "isolated" && opts.systemEvent) throw new Error("Isolated jobs cannot use --system-event; use --message or --session main.");
|
|
if (opts.announce && typeof opts.deliver === "boolean") throw new Error("Choose --announce or --no-deliver (not multiple).");
|
|
const patch = {};
|
|
if (typeof opts.name === "string") patch.name = opts.name;
|
|
if (typeof opts.description === "string") patch.description = opts.description;
|
|
if (opts.enable && opts.disable) throw new Error("Choose --enable or --disable, not both");
|
|
if (opts.enable) patch.enabled = true;
|
|
if (opts.disable) patch.enabled = false;
|
|
if (opts.deleteAfterRun && opts.keepAfterRun) throw new Error("Choose --delete-after-run or --keep-after-run, not both");
|
|
if (opts.deleteAfterRun) patch.deleteAfterRun = true;
|
|
if (opts.keepAfterRun) patch.deleteAfterRun = false;
|
|
if (typeof opts.session === "string") patch.sessionTarget = opts.session;
|
|
if (typeof opts.wake === "string") patch.wakeMode = opts.wake;
|
|
if (opts.agent && opts.clearAgent) throw new Error("Use --agent or --clear-agent, not both");
|
|
if (typeof opts.agent === "string" && opts.agent.trim()) patch.agentId = sanitizeAgentId(opts.agent.trim());
|
|
if (opts.clearAgent) patch.agentId = null;
|
|
if ([
|
|
opts.at,
|
|
opts.every,
|
|
opts.cron
|
|
].filter(Boolean).length > 1) throw new Error("Choose at most one schedule change");
|
|
if (opts.at) {
|
|
const atIso = parseAt(String(opts.at));
|
|
if (!atIso) throw new Error("Invalid --at");
|
|
patch.schedule = {
|
|
kind: "at",
|
|
at: atIso
|
|
};
|
|
} else if (opts.every) {
|
|
const everyMs = parseDurationMs(String(opts.every));
|
|
if (!everyMs) throw new Error("Invalid --every");
|
|
patch.schedule = {
|
|
kind: "every",
|
|
everyMs
|
|
};
|
|
} else if (opts.cron) patch.schedule = {
|
|
kind: "cron",
|
|
expr: String(opts.cron),
|
|
tz: typeof opts.tz === "string" && opts.tz.trim() ? opts.tz.trim() : void 0
|
|
};
|
|
const hasSystemEventPatch = typeof opts.systemEvent === "string";
|
|
const model = typeof opts.model === "string" && opts.model.trim() ? opts.model.trim() : void 0;
|
|
const thinking = typeof opts.thinking === "string" && opts.thinking.trim() ? opts.thinking.trim() : void 0;
|
|
const timeoutSeconds = opts.timeoutSeconds ? Number.parseInt(String(opts.timeoutSeconds), 10) : void 0;
|
|
const hasTimeoutSeconds = Boolean(timeoutSeconds && Number.isFinite(timeoutSeconds));
|
|
const hasDeliveryModeFlag = opts.announce || typeof opts.deliver === "boolean";
|
|
const hasDeliveryTarget = typeof opts.channel === "string" || typeof opts.to === "string";
|
|
const hasBestEffort = typeof opts.bestEffortDeliver === "boolean";
|
|
const hasAgentTurnPatch = typeof opts.message === "string" || Boolean(model) || Boolean(thinking) || hasTimeoutSeconds || hasDeliveryModeFlag || hasDeliveryTarget || hasBestEffort;
|
|
if (hasSystemEventPatch && hasAgentTurnPatch) throw new Error("Choose at most one payload change");
|
|
if (hasSystemEventPatch) patch.payload = {
|
|
kind: "systemEvent",
|
|
text: String(opts.systemEvent)
|
|
};
|
|
else if (hasAgentTurnPatch) {
|
|
const payload = { kind: "agentTurn" };
|
|
assignIf(payload, "message", String(opts.message), typeof opts.message === "string");
|
|
assignIf(payload, "model", model, Boolean(model));
|
|
assignIf(payload, "thinking", thinking, Boolean(thinking));
|
|
assignIf(payload, "timeoutSeconds", timeoutSeconds, hasTimeoutSeconds);
|
|
patch.payload = payload;
|
|
}
|
|
if (hasDeliveryModeFlag || hasDeliveryTarget || hasBestEffort) {
|
|
const delivery = { mode: opts.announce || opts.deliver === true ? "announce" : opts.deliver === false ? "none" : "announce" };
|
|
if (typeof opts.channel === "string") {
|
|
const channel = opts.channel.trim();
|
|
delivery.channel = channel ? channel : void 0;
|
|
}
|
|
if (typeof opts.to === "string") {
|
|
const to = opts.to.trim();
|
|
delivery.to = to ? to : void 0;
|
|
}
|
|
if (typeof opts.bestEffortDeliver === "boolean") delivery.bestEffort = opts.bestEffortDeliver;
|
|
patch.delivery = delivery;
|
|
}
|
|
const res = await callGatewayFromCli("cron.update", opts, {
|
|
id,
|
|
patch
|
|
});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
await warnIfCronSchedulerDisabled(opts);
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/cli/cron-cli/register.cron-simple.ts
|
|
function registerCronSimpleCommands(cron) {
|
|
addGatewayClientOptions(cron.command("rm").alias("remove").alias("delete").description("Remove a cron job").argument("<id>", "Job id").option("--json", "Output JSON", false).action(async (id, opts) => {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.remove", opts, { id });
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
addGatewayClientOptions(cron.command("enable").description("Enable a cron job").argument("<id>", "Job id").action(async (id, opts) => {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.update", opts, {
|
|
id,
|
|
patch: { enabled: true }
|
|
});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
await warnIfCronSchedulerDisabled(opts);
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
addGatewayClientOptions(cron.command("disable").description("Disable a cron job").argument("<id>", "Job id").action(async (id, opts) => {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.update", opts, {
|
|
id,
|
|
patch: { enabled: false }
|
|
});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
await warnIfCronSchedulerDisabled(opts);
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
addGatewayClientOptions(cron.command("runs").description("Show cron run history (JSONL-backed)").requiredOption("--id <id>", "Job id").option("--limit <n>", "Max entries (default 50)", "50").action(async (opts) => {
|
|
try {
|
|
const limitRaw = Number.parseInt(String(opts.limit ?? "50"), 10);
|
|
const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : 50;
|
|
const res = await callGatewayFromCli("cron.runs", opts, {
|
|
id: String(opts.id),
|
|
limit
|
|
});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
addGatewayClientOptions(cron.command("run").description("Run a cron job now (debug)").argument("<id>", "Job id").option("--force", "Run even if not due", false).action(async (id, opts) => {
|
|
try {
|
|
const res = await callGatewayFromCli("cron.run", opts, {
|
|
id,
|
|
mode: opts.force ? "force" : "due"
|
|
});
|
|
defaultRuntime.log(JSON.stringify(res, null, 2));
|
|
} catch (err) {
|
|
defaultRuntime.error(danger(String(err)));
|
|
defaultRuntime.exit(1);
|
|
}
|
|
}));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/cli/cron-cli/register.ts
|
|
function registerCronCli(program) {
|
|
const cron = program.command("cron").description("Manage cron jobs (via Gateway)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/cron", "docs.openclaw.ai/cli/cron")}\n`);
|
|
registerCronStatusCommand(cron);
|
|
registerCronListCommand(cron);
|
|
registerCronAddCommand(cron);
|
|
registerCronSimpleCommands(cron);
|
|
registerCronEditCommand(cron);
|
|
}
|
|
|
|
//#endregion
|
|
export { registerCronCli }; |