import { PigApiClient, PigApiError } from './api'; const VERSION = '0.1.0'; const HELP = `Usage: pig [--json] [--api-url URL] [--api-key KEY] Read commands: me accounts [list] [--side SIDE] [--query TEXT] accounts get deals demand | supply commitments [list] allocations [list] capacity availability [--gpu-type TYPE] capacity match --gpu-count N [filters] capacity search [--gpu-type TYPE] [--min-gpu-count N] [--max-price-cents N] capacity margin capacity idle [--threshold FRACTION] [--within-days N] Write commands (requires an API key with write scope and the relevant role): commitments create --account-id ID --name NAME --gpu-type TYPE --gpu-count N --starts-at ISO --ends-at ISO --total-gpu-hours N --cost-per-gpu-hour-cents N commitments update [fields] allocations create --commitment-id ID --demand-deal-id ID --gpu-hours N --price-per-gpu-hour-cents N --starts-at ISO --ends-at ISO --status STATUS allocations hold --commitment-id ID --demand-deal-id ID --gpu-hours N --starts-at ISO --ends-at ISO --hold-expires-at ISO allocations release [--reason TEXT] Configuration: PIG_API_URL and PIG_API_KEY, overridden by --api-url and --api-key. --json writes only JSON to stdout; errors are JSON on stderr with a non-zero exit.`; type Writer = (value: string) => void; export interface RunCliOptions { env?: NodeJS.ProcessEnv; fetchImpl?: typeof fetch; stdout?: Writer; stderr?: Writer; } class CliUsageError extends Error { readonly code = 'invalid_usage'; } interface GlobalOptions { json: boolean; help: boolean; version: boolean; apiUrl?: string; apiKey?: string; tokens: string[]; } type OptionKind = 'flag' | 'value' | 'repeat'; type OptionSpec = Readonly>; type ParsedValue = boolean | string | string[]; interface ParsedOptions { options: Map; positionals: string[]; } interface CommandResult { kind: string; data: unknown; } function usage(message: string): never { throw new CliUsageError(message); } function globalValue(args: readonly string[], index: number, name: string): string { const value = args[index + 1]; if (!value || value.startsWith('--')) usage(`${name} requires a value.`); return value; } function parseGlobals(args: readonly string[]): GlobalOptions { const result: GlobalOptions = { json: false, help: false, version: false, tokens: [], }; for (let index = 0; index < args.length; index += 1) { const token = args[index]; if (token === '--json') { result.json = true; } else if (token === '--help' || token === '-h') { result.help = true; } else if (token === '--version') { result.version = true; } else if (token === '--api-url') { result.apiUrl = globalValue(args, index, '--api-url'); index += 1; } else if (token?.startsWith('--api-url=')) { result.apiUrl = token.slice('--api-url='.length); if (!result.apiUrl) usage('--api-url requires a value.'); } else if (token === '--api-key') { result.apiKey = globalValue(args, index, '--api-key'); index += 1; } else if (token?.startsWith('--api-key=')) { result.apiKey = token.slice('--api-key='.length); if (!result.apiKey) usage('--api-key requires a value.'); } else if (token !== undefined) { result.tokens.push(token); } } return result; } function candidateSecrets(args: readonly string[], env: NodeJS.ProcessEnv): string[] { const values = new Set(); if (env.PIG_API_KEY) values.add(env.PIG_API_KEY); for (let index = 0; index < args.length; index += 1) { const token = args[index]; if (token === '--api-key' && args[index + 1]) values.add(args[index + 1]!); if (token?.startsWith('--api-key=')) values.add(token.slice('--api-key='.length)); } return [...values].filter(Boolean); } function redact(value: string, secrets: readonly string[]): string { return secrets.reduce( (redacted, secret) => redacted.split(secret).join('[REDACTED]'), value, ); } function parseOptions(args: readonly string[], spec: OptionSpec): ParsedOptions { const options = new Map(); const positionals: string[] = []; for (let index = 0; index < args.length; index += 1) { const token = args[index]; if (!token?.startsWith('--')) { if (token !== undefined) positionals.push(token); continue; } const equalsAt = token.indexOf('='); const name = token.slice(2, equalsAt === -1 ? undefined : equalsAt); const inline = equalsAt === -1 ? undefined : token.slice(equalsAt + 1); const kind = spec[name]; if (!kind) usage(`Unknown option --${name}.`); if (kind === 'flag') { if (inline !== undefined) usage(`--${name} does not take a value.`); options.set(name, true); continue; } const value = inline ?? args[index + 1]; if (value === undefined || (inline === undefined && value.startsWith('--'))) { usage(`--${name} requires a value.`); } if (inline === undefined) index += 1; if (kind === 'repeat') { const existing = options.get(name); options.set(name, [...(Array.isArray(existing) ? existing : []), value]); } else { if (options.has(name)) usage(`--${name} may only be provided once.`); options.set(name, value); } } return { options, positionals }; } function expectPositionals( positionals: readonly string[], minimum: number, maximum: number, commandUsage: string, ): void { if (positionals.length < minimum || positionals.length > maximum) { usage(`Usage: ${commandUsage}`); } } function option(options: Map, name: string): string | undefined { const value = options.get(name); return typeof value === 'string' ? value : undefined; } function requiredOption(options: Map, name: string): string { const value = option(options, name); if (value === undefined || value.length === 0) usage(`--${name} is required.`); return value; } function repeated(options: Map, name: string): string[] | undefined { const value = options.get(name); return Array.isArray(value) ? value : undefined; } interface NumberRules { integer?: boolean; minimum?: number; maximum?: number; nullable?: boolean; } function numeric( options: Map, name: string, rules: NumberRules = {}, ): number | null | undefined { const raw = option(options, name); if (raw === undefined) return undefined; if (rules.nullable && raw === 'null') return null; const value = Number(raw); if (!Number.isFinite(value)) usage(`--${name} must be a number.`); if (rules.integer && !Number.isInteger(value)) usage(`--${name} must be an integer.`); if (rules.minimum !== undefined && value < rules.minimum) { usage(`--${name} must be at least ${rules.minimum}.`); } if (rules.maximum !== undefined && value > rules.maximum) { usage(`--${name} must be at most ${rules.maximum}.`); } return value; } function requiredNumeric( options: Map, name: string, rules: NumberRules = {}, ): number { const value = numeric(options, name, rules); if (typeof value !== 'number') usage(`--${name} is required.`); return value; } function booleanValue( options: Map, name: string, ): boolean | undefined { const raw = option(options, name); if (raw === undefined) return undefined; if (raw === 'true') return true; if (raw === 'false') return false; usage(`--${name} must be true or false.`); } function nullableString( options: Map, name: string, ): string | null | undefined { const value = option(options, name); return value === 'null' ? null : value; } function setDefined(target: Record, name: string, value: unknown): void { if (value !== undefined) target[name] = value; } const ACCOUNT_LIST_OPTIONS: OptionSpec = { side: 'value', query: 'value', }; const CAPACITY_MATCH_OPTIONS: OptionSpec = { 'gpu-type': 'value', 'gpu-type-alternative': 'repeat', 'gpu-count': 'value', 'total-gpu-hours': 'value', 'fast-fabric': 'flag', 'min-security-tier': 'value', 'starts-at': 'value', 'ends-at': 'value', 'max-price-cents': 'value', }; const COMMITMENT_OPTIONS: OptionSpec = { 'account-id': 'value', 'site-id': 'value', 'supply-deal-id': 'value', name: 'value', 'gpu-type': 'value', socket: 'value', 'gpu-count': 'value', interconnect: 'value', 'security-tier': 'value', 'starts-at': 'value', 'ends-at': 'value', 'total-gpu-hours': 'value', 'cost-per-gpu-hour-cents': 'value', currency: 'value', 'shape-interval': 'repeat', 'shape-quantity': 'repeat', 'clear-shape': 'flag', 'colocate-with': 'repeat', 'clear-colocate': 'flag', 'is-contiguous': 'value', 'minimum-spend-cents': 'value', 'auto-renew': 'value', 'notice-days': 'value', 'take-or-pay-floor-pct': 'value', 'prepaid-pct': 'value', 'prepaid-amount-cents': 'value', 'useful-life-years': 'value', 'salvage-value-pct': 'value', 'depreciation-start-at': 'value', 'cost-of-capital-bps': 'value', 'financing-instrument': 'value', 'oversubscription-pct': 'value', notes: 'value', }; const COMMITMENT_UPDATE_OPTIONS: OptionSpec = { ...COMMITMENT_OPTIONS, 'terminated-at': 'value', }; const ALLOCATION_BASE_OPTIONS: OptionSpec = { 'commitment-id': 'value', 'demand-deal-id': 'value', 'gpu-hours': 'value', 'price-per-gpu-hour-cents': 'value', currency: 'value', 'starts-at': 'value', 'ends-at': 'value', guarantee: 'value', priority: 'value', 'compliance-decision-id': 'value', notes: 'value', }; function buildCommitmentBody( options: Map, update: boolean, ): Record { const body: Record = {}; const requiredText = (name: string) => update ? option(options, name) : requiredOption(options, name); const requiredNumber = (name: string, rules: NumberRules) => update ? numeric(options, name, rules) : requiredNumeric(options, name, rules); setDefined(body, 'accountId', requiredText('account-id')); setDefined(body, 'siteId', nullableString(options, 'site-id')); setDefined(body, 'supplyDealId', nullableString(options, 'supply-deal-id')); setDefined(body, 'name', requiredText('name')); setDefined(body, 'gpuType', requiredText('gpu-type')); setDefined(body, 'socket', nullableString(options, 'socket')); setDefined(body, 'gpuCount', requiredNumber('gpu-count', { integer: true, minimum: 1 })); setDefined(body, 'interconnectType', option(options, 'interconnect')); setDefined(body, 'securityTier', option(options, 'security-tier')); setDefined(body, 'startsAt', requiredText('starts-at')); setDefined(body, 'endsAt', requiredText('ends-at')); setDefined(body, 'totalGpuHours', requiredNumber('total-gpu-hours', { minimum: 0.01 })); setDefined( body, 'costPerGpuHourCents', requiredNumber('cost-per-gpu-hour-cents', { integer: true, minimum: 0 }), ); setDefined(body, 'currency', option(options, 'currency')); setDefined(body, 'isContiguous', booleanValue(options, 'is-contiguous')); setDefined( body, 'minimumSpendCents', numeric(options, 'minimum-spend-cents', { integer: true, minimum: 0, nullable: true }), ); setDefined(body, 'isAutoRenew', booleanValue(options, 'auto-renew')); setDefined( body, 'noticeDays', numeric(options, 'notice-days', { integer: true, minimum: 0, nullable: true }), ); setDefined( body, 'takeOrPayFloorPct', numeric(options, 'take-or-pay-floor-pct', { minimum: 0, maximum: 100, nullable: true }), ); setDefined( body, 'prepaidPct', numeric(options, 'prepaid-pct', { minimum: 0, maximum: 100, nullable: true }), ); setDefined( body, 'prepaidAmountCents', numeric(options, 'prepaid-amount-cents', { integer: true, minimum: 0, nullable: true }), ); setDefined( body, 'usefulLifeYears', numeric(options, 'useful-life-years', { minimum: 0, maximum: 100, nullable: true }), ); setDefined( body, 'salvageValuePct', numeric(options, 'salvage-value-pct', { minimum: 0, maximum: 100, nullable: true }), ); setDefined(body, 'depreciationStartAt', nullableString(options, 'depreciation-start-at')); setDefined( body, 'costOfCapitalBps', numeric(options, 'cost-of-capital-bps', { integer: true, minimum: 0, nullable: true }), ); setDefined(body, 'financingInstrument', nullableString(options, 'financing-instrument')); setDefined( body, 'oversubscriptionPct', numeric(options, 'oversubscription-pct', { minimum: 0, maximum: 1_000 }), ); setDefined(body, 'notes', nullableString(options, 'notes')); if (update) setDefined(body, 'terminatedAt', nullableString(options, 'terminated-at')); const intervals = repeated(options, 'shape-interval'); const quantityValues = repeated(options, 'shape-quantity'); const clearShape = options.get('clear-shape') === true; if (clearShape && (intervals || quantityValues)) { usage('--clear-shape cannot be combined with shape intervals or quantities.'); } if (clearShape) body.shape = null; if (intervals || quantityValues) { if (!intervals || !quantityValues) { usage('--shape-interval and --shape-quantity must be provided together.'); } const quantities = quantityValues.map((raw) => { const value = Number(raw); if (!Number.isInteger(value) || value < 0) { usage('--shape-quantity must contain non-negative integers.'); } return value; }); body.shape = { intervals, quantities }; } const colocateWith = repeated(options, 'colocate-with'); const clearColocate = options.get('clear-colocate') === true; if (clearColocate && colocateWith) { usage('--clear-colocate cannot be combined with --colocate-with.'); } if (clearColocate) body.colocateWith = []; if (colocateWith) body.colocateWith = colocateWith; if (update && Object.keys(body).length === 0) { usage('At least one commitment field is required for update.'); } return body; } function buildAllocationBase( options: Map, priceRequired: boolean, ): Record { const body: Record = { capacityCommitmentId: requiredOption(options, 'commitment-id'), demandDealId: requiredOption(options, 'demand-deal-id'), gpuHours: requiredNumeric(options, 'gpu-hours', { minimum: 0.01 }), startsAt: requiredOption(options, 'starts-at'), endsAt: requiredOption(options, 'ends-at'), }; const price = priceRequired ? requiredNumeric(options, 'price-per-gpu-hour-cents', { integer: true, minimum: 0 }) : numeric(options, 'price-per-gpu-hour-cents', { integer: true, minimum: 0 }); setDefined(body, 'pricePerGpuHourCents', price); setDefined(body, 'currency', option(options, 'currency')); setDefined(body, 'guaranteeType', option(options, 'guarantee')); setDefined(body, 'priority', numeric(options, 'priority', { integer: true, minimum: 0 })); setDefined(body, 'complianceDecisionId', nullableString(options, 'compliance-decision-id')); setDefined(body, 'notes', nullableString(options, 'notes')); return body; } async function accountsCommand( api: PigApiClient, tokens: readonly string[], ): Promise { if (tokens[0] === 'get') { const parsed = parseOptions(tokens.slice(1), {}); expectPositionals(parsed.positionals, 1, 1, 'pig accounts get '); return { kind: 'account', data: await api.request(`/api/accounts/${encodeURIComponent(parsed.positionals[0]!)}`), }; } const listTokens = tokens[0] === 'list' ? tokens.slice(1) : tokens; if (listTokens[0] && !listTokens[0]!.startsWith('--')) { usage('Usage: pig accounts [list] [--side SIDE] [--query TEXT]'); } const parsed = parseOptions(listTokens, ACCOUNT_LIST_OPTIONS); expectPositionals(parsed.positionals, 0, 0, 'pig accounts [list] [options]'); return { kind: 'accounts', data: await api.request('/api/accounts', { query: { side: option(parsed.options, 'side'), q: option(parsed.options, 'query') }, }), }; } async function dealsCommand( api: PigApiClient, tokens: readonly string[], ): Promise { const parsed = parseOptions(tokens, {}); expectPositionals(parsed.positionals, 1, 1, 'pig deals demand|supply'); const side = parsed.positionals[0]; if (side !== 'demand' && side !== 'supply') usage('Deal side must be demand or supply.'); return { kind: `${side}-deals`, data: await api.request(`/api/deals/${side}`) }; } async function commitmentsCommand( api: PigApiClient, tokens: readonly string[], ): Promise { const action = tokens[0]; if (!action || action === 'list') { const parsed = parseOptions(action ? tokens.slice(1) : tokens, {}); expectPositionals(parsed.positionals, 0, 0, 'pig commitments [list]'); return { kind: 'commitments', data: await api.request('/api/commitments') }; } if (action === 'create') { const parsed = parseOptions(tokens.slice(1), COMMITMENT_OPTIONS); expectPositionals(parsed.positionals, 0, 0, 'pig commitments create [fields]'); return { kind: 'commitment-created', data: await api.request('/api/commitments', { method: 'POST', body: buildCommitmentBody(parsed.options, false), }), }; } if (action === 'update') { const parsed = parseOptions(tokens.slice(1), COMMITMENT_UPDATE_OPTIONS); expectPositionals(parsed.positionals, 1, 1, 'pig commitments update [fields]'); return { kind: 'commitment-updated', data: await api.request( `/api/commitments/${encodeURIComponent(parsed.positionals[0]!)}`, { method: 'PATCH', body: buildCommitmentBody(parsed.options, true) }, ), }; } usage('Usage: pig commitments list|create|update'); } async function allocationsCommand( api: PigApiClient, tokens: readonly string[], ): Promise { const action = tokens[0]; if (!action || action === 'list') { const parsed = parseOptions(action ? tokens.slice(1) : tokens, {}); expectPositionals(parsed.positionals, 0, 0, 'pig allocations [list]'); return { kind: 'allocations', data: await api.request('/api/allocations') }; } if (action === 'create') { const parsed = parseOptions(tokens.slice(1), { ...ALLOCATION_BASE_OPTIONS, status: 'value', }); expectPositionals(parsed.positionals, 0, 0, 'pig allocations create [fields]'); const body = buildAllocationBase(parsed.options, true); body.status = requiredOption(parsed.options, 'status'); return { kind: 'allocation-created', data: await api.request('/api/allocations', { method: 'POST', body }), }; } if (action === 'hold') { const parsed = parseOptions(tokens.slice(1), { ...ALLOCATION_BASE_OPTIONS, 'hold-expires-at': 'value', 'hold-opportunity-cost-cents': 'value', }); expectPositionals(parsed.positionals, 0, 0, 'pig allocations hold [fields]'); const body = buildAllocationBase(parsed.options, false); body.holdExpiresAt = requiredOption(parsed.options, 'hold-expires-at'); setDefined( body, 'holdOpportunityCostCents', numeric(parsed.options, 'hold-opportunity-cost-cents', { integer: true, minimum: 0, nullable: true, }), ); return { kind: 'allocation-held', data: await api.request('/api/allocations/holds', { method: 'POST', body }), }; } if (action === 'release') { const parsed = parseOptions(tokens.slice(1), { reason: 'value' }); expectPositionals(parsed.positionals, 1, 1, 'pig allocations release [--reason TEXT]'); const body: Record = {}; setDefined(body, 'reason', option(parsed.options, 'reason')); return { kind: 'allocation-released', data: await api.request( `/api/allocations/${encodeURIComponent(parsed.positionals[0]!)}/release`, { method: 'POST', body }, ), }; } usage('Usage: pig allocations list|create|hold|release'); } async function capacityCommand( api: PigApiClient, tokens: readonly string[], ): Promise { const action = tokens[0]; if (action === 'availability') { const parsed = parseOptions(tokens.slice(1), { 'gpu-type': 'value' }); expectPositionals(parsed.positionals, 0, 0, 'pig capacity availability [options]'); return { kind: 'capacity-availability', data: await api.request('/api/capacity/availability', { query: { gpuType: option(parsed.options, 'gpu-type') }, }), }; } if (action === 'match') { const parsed = parseOptions(tokens.slice(1), CAPACITY_MATCH_OPTIONS); expectPositionals(parsed.positionals, 0, 0, 'pig capacity match [options]'); const body: Record = { gpuCount: requiredNumeric(parsed.options, 'gpu-count', { integer: true, minimum: 1 }), }; setDefined(body, 'gpuType', option(parsed.options, 'gpu-type')); setDefined(body, 'gpuTypeAlternatives', repeated(parsed.options, 'gpu-type-alternative')); setDefined(body, 'totalGpuHours', numeric(parsed.options, 'total-gpu-hours', { minimum: 0.01 })); if (parsed.options.get('fast-fabric') === true) body.requiresHighSpeedInterconnect = true; setDefined(body, 'minSecurityTier', option(parsed.options, 'min-security-tier')); setDefined(body, 'startsAt', option(parsed.options, 'starts-at')); setDefined(body, 'endsAt', option(parsed.options, 'ends-at')); setDefined( body, 'maxPricePerGpuHourCents', numeric(parsed.options, 'max-price-cents', { integer: true, minimum: 1 }), ); return { kind: 'capacity-matches', data: await api.request('/api/capacity/match', { method: 'POST', body }), }; } if (action === 'search') { const parsed = parseOptions(tokens.slice(1), { 'gpu-type': 'value', 'min-gpu-count': 'value', 'max-price-cents': 'value', 'fast-fabric': 'flag', limit: 'value', }); expectPositionals(parsed.positionals, 0, 0, 'pig capacity search [options]'); return { kind: 'inventory', data: await api.request('/api/inventory', { query: { gpuType: option(parsed.options, 'gpu-type'), minGpuCount: numeric(parsed.options, 'min-gpu-count', { integer: true, minimum: 1 }) ?? undefined, maxPriceCents: numeric(parsed.options, 'max-price-cents', { integer: true, minimum: 1 }) ?? undefined, fastFabric: parsed.options.get('fast-fabric') === true ? true : undefined, limit: numeric(parsed.options, 'limit', { integer: true, minimum: 1, maximum: 100 }) ?? undefined, }, }), }; } if (action === 'margin') { const parsed = parseOptions(tokens.slice(1), {}); expectPositionals(parsed.positionals, 0, 0, 'pig capacity margin'); return { kind: 'margin', data: await api.request('/api/capacity/margin') }; } if (action === 'idle') { const parsed = parseOptions(tokens.slice(1), { threshold: 'value', 'within-days': 'value', }); expectPositionals(parsed.positionals, 0, 0, 'pig capacity idle [options]'); return { kind: 'idle', data: await api.request('/api/capacity/idle', { query: { threshold: numeric(parsed.options, 'threshold', { minimum: 0, maximum: 1 }) ?? undefined, withinDays: numeric(parsed.options, 'within-days', { integer: true, minimum: 1 }) ?? undefined, }, }), }; } usage('Usage: pig capacity availability|match|search|margin|idle'); } async function dispatch(api: PigApiClient, tokens: readonly string[]): Promise { const command = tokens[0]; const rest = tokens.slice(1); if (command === 'me') { const parsed = parseOptions(rest, {}); expectPositionals(parsed.positionals, 0, 0, 'pig me'); return { kind: 'me', data: await api.request('/api/me') }; } if (command === 'accounts') return accountsCommand(api, rest); if (command === 'deals') return dealsCommand(api, rest); if (command === 'commitments') return commitmentsCommand(api, rest); if (command === 'allocations') return allocationsCommand(api, rest); if (command === 'capacity') return capacityCommand(api, rest); usage(`Unknown command ${JSON.stringify(command)}. Run pig --help for usage.`); } function asRecord(value: unknown): Record | null { return value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : null; } function summaryLine(value: unknown): string { const row = asRecord(value); if (!row) return String(value); const nested = asRecord(row.commitment) ?? asRecord(row.deal) ?? row; const name = typeof nested.name === 'string' ? nested.name : undefined; const id = typeof nested.id === 'string' ? nested.id : undefined; const gpuType = typeof nested.gpuType === 'string' ? nested.gpuType : undefined; const gpuCount = typeof nested.gpuCount === 'number' ? nested.gpuCount : undefined; const stage = typeof nested.stage === 'string' ? nested.stage : undefined; const status = typeof nested.status === 'string' ? nested.status : undefined; const accountName = typeof row.accountName === 'string' ? row.accountName : undefined; return [ name ?? accountName ?? id ?? 'record', gpuType, gpuCount ? `${gpuCount} GPUs` : undefined, stage ?? status, id, ] .filter(Boolean) .join(' | '); } function humanOutput(result: CommandResult): string { const data = result.data; if (result.kind === 'me') { const me = asRecord(data); if (me) { const teams = Array.isArray(me.teams) ? me.teams.map((team) => { const value = asRecord(team); return value ? `${String(value.team)} (${String(value.role)})` : String(team); }) : []; return [ `${String(me.name ?? 'Unknown')} <${String(me.email ?? 'no email')}>`, teams.length ? `Teams: ${teams.join(', ')}` : 'Teams: none', me.isPlatformAdmin === true ? 'Platform admin.' : undefined, ] .filter(Boolean) .join('\n'); } } if (Array.isArray(data)) { if (data.length === 0) return 'No results.'; return `${data.length} result(s)\n${data.map((row) => `- ${summaryLine(row)}`).join('\n')}`; } if (result.kind.endsWith('-deals')) { const value = asRecord(data); if (value && Array.isArray(value.deals)) { return `${value.deals.length} deal(s)\n${value.deals .map((row) => `- ${summaryLine(row)}`) .join('\n')}`; } } if (result.kind === 'margin') { const value = asRecord(data); const totals = asRecord(value?.totals); if (totals) { const marginPct = typeof totals.grossMarginPct === 'number' ? `${(totals.grossMarginPct * 100).toFixed(1)}%` : 'n/a'; return [ `Revenue: ${String(totals.revenueCents ?? 0)} cents`, `Cost: ${String(totals.costCents ?? 0)} cents`, `Gross margin: ${String(totals.grossMarginCents ?? 0)} cents (${marginPct})`, `Idle: ${String(totals.idleGpuHours ?? 0)} GPU-hours`, ].join('\n'); } } if ( result.kind.endsWith('-created') || result.kind.endsWith('-updated') || result.kind.endsWith('-held') || result.kind.endsWith('-released') ) { return `${result.kind.replaceAll('-', ' ')}: ${summaryLine(data)}`; } return JSON.stringify(data, null, 2); } function errorShape(error: unknown): { error: Record; exitCode: number } { if (error instanceof PigApiError) { return { error: { code: error.code, message: error.message, ...(error.status !== undefined ? { status: error.status } : {}), ...(error.details !== undefined ? { details: error.details } : {}), }, exitCode: 1, }; } if (error instanceof CliUsageError) { return { error: { code: error.code, message: error.message }, exitCode: 2 }; } return { error: { code: 'internal_error', message: error instanceof Error ? error.message : 'Unexpected CLI failure.', }, exitCode: 1, }; } function validateApiUrl(value: string): string { let url: URL; try { url = new URL(value); } catch { usage('PIG_API_URL/--api-url must be a valid HTTP(S) URL.'); } if (url.protocol !== 'http:' && url.protocol !== 'https:') { usage('PIG_API_URL/--api-url must use HTTP or HTTPS.'); } return value; } export async function runCli(args: readonly string[], options: RunCliOptions = {}): Promise { const env = options.env ?? process.env; const stdout = options.stdout ?? ((value: string) => process.stdout.write(value)); const stderr = options.stderr ?? ((value: string) => process.stderr.write(value)); const secrets = candidateSecrets(args, env); const jsonRequested = args.includes('--json'); try { const global = parseGlobals(args); if (global.help || global.tokens.length === 0) { stdout(global.json ? `${JSON.stringify({ usage: HELP })}\n` : `${HELP}\n`); return 0; } if (global.version) { stdout(global.json ? `${JSON.stringify({ version: VERSION })}\n` : `${VERSION}\n`); return 0; } const apiUrl = global.apiUrl ?? env.PIG_API_URL; const apiKey = global.apiKey ?? env.PIG_API_KEY; if (!apiUrl) usage('Set PIG_API_URL or pass --api-url.'); if (!apiKey) usage('Set PIG_API_KEY or pass --api-key.'); const api = new PigApiClient({ baseUrl: validateApiUrl(apiUrl), apiKey, fetchImpl: options.fetchImpl, }); const result = await dispatch(api, global.tokens); const output = global.json ? JSON.stringify(result.data ?? null) : humanOutput(result); stdout(`${redact(output, secrets)}\n`); return 0; } catch (error) { const shaped = errorShape(error); const output = jsonRequested ? JSON.stringify({ error: shaped.error }) : `Error [${String(shaped.error.code)}]: ${String(shaped.error.message)}`; stderr(`${redact(output, secrets)}\n`); return shaped.exitCode; } }