api-to-cli 0.1.2 → 0.1.3
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/README.md +126 -4
- package/examples/openapi/sample-openapi-agent/README.md +12 -0
- package/examples/openapi/sample-openapi-agent/agentbridge.manifest.json +85 -0
- package/examples/openapi/sample-openapi-agent/cli/README.md +18 -0
- package/examples/openapi/sample-openapi-agent/cli/bin/sample-crm-api.js +64 -0
- package/examples/openapi/sample-openapi-agent/cli/commands/create-contact.js +59 -0
- package/examples/openapi/sample-openapi-agent/cli/commands/delete-contacts-by-contactid.js +45 -0
- package/examples/openapi/sample-openapi-agent/cli/commands/get-contacts-by-contactid.js +45 -0
- package/examples/openapi/sample-openapi-agent/cli/commands/list-contacts.js +45 -0
- package/examples/openapi/sample-openapi-agent/cli/commands/patch-contacts-by-contactid.js +60 -0
- package/examples/openapi/sample-openapi-agent/cli/lib/client.js +244 -0
- package/examples/openapi/sample-openapi-agent/cli/lib/output.js +21 -0
- package/examples/openapi/sample-openapi-agent/cli/package.json +16 -0
- package/examples/openapi/sample-openapi-agent/skill/SKILL.md +50 -0
- package/examples/openapi/sample-openapi-cli/README.md +18 -0
- package/examples/openapi/sample-openapi-cli/bin/sample-crm-api.js +64 -0
- package/examples/openapi/sample-openapi-cli/commands/create-contact.js +59 -0
- package/examples/openapi/sample-openapi-cli/commands/delete-contacts-by-contactid.js +45 -0
- package/examples/openapi/sample-openapi-cli/commands/get-contacts-by-contactid.js +45 -0
- package/examples/openapi/sample-openapi-cli/commands/list-contacts.js +45 -0
- package/examples/openapi/sample-openapi-cli/commands/patch-contacts-by-contactid.js +60 -0
- package/examples/openapi/sample-openapi-cli/lib/client.js +244 -0
- package/examples/openapi/sample-openapi-cli/lib/output.js +21 -0
- package/examples/openapi/sample-openapi-cli/node_modules/.package-lock.json +15 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/LICENSE +22 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/Readme.md +1157 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/esm.mjs +16 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/index.js +24 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/lib/argument.js +149 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/lib/command.js +2509 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/lib/error.js +39 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/lib/help.js +520 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/lib/option.js +330 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/package-support.json +16 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/package.json +84 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/typings/esm.d.mts +3 -0
- package/examples/openapi/sample-openapi-cli/node_modules/commander/typings/index.d.ts +969 -0
- package/examples/openapi/sample-openapi-cli/package.json +16 -0
- package/examples/openapi/sample-openapi.yaml +67 -0
- package/examples/trello/trelloapi-agent/README.md +1 -0
- package/examples/trello/trelloapi-agent/agentbridge.manifest.json +1 -1
- package/examples/trello/trelloapi-agent/cli/commands/get-board.js +4 -0
- package/examples/trello/trelloapi-agent/cli/commands/list-board-lists.js +4 -0
- package/examples/trello/trelloapi-agent/cli/commands/list-list-cards.js +4 -0
- package/examples/trello/trelloapi-agent/cli/lib/client.js +174 -9
- package/examples/trello/trelloapi-cli/commands/get-board.js +4 -0
- package/examples/trello/trelloapi-cli/commands/list-board-lists.js +4 -0
- package/examples/trello/trelloapi-cli/commands/list-list-cards.js +4 -0
- package/examples/trello/trelloapi-cli/lib/client.js +174 -9
- package/package.json +8 -2
- package/src/commands/doctor.js +234 -0
- package/src/commands/generate.js +4 -8
- package/src/commands/init.js +154 -0
- package/src/commands/scaffold.js +9 -9
- package/src/commands/validate.js +6 -10
- package/src/index.js +21 -5
- package/src/lib/generate-cli.js +208 -15
- package/src/lib/generate-skill.js +24 -2
- package/src/lib/load-config.js +39 -3
- package/src/lib/openapi-to-config.js +314 -0
- package/src/lib/resolve-config-input.js +50 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sample-crm-api-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "sample-crm-api CLI generated by AgentBridge",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sample-crm-api": "./bin/sample-crm-api.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node ./bin/sample-crm-api.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.1.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: Sample CRM API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
servers:
|
|
6
|
+
- url: https://api.example-crm.com/v1
|
|
7
|
+
paths:
|
|
8
|
+
/contacts:
|
|
9
|
+
get:
|
|
10
|
+
summary: List contacts
|
|
11
|
+
operationId: listContacts
|
|
12
|
+
parameters:
|
|
13
|
+
- in: query
|
|
14
|
+
name: limit
|
|
15
|
+
required: false
|
|
16
|
+
schema:
|
|
17
|
+
type: integer
|
|
18
|
+
description: Max contacts to return
|
|
19
|
+
post:
|
|
20
|
+
summary: Create contact
|
|
21
|
+
operationId: createContact
|
|
22
|
+
requestBody:
|
|
23
|
+
required: true
|
|
24
|
+
content:
|
|
25
|
+
application/json:
|
|
26
|
+
schema:
|
|
27
|
+
type: object
|
|
28
|
+
required:
|
|
29
|
+
- name
|
|
30
|
+
properties:
|
|
31
|
+
name:
|
|
32
|
+
type: string
|
|
33
|
+
description: Contact full name
|
|
34
|
+
email:
|
|
35
|
+
type: string
|
|
36
|
+
description: Contact email
|
|
37
|
+
subscribed:
|
|
38
|
+
type: boolean
|
|
39
|
+
description: Newsletter subscription status
|
|
40
|
+
|
|
41
|
+
/contacts/{contactId}:
|
|
42
|
+
parameters:
|
|
43
|
+
- in: path
|
|
44
|
+
name: contactId
|
|
45
|
+
required: true
|
|
46
|
+
schema:
|
|
47
|
+
type: string
|
|
48
|
+
description: Contact ID
|
|
49
|
+
get:
|
|
50
|
+
summary: Get contact by ID
|
|
51
|
+
patch:
|
|
52
|
+
summary: Update contact fields
|
|
53
|
+
requestBody:
|
|
54
|
+
required: true
|
|
55
|
+
content:
|
|
56
|
+
application/json:
|
|
57
|
+
schema:
|
|
58
|
+
type: object
|
|
59
|
+
properties:
|
|
60
|
+
name:
|
|
61
|
+
type: string
|
|
62
|
+
description: Updated full name
|
|
63
|
+
email:
|
|
64
|
+
type: string
|
|
65
|
+
description: Updated email
|
|
66
|
+
delete:
|
|
67
|
+
summary: Delete contact
|
|
@@ -17,6 +17,10 @@ const command = {
|
|
|
17
17
|
|
|
18
18
|
async function getBoard(options) {
|
|
19
19
|
try {
|
|
20
|
+
if (command.method !== 'GET' && !options.yes) {
|
|
21
|
+
throw new Error('This operation changes state. Re-run with --yes to confirm.');
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const data = await request(command, options);
|
|
21
25
|
output.json(data, Boolean(options.pretty));
|
|
22
26
|
} catch (error) {
|
|
@@ -17,6 +17,10 @@ const command = {
|
|
|
17
17
|
|
|
18
18
|
async function listBoardLists(options) {
|
|
19
19
|
try {
|
|
20
|
+
if (command.method !== 'GET' && !options.yes) {
|
|
21
|
+
throw new Error('This operation changes state. Re-run with --yes to confirm.');
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const data = await request(command, options);
|
|
21
25
|
output.json(data, Boolean(options.pretty));
|
|
22
26
|
} catch (error) {
|
|
@@ -17,6 +17,10 @@ const command = {
|
|
|
17
17
|
|
|
18
18
|
async function listListCards(options) {
|
|
19
19
|
try {
|
|
20
|
+
if (command.method !== 'GET' && !options.yes) {
|
|
21
|
+
throw new Error('This operation changes state. Re-run with --yes to confirm.');
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const data = await request(command, options);
|
|
21
25
|
output.json(data, Boolean(options.pretty));
|
|
22
26
|
} catch (error) {
|
|
@@ -1,3 +1,161 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
function toKebab(name) {
|
|
4
|
+
return String(name)
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
9
|
+
.replace(/^-+|-+$/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function toCamelCase(name) {
|
|
13
|
+
return String(name).replace(/[-_]+([a-zA-Z0-9])/g, (_m, g1) => g1.toUpperCase());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readOption(options, name) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(options, name)) {
|
|
18
|
+
return options[name];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const camel = toCamelCase(name);
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(options, camel)) {
|
|
23
|
+
return options[camel];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function coerceValue(value, type) {
|
|
30
|
+
if (value === undefined || value === null || value === '') {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (type === 'number') {
|
|
35
|
+
const parsed = Number(value);
|
|
36
|
+
if (Number.isNaN(parsed)) {
|
|
37
|
+
throw new Error(`Expected number but received: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (type === 'boolean') {
|
|
43
|
+
if (typeof value === 'boolean') {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const normalized = String(value).toLowerCase();
|
|
48
|
+
if (normalized === 'true' || normalized === '1') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (normalized === 'false' || normalized === '0') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error(`Expected boolean but received: ${value}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return String(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseJsonText(raw, label) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(raw);
|
|
65
|
+
} catch (_error) {
|
|
66
|
+
throw new Error(`Invalid JSON for ${label}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseJsonBody(rawBody) {
|
|
71
|
+
if (rawBody === undefined || rawBody === null || rawBody === '') {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return parseJsonText(rawBody, '--body');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseBodyFromStdin(enabled) {
|
|
79
|
+
if (!enabled) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
84
|
+
if (!raw) {
|
|
85
|
+
throw new Error('Expected JSON on stdin because --body-stdin was provided');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return parseJsonText(raw, '--body-stdin');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildRequestBody(command, options) {
|
|
92
|
+
const requestBody = command.requestBody || null;
|
|
93
|
+
if (!requestBody) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const direct = parseJsonBody(readOption(options, 'body'));
|
|
98
|
+
const stdin = parseBodyFromStdin(Boolean(readOption(options, 'body-stdin')));
|
|
99
|
+
|
|
100
|
+
if (direct !== null && stdin !== null) {
|
|
101
|
+
throw new Error('Use either --body or --body-stdin, not both');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const properties = requestBody.properties || {};
|
|
105
|
+
const hasBodyProps = Object.keys(properties).length > 0;
|
|
106
|
+
|
|
107
|
+
let payload = direct !== null ? direct : stdin !== null ? stdin : hasBodyProps ? {} : null;
|
|
108
|
+
if (payload !== null && (typeof payload !== 'object' || Array.isArray(payload))) {
|
|
109
|
+
throw new Error('Request body must be a JSON object');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (payload === null && requestBody.required) {
|
|
113
|
+
throw new Error('Missing required request body. Provide --body, --body-stdin, or body field flags.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let hadBodyFlag = false;
|
|
117
|
+
Object.entries(properties).forEach(([propName, schema]) => {
|
|
118
|
+
const optionName = `body-${toKebab(propName)}`;
|
|
119
|
+
const raw = readOption(options, optionName);
|
|
120
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (payload === null) {
|
|
125
|
+
payload = {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
payload[propName] = coerceValue(raw, schema.type);
|
|
129
|
+
hadBodyFlag = true;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (hasBodyProps) {
|
|
133
|
+
if (payload === null) {
|
|
134
|
+
payload = {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Object.entries(properties).forEach(([propName, schema]) => {
|
|
138
|
+
if (!schema.required) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!Object.prototype.hasOwnProperty.call(payload, propName) || payload[propName] === undefined || payload[propName] === null || payload[propName] === '') {
|
|
143
|
+
const optionName = `--body-${toKebab(propName)}`;
|
|
144
|
+
throw new Error(`Missing required request body field: ${optionName}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (payload !== null) {
|
|
150
|
+
const isEmptyObject = typeof payload === 'object' && !Array.isArray(payload) && Object.keys(payload).length === 0;
|
|
151
|
+
if (isEmptyObject && !requestBody.required && !hadBodyFlag && direct === null && stdin === null) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return payload;
|
|
157
|
+
}
|
|
158
|
+
|
|
1
159
|
async function request(command, options) {
|
|
2
160
|
const auth = {
|
|
3
161
|
"credentials": [
|
|
@@ -38,16 +196,17 @@ async function request(command, options) {
|
|
|
38
196
|
});
|
|
39
197
|
|
|
40
198
|
Object.entries(commandParams).forEach(([name, schema]) => {
|
|
41
|
-
const
|
|
199
|
+
const raw = readOption(options, name);
|
|
42
200
|
|
|
43
|
-
if ((
|
|
201
|
+
if ((raw === undefined || raw === null || raw === '') && schema.required) {
|
|
44
202
|
throw new Error(`Missing required parameter: --${name}`);
|
|
45
203
|
}
|
|
46
204
|
|
|
47
|
-
if (
|
|
205
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
48
206
|
return;
|
|
49
207
|
}
|
|
50
208
|
|
|
209
|
+
const value = coerceValue(raw, schema.type);
|
|
51
210
|
const token = `{${name}}`;
|
|
52
211
|
|
|
53
212
|
if (resolvedPath.includes(token)) {
|
|
@@ -60,29 +219,35 @@ async function request(command, options) {
|
|
|
60
219
|
|
|
61
220
|
const query = params.toString();
|
|
62
221
|
const url = 'https://api.trello.com/1' + resolvedPath + (query ? '?' + query : '');
|
|
222
|
+
const requestBody = buildRequestBody(command, options);
|
|
223
|
+
|
|
224
|
+
if (requestBody !== null) {
|
|
225
|
+
headers['content-type'] = 'application/json';
|
|
226
|
+
}
|
|
63
227
|
|
|
64
228
|
const response = await fetch(url, {
|
|
65
229
|
method: command.method,
|
|
66
|
-
headers
|
|
230
|
+
headers,
|
|
231
|
+
body: requestBody !== null ? JSON.stringify(requestBody) : undefined
|
|
67
232
|
});
|
|
68
233
|
|
|
69
234
|
const text = await response.text();
|
|
70
|
-
let
|
|
235
|
+
let responseBody = text;
|
|
71
236
|
|
|
72
237
|
try {
|
|
73
|
-
|
|
238
|
+
responseBody = text ? JSON.parse(text) : null;
|
|
74
239
|
} catch (_err) {
|
|
75
|
-
|
|
240
|
+
responseBody = text;
|
|
76
241
|
}
|
|
77
242
|
|
|
78
243
|
if (!response.ok) {
|
|
79
244
|
const error = new Error(`HTTP ${response.status}`);
|
|
80
245
|
error.statusCode = response.status;
|
|
81
|
-
error.responseBody =
|
|
246
|
+
error.responseBody = responseBody;
|
|
82
247
|
throw error;
|
|
83
248
|
}
|
|
84
249
|
|
|
85
|
-
return
|
|
250
|
+
return responseBody;
|
|
86
251
|
}
|
|
87
252
|
|
|
88
253
|
module.exports = {
|
|
@@ -17,6 +17,10 @@ const command = {
|
|
|
17
17
|
|
|
18
18
|
async function getBoard(options) {
|
|
19
19
|
try {
|
|
20
|
+
if (command.method !== 'GET' && !options.yes) {
|
|
21
|
+
throw new Error('This operation changes state. Re-run with --yes to confirm.');
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const data = await request(command, options);
|
|
21
25
|
output.json(data, Boolean(options.pretty));
|
|
22
26
|
} catch (error) {
|
|
@@ -17,6 +17,10 @@ const command = {
|
|
|
17
17
|
|
|
18
18
|
async function listBoardLists(options) {
|
|
19
19
|
try {
|
|
20
|
+
if (command.method !== 'GET' && !options.yes) {
|
|
21
|
+
throw new Error('This operation changes state. Re-run with --yes to confirm.');
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const data = await request(command, options);
|
|
21
25
|
output.json(data, Boolean(options.pretty));
|
|
22
26
|
} catch (error) {
|
|
@@ -17,6 +17,10 @@ const command = {
|
|
|
17
17
|
|
|
18
18
|
async function listListCards(options) {
|
|
19
19
|
try {
|
|
20
|
+
if (command.method !== 'GET' && !options.yes) {
|
|
21
|
+
throw new Error('This operation changes state. Re-run with --yes to confirm.');
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const data = await request(command, options);
|
|
21
25
|
output.json(data, Boolean(options.pretty));
|
|
22
26
|
} catch (error) {
|
|
@@ -1,3 +1,161 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
function toKebab(name) {
|
|
4
|
+
return String(name)
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
9
|
+
.replace(/^-+|-+$/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function toCamelCase(name) {
|
|
13
|
+
return String(name).replace(/[-_]+([a-zA-Z0-9])/g, (_m, g1) => g1.toUpperCase());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readOption(options, name) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(options, name)) {
|
|
18
|
+
return options[name];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const camel = toCamelCase(name);
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(options, camel)) {
|
|
23
|
+
return options[camel];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function coerceValue(value, type) {
|
|
30
|
+
if (value === undefined || value === null || value === '') {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (type === 'number') {
|
|
35
|
+
const parsed = Number(value);
|
|
36
|
+
if (Number.isNaN(parsed)) {
|
|
37
|
+
throw new Error(`Expected number but received: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (type === 'boolean') {
|
|
43
|
+
if (typeof value === 'boolean') {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const normalized = String(value).toLowerCase();
|
|
48
|
+
if (normalized === 'true' || normalized === '1') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (normalized === 'false' || normalized === '0') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error(`Expected boolean but received: ${value}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return String(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseJsonText(raw, label) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(raw);
|
|
65
|
+
} catch (_error) {
|
|
66
|
+
throw new Error(`Invalid JSON for ${label}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseJsonBody(rawBody) {
|
|
71
|
+
if (rawBody === undefined || rawBody === null || rawBody === '') {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return parseJsonText(rawBody, '--body');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseBodyFromStdin(enabled) {
|
|
79
|
+
if (!enabled) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
84
|
+
if (!raw) {
|
|
85
|
+
throw new Error('Expected JSON on stdin because --body-stdin was provided');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return parseJsonText(raw, '--body-stdin');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildRequestBody(command, options) {
|
|
92
|
+
const requestBody = command.requestBody || null;
|
|
93
|
+
if (!requestBody) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const direct = parseJsonBody(readOption(options, 'body'));
|
|
98
|
+
const stdin = parseBodyFromStdin(Boolean(readOption(options, 'body-stdin')));
|
|
99
|
+
|
|
100
|
+
if (direct !== null && stdin !== null) {
|
|
101
|
+
throw new Error('Use either --body or --body-stdin, not both');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const properties = requestBody.properties || {};
|
|
105
|
+
const hasBodyProps = Object.keys(properties).length > 0;
|
|
106
|
+
|
|
107
|
+
let payload = direct !== null ? direct : stdin !== null ? stdin : hasBodyProps ? {} : null;
|
|
108
|
+
if (payload !== null && (typeof payload !== 'object' || Array.isArray(payload))) {
|
|
109
|
+
throw new Error('Request body must be a JSON object');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (payload === null && requestBody.required) {
|
|
113
|
+
throw new Error('Missing required request body. Provide --body, --body-stdin, or body field flags.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let hadBodyFlag = false;
|
|
117
|
+
Object.entries(properties).forEach(([propName, schema]) => {
|
|
118
|
+
const optionName = `body-${toKebab(propName)}`;
|
|
119
|
+
const raw = readOption(options, optionName);
|
|
120
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (payload === null) {
|
|
125
|
+
payload = {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
payload[propName] = coerceValue(raw, schema.type);
|
|
129
|
+
hadBodyFlag = true;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (hasBodyProps) {
|
|
133
|
+
if (payload === null) {
|
|
134
|
+
payload = {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Object.entries(properties).forEach(([propName, schema]) => {
|
|
138
|
+
if (!schema.required) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!Object.prototype.hasOwnProperty.call(payload, propName) || payload[propName] === undefined || payload[propName] === null || payload[propName] === '') {
|
|
143
|
+
const optionName = `--body-${toKebab(propName)}`;
|
|
144
|
+
throw new Error(`Missing required request body field: ${optionName}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (payload !== null) {
|
|
150
|
+
const isEmptyObject = typeof payload === 'object' && !Array.isArray(payload) && Object.keys(payload).length === 0;
|
|
151
|
+
if (isEmptyObject && !requestBody.required && !hadBodyFlag && direct === null && stdin === null) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return payload;
|
|
157
|
+
}
|
|
158
|
+
|
|
1
159
|
async function request(command, options) {
|
|
2
160
|
const auth = {
|
|
3
161
|
"credentials": [
|
|
@@ -38,16 +196,17 @@ async function request(command, options) {
|
|
|
38
196
|
});
|
|
39
197
|
|
|
40
198
|
Object.entries(commandParams).forEach(([name, schema]) => {
|
|
41
|
-
const
|
|
199
|
+
const raw = readOption(options, name);
|
|
42
200
|
|
|
43
|
-
if ((
|
|
201
|
+
if ((raw === undefined || raw === null || raw === '') && schema.required) {
|
|
44
202
|
throw new Error(`Missing required parameter: --${name}`);
|
|
45
203
|
}
|
|
46
204
|
|
|
47
|
-
if (
|
|
205
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
48
206
|
return;
|
|
49
207
|
}
|
|
50
208
|
|
|
209
|
+
const value = coerceValue(raw, schema.type);
|
|
51
210
|
const token = `{${name}}`;
|
|
52
211
|
|
|
53
212
|
if (resolvedPath.includes(token)) {
|
|
@@ -60,29 +219,35 @@ async function request(command, options) {
|
|
|
60
219
|
|
|
61
220
|
const query = params.toString();
|
|
62
221
|
const url = 'https://api.trello.com/1' + resolvedPath + (query ? '?' + query : '');
|
|
222
|
+
const requestBody = buildRequestBody(command, options);
|
|
223
|
+
|
|
224
|
+
if (requestBody !== null) {
|
|
225
|
+
headers['content-type'] = 'application/json';
|
|
226
|
+
}
|
|
63
227
|
|
|
64
228
|
const response = await fetch(url, {
|
|
65
229
|
method: command.method,
|
|
66
|
-
headers
|
|
230
|
+
headers,
|
|
231
|
+
body: requestBody !== null ? JSON.stringify(requestBody) : undefined
|
|
67
232
|
});
|
|
68
233
|
|
|
69
234
|
const text = await response.text();
|
|
70
|
-
let
|
|
235
|
+
let responseBody = text;
|
|
71
236
|
|
|
72
237
|
try {
|
|
73
|
-
|
|
238
|
+
responseBody = text ? JSON.parse(text) : null;
|
|
74
239
|
} catch (_err) {
|
|
75
|
-
|
|
240
|
+
responseBody = text;
|
|
76
241
|
}
|
|
77
242
|
|
|
78
243
|
if (!response.ok) {
|
|
79
244
|
const error = new Error(`HTTP ${response.status}`);
|
|
80
245
|
error.statusCode = response.status;
|
|
81
|
-
error.responseBody =
|
|
246
|
+
error.responseBody = responseBody;
|
|
82
247
|
throw error;
|
|
83
248
|
}
|
|
84
249
|
|
|
85
|
-
return
|
|
250
|
+
return responseBody;
|
|
86
251
|
}
|
|
87
252
|
|
|
88
253
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-to-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Generate AI-agent-friendly CLIs, skills, and manifests from API configs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -36,11 +36,17 @@
|
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"js-yaml": "^4.1.0"
|
|
41
|
+
},
|
|
39
42
|
"scripts": {
|
|
40
43
|
"validate:trello": "node ./bin/api-to-cli.js validate --config ./examples/trello/api-to-cli.config.js",
|
|
41
44
|
"generate:trello": "node ./bin/api-to-cli.js generate --config ./examples/trello/api-to-cli.config.js --output ./examples/trello/trelloapi-cli",
|
|
42
45
|
"scaffold:trello": "node ./bin/api-to-cli.js scaffold --config ./examples/trello/api-to-cli.config.js --output ./examples/trello/trelloapi-agent",
|
|
43
|
-
"
|
|
46
|
+
"validate:openapi": "node ./bin/api-to-cli.js validate --spec ./examples/openapi/sample-openapi.yaml",
|
|
47
|
+
"generate:openapi": "node ./bin/api-to-cli.js generate --spec ./examples/openapi/sample-openapi.yaml --output ./examples/openapi/sample-openapi-cli",
|
|
48
|
+
"scaffold:openapi": "node ./bin/api-to-cli.js scaffold --spec ./examples/openapi/sample-openapi.yaml --output ./examples/openapi/sample-openapi-agent",
|
|
49
|
+
"test:smoke": "npm run validate:trello && npm run generate:trello && npm run scaffold:trello && npm run validate:openapi && npm run generate:openapi && npm run scaffold:openapi",
|
|
44
50
|
"pack:check": "npm pack --dry-run"
|
|
45
51
|
}
|
|
46
52
|
}
|