@ultra-network/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/cli.cjs +541 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +520 -0
- package/dist/cli.js.map +1 -0
- package/package.json +49 -0
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../../ultra-api-client/src/loadSpec.ts","../../ultra-api-client/src/toolGenerator.ts","../../ultra-api-client/src/httpExecutor.ts","../src/parseArgs.ts","../src/help.ts","../src/coerce.ts","../src/readBody.ts"],"sourcesContent":["/**\n * Ultra Network CLI entry point.\n *\n * Boot sequence:\n * 1. parseArgs(process.argv.slice(2))\n * 2. Load OpenAPI spec (env / --spec)\n * 3. Extract operations\n * 4. Dispatch:\n * - help → print top-level\n * - command-help → print per-command help\n * - version → print CLI + spec version\n * - command → coerce flags, read body, executeOperation, print result\n *\n * Exit codes:\n * 0 2xx response\n * 1 4xx response (client error)\n * 2 5xx response or network failure\n * 3 usage/parse error (bad flags, unknown command)\n * 4 spec load failure\n */\n\nimport {\n loadSpec,\n extractOperations,\n executeOperation,\n type ExtractedOperation,\n} from '@o-orig/ultra-api-client';\nimport { parseArgs, type ParsedArgs } from './parseArgs';\nimport { renderTopLevelHelp, renderCommandHelp, renderVersion } from './help';\nimport { coerceArgs } from './coerce';\nimport { readBody } from './readBody';\n\nconst CLI_VERSION = '1.0.0';\nconst SPEC_URL_DEFAULT = 'https://ultranetwork.co/api/v1/openapi.json';\n\nexport interface RunDeps {\n argv?: string[];\n env?: NodeJS.ProcessEnv;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n stderr?: NodeJS.WritableStream;\n fetchImpl?: typeof fetch;\n /** Inject a spec for tests; skips loadSpec. */\n specOverride?: Awaited<ReturnType<typeof loadSpec>>;\n}\n\nexport async function run(deps: RunDeps = {}): Promise<number> {\n const argv = deps.argv ?? process.argv.slice(2);\n const env = deps.env ?? process.env;\n const stdout = deps.stdout ?? process.stdout;\n const stderr = deps.stderr ?? process.stderr;\n const stdin = deps.stdin ?? process.stdin;\n\n let parsed: ParsedArgs;\n try {\n parsed = parseArgs(argv);\n } catch (err) {\n stderr.write(`Error: ${(err as Error).message}\\n`);\n return 3;\n }\n\n const specSource =\n parsed.global.spec || (env.ULTRA_API_SPEC || SPEC_URL_DEFAULT).trim();\n const tagFilter = (env.ULTRA_API_TAGS || '')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n let doc: Awaited<ReturnType<typeof loadSpec>>;\n try {\n doc = deps.specOverride ?? (await loadSpec(specSource));\n } catch (err) {\n stderr.write(`Failed to load OpenAPI spec from ${specSource}: ${(err as Error).message}\\n`);\n return 4;\n }\n\n const operations = extractOperations(doc, { tagFilter });\n const opsByName = new Map(operations.map((o) => [o.name, o]));\n\n if (parsed.kind === 'help') {\n stdout.write(renderTopLevelHelp(operations));\n stdout.write('\\n');\n return 0;\n }\n if (parsed.kind === 'version') {\n stdout.write(renderVersion(CLI_VERSION, doc.info?.version));\n stdout.write('\\n');\n return 0;\n }\n if (parsed.kind === 'command-help') {\n const op = opsByName.get(parsed.command!);\n if (!op) return unknownCommand(parsed.command!, operations, stderr);\n stdout.write(renderCommandHelp(op));\n stdout.write('\\n');\n return 0;\n }\n\n // kind === 'command'\n const op = opsByName.get(parsed.command!);\n if (!op) return unknownCommand(parsed.command!, operations, stderr);\n\n const apiKey = (env.ULTRA_API_KEY || '').trim();\n if (!apiKey && !parsed.global.quiet) {\n stderr.write('[ultra] WARNING: ULTRA_API_KEY is not set — call will likely 401.\\n');\n }\n\n let args: Record<string, unknown>;\n try {\n args = coerceArgs(parsed.flags, op.inputSchema);\n } catch (err) {\n stderr.write(`Argument error: ${(err as Error).message}\\n`);\n return 3;\n }\n try {\n const body = await readBody(parsed.bodySource, stdin);\n if (body !== undefined) args.body = body;\n } catch (err) {\n stderr.write(`Body error: ${(err as Error).message}\\n`);\n return 3;\n }\n if (op.hasBody && args.body === undefined && requiresBody(op)) {\n stderr.write(`This command requires --body=<json|@file|->. Run \\`ultra ${op.name} --help\\`.\\n`);\n return 3;\n }\n\n const baseUrl =\n parsed.global.baseUrl ||\n (env.ULTRA_API_BASE_URL || '').trim() ||\n doc.servers?.[0]?.url ||\n 'https://ultranetwork.co/api/v1';\n\n let result: Awaited<ReturnType<typeof executeOperation>>;\n try {\n result = await executeOperation(\n op,\n args,\n { baseUrl, apiKey: apiKey || undefined, userAgent: `ultra-cli/${CLI_VERSION}` },\n deps.fetchImpl,\n );\n } catch (err) {\n stderr.write(`Network error: ${(err as Error).message}\\n`);\n return 2;\n }\n\n if (parsed.global.status && !parsed.global.quiet) {\n stderr.write(`HTTP ${result.status}${result.request_id ? ` (request_id=${result.request_id})` : ''}\\n`);\n }\n\n if (parsed.global.raw) {\n const text = typeof result.body === 'string' ? result.body : JSON.stringify(result.body);\n stdout.write(text);\n stdout.write('\\n');\n } else {\n stdout.write(JSON.stringify(result.body, null, 2));\n stdout.write('\\n');\n }\n\n if (result.status >= 500) return 2;\n if (result.status >= 400) return 1;\n return 0;\n}\n\nfunction unknownCommand(\n name: string,\n operations: ExtractedOperation[],\n stderr: NodeJS.WritableStream,\n): number {\n stderr.write(`Unknown command: ${name}\\n`);\n const suggestions = operations\n .map((o) => o.name)\n .filter((n) => n.startsWith(name.slice(0, Math.max(3, Math.floor(name.length / 2)))))\n .slice(0, 5);\n if (suggestions.length) {\n stderr.write(`Did you mean: ${suggestions.join(', ')}?\\n`);\n }\n stderr.write('Run `ultra --help` to list commands.\\n');\n return 3;\n}\n\nfunction requiresBody(op: ExtractedOperation): boolean {\n const required = (op.inputSchema as { required?: string[] }).required ?? [];\n return required.includes('body');\n}\n\n// CLI entry — only invoke when this file is run directly (not when imported by tests).\nconst isDirect = typeof require !== 'undefined' && require.main === module;\nif (isDirect) {\n run().then(\n (code) => {\n process.exit(code);\n },\n (err) => {\n process.stderr.write(`[ultra] fatal: ${(err as Error).message}\\n`);\n process.exit(2);\n },\n );\n}\n","import { readFile } from 'node:fs/promises';\n\nexport interface MinimalOpenApiDoc {\n openapi: string;\n info?: { title?: string; version?: string };\n servers?: Array<{ url: string }>;\n paths?: Record<string, Record<string, unknown>>;\n components?: { schemas?: Record<string, unknown> };\n}\n\n/**\n * Load an OpenAPI document from a URL or local file path.\n * URL is preferred for production (always fresh); file path useful for local dev.\n */\nexport async function loadSpec(source: string): Promise<MinimalOpenApiDoc> {\n if (/^https?:\\/\\//.test(source)) {\n const res = await fetch(source);\n if (!res.ok) {\n throw new Error(`Failed to fetch OpenAPI spec from ${source}: HTTP ${res.status}`);\n }\n return (await res.json()) as MinimalOpenApiDoc;\n }\n const text = await readFile(source, 'utf8');\n return JSON.parse(text) as MinimalOpenApiDoc;\n}\n","/**\n * Walk an OpenAPI 3.1 document and emit one ExtractedOperation per\n * (method, path) pair. The emitted shape is consumer-agnostic — MCP\n * uses `name`/`description`/`inputSchema` as MCP tool fields; the CLI\n * uses the same triple to render subcommand help.\n *\n * Naming convention:\n * - Prefer operationId if present (snake-cased).\n * - Else derive from method + path: `get /trips` → `get_trips`,\n * `get /trips/{id}` → `get_trips_by_id`.\n */\n\nimport type { MinimalOpenApiDoc } from './loadSpec';\nimport type { ExtractedOperation } from './types';\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;\n\ninterface RawOp {\n operationId?: string;\n summary?: string;\n description?: string;\n tags?: string[];\n parameters?: Array<{\n name: string;\n in: 'path' | 'query' | 'header' | 'cookie';\n required?: boolean;\n description?: string;\n schema?: Record<string, unknown>;\n }>;\n requestBody?: {\n required?: boolean;\n content?: Record<string, { schema?: Record<string, unknown> }>;\n };\n}\n\nfunction deriveToolName(method: string, path: string): string {\n const slug = path\n .replace(/^\\/+/, '')\n .split('/')\n .map((seg) => {\n const param = /^\\{(.+)\\}$/.exec(seg);\n return param ? `by_${param[1]}` : seg;\n })\n .join('_');\n return `${method}_${slug}`.replace(/[^a-z0-9_]/gi, '_').toLowerCase();\n}\n\nexport function extractOperations(\n doc: MinimalOpenApiDoc,\n opts: { tagFilter?: string[] } = {},\n): ExtractedOperation[] {\n const out: ExtractedOperation[] = [];\n const tagFilter = (opts.tagFilter ?? []).map((t) => t.toLowerCase());\n\n for (const [pathTemplate, pathItem] of Object.entries(doc.paths ?? {})) {\n for (const method of HTTP_METHODS) {\n const op = (pathItem as Record<string, RawOp>)[method];\n if (!op) continue;\n const opTags = (op.tags ?? []).map((t) => t.toLowerCase());\n if (tagFilter.length && !opTags.some((t) => tagFilter.includes(t))) continue;\n\n const params = op.parameters ?? [];\n const pathParams = params.filter((p) => p.in === 'path').map((p) => p.name);\n const queryParams = params.filter((p) => p.in === 'query').map((p) => p.name);\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const p of params) {\n if (p.in !== 'path' && p.in !== 'query') continue;\n const schema = (p.schema ?? { type: 'string' }) as Record<string, unknown>;\n properties[p.name] = p.description\n ? { ...schema, description: p.description }\n : schema;\n if (p.required) required.push(p.name);\n }\n\n let hasBody = false;\n let bodyContentType: string | undefined;\n if (op.requestBody?.content) {\n const ct = Object.keys(op.requestBody.content)[0];\n if (ct) {\n bodyContentType = ct;\n const bodySchema = op.requestBody.content[ct].schema;\n if (bodySchema) {\n properties.body = bodySchema;\n hasBody = true;\n if (op.requestBody.required) required.push('body');\n }\n }\n }\n\n const inputSchema: Record<string, unknown> = {\n type: 'object',\n properties,\n ...(required.length ? { required } : {}),\n };\n\n out.push({\n name: op.operationId\n ? toSnake(op.operationId)\n : deriveToolName(method, pathTemplate),\n description: op.summary || op.description || `${method.toUpperCase()} ${pathTemplate}`,\n method,\n pathTemplate,\n inputSchema,\n pathParams,\n queryParams,\n hasBody,\n bodyContentType,\n });\n }\n }\n\n const seen = new Set<string>();\n return out.filter((o) => {\n if (seen.has(o.name)) return false;\n seen.add(o.name);\n return true;\n });\n}\n\nfunction toSnake(s: string): string {\n return s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^a-z0-9_]/gi, '_').toLowerCase();\n}\n","/**\n * Execute an operation against the live HTTP API.\n *\n * Responsibilities:\n * - Substitute `{param}` placeholders in the path\n * - Build query string from declared query params\n * - Set Authorization header from the configured API key\n * - JSON-encode the body if present\n * - Return a normalized response envelope the caller can serialize\n *\n * The executor is deliberately ignorant of business shapes — it just routes\n * inputs to HTTP and returns whatever the server sends back.\n */\n\nimport type { ExtractedOperation } from './types';\n\nexport interface ExecuteOptions {\n baseUrl: string;\n apiKey?: string;\n /** Override the default `User-Agent`. */\n userAgent?: string;\n}\n\nexport interface ExecuteResult {\n /** HTTP status code. */\n status: number;\n /** Parsed JSON when content-type is application/json, raw text otherwise. */\n body: unknown;\n /** Echoed for traceability. */\n request_id?: string;\n}\n\nexport async function executeOperation(\n op: ExtractedOperation,\n args: Record<string, unknown>,\n opts: ExecuteOptions,\n fetchImpl: typeof fetch = fetch,\n): Promise<ExecuteResult> {\n let path = op.pathTemplate;\n for (const name of op.pathParams) {\n const v = args[name];\n if (v === undefined || v === null) {\n throw new Error(`Missing required path parameter: ${name}`);\n }\n path = path.replace(`{${name}}`, encodeURIComponent(String(v)));\n }\n\n const url = new URL(opts.baseUrl.replace(/\\/$/, '') + path);\n for (const name of op.queryParams) {\n const v = args[name];\n if (v === undefined || v === null) continue;\n if (Array.isArray(v)) {\n v.forEach((item) => url.searchParams.append(name, String(item)));\n } else {\n url.searchParams.set(name, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'User-Agent': opts.userAgent || 'ultra-api-client/1.0',\n };\n if (opts.apiKey) {\n // Reject malformed keys BEFORE handing to fetch — runtime Headers.append\n // echoes invalid values in its error message, which leaks the key into\n // logs/transcripts. Validate first; throw a sanitized error if bad.\n if (!/^ulk_[A-Za-z0-9_-]+$/.test(opts.apiKey)) {\n throw new Error(\n 'Invalid API key format (expected `ulk_…`). ' +\n 'Check that ULTRA_API_KEY contains exactly one key value — ' +\n 'multi-line input or a grep with multiple matches will fail here.',\n );\n }\n headers.Authorization = `Bearer ${opts.apiKey}`;\n }\n\n let body: string | undefined;\n if (op.hasBody && args.body !== undefined) {\n headers['Content-Type'] = op.bodyContentType || 'application/json';\n body = typeof args.body === 'string' ? args.body : JSON.stringify(args.body);\n }\n\n const res = await fetchImpl(url.toString(), {\n method: op.method.toUpperCase(),\n headers,\n body,\n });\n\n const contentType = res.headers.get('content-type') || '';\n const parsed: unknown = contentType.includes('application/json')\n ? await res.json().catch(() => null)\n : await res.text();\n\n return {\n status: res.status,\n body: parsed,\n request_id: res.headers.get('x-request-id') ?? undefined,\n };\n}\n","/**\n * Argv parser for the `ultra` CLI.\n *\n * Grammar:\n * ultra → { kind: 'help' }\n * ultra --help | -h → { kind: 'help' }\n * ultra --version | -V → { kind: 'version' }\n * ultra <command> → { kind: 'command', command, flags }\n * ultra <command> --help → { kind: 'command-help', command }\n * ultra <command> --flag=value … → flags['flag'] = 'value'\n * ultra <command> --flag value … → flags['flag'] = 'value'\n * ultra <command> --flag … → flags['flag'] = true (boolean)\n * ultra <command> --body=@path | - → bodySource = { kind:'file', path } / { kind:'stdin' }\n * ultra <command> --body=<json> → bodySource = { kind:'inline', text }\n * ultra <command> --repeated=a --repeated=b → flags['repeated'] = ['a','b']\n *\n * Global flags (apply before the command):\n * --spec=<url|path> Override OpenAPI spec source\n * --base-url=<url> Override server base URL\n * --status Print HTTP status to stderr alongside body\n * --raw Print response body unchanged (no JSON pretty-print)\n * --quiet Suppress progress lines on stderr\n */\n\nexport interface ParsedArgs {\n kind: 'help' | 'command-help' | 'command' | 'version';\n command?: string;\n flags: Record<string, string | string[] | boolean>;\n bodySource?: { kind: 'inline'; text: string } | { kind: 'file'; path: string } | { kind: 'stdin' };\n global: {\n spec?: string;\n baseUrl?: string;\n status?: boolean;\n raw?: boolean;\n quiet?: boolean;\n };\n}\n\nconst GLOBAL_FLAGS = new Set(['spec', 'base-url', 'status', 'raw', 'quiet']);\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const flags: Record<string, string | string[] | boolean> = {};\n const global: ParsedArgs['global'] = {};\n let command: string | undefined;\n let bodySource: ParsedArgs['bodySource'];\n let wantsHelp = false;\n let wantsVersion = false;\n\n const tokens = [...argv];\n\n // Phase 1: peel off global flags + locate the command.\n // Global flags MAY appear before OR after the command (forgiving UX).\n const remaining: string[] = [];\n while (tokens.length) {\n const t = tokens.shift()!;\n if (t === '--help' || t === '-h') {\n wantsHelp = true;\n continue;\n }\n if (t === '--version' || t === '-V') {\n wantsVersion = true;\n continue;\n }\n const m = /^--([^=]+)(?:=(.*))?$/.exec(t);\n if (m && GLOBAL_FLAGS.has(m[1])) {\n const key = m[1];\n const value = m[2] ?? (tokens[0] && !tokens[0].startsWith('--') ? tokens.shift()! : 'true');\n if (key === 'spec') global.spec = value;\n else if (key === 'base-url') global.baseUrl = value;\n else if (key === 'status') global.status = value === 'true' || value === '';\n else if (key === 'raw') global.raw = value === 'true' || value === '';\n else if (key === 'quiet') global.quiet = value === 'true' || value === '';\n continue;\n }\n if (!command && !t.startsWith('-')) {\n command = t;\n continue;\n }\n remaining.push(t);\n }\n\n if (wantsVersion) return { kind: 'version', flags, global };\n if (!command) return { kind: 'help', flags, global };\n if (wantsHelp) return { kind: 'command-help', command, flags, global };\n\n // Phase 2: parse per-command flags from `remaining`.\n while (remaining.length) {\n const t = remaining.shift()!;\n const m = /^--([^=]+)(?:=(.*))?$/.exec(t);\n if (!m) {\n throw new Error(`Unexpected positional argument: ${t}`);\n }\n const key = m[1];\n let value: string | undefined = m[2];\n if (value === undefined) {\n // `--flag value` OR `--flag` (boolean)\n if (remaining[0] !== undefined && !remaining[0].startsWith('--')) {\n value = remaining.shift();\n }\n }\n\n if (key === 'body') {\n if (value === undefined) throw new Error('--body requires a value');\n if (value === '-') bodySource = { kind: 'stdin' };\n else if (value.startsWith('@')) bodySource = { kind: 'file', path: value.slice(1) };\n else bodySource = { kind: 'inline', text: value };\n continue;\n }\n\n if (value === undefined) {\n flags[key] = true;\n continue;\n }\n const existing = flags[key];\n if (existing === undefined) {\n flags[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else if (typeof existing === 'string') {\n flags[key] = [existing, value];\n } else {\n // boolean + new value: replace with the value.\n flags[key] = value;\n }\n }\n\n return { kind: 'command', command, flags, bodySource, global };\n}\n","/**\n * Help text rendering for the `ultra` CLI.\n *\n * Help reads from the loaded OpenAPI spec — there are no hardcoded command\n * lists. Every operation surfaced in the spec becomes a documented command.\n */\n\nimport type { ExtractedOperation } from '@o-orig/ultra-api-client';\n\nconst HEADER = `ultra — Ultra Network public API CLI\n\nA spec-driven wrapper over /api/v1/*. Every endpoint in the OpenAPI document\nbecomes a subcommand. Use \\`ultra <command> --help\\` for per-command flags.\n\nENVIRONMENT\n ULTRA_API_KEY Bearer key for /api/v1 (required for non-discovery)\n ULTRA_API_SPEC OpenAPI source (default: https://ultranetwork.co/api/v1/openapi.json)\n ULTRA_API_BASE_URL Override server base URL\n ULTRA_API_TAGS CSV tag filter for which operations are exposed\n\nGLOBAL FLAGS\n --spec=<url|path> Override the spec source for this invocation\n --base-url=<url> Override the base URL for this invocation\n --status Print HTTP status + request_id to stderr\n --raw Print response body unchanged (no JSON pretty-print)\n --quiet Suppress progress lines on stderr\n --help, -h Show this help (or per-command help)\n --version, -V Print CLI + spec version\n`;\n\nexport function renderTopLevelHelp(operations: ExtractedOperation[]): string {\n const out: string[] = [HEADER, 'COMMANDS'];\n if (operations.length === 0) {\n out.push(' (no operations available — check ULTRA_API_SPEC)');\n return out.join('\\n');\n }\n\n // Group by first path segment (`/trips/*` → `trips`).\n const groups = new Map<string, ExtractedOperation[]>();\n for (const op of operations) {\n const group = op.pathTemplate.replace(/^\\/+/, '').split('/')[0] || 'misc';\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(op);\n }\n\n const nameWidth = Math.min(40, Math.max(...operations.map((o) => o.name.length)));\n\n for (const [group, ops] of [...groups.entries()].sort()) {\n out.push('');\n out.push(` ${group}/`);\n for (const op of ops.sort((a, b) => a.name.localeCompare(b.name))) {\n const padded = op.name.padEnd(nameWidth);\n out.push(` ${padded} ${op.description}`);\n }\n }\n out.push('');\n out.push('Run `ultra <command> --help` for input details.');\n return out.join('\\n');\n}\n\nexport function renderCommandHelp(op: ExtractedOperation): string {\n const out: string[] = [];\n out.push(`ultra ${op.name}`);\n out.push('');\n out.push(` ${op.description}`);\n out.push(` ${op.method.toUpperCase()} ${op.pathTemplate}`);\n out.push('');\n\n const schema = op.inputSchema as {\n properties?: Record<string, { description?: string; type?: string; enum?: unknown[] }>;\n required?: string[];\n };\n const required = new Set(schema.required ?? []);\n const props = schema.properties ?? {};\n\n if (op.pathParams.length) {\n out.push('PATH PARAMETERS (required)');\n for (const name of op.pathParams) {\n out.push(formatParam(name, props[name], required.has(name)));\n }\n out.push('');\n }\n if (op.queryParams.length) {\n out.push('QUERY PARAMETERS');\n for (const name of op.queryParams) {\n out.push(formatParam(name, props[name], required.has(name)));\n }\n out.push('');\n }\n if (op.hasBody) {\n out.push('REQUEST BODY');\n out.push(` --body=<json> | --body=@path/to/file.json | --body=- (stdin)`);\n out.push(` Content-Type: ${op.bodyContentType || 'application/json'}`);\n if (required.has('body')) out.push(' (required)');\n out.push('');\n }\n\n out.push('EXAMPLE');\n out.push(formatExample(op));\n return out.join('\\n');\n}\n\nfunction formatParam(\n name: string,\n schema: { description?: string; type?: string; enum?: unknown[] } | undefined,\n isRequired: boolean,\n): string {\n const tag = isRequired ? ' (required)' : '';\n const type = schema?.type ? ` <${schema.type}>` : '';\n const desc = schema?.description ? ` — ${schema.description}` : '';\n const enumVals = schema?.enum?.length ? ` [${schema.enum.join('|')}]` : '';\n return ` --${name}${type}${tag}${enumVals}${desc}`;\n}\n\nfunction formatExample(op: ExtractedOperation): string {\n const parts: string[] = [' ultra', op.name];\n for (const p of op.pathParams) parts.push(`--${p}=<${p}>`);\n for (const q of op.queryParams.slice(0, 2)) parts.push(`--${q}=<value>`);\n if (op.hasBody) parts.push(`--body='{\"…\":\"…\"}'`);\n return parts.join(' ');\n}\n\nexport function renderVersion(cliVersion: string, specVersion: string | undefined): string {\n return `ultra ${cliVersion}\\nspec ${specVersion ?? '(unknown)'}`;\n}\n","/**\n * Coerce string CLI flags to the JSON types the API expects.\n *\n * CLI args arrive as strings (or string arrays for repeated flags). The\n * OpenAPI inputSchema tells us the target type — we cast accordingly so\n * the downstream HTTP executor sends `?limit=25` not `?limit=\"25\"`-style\n * type-mismatched payloads.\n */\n\ninterface PropSchema {\n type?: string;\n items?: PropSchema;\n}\n\nexport function coerceArgs(\n rawFlags: Record<string, string | string[] | boolean>,\n inputSchema: Record<string, unknown>,\n): Record<string, unknown> {\n const props = (inputSchema as { properties?: Record<string, PropSchema> }).properties ?? {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(rawFlags)) {\n const schema = props[k];\n out[k] = coerceOne(v, schema);\n }\n return out;\n}\n\nfunction coerceOne(v: string | string[] | boolean, schema: PropSchema | undefined): unknown {\n if (typeof v === 'boolean') return v;\n if (Array.isArray(v)) return v.map((x) => coerceScalar(x, schema?.items ?? schema));\n return coerceScalar(v, schema);\n}\n\nfunction coerceScalar(s: string, schema: PropSchema | undefined): unknown {\n const t = schema?.type;\n if (t === 'integer') {\n const n = Number.parseInt(s, 10);\n if (Number.isNaN(n)) throw new Error(`Expected integer, got: ${s}`);\n return n;\n }\n if (t === 'number') {\n const n = Number(s);\n if (Number.isNaN(n)) throw new Error(`Expected number, got: ${s}`);\n return n;\n }\n if (t === 'boolean') {\n if (s === 'true' || s === '1') return true;\n if (s === 'false' || s === '0') return false;\n throw new Error(`Expected boolean, got: ${s}`);\n }\n return s;\n}\n","import { readFile } from 'node:fs/promises';\nimport type { ParsedArgs } from './parseArgs';\n\n/**\n * Resolve a --body source (inline JSON, file path, stdin) to a parsed value.\n * Returns undefined if no body was provided.\n */\nexport async function readBody(\n bodySource: ParsedArgs['bodySource'],\n stdin: NodeJS.ReadableStream = process.stdin,\n): Promise<unknown> {\n if (!bodySource) return undefined;\n let text: string;\n if (bodySource.kind === 'inline') {\n text = bodySource.text;\n } else if (bodySource.kind === 'file') {\n text = await readFile(bodySource.path, 'utf8');\n } else {\n text = await collectStream(stdin);\n }\n try {\n return JSON.parse(text);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`--body is not valid JSON: ${msg}`);\n }\n}\n\nasync function collectStream(s: NodeJS.ReadableStream): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of s) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString('utf8');\n}\n"],"mappings":"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","names":["import_promises","op"]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// ../ultra-api-client/src/loadSpec.ts
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
async function loadSpec(source) {
|
|
11
|
+
if (/^https?:\/\//.test(source)) {
|
|
12
|
+
const res = await fetch(source);
|
|
13
|
+
if (!res.ok) {
|
|
14
|
+
throw new Error(`Failed to fetch OpenAPI spec from ${source}: HTTP ${res.status}`);
|
|
15
|
+
}
|
|
16
|
+
return await res.json();
|
|
17
|
+
}
|
|
18
|
+
const text = await readFile(source, "utf8");
|
|
19
|
+
return JSON.parse(text);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ../ultra-api-client/src/toolGenerator.ts
|
|
23
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
24
|
+
function deriveToolName(method, path) {
|
|
25
|
+
const slug = path.replace(/^\/+/, "").split("/").map((seg) => {
|
|
26
|
+
const param = /^\{(.+)\}$/.exec(seg);
|
|
27
|
+
return param ? `by_${param[1]}` : seg;
|
|
28
|
+
}).join("_");
|
|
29
|
+
return `${method}_${slug}`.replace(/[^a-z0-9_]/gi, "_").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function extractOperations(doc, opts = {}) {
|
|
32
|
+
const out = [];
|
|
33
|
+
const tagFilter = (opts.tagFilter ?? []).map((t) => t.toLowerCase());
|
|
34
|
+
for (const [pathTemplate, pathItem] of Object.entries(doc.paths ?? {})) {
|
|
35
|
+
for (const method of HTTP_METHODS) {
|
|
36
|
+
const op = pathItem[method];
|
|
37
|
+
if (!op) continue;
|
|
38
|
+
const opTags = (op.tags ?? []).map((t) => t.toLowerCase());
|
|
39
|
+
if (tagFilter.length && !opTags.some((t) => tagFilter.includes(t))) continue;
|
|
40
|
+
const params = op.parameters ?? [];
|
|
41
|
+
const pathParams = params.filter((p) => p.in === "path").map((p) => p.name);
|
|
42
|
+
const queryParams = params.filter((p) => p.in === "query").map((p) => p.name);
|
|
43
|
+
const properties = {};
|
|
44
|
+
const required = [];
|
|
45
|
+
for (const p of params) {
|
|
46
|
+
if (p.in !== "path" && p.in !== "query") continue;
|
|
47
|
+
const schema = p.schema ?? { type: "string" };
|
|
48
|
+
properties[p.name] = p.description ? { ...schema, description: p.description } : schema;
|
|
49
|
+
if (p.required) required.push(p.name);
|
|
50
|
+
}
|
|
51
|
+
let hasBody = false;
|
|
52
|
+
let bodyContentType;
|
|
53
|
+
if (op.requestBody?.content) {
|
|
54
|
+
const ct = Object.keys(op.requestBody.content)[0];
|
|
55
|
+
if (ct) {
|
|
56
|
+
bodyContentType = ct;
|
|
57
|
+
const bodySchema = op.requestBody.content[ct].schema;
|
|
58
|
+
if (bodySchema) {
|
|
59
|
+
properties.body = bodySchema;
|
|
60
|
+
hasBody = true;
|
|
61
|
+
if (op.requestBody.required) required.push("body");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const inputSchema = {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties,
|
|
68
|
+
...required.length ? { required } : {}
|
|
69
|
+
};
|
|
70
|
+
out.push({
|
|
71
|
+
name: op.operationId ? toSnake(op.operationId) : deriveToolName(method, pathTemplate),
|
|
72
|
+
description: op.summary || op.description || `${method.toUpperCase()} ${pathTemplate}`,
|
|
73
|
+
method,
|
|
74
|
+
pathTemplate,
|
|
75
|
+
inputSchema,
|
|
76
|
+
pathParams,
|
|
77
|
+
queryParams,
|
|
78
|
+
hasBody,
|
|
79
|
+
bodyContentType
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const seen = /* @__PURE__ */ new Set();
|
|
84
|
+
return out.filter((o) => {
|
|
85
|
+
if (seen.has(o.name)) return false;
|
|
86
|
+
seen.add(o.name);
|
|
87
|
+
return true;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function toSnake(s) {
|
|
91
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]/gi, "_").toLowerCase();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ../ultra-api-client/src/httpExecutor.ts
|
|
95
|
+
async function executeOperation(op, args, opts, fetchImpl = fetch) {
|
|
96
|
+
let path = op.pathTemplate;
|
|
97
|
+
for (const name of op.pathParams) {
|
|
98
|
+
const v = args[name];
|
|
99
|
+
if (v === void 0 || v === null) {
|
|
100
|
+
throw new Error(`Missing required path parameter: ${name}`);
|
|
101
|
+
}
|
|
102
|
+
path = path.replace(`{${name}}`, encodeURIComponent(String(v)));
|
|
103
|
+
}
|
|
104
|
+
const url = new URL(opts.baseUrl.replace(/\/$/, "") + path);
|
|
105
|
+
for (const name of op.queryParams) {
|
|
106
|
+
const v = args[name];
|
|
107
|
+
if (v === void 0 || v === null) continue;
|
|
108
|
+
if (Array.isArray(v)) {
|
|
109
|
+
v.forEach((item) => url.searchParams.append(name, String(item)));
|
|
110
|
+
} else {
|
|
111
|
+
url.searchParams.set(name, String(v));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const headers = {
|
|
115
|
+
Accept: "application/json",
|
|
116
|
+
"User-Agent": opts.userAgent || "ultra-api-client/1.0"
|
|
117
|
+
};
|
|
118
|
+
if (opts.apiKey) {
|
|
119
|
+
if (!/^ulk_[A-Za-z0-9_-]+$/.test(opts.apiKey)) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
"Invalid API key format (expected `ulk_\u2026`). Check that ULTRA_API_KEY contains exactly one key value \u2014 multi-line input or a grep with multiple matches will fail here."
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
headers.Authorization = `Bearer ${opts.apiKey}`;
|
|
125
|
+
}
|
|
126
|
+
let body;
|
|
127
|
+
if (op.hasBody && args.body !== void 0) {
|
|
128
|
+
headers["Content-Type"] = op.bodyContentType || "application/json";
|
|
129
|
+
body = typeof args.body === "string" ? args.body : JSON.stringify(args.body);
|
|
130
|
+
}
|
|
131
|
+
const res = await fetchImpl(url.toString(), {
|
|
132
|
+
method: op.method.toUpperCase(),
|
|
133
|
+
headers,
|
|
134
|
+
body
|
|
135
|
+
});
|
|
136
|
+
const contentType = res.headers.get("content-type") || "";
|
|
137
|
+
const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text();
|
|
138
|
+
return {
|
|
139
|
+
status: res.status,
|
|
140
|
+
body: parsed,
|
|
141
|
+
request_id: res.headers.get("x-request-id") ?? void 0
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/parseArgs.ts
|
|
146
|
+
var GLOBAL_FLAGS = /* @__PURE__ */ new Set(["spec", "base-url", "status", "raw", "quiet"]);
|
|
147
|
+
function parseArgs(argv) {
|
|
148
|
+
const flags = {};
|
|
149
|
+
const global = {};
|
|
150
|
+
let command;
|
|
151
|
+
let bodySource;
|
|
152
|
+
let wantsHelp = false;
|
|
153
|
+
let wantsVersion = false;
|
|
154
|
+
const tokens = [...argv];
|
|
155
|
+
const remaining = [];
|
|
156
|
+
while (tokens.length) {
|
|
157
|
+
const t = tokens.shift();
|
|
158
|
+
if (t === "--help" || t === "-h") {
|
|
159
|
+
wantsHelp = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (t === "--version" || t === "-V") {
|
|
163
|
+
wantsVersion = true;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const m = /^--([^=]+)(?:=(.*))?$/.exec(t);
|
|
167
|
+
if (m && GLOBAL_FLAGS.has(m[1])) {
|
|
168
|
+
const key = m[1];
|
|
169
|
+
const value = m[2] ?? (tokens[0] && !tokens[0].startsWith("--") ? tokens.shift() : "true");
|
|
170
|
+
if (key === "spec") global.spec = value;
|
|
171
|
+
else if (key === "base-url") global.baseUrl = value;
|
|
172
|
+
else if (key === "status") global.status = value === "true" || value === "";
|
|
173
|
+
else if (key === "raw") global.raw = value === "true" || value === "";
|
|
174
|
+
else if (key === "quiet") global.quiet = value === "true" || value === "";
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!command && !t.startsWith("-")) {
|
|
178
|
+
command = t;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
remaining.push(t);
|
|
182
|
+
}
|
|
183
|
+
if (wantsVersion) return { kind: "version", flags, global };
|
|
184
|
+
if (!command) return { kind: "help", flags, global };
|
|
185
|
+
if (wantsHelp) return { kind: "command-help", command, flags, global };
|
|
186
|
+
while (remaining.length) {
|
|
187
|
+
const t = remaining.shift();
|
|
188
|
+
const m = /^--([^=]+)(?:=(.*))?$/.exec(t);
|
|
189
|
+
if (!m) {
|
|
190
|
+
throw new Error(`Unexpected positional argument: ${t}`);
|
|
191
|
+
}
|
|
192
|
+
const key = m[1];
|
|
193
|
+
let value = m[2];
|
|
194
|
+
if (value === void 0) {
|
|
195
|
+
if (remaining[0] !== void 0 && !remaining[0].startsWith("--")) {
|
|
196
|
+
value = remaining.shift();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (key === "body") {
|
|
200
|
+
if (value === void 0) throw new Error("--body requires a value");
|
|
201
|
+
if (value === "-") bodySource = { kind: "stdin" };
|
|
202
|
+
else if (value.startsWith("@")) bodySource = { kind: "file", path: value.slice(1) };
|
|
203
|
+
else bodySource = { kind: "inline", text: value };
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (value === void 0) {
|
|
207
|
+
flags[key] = true;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const existing = flags[key];
|
|
211
|
+
if (existing === void 0) {
|
|
212
|
+
flags[key] = value;
|
|
213
|
+
} else if (Array.isArray(existing)) {
|
|
214
|
+
existing.push(value);
|
|
215
|
+
} else if (typeof existing === "string") {
|
|
216
|
+
flags[key] = [existing, value];
|
|
217
|
+
} else {
|
|
218
|
+
flags[key] = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { kind: "command", command, flags, bodySource, global };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/help.ts
|
|
225
|
+
var HEADER = `ultra \u2014 Ultra Network public API CLI
|
|
226
|
+
|
|
227
|
+
A spec-driven wrapper over /api/v1/*. Every endpoint in the OpenAPI document
|
|
228
|
+
becomes a subcommand. Use \`ultra <command> --help\` for per-command flags.
|
|
229
|
+
|
|
230
|
+
ENVIRONMENT
|
|
231
|
+
ULTRA_API_KEY Bearer key for /api/v1 (required for non-discovery)
|
|
232
|
+
ULTRA_API_SPEC OpenAPI source (default: https://ultranetwork.co/api/v1/openapi.json)
|
|
233
|
+
ULTRA_API_BASE_URL Override server base URL
|
|
234
|
+
ULTRA_API_TAGS CSV tag filter for which operations are exposed
|
|
235
|
+
|
|
236
|
+
GLOBAL FLAGS
|
|
237
|
+
--spec=<url|path> Override the spec source for this invocation
|
|
238
|
+
--base-url=<url> Override the base URL for this invocation
|
|
239
|
+
--status Print HTTP status + request_id to stderr
|
|
240
|
+
--raw Print response body unchanged (no JSON pretty-print)
|
|
241
|
+
--quiet Suppress progress lines on stderr
|
|
242
|
+
--help, -h Show this help (or per-command help)
|
|
243
|
+
--version, -V Print CLI + spec version
|
|
244
|
+
`;
|
|
245
|
+
function renderTopLevelHelp(operations) {
|
|
246
|
+
const out = [HEADER, "COMMANDS"];
|
|
247
|
+
if (operations.length === 0) {
|
|
248
|
+
out.push(" (no operations available \u2014 check ULTRA_API_SPEC)");
|
|
249
|
+
return out.join("\n");
|
|
250
|
+
}
|
|
251
|
+
const groups = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const op of operations) {
|
|
253
|
+
const group = op.pathTemplate.replace(/^\/+/, "").split("/")[0] || "misc";
|
|
254
|
+
if (!groups.has(group)) groups.set(group, []);
|
|
255
|
+
groups.get(group).push(op);
|
|
256
|
+
}
|
|
257
|
+
const nameWidth = Math.min(40, Math.max(...operations.map((o) => o.name.length)));
|
|
258
|
+
for (const [group, ops] of [...groups.entries()].sort()) {
|
|
259
|
+
out.push("");
|
|
260
|
+
out.push(` ${group}/`);
|
|
261
|
+
for (const op of ops.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
262
|
+
const padded = op.name.padEnd(nameWidth);
|
|
263
|
+
out.push(` ${padded} ${op.description}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
out.push("");
|
|
267
|
+
out.push("Run `ultra <command> --help` for input details.");
|
|
268
|
+
return out.join("\n");
|
|
269
|
+
}
|
|
270
|
+
function renderCommandHelp(op) {
|
|
271
|
+
const out = [];
|
|
272
|
+
out.push(`ultra ${op.name}`);
|
|
273
|
+
out.push("");
|
|
274
|
+
out.push(` ${op.description}`);
|
|
275
|
+
out.push(` ${op.method.toUpperCase()} ${op.pathTemplate}`);
|
|
276
|
+
out.push("");
|
|
277
|
+
const schema = op.inputSchema;
|
|
278
|
+
const required = new Set(schema.required ?? []);
|
|
279
|
+
const props = schema.properties ?? {};
|
|
280
|
+
if (op.pathParams.length) {
|
|
281
|
+
out.push("PATH PARAMETERS (required)");
|
|
282
|
+
for (const name of op.pathParams) {
|
|
283
|
+
out.push(formatParam(name, props[name], required.has(name)));
|
|
284
|
+
}
|
|
285
|
+
out.push("");
|
|
286
|
+
}
|
|
287
|
+
if (op.queryParams.length) {
|
|
288
|
+
out.push("QUERY PARAMETERS");
|
|
289
|
+
for (const name of op.queryParams) {
|
|
290
|
+
out.push(formatParam(name, props[name], required.has(name)));
|
|
291
|
+
}
|
|
292
|
+
out.push("");
|
|
293
|
+
}
|
|
294
|
+
if (op.hasBody) {
|
|
295
|
+
out.push("REQUEST BODY");
|
|
296
|
+
out.push(` --body=<json> | --body=@path/to/file.json | --body=- (stdin)`);
|
|
297
|
+
out.push(` Content-Type: ${op.bodyContentType || "application/json"}`);
|
|
298
|
+
if (required.has("body")) out.push(" (required)");
|
|
299
|
+
out.push("");
|
|
300
|
+
}
|
|
301
|
+
out.push("EXAMPLE");
|
|
302
|
+
out.push(formatExample(op));
|
|
303
|
+
return out.join("\n");
|
|
304
|
+
}
|
|
305
|
+
function formatParam(name, schema, isRequired) {
|
|
306
|
+
const tag = isRequired ? " (required)" : "";
|
|
307
|
+
const type = schema?.type ? ` <${schema.type}>` : "";
|
|
308
|
+
const desc = schema?.description ? ` \u2014 ${schema.description}` : "";
|
|
309
|
+
const enumVals = schema?.enum?.length ? ` [${schema.enum.join("|")}]` : "";
|
|
310
|
+
return ` --${name}${type}${tag}${enumVals}${desc}`;
|
|
311
|
+
}
|
|
312
|
+
function formatExample(op) {
|
|
313
|
+
const parts = [" ultra", op.name];
|
|
314
|
+
for (const p of op.pathParams) parts.push(`--${p}=<${p}>`);
|
|
315
|
+
for (const q of op.queryParams.slice(0, 2)) parts.push(`--${q}=<value>`);
|
|
316
|
+
if (op.hasBody) parts.push(`--body='{"\u2026":"\u2026"}'`);
|
|
317
|
+
return parts.join(" ");
|
|
318
|
+
}
|
|
319
|
+
function renderVersion(cliVersion, specVersion) {
|
|
320
|
+
return `ultra ${cliVersion}
|
|
321
|
+
spec ${specVersion ?? "(unknown)"}`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/coerce.ts
|
|
325
|
+
function coerceArgs(rawFlags, inputSchema) {
|
|
326
|
+
const props = inputSchema.properties ?? {};
|
|
327
|
+
const out = {};
|
|
328
|
+
for (const [k, v] of Object.entries(rawFlags)) {
|
|
329
|
+
const schema = props[k];
|
|
330
|
+
out[k] = coerceOne(v, schema);
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
function coerceOne(v, schema) {
|
|
335
|
+
if (typeof v === "boolean") return v;
|
|
336
|
+
if (Array.isArray(v)) return v.map((x) => coerceScalar(x, schema?.items ?? schema));
|
|
337
|
+
return coerceScalar(v, schema);
|
|
338
|
+
}
|
|
339
|
+
function coerceScalar(s, schema) {
|
|
340
|
+
const t = schema?.type;
|
|
341
|
+
if (t === "integer") {
|
|
342
|
+
const n = Number.parseInt(s, 10);
|
|
343
|
+
if (Number.isNaN(n)) throw new Error(`Expected integer, got: ${s}`);
|
|
344
|
+
return n;
|
|
345
|
+
}
|
|
346
|
+
if (t === "number") {
|
|
347
|
+
const n = Number(s);
|
|
348
|
+
if (Number.isNaN(n)) throw new Error(`Expected number, got: ${s}`);
|
|
349
|
+
return n;
|
|
350
|
+
}
|
|
351
|
+
if (t === "boolean") {
|
|
352
|
+
if (s === "true" || s === "1") return true;
|
|
353
|
+
if (s === "false" || s === "0") return false;
|
|
354
|
+
throw new Error(`Expected boolean, got: ${s}`);
|
|
355
|
+
}
|
|
356
|
+
return s;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/readBody.ts
|
|
360
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
361
|
+
async function readBody(bodySource, stdin = process.stdin) {
|
|
362
|
+
if (!bodySource) return void 0;
|
|
363
|
+
let text;
|
|
364
|
+
if (bodySource.kind === "inline") {
|
|
365
|
+
text = bodySource.text;
|
|
366
|
+
} else if (bodySource.kind === "file") {
|
|
367
|
+
text = await readFile2(bodySource.path, "utf8");
|
|
368
|
+
} else {
|
|
369
|
+
text = await collectStream(stdin);
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
return JSON.parse(text);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
375
|
+
throw new Error(`--body is not valid JSON: ${msg}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function collectStream(s) {
|
|
379
|
+
const chunks = [];
|
|
380
|
+
for await (const chunk of s) {
|
|
381
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
382
|
+
}
|
|
383
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/cli.ts
|
|
387
|
+
var CLI_VERSION = "1.0.0";
|
|
388
|
+
var SPEC_URL_DEFAULT = "https://ultranetwork.co/api/v1/openapi.json";
|
|
389
|
+
async function run(deps = {}) {
|
|
390
|
+
const argv = deps.argv ?? process.argv.slice(2);
|
|
391
|
+
const env = deps.env ?? process.env;
|
|
392
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
393
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
394
|
+
const stdin = deps.stdin ?? process.stdin;
|
|
395
|
+
let parsed;
|
|
396
|
+
try {
|
|
397
|
+
parsed = parseArgs(argv);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
stderr.write(`Error: ${err.message}
|
|
400
|
+
`);
|
|
401
|
+
return 3;
|
|
402
|
+
}
|
|
403
|
+
const specSource = parsed.global.spec || (env.ULTRA_API_SPEC || SPEC_URL_DEFAULT).trim();
|
|
404
|
+
const tagFilter = (env.ULTRA_API_TAGS || "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
405
|
+
let doc;
|
|
406
|
+
try {
|
|
407
|
+
doc = deps.specOverride ?? await loadSpec(specSource);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
stderr.write(`Failed to load OpenAPI spec from ${specSource}: ${err.message}
|
|
410
|
+
`);
|
|
411
|
+
return 4;
|
|
412
|
+
}
|
|
413
|
+
const operations = extractOperations(doc, { tagFilter });
|
|
414
|
+
const opsByName = new Map(operations.map((o) => [o.name, o]));
|
|
415
|
+
if (parsed.kind === "help") {
|
|
416
|
+
stdout.write(renderTopLevelHelp(operations));
|
|
417
|
+
stdout.write("\n");
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
420
|
+
if (parsed.kind === "version") {
|
|
421
|
+
stdout.write(renderVersion(CLI_VERSION, doc.info?.version));
|
|
422
|
+
stdout.write("\n");
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
if (parsed.kind === "command-help") {
|
|
426
|
+
const op2 = opsByName.get(parsed.command);
|
|
427
|
+
if (!op2) return unknownCommand(parsed.command, operations, stderr);
|
|
428
|
+
stdout.write(renderCommandHelp(op2));
|
|
429
|
+
stdout.write("\n");
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
const op = opsByName.get(parsed.command);
|
|
433
|
+
if (!op) return unknownCommand(parsed.command, operations, stderr);
|
|
434
|
+
const apiKey = (env.ULTRA_API_KEY || "").trim();
|
|
435
|
+
if (!apiKey && !parsed.global.quiet) {
|
|
436
|
+
stderr.write("[ultra] WARNING: ULTRA_API_KEY is not set \u2014 call will likely 401.\n");
|
|
437
|
+
}
|
|
438
|
+
let args;
|
|
439
|
+
try {
|
|
440
|
+
args = coerceArgs(parsed.flags, op.inputSchema);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
stderr.write(`Argument error: ${err.message}
|
|
443
|
+
`);
|
|
444
|
+
return 3;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const body = await readBody(parsed.bodySource, stdin);
|
|
448
|
+
if (body !== void 0) args.body = body;
|
|
449
|
+
} catch (err) {
|
|
450
|
+
stderr.write(`Body error: ${err.message}
|
|
451
|
+
`);
|
|
452
|
+
return 3;
|
|
453
|
+
}
|
|
454
|
+
if (op.hasBody && args.body === void 0 && requiresBody(op)) {
|
|
455
|
+
stderr.write(`This command requires --body=<json|@file|->. Run \`ultra ${op.name} --help\`.
|
|
456
|
+
`);
|
|
457
|
+
return 3;
|
|
458
|
+
}
|
|
459
|
+
const baseUrl = parsed.global.baseUrl || (env.ULTRA_API_BASE_URL || "").trim() || doc.servers?.[0]?.url || "https://ultranetwork.co/api/v1";
|
|
460
|
+
let result;
|
|
461
|
+
try {
|
|
462
|
+
result = await executeOperation(
|
|
463
|
+
op,
|
|
464
|
+
args,
|
|
465
|
+
{ baseUrl, apiKey: apiKey || void 0, userAgent: `ultra-cli/${CLI_VERSION}` },
|
|
466
|
+
deps.fetchImpl
|
|
467
|
+
);
|
|
468
|
+
} catch (err) {
|
|
469
|
+
stderr.write(`Network error: ${err.message}
|
|
470
|
+
`);
|
|
471
|
+
return 2;
|
|
472
|
+
}
|
|
473
|
+
if (parsed.global.status && !parsed.global.quiet) {
|
|
474
|
+
stderr.write(`HTTP ${result.status}${result.request_id ? ` (request_id=${result.request_id})` : ""}
|
|
475
|
+
`);
|
|
476
|
+
}
|
|
477
|
+
if (parsed.global.raw) {
|
|
478
|
+
const text = typeof result.body === "string" ? result.body : JSON.stringify(result.body);
|
|
479
|
+
stdout.write(text);
|
|
480
|
+
stdout.write("\n");
|
|
481
|
+
} else {
|
|
482
|
+
stdout.write(JSON.stringify(result.body, null, 2));
|
|
483
|
+
stdout.write("\n");
|
|
484
|
+
}
|
|
485
|
+
if (result.status >= 500) return 2;
|
|
486
|
+
if (result.status >= 400) return 1;
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
489
|
+
function unknownCommand(name, operations, stderr) {
|
|
490
|
+
stderr.write(`Unknown command: ${name}
|
|
491
|
+
`);
|
|
492
|
+
const suggestions = operations.map((o) => o.name).filter((n) => n.startsWith(name.slice(0, Math.max(3, Math.floor(name.length / 2))))).slice(0, 5);
|
|
493
|
+
if (suggestions.length) {
|
|
494
|
+
stderr.write(`Did you mean: ${suggestions.join(", ")}?
|
|
495
|
+
`);
|
|
496
|
+
}
|
|
497
|
+
stderr.write("Run `ultra --help` to list commands.\n");
|
|
498
|
+
return 3;
|
|
499
|
+
}
|
|
500
|
+
function requiresBody(op) {
|
|
501
|
+
const required = op.inputSchema.required ?? [];
|
|
502
|
+
return required.includes("body");
|
|
503
|
+
}
|
|
504
|
+
var isDirect = typeof __require !== "undefined" && __require.main === module;
|
|
505
|
+
if (isDirect) {
|
|
506
|
+
run().then(
|
|
507
|
+
(code) => {
|
|
508
|
+
process.exit(code);
|
|
509
|
+
},
|
|
510
|
+
(err) => {
|
|
511
|
+
process.stderr.write(`[ultra] fatal: ${err.message}
|
|
512
|
+
`);
|
|
513
|
+
process.exit(2);
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
export {
|
|
518
|
+
run
|
|
519
|
+
};
|
|
520
|
+
//# sourceMappingURL=cli.js.map
|