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
137 lines (136 loc) • 6.27 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/.
import { cloneExecutionJson, cloneExecutionJsonObject } from "./json.js";
export const EXECUTION_SOURCE_SCHEMA_VERSION = 1;
/** Current internal adapter identifiers are lowercase kebab-case registry keys. */
export const EXECUTION_ADAPTER_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
/**
* The model-work tool policy: unattended model work (improve, the judges, index
* passes, remember) may read, edit only inside the dispatch's own scratch
* working directory, and run `akm search` and `akm show`; the stash stays
* read-only to it. A transport grants what it can confine and refuses the policy
* at build when it can confine nothing; an LLM has no tools. A request carries it
* as `authorization.policy.id`, set only when its caller asks (`modelWork`): no
* `tools` value names it, so an asset's own `tools:` cannot.
*/
export const MODEL_WORK_POLICY_ID = "model-work";
function requireRecord(value, path) {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
throw new TypeError(`${path} must be an object`);
}
return value;
}
export function createAdapterExtensions(first, second, ...rest) {
const entries = typeof first === "string"
? [[first, second]]
: [first, ...(second === undefined ? [] : [second]), ...rest];
const out = {};
for (const [owner, values] of entries)
out[owner] = cloneExecutionJsonObject(values, `extensions.${owner}`);
return Object.freeze(out);
}
export function cloneAdapterExtensions(value, path) {
const out = {};
for (const [owner, fields] of Object.entries(requireRecord(value, path))) {
out[owner] = cloneExecutionJsonObject(fields, `${path}.${owner}`);
}
return Object.freeze(out);
}
/** A selector or layer identifier that may cross the durable execution boundary. */
export function requireStableExecutionSelector(value, path) {
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
throw new TypeError(`${path} must be a non-empty stable identifier`);
}
return value;
}
export function isPortableExecutionAgentSelector(value) {
return /^(?:[^/]+\/\/)?agents\/[^/]/.test(value);
}
export function executionPersonaMatchesSelector(selector, personaRef) {
return selector.includes("//") ? selector === personaRef : personaRef.endsWith(`//${selector}`);
}
const IDENTITY_KEYS = ["ref", "bundle", "adapter", "file", "hash"];
function identityFrom(value, path) {
const record = requireRecord(value, path);
const out = {};
for (const key of IDENTITY_KEYS) {
const field = record[key];
if (typeof field !== "string" || field.length === 0)
throw new TypeError(`${path}.${key} must be a non-empty string`);
out[key] = field;
}
return Object.freeze(out);
}
/** Copy and freeze an identity produced by an adapter renderer. */
export function createExecutionSourceIdentity(input) {
return identityFrom(input, "execution source identity");
}
/** Read a source identity back from a frozen request. */
export function decodeExecutionSourceIdentity(value, path = "execution source identity") {
return identityFrom(value, path);
}
/** Type-check the common frontmatter defaults; unknown keys are ignored. */
export function cloneUnresolvedExecutionDefaults(input, path = "execution source defaults") {
const record = requireRecord(input, path);
const present = (key) => Object.hasOwn(record, key) && record[key] !== undefined;
const out = {};
for (const key of ["agent", "engine", "model", "workspace"]) {
if (!present(key))
continue;
const value = record[key];
if (value !== null && typeof value !== "string")
throw new TypeError(`${path}.${key} must be a string or null`);
out[key] = value;
}
if (present("timeout")) {
const timeout = record.timeout;
if (timeout !== null && typeof timeout !== "string" && typeof timeout !== "number") {
throw new TypeError(`${path}.timeout must be a string, number, or null`);
}
out.timeout = timeout;
}
for (const key of ["inference", "outputSchema", "runtime"]) {
if (!present(key))
continue;
out[key] = record[key] === null ? null : cloneExecutionJsonObject(record[key], `${path}.${key}`);
}
if (present("environment")) {
const environment = record.environment === null ? null : cloneExecutionJsonObject(record.environment, `${path}.environment`);
if (environment !== null && Object.values(environment).some((value) => typeof value !== "string")) {
throw new TypeError(`${path}.environment values must be strings`);
}
out.environment = environment;
}
if (present("tools"))
out.tools = cloneToolSelection(record.tools, `${path}.tools`);
return Object.freeze(out);
}
export function cloneToolSelection(value, path = "tools") {
if (value === null || typeof value === "string")
return value;
const cloned = cloneExecutionJson(value, path);
if (Array.isArray(cloned)) {
if (cloned.some((tool) => typeof tool !== "string"))
throw new TypeError(`${path} array values must be strings`);
return cloned;
}
return cloneExecutionJsonObject(cloned, path);
}
export function createAdapterRenderedExecutionSource(input) {
const { kind, content } = input;
if (kind !== "command" && kind !== "persona")
throw new TypeError("execution source kind is invalid");
if (typeof content !== "string")
throw new TypeError("execution source content must be a string");
return Object.freeze({
schemaVersion: EXECUTION_SOURCE_SCHEMA_VERSION,
kind,
content,
defaults: cloneUnresolvedExecutionDefaults(input.defaults ?? {}),
identity: createExecutionSourceIdentity(input.identity),
...(input.extensions
? { extensions: cloneAdapterExtensions(input.extensions, "execution source extensions") }
: {}),
});
}