#!/usr/bin/env node const { randomUUID } = require('crypto'); const { registerNode, randomTokenHex } = require('./register-node'); function getArgs() { return process.argv.slice(2); } function getFlagValue(args, flag, fallback) { const index = args.indexOf(flag); if (index === -1 || index + 1 >= args.length) { return fallback; } return args[index + 1]; } function hasFlag(args, flag) { return args.includes(flag); } function jsonOut(value) { process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); } async function fetchEnvelope(url, options) { const response = await fetch(url, options); const payload = await response.json(); if (!response.ok || payload.error) { const error = new Error(payload && payload.error ? payload.error.message : `Request failed: ${response.status}`); error.payload = payload; throw error; } return payload; } function buildContextHeaders() { return { 'x-request-id': randomUUID(), 'x-correlation-id': randomUUID(), }; } function usage() { process.stdout.write([ 'Skipper CLI', '', 'Commands:', ' skipper health --url ', ' skipper deploy apply --url --admin-token --tenant-id [--idempotency-key ]', ' skipper node register --node-id [--role ] [--region ] [--token ]', ' skipper node show --url --admin-token --node-id ', '', ].join('\n')); } async function cmdHealth(args) { const baseUrl = getFlagValue(args, '--url', process.env.SKIPPER_URL || 'http://127.0.0.1:3000'); const payload = await fetchEnvelope(`${baseUrl.replace(/\/$/, '')}/v1/health`, { headers: buildContextHeaders(), }); jsonOut(payload); } async function cmdDeployApply(args) { const baseUrl = getFlagValue(args, '--url', process.env.SKIPPER_URL || 'http://127.0.0.1:3000'); const adminToken = getFlagValue(args, '--admin-token', process.env.ADMIN_TOKEN); const tenantId = getFlagValue(args, '--tenant-id', process.env.TENANT_ID); const idempotencyKey = getFlagValue(args, '--idempotency-key', process.env.IDEMPOTENCY_KEY || randomUUID()); if (!adminToken) { throw new Error('Missing admin token. Use --admin-token or ADMIN_TOKEN.'); } if (!tenantId) { throw new Error('Missing tenant id. Use --tenant-id or TENANT_ID.'); } const payload = await fetchEnvelope(`${baseUrl.replace(/\/$/, '')}/v1/deployments/${tenantId}/apply`, { method: 'POST', headers: { ...buildContextHeaders(), 'x-admin-token': adminToken, 'x-idempotency-key': idempotencyKey, }, }); jsonOut(payload); } async function cmdNodeRegister(args) { const nodeId = getFlagValue(args, '--node-id', process.env.NODE_ID); const role = getFlagValue(args, '--role', process.env.NODE_ROLE || 'worker'); const region = getFlagValue(args, '--region', process.env.NODE_REGION || 'default'); const token = getFlagValue(args, '--token', process.env.NODE_TOKEN || randomTokenHex(32)); if (!nodeId) { throw new Error('Missing node id. Use --node-id or NODE_ID.'); } jsonOut(await registerNode({ nodeId, role, region, token })); } async function cmdNodeShow(args) { const baseUrl = getFlagValue(args, '--url', process.env.SKIPPER_URL || 'http://127.0.0.1:3000'); const adminToken = getFlagValue(args, '--admin-token', process.env.ADMIN_TOKEN); const nodeId = getFlagValue(args, '--node-id', process.env.NODE_ID); if (!adminToken) { throw new Error('Missing admin token. Use --admin-token or ADMIN_TOKEN.'); } if (!nodeId) { throw new Error('Missing node id. Use --node-id or NODE_ID.'); } const payload = await fetchEnvelope(`${baseUrl.replace(/\/$/, '')}/v1/resources/node/${nodeId}`, { headers: { ...buildContextHeaders(), 'x-admin-token': adminToken, }, }); jsonOut(payload); } async function main() { const args = getArgs(); if (args.length === 0 || hasFlag(args, '--help') || hasFlag(args, '-h')) { usage(); return; } const [group, command, subcommand] = args; if (group === 'health') { await cmdHealth(args.slice(1)); return; } if (group === 'deploy' && command === 'apply') { await cmdDeployApply(args.slice(2)); return; } if (group === 'node' && command === 'register') { await cmdNodeRegister(args.slice(2)); return; } if (group === 'node' && command === 'show') { await cmdNodeShow(args.slice(2)); return; } throw new Error(`Unknown command: ${args.join(' ')}`); } main().catch((error) => { process.stderr.write(`${error.message}\n`); if (error.payload) { process.stderr.write(`${JSON.stringify(error.payload, null, 2)}\n`); } process.exit(1); });