Files
pig/apps/cli/src/cli.ts
T
2026-08-13 01:39:01 -07:00

885 lines
30 KiB
TypeScript

import { PigApiClient, PigApiError } from './api';
const VERSION = '0.1.0';
const HELP = `Usage: pig [--json] [--api-url URL] [--api-key KEY] <command>
Read commands:
me
accounts [list] [--side SIDE] [--query TEXT]
accounts get <account-id>
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 <commitment-id> [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 <allocation-id> [--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<Record<string, OptionKind>>;
type ParsedValue = boolean | string | string[];
interface ParsedOptions {
options: Map<string, ParsedValue>;
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<string>();
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<string, ParsedValue>();
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<string, ParsedValue>, name: string): string | undefined {
const value = options.get(name);
return typeof value === 'string' ? value : undefined;
}
function requiredOption(options: Map<string, ParsedValue>, name: string): string {
const value = option(options, name);
if (value === undefined || value.length === 0) usage(`--${name} is required.`);
return value;
}
function repeated(options: Map<string, ParsedValue>, 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<string, ParsedValue>,
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<string, ParsedValue>,
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<string, ParsedValue>,
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<string, ParsedValue>,
name: string,
): string | null | undefined {
const value = option(options, name);
return value === 'null' ? null : value;
}
function setDefined(target: Record<string, unknown>, 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<string, ParsedValue>,
update: boolean,
): Record<string, unknown> {
const body: Record<string, unknown> = {};
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<string, ParsedValue>,
priceRequired: boolean,
): Record<string, unknown> {
const body: Record<string, unknown> = {
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<CommandResult> {
if (tokens[0] === 'get') {
const parsed = parseOptions(tokens.slice(1), {});
expectPositionals(parsed.positionals, 1, 1, 'pig accounts get <account-id>');
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<CommandResult> {
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<CommandResult> {
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 <id> [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<CommandResult> {
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 <id> [--reason TEXT]');
const body: Record<string, unknown> = {};
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<CommandResult> {
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<string, unknown> = {
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<CommandResult> {
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<string, unknown> | null {
return value !== null && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: 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<string, unknown>; 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<number> {
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;
}
}