import { listChannelPlugins } from "../../channels/plugins/index.js"; import { parseAbsoluteTimeMs } from "../../cron/parse.js"; import { resolveCronStaggerMs } from "../../cron/stagger.js"; import type { CronDeliveryPreview, CronJob, CronSchedule } from "../../cron/types.js"; import { danger } from "../../globals.js"; import { formatDurationHuman } from "../../infra/format-time/format-duration.ts"; import {
isOffsetlessIsoDateTime,
parseOffsetlessIsoDateTimeInTimeZone,
} from "../../infra/format-time/parse-offsetless-zoned-datetime.js"; import { defaultRuntime, type RuntimeEnv } from "../../runtime.js"; import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../../shared/string-coerce.js"; import { colorize, isRich, theme } from "../../terminal/theme.js"; import type { GatewayRpcOpts } from "../gateway-rpc.js"; import { callGatewayFromCli } from "../gateway-rpc.js";
export const getCronChannelOptions = () => { // Keep help truthful even before the plugin registry is bootstrapped. const pluginIds = listChannelPlugins()
.map((plugin) => plugin.id)
.filter(Boolean); return pluginIds.length > 0 ? ["last", ...pluginIds].join("|") : "last|<channel-id>";
};
export function printCronJson(value: unknown) {
defaultRuntime.writeJson(value);
}
export function handleCronCliError(err: unknown) {
defaultRuntime.error(danger(String(err)));
defaultRuntime.exit(1);
}
export async function warnIfCronSchedulerDisabled(opts: GatewayRpcOpts) { try { const res = (await callGatewayFromCli("cron.status", opts, {})) as {
enabled?: boolean;
storePath?: string;
}; 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 { // Ignore status failures (older gateway, offline, etc.)
}
}
export function parseDurationMs(input: string): number | null { const raw = input.trim(); if (!raw) { returnnull;
} const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i); if (!match) { returnnull;
} const n = Number.parseFloat(match[1] ?? ""); if (!Number.isFinite(n) || n <= 0) { returnnull;
} const unit = normalizeLowercaseStringOrEmpty(match[2] ?? ""); const factor =
unit === "ms"
? 1
: unit === "s"
? 1000
: unit === "m"
? 60_000
: unit === "h"
? 3_600_000
: 86_400_000; return Math.floor(n * factor);
}
export function parseCronStaggerMs(params: {
staggerRaw: string;
useExact: boolean;
}): number | undefined { if (params.useExact) { return0;
} if (!params.staggerRaw) { return undefined;
} const parsed = parseDurationMs(params.staggerRaw); if (!parsed) { thrownew Error("Invalid --stagger; use e.g. 30s, 1m, 5m");
} return parsed;
}
/** *Parseaone-shot`--at`valueintoanISOstring(UTC). * *When`tz`isprovidedandtheinputisanoffset-lessdatetime *(e.g.`2026-03-23T23:00:00`),thedatetimeisinterpretedin *thatIANAtimezoneinsteadofUTC.
*/
export function parseAt(input: string, tz?: string): string | null { const raw = input.trim(); if (!raw) { returnnull;
}
// If a timezone is provided and the input looks like an offset-less ISO datetime, // resolve it in the given IANA timezone so users get the time they expect. if (tz && isOffsetlessIsoDateTime(raw)) { return parseOffsetlessIsoDateTimeInTimeZone(raw, tz);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.