@oriva/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/dist/output.js ADDED
@@ -0,0 +1,36 @@
1
+ export function renderEnvelope(result) {
2
+ const ok = result.status >= 200 && result.status < 300;
3
+ // Body may be a structured error object (e.g. { error: {...} }) or a success payload.
4
+ // Heuristic: if body has an `error` field AND we got a 4xx/5xx, surface that as `error`.
5
+ let data = null;
6
+ let error = null;
7
+ if (ok) {
8
+ data = result.body;
9
+ }
10
+ else {
11
+ error =
12
+ result.body &&
13
+ typeof result.body === 'object' &&
14
+ 'error' in result.body
15
+ ? result.body.error
16
+ : result.body;
17
+ }
18
+ return {
19
+ ok,
20
+ status: result.status,
21
+ data,
22
+ error,
23
+ request_id: result.request_id,
24
+ url: result.url,
25
+ method: result.method,
26
+ };
27
+ }
28
+ export function renderOutput(result, opts) {
29
+ if (opts.json) {
30
+ return JSON.stringify(renderEnvelope(result), null, 2);
31
+ }
32
+ if (opts.raw) {
33
+ return typeof result.body === 'string' ? result.body : JSON.stringify(result.body);
34
+ }
35
+ return JSON.stringify(result.body, null, 2);
36
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Argv parser for the `oriva` CLI.
3
+ *
4
+ * Ported from ultra-cli (~/ultra-network/packages/ultra-cli/src/parseArgs.ts).
5
+ * Adds three new global flags: --json, --profile, --api-key.
6
+ *
7
+ * Grammar:
8
+ * oriva → { kind: 'help' }
9
+ * oriva --help | -h → { kind: 'help' }
10
+ * oriva --version | -V → { kind: 'version' }
11
+ * oriva <command> → { kind: 'command', command, flags }
12
+ * oriva <command> --help → { kind: 'command-help', command }
13
+ * oriva <command> --flag=value … → flags['flag'] = 'value'
14
+ * oriva <command> --flag value … → flags['flag'] = 'value'
15
+ * oriva <command> --flag … → flags['flag'] = true (boolean)
16
+ * oriva <command> --body=@path | - → bodySource = { kind:'file', path } / { kind:'stdin' }
17
+ * oriva <command> --body=<json> → bodySource = { kind:'inline', text }
18
+ * oriva <command> --repeated=a --repeated=b → flags['repeated'] = ['a','b']
19
+ *
20
+ * Global flags (apply before OR after the command — forgiving UX):
21
+ * --spec=<url|path> Override OpenAPI spec source
22
+ * --base-url=<url> Override server base URL
23
+ * --show-status Print HTTP status to stderr alongside body
24
+ * --raw Print response body unchanged (no JSON pretty-print)
25
+ * --json Emit envelope { ok, status, data, error, request_id }
26
+ * --quiet Suppress progress lines on stderr
27
+ * --profile=<name> Select a config-file profile
28
+ * --api-key=<key> Override ORIVA_API_KEY for this invocation
29
+ */
30
+ export interface ParsedArgs {
31
+ kind: 'help' | 'command-help' | 'command' | 'version';
32
+ command?: string;
33
+ flags: Record<string, string | string[] | boolean>;
34
+ bodySource?: {
35
+ kind: 'inline';
36
+ text: string;
37
+ } | {
38
+ kind: 'file';
39
+ path: string;
40
+ } | {
41
+ kind: 'stdin';
42
+ };
43
+ global: {
44
+ spec?: string;
45
+ baseUrl?: string;
46
+ showStatus?: boolean;
47
+ raw?: boolean;
48
+ json?: boolean;
49
+ quiet?: boolean;
50
+ profile?: string;
51
+ apiKey?: string;
52
+ };
53
+ }
54
+ export declare function parseArgs(argv: string[]): ParsedArgs;
55
+ //# sourceMappingURL=parseArgs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseArgs.d.ts","sourceRoot":"","sources":["../src/parseArgs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC;IACnD,UAAU,CAAC,EACP;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAChC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC9B;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IACtB,MAAM,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAaD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAiGpD"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Argv parser for the `oriva` CLI.
3
+ *
4
+ * Ported from ultra-cli (~/ultra-network/packages/ultra-cli/src/parseArgs.ts).
5
+ * Adds three new global flags: --json, --profile, --api-key.
6
+ *
7
+ * Grammar:
8
+ * oriva → { kind: 'help' }
9
+ * oriva --help | -h → { kind: 'help' }
10
+ * oriva --version | -V → { kind: 'version' }
11
+ * oriva <command> → { kind: 'command', command, flags }
12
+ * oriva <command> --help → { kind: 'command-help', command }
13
+ * oriva <command> --flag=value … → flags['flag'] = 'value'
14
+ * oriva <command> --flag value … → flags['flag'] = 'value'
15
+ * oriva <command> --flag … → flags['flag'] = true (boolean)
16
+ * oriva <command> --body=@path | - → bodySource = { kind:'file', path } / { kind:'stdin' }
17
+ * oriva <command> --body=<json> → bodySource = { kind:'inline', text }
18
+ * oriva <command> --repeated=a --repeated=b → flags['repeated'] = ['a','b']
19
+ *
20
+ * Global flags (apply before OR after the command — forgiving UX):
21
+ * --spec=<url|path> Override OpenAPI spec source
22
+ * --base-url=<url> Override server base URL
23
+ * --show-status Print HTTP status to stderr alongside body
24
+ * --raw Print response body unchanged (no JSON pretty-print)
25
+ * --json Emit envelope { ok, status, data, error, request_id }
26
+ * --quiet Suppress progress lines on stderr
27
+ * --profile=<name> Select a config-file profile
28
+ * --api-key=<key> Override ORIVA_API_KEY for this invocation
29
+ */
30
+ const GLOBAL_FLAGS = new Set([
31
+ 'spec',
32
+ 'base-url',
33
+ 'show-status',
34
+ 'raw',
35
+ 'json',
36
+ 'quiet',
37
+ 'profile',
38
+ 'api-key',
39
+ ]);
40
+ export function parseArgs(argv) {
41
+ const flags = {};
42
+ const global = {};
43
+ let command;
44
+ let bodySource;
45
+ let wantsHelp = false;
46
+ let wantsVersion = false;
47
+ const tokens = [...argv];
48
+ // Phase 1: peel off global flags + locate the command.
49
+ const remaining = [];
50
+ while (tokens.length) {
51
+ const t = tokens.shift();
52
+ if (t === '--help' || t === '-h') {
53
+ wantsHelp = true;
54
+ continue;
55
+ }
56
+ if (t === '--version' || t === '-V') {
57
+ wantsVersion = true;
58
+ continue;
59
+ }
60
+ const m = /^--([^=]+)(?:=(.*))?$/.exec(t);
61
+ if (m && GLOBAL_FLAGS.has(m[1])) {
62
+ const key = m[1];
63
+ // For boolean-style globals, allow `--status` (no value) → true.
64
+ // For value-style globals, consume the next token if not flag-shaped.
65
+ const isBooleanGlobal = ['show-status', 'raw', 'json', 'quiet'].includes(key);
66
+ let value;
67
+ if (m[2] !== undefined) {
68
+ value = m[2];
69
+ }
70
+ else if (!isBooleanGlobal && tokens[0] !== undefined && !tokens[0].startsWith('--')) {
71
+ value = tokens.shift();
72
+ }
73
+ else {
74
+ value = 'true';
75
+ }
76
+ if (key === 'spec')
77
+ global.spec = value;
78
+ else if (key === 'base-url')
79
+ global.baseUrl = value;
80
+ else if (key === 'show-status')
81
+ global.showStatus = value === 'true' || value === '';
82
+ else if (key === 'raw')
83
+ global.raw = value === 'true' || value === '';
84
+ else if (key === 'json')
85
+ global.json = value === 'true' || value === '';
86
+ else if (key === 'quiet')
87
+ global.quiet = value === 'true' || value === '';
88
+ else if (key === 'profile')
89
+ global.profile = value;
90
+ else if (key === 'api-key')
91
+ global.apiKey = value;
92
+ continue;
93
+ }
94
+ if (!command && !t.startsWith('-')) {
95
+ command = t;
96
+ continue;
97
+ }
98
+ remaining.push(t);
99
+ }
100
+ if (wantsVersion)
101
+ return { kind: 'version', flags, global };
102
+ if (!command)
103
+ return { kind: 'help', flags, global };
104
+ if (wantsHelp)
105
+ return { kind: 'command-help', command, flags, global };
106
+ // Phase 2: parse per-command flags from `remaining`.
107
+ while (remaining.length) {
108
+ const t = remaining.shift();
109
+ const m = /^--([^=]+)(?:=(.*))?$/.exec(t);
110
+ if (!m) {
111
+ throw new Error(`Unexpected positional argument: ${t}`);
112
+ }
113
+ const key = m[1];
114
+ let value = m[2];
115
+ if (value === undefined) {
116
+ if (remaining[0] !== undefined && !remaining[0].startsWith('--')) {
117
+ value = remaining.shift();
118
+ }
119
+ }
120
+ if (key === 'body') {
121
+ if (value === undefined)
122
+ throw new Error('--body requires a value');
123
+ if (value === '-')
124
+ bodySource = { kind: 'stdin' };
125
+ else if (value.startsWith('@'))
126
+ bodySource = { kind: 'file', path: value.slice(1) };
127
+ else
128
+ bodySource = { kind: 'inline', text: value };
129
+ continue;
130
+ }
131
+ if (value === undefined) {
132
+ flags[key] = true;
133
+ continue;
134
+ }
135
+ const existing = flags[key];
136
+ if (existing === undefined) {
137
+ flags[key] = value;
138
+ }
139
+ else if (Array.isArray(existing)) {
140
+ existing.push(value);
141
+ }
142
+ else if (typeof existing === 'string') {
143
+ flags[key] = [existing, value];
144
+ }
145
+ else {
146
+ flags[key] = value;
147
+ }
148
+ }
149
+ return { kind: 'command', command, flags, bodySource, global };
150
+ }
@@ -0,0 +1,3 @@
1
+ import type { ParsedArgs } from './parseArgs.js';
2
+ export declare function readBody(bodySource: ParsedArgs['bodySource'], stdin?: NodeJS.ReadableStream): Promise<unknown>;
3
+ //# sourceMappingURL=readBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readBody.d.ts","sourceRoot":"","sources":["../src/readBody.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,UAAU,CAAC,YAAY,CAAC,EACpC,KAAK,GAAE,MAAM,CAAC,cAA8B,GAC3C,OAAO,CAAC,OAAO,CAAC,CAgBlB"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Resolve a --body source (inline JSON, file path, stdin) to a parsed value.
3
+ * Returns undefined if no body was provided.
4
+ *
5
+ * Ported verbatim from ultra-cli (~/ultra-network/packages/ultra-cli/src/readBody.ts).
6
+ */
7
+ import { readFile } from 'node:fs/promises';
8
+ export async function readBody(bodySource, stdin = process.stdin) {
9
+ if (!bodySource)
10
+ return undefined;
11
+ let text;
12
+ if (bodySource.kind === 'inline') {
13
+ text = bodySource.text;
14
+ }
15
+ else if (bodySource.kind === 'file') {
16
+ text = await readFile(bodySource.path, 'utf8');
17
+ }
18
+ else {
19
+ text = await collectStream(stdin);
20
+ }
21
+ try {
22
+ return JSON.parse(text);
23
+ }
24
+ catch (err) {
25
+ const msg = err instanceof Error ? err.message : String(err);
26
+ throw new Error(`--body is not valid JSON: ${msg}`);
27
+ }
28
+ }
29
+ async function collectStream(s) {
30
+ const chunks = [];
31
+ for await (const chunk of s) {
32
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
33
+ }
34
+ return Buffer.concat(chunks).toString('utf8');
35
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Execute an operation against the live HTTP API.
3
+ *
4
+ * Ported from ultra-api-client (~/ultra-network/packages/ultra-api-client/src/httpExecutor.ts);
5
+ * key deviation: API-key regex swapped to Oriva's `oriva_pk_(live|test)_*` format
6
+ * matching @oriva/sdk's documented convention.
7
+ */
8
+ import type { ExtractedOperation } from './types.js';
9
+ export interface ExecuteOptions {
10
+ baseUrl: string;
11
+ apiKey?: string;
12
+ userAgent?: string;
13
+ }
14
+ export interface ExecuteResult {
15
+ status: number;
16
+ body: unknown;
17
+ request_id?: string;
18
+ /** Reconstructed for envelope output. */
19
+ url: string;
20
+ method: string;
21
+ }
22
+ export declare function executeOperation(op: ExtractedOperation, args: Record<string, unknown>, opts: ExecuteOptions, fetchImpl?: typeof fetch): Promise<ExecuteResult>;
23
+ //# sourceMappingURL=httpExecutor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpExecutor.d.ts","sourceRoot":"","sources":["../../src/shared/httpExecutor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,EAAE,cAAc,EACpB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,aAAa,CAAC,CA4DxB"}
@@ -0,0 +1,56 @@
1
+ const ORIVA_KEY_PATTERN = /^oriva_pk_(live|test)_[A-Za-z0-9_-]+$/;
2
+ export async function executeOperation(op, args, opts, fetchImpl = fetch) {
3
+ let path = op.pathTemplate;
4
+ for (const name of op.pathParams) {
5
+ const v = args[name];
6
+ if (v === undefined || v === null) {
7
+ throw new Error(`Missing required path parameter: ${name}`);
8
+ }
9
+ path = path.replace(`{${name}}`, encodeURIComponent(String(v)));
10
+ }
11
+ const url = new URL(opts.baseUrl.replace(/\/$/, '') + path);
12
+ for (const name of op.queryParams) {
13
+ const v = args[name];
14
+ if (v === undefined || v === null)
15
+ continue;
16
+ if (Array.isArray(v)) {
17
+ v.forEach((item) => url.searchParams.append(name, String(item)));
18
+ }
19
+ else {
20
+ url.searchParams.set(name, String(v));
21
+ }
22
+ }
23
+ const headers = {
24
+ Accept: 'application/json',
25
+ 'User-Agent': opts.userAgent || 'oriva-cli/0.1.0',
26
+ };
27
+ if (opts.apiKey) {
28
+ // Reject malformed keys BEFORE handing to fetch — runtime Headers.append
29
+ // echoes invalid values in error messages, which leaks the key into logs/
30
+ // transcripts. Validate first; throw a sanitized error if bad.
31
+ if (!ORIVA_KEY_PATTERN.test(opts.apiKey)) {
32
+ throw new Error('Invalid API key format (expected `oriva_pk_live_…` or `oriva_pk_test_…`). ' +
33
+ 'Check that ORIVA_API_KEY contains exactly one key value — ' +
34
+ 'multi-line input or a grep with multiple matches will fail here.');
35
+ }
36
+ headers.Authorization = `Bearer ${opts.apiKey}`;
37
+ }
38
+ let body;
39
+ if (op.hasBody && args.body !== undefined) {
40
+ headers['Content-Type'] = op.bodyContentType || 'application/json';
41
+ body = typeof args.body === 'string' ? args.body : JSON.stringify(args.body);
42
+ }
43
+ const method = op.method.toUpperCase();
44
+ const res = await fetchImpl(url.toString(), { method, headers, body });
45
+ const contentType = res.headers.get('content-type') || '';
46
+ const parsed = contentType.includes('application/json')
47
+ ? await res.json().catch(() => null)
48
+ : await res.text();
49
+ return {
50
+ status: res.status,
51
+ body: parsed,
52
+ request_id: res.headers.get('x-request-id') ?? undefined,
53
+ url: url.toString(),
54
+ method,
55
+ };
56
+ }
@@ -0,0 +1,24 @@
1
+ export interface MinimalOpenApiDoc {
2
+ openapi: string;
3
+ info?: {
4
+ title?: string;
5
+ version?: string;
6
+ };
7
+ servers?: Array<{
8
+ url: string;
9
+ description?: string;
10
+ }>;
11
+ paths?: Record<string, Record<string, unknown>>;
12
+ components?: {
13
+ schemas?: Record<string, unknown>;
14
+ };
15
+ }
16
+ /**
17
+ * Resolve the bundled-snapshot path. The snapshot lives at the package root.
18
+ * This function self-locates from its own module URL (dist/shared/loadSpec.js
19
+ * → ../../openapi-snapshot.json) so it doesn't depend on the caller's CWD or
20
+ * import-meta location.
21
+ */
22
+ export declare function bundledSnapshotPath(): string;
23
+ export declare function loadSpec(source: string): Promise<MinimalOpenApiDoc>;
24
+ //# sourceMappingURL=loadSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadSpec.d.ts","sourceRoot":"","sources":["../../src/shared/loadSpec.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChD,UAAU,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CACpD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAI5C;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAUzE"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Load an OpenAPI document from a URL, local file path, or the bundled
3
+ * snapshot that ships inside the CLI tarball.
4
+ *
5
+ * Ported from ultra-api-client (~/ultra-network/packages/ultra-api-client/src/loadSpec.ts);
6
+ * adds the bundled-snapshot default so `oriva <op>` has sub-second cold start
7
+ * with zero network — critical for agents calling in tight loops.
8
+ */
9
+ import { readFile } from 'node:fs/promises';
10
+ import { dirname, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ /**
13
+ * Resolve the bundled-snapshot path. The snapshot lives at the package root.
14
+ * This function self-locates from its own module URL (dist/shared/loadSpec.js
15
+ * → ../../openapi-snapshot.json) so it doesn't depend on the caller's CWD or
16
+ * import-meta location.
17
+ */
18
+ export function bundledSnapshotPath() {
19
+ const here = dirname(fileURLToPath(import.meta.url));
20
+ // dist/shared/loadSpec.js -> ../../openapi-snapshot.json
21
+ return resolve(here, '..', '..', 'openapi-snapshot.json');
22
+ }
23
+ export async function loadSpec(source) {
24
+ if (/^https?:\/\//.test(source)) {
25
+ const res = await fetch(source);
26
+ if (!res.ok) {
27
+ throw new Error(`Failed to fetch OpenAPI spec from ${source}: HTTP ${res.status}`);
28
+ }
29
+ return (await res.json());
30
+ }
31
+ const text = await readFile(source, 'utf8');
32
+ return JSON.parse(text);
33
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Walk an OpenAPI 3.1 document and emit one ExtractedOperation per
3
+ * (method, path) pair.
4
+ *
5
+ * Ported from ultra-api-client; key deviations:
6
+ * - Drop `toSnake()` — keep `operationId` verbatim (camelCase) so command
7
+ * names match SDK function names 1:1 (`oriva listProfiles`).
8
+ * - Carry `tags` through onto ExtractedOperation for tag-grouped help.
9
+ */
10
+ import type { MinimalOpenApiDoc } from './loadSpec.js';
11
+ import type { ExtractedOperation } from './types.js';
12
+ export declare function extractOperations(doc: MinimalOpenApiDoc, opts?: {
13
+ tagFilter?: string[];
14
+ }): ExtractedOperation[];
15
+ //# sourceMappingURL=toolGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolGenerator.d.ts","sourceRoot":"","sources":["../../src/shared/toolGenerator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA8CrD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,iBAAiB,EACtB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAClC,kBAAkB,EAAE,CAqEtB"}
@@ -0,0 +1,89 @@
1
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
2
+ function deriveToolName(method, path) {
3
+ // Fallback for ops without operationId — derive camelCase verb + segments
4
+ // (e.g. `get /trips/{id}` -> `getTripsById`).
5
+ const segments = path
6
+ .replace(/^\/+/, '')
7
+ .split('/')
8
+ .map((seg, i) => {
9
+ const param = /^\{(.+)\}$/.exec(seg);
10
+ const word = param ? `By${capitalize(param[1])}` : seg;
11
+ return i === 0 ? word : capitalize(word.replace(/^by/i, 'By'));
12
+ });
13
+ return (method +
14
+ segments
15
+ .map(capitalize)
16
+ .join('')
17
+ .replace(/[^A-Za-z0-9]/g, ''));
18
+ }
19
+ function capitalize(s) {
20
+ return s ? s[0].toUpperCase() + s.slice(1) : s;
21
+ }
22
+ export function extractOperations(doc, opts = {}) {
23
+ const out = [];
24
+ const tagFilter = (opts.tagFilter ?? []).map((t) => t.toLowerCase());
25
+ for (const [pathTemplate, pathItem] of Object.entries(doc.paths ?? {})) {
26
+ for (const method of HTTP_METHODS) {
27
+ const op = pathItem[method];
28
+ if (!op)
29
+ continue;
30
+ const opTags = (op.tags ?? []).map((t) => t);
31
+ const lowerTags = opTags.map((t) => t.toLowerCase());
32
+ if (tagFilter.length && !lowerTags.some((t) => tagFilter.includes(t)))
33
+ continue;
34
+ const params = op.parameters ?? [];
35
+ const pathParams = params.filter((p) => p.in === 'path').map((p) => p.name);
36
+ const queryParams = params.filter((p) => p.in === 'query').map((p) => p.name);
37
+ const properties = {};
38
+ const required = [];
39
+ for (const p of params) {
40
+ if (p.in !== 'path' && p.in !== 'query')
41
+ continue;
42
+ const schema = (p.schema ?? { type: 'string' });
43
+ properties[p.name] = p.description ? { ...schema, description: p.description } : schema;
44
+ if (p.required)
45
+ required.push(p.name);
46
+ }
47
+ let hasBody = false;
48
+ let bodyContentType;
49
+ if (op.requestBody?.content) {
50
+ const ct = Object.keys(op.requestBody.content)[0];
51
+ if (ct) {
52
+ bodyContentType = ct;
53
+ const bodySchema = op.requestBody.content[ct].schema;
54
+ if (bodySchema) {
55
+ properties.body = bodySchema;
56
+ hasBody = true;
57
+ if (op.requestBody.required)
58
+ required.push('body');
59
+ }
60
+ }
61
+ }
62
+ const inputSchema = {
63
+ type: 'object',
64
+ properties,
65
+ ...(required.length ? { required } : {}),
66
+ };
67
+ out.push({
68
+ name: op.operationId ? op.operationId : deriveToolName(method, pathTemplate),
69
+ description: op.summary || op.description || `${method.toUpperCase()} ${pathTemplate}`,
70
+ method,
71
+ pathTemplate,
72
+ inputSchema,
73
+ pathParams,
74
+ queryParams,
75
+ hasBody,
76
+ bodyContentType,
77
+ tags: opTags,
78
+ });
79
+ }
80
+ }
81
+ // Defensive de-dup — operationId collisions silently shadow each other otherwise.
82
+ const seen = new Set();
83
+ return out.filter((o) => {
84
+ if (seen.has(o.name))
85
+ return false;
86
+ seen.add(o.name);
87
+ return true;
88
+ });
89
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Internal shape for an operation extracted from OpenAPI.
3
+ *
4
+ * Ported from ultra-api-client (~/ultra-network/packages/ultra-api-client/src/types.ts).
5
+ * Adds `tags: string[]` so the CLI can group commands by OpenAPI tag rather than
6
+ * by path segment.
7
+ */
8
+ export interface ExtractedOperation {
9
+ /** Tool/command name surfaced to the consumer. Matches SDK function names 1:1 (camelCase). */
10
+ name: string;
11
+ /** Human-readable summary. */
12
+ description: string;
13
+ /** HTTP method. */
14
+ method: 'get' | 'post' | 'put' | 'patch' | 'delete';
15
+ /** Path template with `{param}` placeholders. */
16
+ pathTemplate: string;
17
+ /** Combined JSON Schema for the operation's input. */
18
+ inputSchema: Record<string, unknown>;
19
+ /** Param-name buckets so the executor knows where each input goes. */
20
+ pathParams: string[];
21
+ queryParams: string[];
22
+ hasBody: boolean;
23
+ bodyContentType?: string;
24
+ /** OpenAPI tags. First tag drives help-grouping. */
25
+ tags: string[];
26
+ }
27
+ export interface ServerConfig {
28
+ spec: string;
29
+ baseUrl?: string;
30
+ apiKey?: string;
31
+ tagFilter?: string[];
32
+ }
33
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,8FAA8F;IAC9F,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB;IACnB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,sEAAsE;IACtE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+ export {};