@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
270 lines (243 loc) • 11.5 kB
JavaScript
#!/usr/bin/env node
// Fresh-container install smoke: proves the packed kxm tarball installs into a
// clean Docker container with a brand-new Pi installation, that every runtime
// dependency lands, and that two configured plan providers complete a live
// turn. Secrets come from pass-cli and are injected through a mode-600 env
// file inside a temp directory that is removed afterwards; they never touch
// argv, the image, or the repository.
//
// Usage: just docker-install-smoke
// Requires: docker daemon, pass-cli session, ~/.pi/agent/models-store.json.
import { spawnSync } from "node:child_process";
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { npmPackArtifacts } from "./npm-pack-json.mjs";
const repoRoot = resolve(import.meta.dirname, "..");
const vault = process.env.KXM_SMOKE_VAULT ?? "AI Provider Keys";
const image = process.env.KXM_SMOKE_IMAGE ?? "node:24-slim";
const modelsStorePath = process.env.KXM_SMOKE_MODELS_STORE ?? join(homedir(), ".pi/agent/models-store.json");
// provider id -> { env, item, field, model }. The zai key is stored in the
// SshKey public_key slot of its pass item (upstream quirk); the qwen key is an
// extra Hidden field named "API Key".
const providers = [
{
id: "zai-coding-cn",
env: "ZAI_CODING_CN_API_KEY",
item: process.env.KXM_SMOKE_ZAI_ITEM ?? "Z.ai",
field: process.env.KXM_SMOKE_ZAI_FIELD ?? "public_key",
model: process.env.KXM_SMOKE_ZAI_MODEL ?? "glm-5.3-flash",
},
{
id: "qwen-token-plan",
env: "QWEN_TOKEN_PLAN_API_KEY",
item: process.env.KXM_SMOKE_QWEN_ITEM ?? "Alibaba Cloud Personal Plan",
field: process.env.KXM_SMOKE_QWEN_FIELD ?? "API Key",
model: process.env.KXM_SMOKE_QWEN_MODEL ?? "qwen3.8-flash",
},
];
function die(message) {
process.stderr.write(`docker-install-smoke: ${message}\n`);
process.exit(1);
}
function run(command, args, options = {}) {
const result = spawnSync(command, args, { encoding: "utf8", ...options });
if (result.status !== 0) {
die(`\`${command} ${args.join(" ")}\` failed (${result.status})\n${result.stderr || result.stdout}`);
}
return result;
}
function passItem(title) {
const result = spawnSync(
"pass-cli",
["item", "view", "--vault-name", vault, "--item-title", title, "--output", "json"],
{ encoding: "utf8" },
);
if (result.status !== 0) {
die(`pass-cli could not read item ${JSON.stringify(title)} in vault ${JSON.stringify(vault)}: ${result.stderr}`);
}
return JSON.parse(result.stdout)?.item?.content;
}
// Field values hide in either extra_fields (by name) or the SshKey section.
function extractField(content, field) {
const extra = content.extra_fields?.find((candidate) => candidate.name === field);
if (extra) {
const values = extra.content ?? {};
return values.Hidden ?? values.Text;
}
return content.content?.SshKey?.[field];
}
function main() {
for (const tool of ["docker", "pass-cli", "npm"]) {
if (spawnSync(tool, ["--version"], { encoding: "utf8" }).status !== 0) {
die(`${tool} is required but not on PATH`);
}
}
const daemon = spawnSync("docker", ["info", "--format", "ok"], { encoding: "utf8" });
if (daemon.status !== 0) {
die("docker daemon is not reachable; start Docker and retry");
}
const scratch = mkdtempSync(join(tmpdir(), "kxm-install-smoke-"));
try {
// 1. Pack the working tree. The committed dist must be current; the
// commit gate (`npm run verify` -> check:generated) enforces that.
process.stdout.write("==> npm pack\n");
const packed = run("npm", ["pack", "--pack-destination", scratch, "--json"], { cwd: repoRoot });
const filename = npmPackArtifacts(packed.stdout)[0]?.filename;
if (!filename) die("npm pack produced no tarball");
const tarball = join(scratch, filename);
// 2. Provider models from the local pi catalog (no secrets in this file).
const store = JSON.parse(readFileSync(modelsStorePath, "utf8"));
const configured = {};
const envLines = [];
for (const provider of providers) {
const entry = store[provider.id];
if (!entry) {
die(`provider ${provider.id} is missing from ${modelsStorePath}`);
}
const models = entry.models.map((model) => {
const copy = { ...model };
delete copy.checkedAt;
delete copy.lastModified;
delete copy.etag;
return copy;
});
if (models.length === 0) die(`provider ${provider.id} has no models in ${modelsStorePath}`);
configured[provider.id] = {
baseUrl: models[0].baseUrl,
api: models[0].api ?? "openai-completions",
apiKey: `$${provider.env}`,
models,
};
const key = extractField(passItem(provider.item), provider.field);
if (!key) {
die(`no value found for ${provider.item} / ${provider.field} in vault ${vault}`);
}
process.stdout.write(`==> key for ${provider.id}: item=${JSON.stringify(provider.item)} field=${provider.field} len=${key.length}\n`);
envLines.push(`${provider.env}=${key}`);
}
const modelsJson = join(scratch, "models.json");
writeFileSync(modelsJson, `${JSON.stringify({ providers: configured }, null, 2)}\n`);
// 3. Keys go into a mode-600 env file only; docker --env-file injects them
// into the container environment without ever appearing in argv.
const envFile = join(scratch, "providers.env");
writeFileSync(envFile, `${envLines.join("\n")}\n`, { mode: 0o600 });
chmodSync(envFile, 0o600);
// 4. The in-container flow: fresh pi, both providers live, kxm operator
// install with the documented --omit=peer path, pi package install,
// then the README first-run flow.
const smokeModels = providers.map((provider) => ({
id: provider.id,
env: provider.env,
model: provider.model,
token: `${provider.id.toUpperCase().replaceAll(/[^A-Z]/g, "")}_SMOKE_OK`,
}));
const container = join(scratch, "container.sh");
writeFileSync(container, containerScript(smokeModels, filename));
process.stdout.write(`==> docker run ${image}\n`);
const result = spawnSync("docker", [
"run", "--rm",
"-v", `${scratch}:/mnt:ro`,
"--env-file", envFile,
image, "bash", "/mnt/container.sh",
], { stdio: "inherit" });
if (result.status !== 0) {
die(`container smoke failed with ${result.status}`);
}
process.stdout.write("==> install smoke passed\n");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}
function containerScript(smokeModels, filename) {
const [first, second] = smokeModels;
return `#!/bin/bash
# Generated by scripts/docker-install-smoke.mjs. Runs inside a clean container.
set -uo pipefail
fail() { echo "FAIL: $*" >&2; exit 1; }
echo "== toolchain versions =="
node --version
npm --version
apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq git >/dev/null 2>&1
git --version
echo
echo "== [1/7] fresh pi installation (npm global, --ignore-scripts) =="
npm install -g --ignore-scripts @earendil-works/pi-coding-agent --no-audit --no-fund >/tmp/pi-install.log 2>&1 \\
|| { tail -20 /tmp/pi-install.log; fail "pi global install failed"; }
pi --version || fail "pi not on PATH after global install"
echo
echo "== [2/7] custom providers load with env-var auth =="
mkdir -p ~/.pi/agent
cp /mnt/models.json ~/.pi/agent/models.json
${smokeModels.map((provider) => `env | grep -q '^${provider.env}=.' || fail "${provider.env} env missing"`).join("\n")}
LM=$(pi --list-models 2>&1) || fail "pi --list-models failed: $LM"
${smokeModels.map((provider) => `echo "$LM" | grep -qi "${provider.id}" || fail "${provider.id} models not available: $LM"`).join("\n")}
${smokeModels.map((provider) => `echo " ${provider.id}: $(echo "$LM" | grep -ci '${provider.id}') models listed"`).join("\n")}
echo
echo "== [3/7] live completion: ${first.id} / ${first.model} =="
Z=$(pi --provider ${first.id} --model ${first.model} -p "Reply with exactly this token and nothing else: ${first.token}") \\
|| fail "${first.id} completion failed"
echo " reply: $Z"
echo "$Z" | grep -q "${first.token}" || fail "${first.id} reply missing expected token"
echo
echo "== [4/7] live completion: ${second.id} / ${second.model} =="
Q=$(pi --provider ${second.id} --model ${second.model} -p "Reply with exactly this token and nothing else: ${second.token}") \\
|| fail "${second.id} completion failed"
echo " reply: $Q"
echo "$Q" | grep -q "${second.token}" || fail "${second.id} reply missing expected token"
echo
echo "== [5/7] kxm operator CLI: documented global --omit=peer tarball install =="
GROOT_BEFORE=$(ls "$(npm root -g)")
npm install --global --omit=peer --no-audit --no-fund /mnt/${filename} >/tmp/kxm-install.log 2>&1 \\
|| { tail -20 /tmp/kxm-install.log; fail "kxm global install failed"; }
kxm hub help 2>&1 | grep -q "Usage: kxm hub" || fail "kxm hub help did not run (runtime deps missing?)"
echo " kxm bin runs: OK"
GROOT=$(npm root -g)
for d in @anthropic-ai/claude-agent-sdk @earendil-works/pi-tui @modelcontextprotocol/sdk yaml; do
if [ -d "$GROOT/@kontextmind/kxm/node_modules/$d" ] || [ -d "$GROOT/$d" ]; then
echo " dependency installed: $d"
else
fail "dependency NOT installed: $d"
fi
done
if [ -d "$GROOT/typebox" ] && ! echo "$GROOT_BEFORE" | grep -q "^typebox$"; then
fail "peer typebox was installed despite --omit=peer"
fi
echo " peers correctly omitted (--omit=peer); CLI still runs"
echo
echo "== [6/7] pi package surface (extension + skills) =="
mkdir -p /opt/kxm-pkg
tar -xzf /mnt/${filename} -C /opt/kxm-pkg --strip-components=1
cd /opt/kxm-pkg
npm install --omit=dev --no-audit --no-fund >/tmp/pkg-install.log 2>&1 \\
|| { tail -20 /tmp/pkg-install.log; fail "package npm install failed (pi git-install step)"; }
pi install /opt/kxm-pkg >/tmp/pi-install-pkg.log 2>&1 || { tail -20 /tmp/pi-install-pkg.log; fail "pi install of kxm package failed"; }
pi list 2>&1 | grep -qi "kxm" || fail "pi list does not show kxm"
echo " pi list shows kxm package: OK"
echo
echo "== [7/7] first-run flow: kxm init + hub + bind + session brief =="
mkdir -p /testproj && cd /testproj && git init -q .
kxm init --json --name docker-smoke --project-id prj_01JDOCKERTESTSMOKE000000000 || fail "kxm init failed"
export KXM_AUTH_TOKEN="docker-admin-token"
export KXM_PROJECT_TOKENS='{"docker-smoke":"proj-token"}'
nohup kxm hub start >/tmp/hub.log 2>&1 &
for i in $(seq 1 30); do grep -q "listening at" /tmp/hub.log 2>/dev/null && break; sleep 1; done
grep "listening at" /tmp/hub.log || { cat /tmp/hub.log; fail "hub did not start"; }
kxm hub bind http://127.0.0.1:7331 || fail "hub bind failed"
kxm session brief >/dev/null || fail "session brief failed"
echo " first-run flow: OK"
echo
echo "== final: pi print-mode run with kxm extension loaded =="
OUT=$(pi --provider ${first.id} --model ${first.model} -p "Reply with exactly: FINAL_OK" 2>/tmp/pi-ext.err) \\
|| { cat /tmp/pi-ext.err; fail "final pi run failed"; }
if grep -iE "failed to load|extension error|cannot find" /tmp/pi-ext.err >/dev/null; then
cat /tmp/pi-ext.err; fail "kxm extension load errors in pi"
fi
echo " no extension load errors"
echo " final reply: $OUT"
echo "$OUT" | grep -q "FINAL_OK" || fail "final reply missing expected token"
echo
echo "ALL CHECKS PASSED"
`;
}
main();