UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

129 lines (128 loc) • 5.26 kB
// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. /** * Secret-free input to every native scheduler backend, and the backend * contract itself. * * A binding carries only stable source identity, trigger identity, and the * public CLI tail; its install options add the launcher and the AKM * directory environment the row sets inline. Source content, action inputs, * task environment values, and credentials never cross this boundary or * reach an OS scheduler. */ import { createHash } from "node:crypto"; import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js"; import { UsageError } from "../core/errors.js"; import { canonicalInputJson } from "../execution/input-contract.js"; import { normaliseTaskConceptId } from "./task-id.js"; export function compileTaskSchedulerBindings(input) { const id = normaliseTaskConceptId(input.id); const ref = assertQualifiedRef(input.qualifiedRef, "task"); const bundle = parseBundleRef(ref).bundle; if (!bundle) throw new Error("invariant: qualified scheduler task ref lost its bundle"); return Object.freeze(input.schedules.map((schedule) => { const bindingId = schedule.ordinal === 0 ? id : digestBindingId("task", ref, schedule.ordinal); return freezeBinding({ id: bindingId, nativeId: schedulerNativeBindingId(bindingId), logicalSource: { kind: "task", ref }, cron: schedule.cron, source: schedule.source, ordinal: schedule.ordinal, enabled: true, invocation: ["task", "run", id, "--bundle", bundle, "--scheduled", ...schedulerInputFlagTail(schedule.inputs)], }); })); } /** * The canonically-sorted `--<name> <value>` flag tail for one schedule * entry's `inputs`. A value whose text starts with `-` uses the inline * `--<name>=<value>` form, the only spelling `parseTaskInputFlags` cannot * mistake for a flag. */ function schedulerInputFlagTail(inputs) { if (!inputs) return []; const tail = []; for (const name of Object.keys(inputs).sort()) { const text = schedulerInputFlagValueText(inputs[name]); if (text.startsWith("-")) tail.push(`--${name}=${text}`); else tail.push(`--${name}`, text); } return tail; } function schedulerInputFlagValueText(value) { if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return String(value); return canonicalInputJson(value); } export function compileWorkflowSchedulerBindings(input) { const ref = assertQualifiedRef(input.qualifiedRef, "workflow"); return Object.freeze(input.schedules.map((schedule) => { const bindingId = digestBindingId("workflow", ref, schedule.ordinal); return freezeBinding({ id: bindingId, nativeId: bindingId, logicalSource: { kind: "workflow", ref }, cron: schedule.cron, source: schedule.source, ordinal: schedule.ordinal, enabled: true, invocation: ["workflow", "run", ref], }); })); } /** * Map a logical binding id to the flat portable token persisted by native * schedulers. Flat ids are byte-for-byte stable; only nested standalone * component ids need an encoded native spelling. */ export function schedulerNativeBindingId(id) { if (!id.includes("/")) return id; const digest = createHash("sha256") .update(JSON.stringify(["nested-task", id])) .digest("hex") .slice(0, 32); return `task-${digest}`; } export function schedulerBindingNativeId(binding) { return binding.nativeId ?? schedulerNativeBindingId(binding.id); } /** * Recover the logical id of an installed row from its native spelling and * public invocation: an ordinal-zero task row is its task id (possibly a * nested one behind a digest spelling); every other row is its native id. */ export function schedulerLogicalBindingId(nativeId, invocation) { const taskId = invocation[0] === "task" && invocation[1] === "run" ? invocation[2] : undefined; return taskId !== undefined && schedulerNativeBindingId(taskId) === nativeId ? taskId : nativeId; } function digestBindingId(kind, ref, ordinal) { const prefix = kind === "workflow" ? "wf" : "task"; const digest = createHash("sha256") .update(JSON.stringify([kind, ref, ordinal])) .digest("hex") .slice(0, 32); return `${prefix}-${digest}`; } function assertQualifiedRef(value, kind) { const parsed = parseBundleRef(value); if (!parsed.bundle || parsed.fragment !== undefined || bundleRefToString(parsed) !== value) { throw new UsageError(`${kind} scheduler bindings require one canonical fully-qualified ref.`, "INVALID_FLAG_VALUE"); } return value; } function freezeBinding(binding) { return Object.freeze({ ...binding, logicalSource: Object.freeze({ ...binding.logicalSource }), invocation: Object.freeze([...binding.invocation]), }); }