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
JavaScript
// 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]),
});
}