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
118 lines (117 loc) • 5.51 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/.
/**
* Shared collect-input / probe / derive-config helpers for the wizard's
* connection steps (§4.6 dedup of the three near-identical steps in
* connection.ts). Prompt copy, validation messages, and the probe's spinner
* text are shared VERBATIM so the wizard's observable flow is unchanged;
* each step keeps its own option assembly and branching.
*/
import * as p from "../../cli/clack.js";
import { probeLlmReachable } from "../../llm/client.js";
import { prompt, promptOrBack } from "../prompt.js";
// ── Derive-config ───────────────────────────────────────────────────────────
/** Standard chat-connection config shared by every provider branch. */
export function llmConnection(provider, endpoint, model, extra) {
return {
provider,
endpoint,
model,
temperature: 0.3,
...(extra ?? {}),
};
}
/** The "Keep current: ..." select option appended when a connection exists. */
export function keepCurrentOption(current) {
return {
value: "keep",
label: `Keep current: ${current.provider ?? current.endpoint}`,
hint: current.model,
};
}
/** Hint text for the "LM Studio / local server" select option. */
export function lmStudioOptionHint(lmStudio) {
return lmStudio?.available
? `${lmStudio.models.length} model${lmStudio.models.length === 1 ? "" : "s"} detected`
: "http://localhost:1234";
}
// ── Collect-input ───────────────────────────────────────────────────────────
/** Non-empty model-name text prompt shared by every provider branch. */
export async function promptModelName(opts) {
return prompt(() => p.text({
message: opts.message ?? "Model name:",
placeholder: opts.placeholder,
...(opts.defaultValue ? { defaultValue: opts.defaultValue } : {}),
validate: (v) => (!v?.trim() ? "Model name cannot be empty" : undefined),
}));
}
/** http(s)-validated endpoint text prompt shared by every endpoint branch. */
export async function promptEndpointUrl(opts) {
const schemeMessage = opts.schemeMessage ?? "Must start with http:// or https://";
return prompt(() => p.text({
message: opts.message ?? "Endpoint URL:",
placeholder: opts.placeholder,
...(opts.defaultValue ? { defaultValue: opts.defaultValue } : {}),
validate: (v) => {
if (!v?.trim())
return "Endpoint cannot be empty";
if (!v.startsWith("http://") && !v.startsWith("https://"))
return schemeMessage;
},
}));
}
/**
* LM Studio model selection: pick from the detected models (with an
* "Enter manually..." escape hatch) or fall back to a free-text prompt when
* nothing was detected.
*/
export async function promptLmStudioModel(lmStudio, currentModel) {
const lmsModels = lmStudio?.available && lmStudio.models.length > 0 ? lmStudio.models : [];
if (lmsModels.length > 0) {
const modelChoice = await prompt(() => p.select({
message: "Model name:",
options: [
...lmsModels.map((m) => ({ value: m, label: m })),
{ value: "__manual__", label: "Enter manually..." },
],
initialValue: currentModel && lmsModels.includes(currentModel) ? currentModel : lmsModels[0],
}));
if (modelChoice !== "__manual__")
return modelChoice;
}
return promptModelName({ placeholder: currentModel ?? "local-model", defaultValue: currentModel });
}
/**
* Optional API-key ENV VAR NAME prompt (never the key value — saveConfig()
* strips secrets). Escape backs out with `null`.
*/
export async function promptApiKeyEnvVarName() {
return promptOrBack(() => p.text({
message: "API key environment variable name (optional):",
placeholder: "CUSTOM_LLM_API_KEY",
validate: (value) => value && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
? "Use an environment variable name, not a key value"
: undefined,
}));
}
// ── Probe ───────────────────────────────────────────────────────────────────
/**
* Best-effort reachability probe — never blocks setup. Does NOT probe or
* cache structured-output support: `chatCompletion` now attempts
* `response_format: json_schema` fresh on every call that supplies a schema
* and falls back once per call on a 4xx (see `llm/client.ts`), so there is
* no verdict for setup to determine or persist here.
*/
export async function probeLlmConnection(llm) {
const probeSpin = p.spinner();
probeSpin.start("Probing LLM connectivity...");
const probe = await probeLlmReachable(llm);
if (probe.reachable) {
probeSpin.stop("LLM reachable.");
}
else {
probeSpin.stop("LLM not reachable.");
p.log.warn(`Could not reach the LLM endpoint${probe.error ? ` (${probe.error})` : ""}. Configuration was saved; verify your endpoint and API key, then retry.`);
}
}