import { defineCommand } from "citty"; import { apiRequest, KuberApiError, type ApiRequestInit } from "../lib/api"; import { ctx } from "../lib/context"; import { readTrust, resolveTrustIdentity, updateTrust, type LocalTrustRecord, } from "../lib/trust"; import type { WorkspaceTrustResponse } from "../shared/api"; function route(project: string) { return `/workspaces/${encodeURIComponent(project)}/trust`; } type TrustApiRequest = (path: string, init?: ApiRequestInit) => Promise; async function current() { const { project, cwd } = ctx(); return resolveTrustIdentity(project, cwd); } export async function grantTrust( identity: LocalTrustRecord, request: TrustApiRequest = apiRequest, ): Promise { await request(route(identity.project), { method: "POST", json: { fingerprint: identity.fingerprint }, }); await updateTrust((records) => [ ...records.filter( (record) => record.project !== identity.project || record.fingerprint !== identity.fingerprint, ), identity, ]); console.log(`Trusted this directory for namespace ${identity.project}`); } export async function statusTrust( identity: LocalTrustRecord, request: TrustApiRequest = apiRequest, ): Promise { const local = (await readTrust()).some( (record) => record.project === identity.project && record.fingerprint === identity.fingerprint, ); const remote = await request(route(identity.project)); const registered = remote.fingerprints.includes(identity.fingerprint); console.log(`Namespace: ${identity.project}`); console.log(`Local: ${local ? "trusted" : "untrusted"}`); console.log(`Server: ${registered ? "registered" : "not registered"}`); } export async function revokeTrust( identity: LocalTrustRecord, request: TrustApiRequest = apiRequest, ): Promise { let remoteError: unknown; try { await request( `${route(identity.project)}?fingerprint=${encodeURIComponent(identity.fingerprint)}`, { method: "DELETE" }, ); } catch (error) { // A missing server record is already revoked; all other failures are // reported after the local registration is removed. if (!(error instanceof KuberApiError && error.status === 404)) remoteError = error; } await updateTrust((records) => records.filter( (record) => record.project !== identity.project || record.fingerprint !== identity.fingerprint, ), ); if (remoteError) { const detail = remoteError instanceof Error ? remoteError.message : String(remoteError); throw new Error( `Removed local trust for namespace ${identity.project}, but failed to revoke the server registration: ${detail}`, { cause: remoteError }, ); } console.log(`Revoked trust for namespace ${identity.project}`); } export const trust = defineCommand({ meta: { name: "trust", description: "Trust this directory for kuber up" }, subCommands: { status: defineCommand({ meta: { name: "status", description: "Show local and server trust" }, async run() { await statusTrust(await current()); }, }), revoke: defineCommand({ meta: { name: "revoke", description: "Revoke this directory trust" }, async run() { await revokeTrust(await current()); }, }), }, async run({ args }) { // citty runs a command's handler after its selected subcommand handler. // Only a bare `trust` invocation has no positional arguments here. if (args._.length > 0) return; await grantTrust(await current()); }, });