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
139 lines (138 loc) • 5.98 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/.
/**
* Registry-driven stash discovery for the setup wizard.
*
* Fetches the list of available stashes from the official AKM registry,
* using a cached result when available. Falls back to FALLBACK_STASHES
* when the registry is unreachable or returns no results.
*
* Default-selected registry IDs are also bound to an authenticated install
* target below so a configured registry cannot preselect a different source
* merely by reusing a trusted display ID.
*/
import { hasRegistryUrlCredentials } from "../core/registry-url.js";
import { fetchRegistryJson } from "../registry/network.js";
import { parseRegistryRef } from "../registry/resolve.js";
// ── Default selections ──────────────────────────────────────────────────────
const AUTHENTICATED_DEFAULT_TARGETS = {
"itlackey/akm-stash": {
installType: "git",
url: "https://github.com/itlackey/akm-stash",
},
};
// ── Fallback list ───────────────────────────────────────────────────────────
/**
* Hardcoded stash list used when the registry is unreachable.
* Mirrors the previous RECOMMENDED_GITHUB_REPOS constant.
*/
const FALLBACK_STASHES = [
{
id: "itlackey/akm-stash",
name: "itlackey/akm-stash",
description: "Official AKM onboarding bundle",
url: "https://github.com/itlackey/akm-stash",
installType: "git",
source: "fallback",
defaultSelected: true,
},
{
id: "andrewyng/context-hub",
name: "andrewyng/context-hub",
description: "Optional community prompt and context bundle",
url: "https://github.com/andrewyng/context-hub",
installType: "git",
source: "fallback",
defaultSelected: false,
},
];
// ── Test seam ────────────────────────────────────────────────────────────────
// Swap-and-restore override. Inert in production; only tests call the setter.
let loadSetupStashesOverride;
/** TEST-ONLY. Swap the implementation of `loadSetupStashes`; pass undefined to restore. */
export function _setLoadSetupStashesForTests(fake) {
loadSetupStashesOverride = fake;
}
// ── Loader ──────────────────────────────────────────────────────────────────
/**
* Fetch available stashes from the registry and map to SetupBundleEntry[].
*
* Falls back to FALLBACK_STASHES on network failure, parse error, or
* empty response — setup never crashes due to a registry outage.
*
* @param registryUrl URL of the registry index JSON.
* @param timeoutMs Fetch timeout in ms (default: 4000).
*/
export async function loadSetupStashes(registryUrl, timeoutMs = 4000) {
if (hasRegistryUrlCredentials(registryUrl))
return FALLBACK_STASHES;
if (loadSetupStashesOverride)
return loadSetupStashesOverride(registryUrl, timeoutMs);
return loadSetupStashesReal(registryUrl, timeoutMs);
}
async function loadSetupStashesReal(registryUrl, timeoutMs = 4000) {
try {
const raw = await fetchRegistryJson(registryUrl, {
headers: { Accept: "application/json" },
timeoutMs,
retries: 0,
});
if (!Array.isArray(raw?.stashes) || raw.stashes.length === 0)
return FALLBACK_STASHES;
const entries = raw.stashes.flatMap((item) => {
if (!item || typeof item !== "object")
return [];
const s = item;
const id = typeof s.id === "string" ? s.id : "";
const name = typeof s.name === "string" ? s.name : id;
const description = typeof s.description === "string" ? s.description : "";
const target = setupInstallTarget(s.source, s.ref);
if (!id || !target)
return [];
return [
{
id,
name,
description,
...target,
source: "registry",
defaultSelected: authenticatedDefaultTarget(id, target),
},
];
});
return entries.length > 0 ? entries : FALLBACK_STASHES;
}
catch {
return FALLBACK_STASHES;
}
}
function authenticatedDefaultTarget(id, target) {
const expected = AUTHENTICATED_DEFAULT_TARGETS[id];
return expected?.installType === target.installType && expected.url === target.url;
}
function setupInstallTarget(source, ref) {
if (typeof ref !== "string")
return undefined;
try {
if (source === "npm") {
const candidate = ref.startsWith("npm:") ? ref : `npm:${ref}`;
const parsed = parseRegistryRef(candidate);
return parsed.source === "npm" ? { installType: "npm", url: candidate } : undefined;
}
if (source === "github") {
const candidate = ref.startsWith("github:") ? ref : `github:${ref}`;
const parsed = parseRegistryRef(candidate);
if (parsed.source !== "github")
return undefined;
return {
installType: "git",
url: `https://github.com/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`,
};
}
}
catch {
// Malformed registry install refs are omitted without breaking setup.
}
return undefined;
}