| |
| |
| |
| |
| import { |
| API_KEY_HEADER, |
| AUTH_HINT, |
| DEFAULT_SPEC_URL, |
| HELP, |
| MCP_AUTH_ERROR_CODE, |
| USER_AGENT, |
| UsageError, |
| VERSION, |
| formatOutput, |
| parseArgs, |
| planRequest, |
| renderListing, |
| resolveConfig, |
| summarizeSpec, |
| } from './core.mjs'; |
|
|
| const DEFAULT_TIMEOUT_MS = 30000; |
|
|
| |
| |
| |
| function parseBody(text, headers) { |
| const contentType = (headers && headers.get && headers.get('content-type')) || ''; |
| let payload = text; |
| if (contentType.includes('text/event-stream') || /^(event|data):/m.test(text)) { |
| const dataLines = text |
| .split(/\r?\n/) |
| .filter((line) => line.startsWith('data:')) |
| .map((line) => line.slice(5).trim()); |
| payload = dataLines[dataLines.length - 1] || ''; |
| } |
| if (!payload) return text; |
| try { |
| return JSON.parse(payload); |
| } catch { |
| return text; |
| } |
| } |
|
|
| async function withTimeout(timeoutOpt, fn) { |
| const ms = Number(timeoutOpt) > 0 ? Number(timeoutOpt) : DEFAULT_TIMEOUT_MS; |
| const controller = new AbortController(); |
| const timer = setTimeout(() => controller.abort(new Error(`request timed out after ${ms}ms`)), ms); |
| try { |
| return await fn(controller.signal); |
| } finally { |
| clearTimeout(timer); |
| } |
| } |
|
|
| export async function run(argv, io = {}) { |
| const fetchImpl = io.fetch || globalThis.fetch; |
| const env = io.env || (typeof process !== 'undefined' ? process.env : {}); |
| const stdout = io.stdout || ((s) => process.stdout.write(s)); |
| const stderr = io.stderr || ((s) => process.stderr.write(s)); |
|
|
| const parsed = parseArgs(argv); |
| const { command, options } = parsed; |
|
|
| if (options.version) { |
| stdout(`${VERSION}\n`); |
| return 0; |
| } |
| if (options.help || !command || command === 'help') { |
| stdout(`${HELP}\n`); |
| return 0; |
| } |
|
|
| try { |
| if (!fetchImpl) { |
| throw new Error('global fetch is unavailable — Node 18+ is required'); |
| } |
|
|
| const config = resolveConfig(env); |
| const plan = planRequest(parsed, config); |
|
|
| if (plan.kind === 'list') { |
| const specUrl = plan.specUrl || DEFAULT_SPEC_URL; |
| const headers = { 'user-agent': USER_AGENT, accept: 'application/json' }; |
| const apiKey = options.apiKey || config.apiKey; |
| if (apiKey) headers[API_KEY_HEADER] = apiKey; |
| const res = await withTimeout(options.timeout, (signal) => |
| fetchImpl(specUrl, { headers, signal }), |
| ); |
| const spec = parseBody(await res.text(), res.headers); |
| if (!res.ok) { |
| stderr(`${formatOutput(spec, options)}\n`); |
| if (res.status === 401) stderr(`${AUTH_HINT}\n`); |
| return 1; |
| } |
| stdout(`${renderListing(summarizeSpec(spec, plan.service))}\n`); |
| return 0; |
| } |
|
|
| const res = await withTimeout(options.timeout, (signal) => |
| fetchImpl(plan.url, { method: plan.method, headers: plan.headers, body: plan.body, signal }), |
| ); |
| const value = parseBody(await res.text(), res.headers); |
|
|
| if (plan.kind === 'mcp') { |
| if (value && typeof value === 'object' && value.error && typeof value.error === 'object') { |
| stderr(`${formatOutput(value.error, options)}\n`); |
| if (value.error.code === MCP_AUTH_ERROR_CODE) { |
| stderr(`${AUTH_HINT}\n`); |
| } |
| return 1; |
| } |
| if (!res.ok) { |
| stderr(`${formatOutput(value, options)}\n`); |
| if (res.status === 401) stderr(`${AUTH_HINT}\n`); |
| return 1; |
| } |
| const result = value && typeof value === 'object' && 'result' in value ? value.result : value; |
| stdout(`${formatOutput(result, options)}\n`); |
| return 0; |
| } |
|
|
| |
| if (!res.ok) { |
| stderr(`${formatOutput(value, options)}\n`); |
| if (res.status === 401) stderr(`${AUTH_HINT}\n`); |
| return 1; |
| } |
| stdout(`${formatOutput(value, options)}\n`); |
| return 0; |
| } catch (err) { |
| if (err instanceof UsageError) { |
| stderr(`Error: ${err.message}\n`); |
| return 2; |
| } |
| stderr(`Error: ${err && err.message ? err.message : String(err)}\n`); |
| return 1; |
| } |
| } |
|
|
| export default run; |
|
|