// The consumer's scheduled run: take the ACCESS token the mesh delivered and write it where the // Claude CLI reads it, access-token-only (novox/hq ADR 0050). The refresh token is never here to // strip — the manager holds it, and a holder's delivery has only ever been the access token. // // What the host delivers, per the manifest: // secrets.model-access -> a file holding the sealed-then-unsealed ACCESS token (the host opened it // with this node's private key; this process reads plaintext). // binds.model-access -> a JSON file of the non-secret facts the licence serves (which licence, // model, and — when the control plane carries them — grant expiry/scopes). // // Runs as `mesh-tools run` (no broker) on a schedule, so it is idempotent: same token in, same file // out. import { readFileSync } from "node:fs"; import { deliver, type DeliveredGrant } from "../credentials.js"; import { readAccountUuid, check } from "../identity.js"; function required(name: string): string { const v = process.env[name]; if (!v) throw new Error(`${name} is not set — the consumer runtime was deployed without it`); return v; } /** Read optional non-secret grant metadata (expiry, scopes, subscription) from the bound facts file. */ function readBoundMeta(path: string | undefined): Partial { if (!path) return {}; try { const raw = JSON.parse(readFileSync(path, "utf8")) as Record; return { expiresAt: typeof raw.expiresAt === "number" ? raw.expiresAt : null, refreshTokenExpiresAt: typeof raw.refreshTokenExpiresAt === "number" ? raw.refreshTokenExpiresAt : null, scopes: Array.isArray(raw.scopes) ? (raw.scopes as string[]) : null, subscriptionType: typeof raw.subscriptionType === "string" ? raw.subscriptionType : null, }; } catch { return {}; } } function main(): void { const accessToken = readFileSync(required("MESH_MODEL_ACCESS_SECRET_FILE"), "utf8").trim(); if (!accessToken) { // Nothing was delivered — which reads exactly like a credential that never arrived, so it is // said rather than written as an empty file the CLI would take for a login it should not do. throw new Error("[anthropic-consumer] the delivered access token is empty; nothing was written"); } const meta = readBoundMeta(process.env.MESH_MODEL_ACCESS_BIND_FILE); const grant: DeliveredGrant = { accessToken, ...meta }; const target = process.env.MESH_CLAUDE_CREDENTIALS_FILE ?? `${homedir()}/.claude/.credentials.json`; deliver(target, grant); console.error(`[anthropic-consumer] wrote an access-token-only credential to ${target}`); // The mis-binding guard, best-effort and fail-closed. The expected account uuid is not yet plumbed // (identity.ts TODO), so this reports what it can see rather than acting on it — it never delivers // to a wrong account because it never learns one to deliver to. const identityFile = process.env.MESH_CLAUDE_IDENTITY_FILE ?? `${homedir()}/.claude.json`; const found = readAccountUuid(identityFile); const expected = process.env.MESH_MODEL_ACCESS_ACCOUNT_UUID ?? null; const verdict = check(found, expected); if (verdict.state === "wrong-account") { throw new Error( `[anthropic-consumer] the CLI is logged in as ${verdict.found}, not the licensed ${verdict.expected}; refusing`, ); } console.error(`[anthropic-consumer] identity check: ${verdict.state}`); } function homedir(): string { return process.env.HOME ?? "/root"; } main();