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,244 @@
|
|
|
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
|
+
|
|
159
|
+
async function request(command, options) {
|
|
160
|
+
const auth = {
|
|
161
|
+
"credentials": []
|
|
162
|
+
};
|
|
163
|
+
const params = new URLSearchParams();
|
|
164
|
+
const commandParams = command.params || {};
|
|
165
|
+
let resolvedPath = command.path;
|
|
166
|
+
const headers = {
|
|
167
|
+
accept: 'application/json'
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
(auth.credentials || []).forEach((credential) => {
|
|
171
|
+
const envValue = process.env[credential.envVar];
|
|
172
|
+
|
|
173
|
+
if (!envValue) {
|
|
174
|
+
throw new Error(`Missing required auth environment variable: ${credential.envVar}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const authValue = credential.prefix ? `${credential.prefix}${envValue}` : envValue;
|
|
178
|
+
|
|
179
|
+
if (credential.in === 'header') {
|
|
180
|
+
headers[credential.name] = authValue;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
params.append(credential.name, authValue);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
Object.entries(commandParams).forEach(([name, schema]) => {
|
|
188
|
+
const raw = readOption(options, name);
|
|
189
|
+
|
|
190
|
+
if ((raw === undefined || raw === null || raw === '') && schema.required) {
|
|
191
|
+
throw new Error(`Missing required parameter: --${name}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const value = coerceValue(raw, schema.type);
|
|
199
|
+
const token = `{${name}}`;
|
|
200
|
+
|
|
201
|
+
if (resolvedPath.includes(token)) {
|
|
202
|
+
resolvedPath = resolvedPath.replaceAll(token, encodeURIComponent(String(value)));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
params.append(name, String(value));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const query = params.toString();
|
|
210
|
+
const url = 'https://api.example-crm.com/v1' + resolvedPath + (query ? '?' + query : '');
|
|
211
|
+
const requestBody = buildRequestBody(command, options);
|
|
212
|
+
|
|
213
|
+
if (requestBody !== null) {
|
|
214
|
+
headers['content-type'] = 'application/json';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const response = await fetch(url, {
|
|
218
|
+
method: command.method,
|
|
219
|
+
headers,
|
|
220
|
+
body: requestBody !== null ? JSON.stringify(requestBody) : undefined
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const text = await response.text();
|
|
224
|
+
let responseBody = text;
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
responseBody = text ? JSON.parse(text) : null;
|
|
228
|
+
} catch (_err) {
|
|
229
|
+
responseBody = text;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
const error = new Error(`HTTP ${response.status}`);
|
|
234
|
+
error.statusCode = response.status;
|
|
235
|
+
error.responseBody = responseBody;
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return responseBody;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
request
|
|
244
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function json(data, pretty) {
|
|
2
|
+
const text = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
3
|
+
process.stdout.write(text + '\n');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function error(payload, pretty) {
|
|
7
|
+
const envelope = {
|
|
8
|
+
error: true,
|
|
9
|
+
code: payload.code || 'REQUEST_FAILED',
|
|
10
|
+
message: payload.message || 'Request failed',
|
|
11
|
+
details: payload.details || {}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const text = pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope);
|
|
15
|
+
process.stderr.write(text + '\n');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
json,
|
|
20
|
+
error
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 3,
|
|
3
|
+
"requires": true,
|
|
4
|
+
"packages": {
|
|
5
|
+
"node_modules/commander": {
|
|
6
|
+
"version": "12.1.0",
|
|
7
|
+
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
|
8
|
+
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|