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,234 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { loadConfig } = require('../lib/load-config');
|
|
4
|
+
const { loadConfigFromOpenApi } = require('../lib/openapi-to-config');
|
|
5
|
+
|
|
6
|
+
const COMMON_SPEC_PATHS = [
|
|
7
|
+
'/openapi.json',
|
|
8
|
+
'/openapi.yaml',
|
|
9
|
+
'/openapi.yml',
|
|
10
|
+
'/swagger.json',
|
|
11
|
+
'/v1/openapi.json',
|
|
12
|
+
'/v1/swagger.json',
|
|
13
|
+
'/.well-known/openapi.json'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function compareVersions(a, b) {
|
|
17
|
+
const pa = a.split('.').map((n) => Number(n));
|
|
18
|
+
const pb = b.split('.').map((n) => Number(n));
|
|
19
|
+
const len = Math.max(pa.length, pb.length);
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < len; i += 1) {
|
|
22
|
+
const da = pa[i] || 0;
|
|
23
|
+
const db = pb[i] || 0;
|
|
24
|
+
if (da > db) {
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
if (da < db) {
|
|
28
|
+
return -1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeCheck(name, ok, details, fix) {
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
ok,
|
|
39
|
+
details,
|
|
40
|
+
fix: fix || null
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isHttpUrl(value) {
|
|
45
|
+
return /^https?:\/\//i.test(String(value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeBaseUrl(url) {
|
|
49
|
+
return String(url).replace(/\/+$/, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildSpecCandidates(baseUrl) {
|
|
53
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
54
|
+
const directLooksLikeSpec = /(openapi|swagger)\.(json|ya?ml)$/i.test(normalized);
|
|
55
|
+
|
|
56
|
+
const candidates = [normalized];
|
|
57
|
+
if (!directLooksLikeSpec) {
|
|
58
|
+
COMMON_SPEC_PATHS.forEach((specPath) => {
|
|
59
|
+
candidates.push(`${normalized}${specPath}`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [...new Set(candidates)];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function fetchWithTimeout(url, timeoutMs) {
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
71
|
+
return response;
|
|
72
|
+
} finally {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function doctor(flags) {
|
|
78
|
+
const checks = [];
|
|
79
|
+
|
|
80
|
+
const nodeVersion = process.versions.node;
|
|
81
|
+
const nodeOk = compareVersions(nodeVersion, '18.0.0') >= 0;
|
|
82
|
+
checks.push(
|
|
83
|
+
makeCheck(
|
|
84
|
+
'node-version',
|
|
85
|
+
nodeOk,
|
|
86
|
+
`Detected Node.js ${nodeVersion}`,
|
|
87
|
+
nodeOk ? null : 'Upgrade Node.js to v18 or newer.'
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const fetchOk = typeof fetch === 'function';
|
|
92
|
+
checks.push(
|
|
93
|
+
makeCheck(
|
|
94
|
+
'fetch-availability',
|
|
95
|
+
fetchOk,
|
|
96
|
+
fetchOk ? 'Global fetch is available.' : 'Global fetch is not available.',
|
|
97
|
+
fetchOk ? null : 'Use Node.js v18+ where fetch is built in.'
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const cwd = process.cwd();
|
|
102
|
+
const cwdWritable = (() => {
|
|
103
|
+
try {
|
|
104
|
+
fs.accessSync(cwd, fs.constants.W_OK);
|
|
105
|
+
return true;
|
|
106
|
+
} catch (_error) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
checks.push(
|
|
111
|
+
makeCheck(
|
|
112
|
+
'cwd-writable',
|
|
113
|
+
cwdWritable,
|
|
114
|
+
`Working directory: ${cwd}`,
|
|
115
|
+
cwdWritable ? null : 'Use a directory where you have write permissions.'
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (flags.config) {
|
|
120
|
+
const configPath = path.resolve(process.cwd(), String(flags.config));
|
|
121
|
+
try {
|
|
122
|
+
const config = loadConfig(configPath);
|
|
123
|
+
checks.push(
|
|
124
|
+
makeCheck(
|
|
125
|
+
'config-validation',
|
|
126
|
+
true,
|
|
127
|
+
`Config valid: ${configPath} (${config.commands.length} commands)`,
|
|
128
|
+
null
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
checks.push(
|
|
133
|
+
makeCheck(
|
|
134
|
+
'config-validation',
|
|
135
|
+
false,
|
|
136
|
+
`Config invalid: ${configPath} (${error.message})`,
|
|
137
|
+
'Fix the config schema and re-run doctor.'
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (flags.spec) {
|
|
144
|
+
const specInput = String(flags.spec);
|
|
145
|
+
try {
|
|
146
|
+
const config = await loadConfigFromOpenApi({
|
|
147
|
+
specInput,
|
|
148
|
+
name: flags.name ? String(flags.name) : undefined,
|
|
149
|
+
version: flags.version ? String(flags.version) : undefined
|
|
150
|
+
});
|
|
151
|
+
checks.push(
|
|
152
|
+
makeCheck(
|
|
153
|
+
'spec-parse',
|
|
154
|
+
true,
|
|
155
|
+
`Spec parsed: ${specInput} (${config.commands.length} commands)`
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
checks.push(
|
|
160
|
+
makeCheck(
|
|
161
|
+
'spec-parse',
|
|
162
|
+
false,
|
|
163
|
+
`Spec parse failed: ${specInput} (${error.message})`,
|
|
164
|
+
'Ensure spec is valid OpenAPI 3.x JSON/YAML and accessible.'
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (flags.url) {
|
|
171
|
+
const baseUrl = String(flags.url);
|
|
172
|
+
const isUrl = isHttpUrl(baseUrl);
|
|
173
|
+
|
|
174
|
+
if (!isUrl) {
|
|
175
|
+
checks.push(
|
|
176
|
+
makeCheck(
|
|
177
|
+
'url-format',
|
|
178
|
+
false,
|
|
179
|
+
`URL is not HTTP(S): ${baseUrl}`,
|
|
180
|
+
'Pass a full base URL like https://api.example.com'
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
checks.push(makeCheck('url-format', true, `Valid HTTP(S) URL: ${baseUrl}`));
|
|
185
|
+
const candidates = buildSpecCandidates(baseUrl);
|
|
186
|
+
let discovered = null;
|
|
187
|
+
|
|
188
|
+
for (const candidate of candidates) {
|
|
189
|
+
try {
|
|
190
|
+
const response = await fetchWithTimeout(candidate, 4000);
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const text = await response.text();
|
|
196
|
+
if (/("openapi"\s*:|^openapi\s*:)/m.test(text)) {
|
|
197
|
+
discovered = candidate;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
} catch (_error) {
|
|
201
|
+
// continue checking candidates
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
checks.push(
|
|
206
|
+
makeCheck(
|
|
207
|
+
'openapi-discovery',
|
|
208
|
+
Boolean(discovered),
|
|
209
|
+
discovered
|
|
210
|
+
? `Discovered OpenAPI candidate: ${discovered}`
|
|
211
|
+
: `No OpenAPI discovered from ${baseUrl}`,
|
|
212
|
+
discovered ? null : 'Run init anyway to generate starter config, then edit endpoints manually.'
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const ok = checks.every((check) => check.ok);
|
|
219
|
+
|
|
220
|
+
console.log(
|
|
221
|
+
JSON.stringify({
|
|
222
|
+
ok,
|
|
223
|
+
command: 'doctor',
|
|
224
|
+
checks,
|
|
225
|
+
next: ok
|
|
226
|
+
? 'Environment looks good. Run generate or scaffold.'
|
|
227
|
+
: 'Address failed checks and rerun doctor.'
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
doctor
|
|
234
|
+
};
|
package/src/commands/generate.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const { loadConfig } = require('../lib/load-config');
|
|
3
2
|
const { generateCliProject } = require('../lib/generate-cli');
|
|
3
|
+
const { resolveConfigInput } = require('../lib/resolve-config-input');
|
|
4
4
|
|
|
5
5
|
async function generate(flags) {
|
|
6
|
-
if (!flags.config) {
|
|
7
|
-
throw new Error('Missing required flag: --config <path>');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
6
|
if (!flags.output) {
|
|
11
7
|
throw new Error('Missing required flag: --output <dir>');
|
|
12
8
|
}
|
|
13
9
|
|
|
14
|
-
const configPath = path.resolve(process.cwd(), String(flags.config));
|
|
15
10
|
const outputPath = path.resolve(process.cwd(), String(flags.output));
|
|
16
|
-
|
|
17
|
-
const config = loadConfig(configPath);
|
|
11
|
+
const { config, source } = await resolveConfigInput(flags);
|
|
18
12
|
|
|
19
13
|
generateCliProject({
|
|
20
14
|
config,
|
|
@@ -26,6 +20,8 @@ async function generate(flags) {
|
|
|
26
20
|
ok: true,
|
|
27
21
|
command: 'generate',
|
|
28
22
|
outputPath,
|
|
23
|
+
inputType: source.type,
|
|
24
|
+
input: source.value,
|
|
29
25
|
generatedCommands: config.commands.map((command) => command.name)
|
|
30
26
|
})
|
|
31
27
|
);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { loadConfigFromOpenApi } = require('../lib/openapi-to-config');
|
|
4
|
+
|
|
5
|
+
const COMMON_SPEC_PATHS = [
|
|
6
|
+
'/openapi.json',
|
|
7
|
+
'/openapi.yaml',
|
|
8
|
+
'/openapi.yml',
|
|
9
|
+
'/swagger.json',
|
|
10
|
+
'/v1/openapi.json',
|
|
11
|
+
'/v1/swagger.json',
|
|
12
|
+
'/.well-known/openapi.json'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function normalizeBaseUrl(url) {
|
|
16
|
+
return String(url).replace(/\/+$/, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function inferNameFromUrl(urlValue) {
|
|
20
|
+
try {
|
|
21
|
+
const host = new URL(urlValue).hostname;
|
|
22
|
+
const label = host.split('.').filter(Boolean)[0] || 'myapi';
|
|
23
|
+
return label.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase();
|
|
24
|
+
} catch (_error) {
|
|
25
|
+
return 'myapi';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildSpecCandidates(baseUrl) {
|
|
30
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
31
|
+
const directLooksLikeSpec = /(openapi|swagger)\.(json|ya?ml)$/i.test(normalized);
|
|
32
|
+
|
|
33
|
+
const candidates = [normalized];
|
|
34
|
+
if (!directLooksLikeSpec) {
|
|
35
|
+
COMMON_SPEC_PATHS.forEach((specPath) => {
|
|
36
|
+
candidates.push(`${normalized}${specPath}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [...new Set(candidates)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderConfig(config) {
|
|
44
|
+
return `module.exports = ${JSON.stringify(config, null, 2)};\n`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderStarterConfig({ name, version, apiBase }) {
|
|
48
|
+
const starter = {
|
|
49
|
+
name,
|
|
50
|
+
version,
|
|
51
|
+
apiBase,
|
|
52
|
+
auth: {
|
|
53
|
+
credentials: [
|
|
54
|
+
{
|
|
55
|
+
envVar: `${name.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_API_KEY`,
|
|
56
|
+
in: 'header',
|
|
57
|
+
name: 'Authorization',
|
|
58
|
+
prefix: 'Bearer '
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
commands: [
|
|
63
|
+
{
|
|
64
|
+
name: 'health',
|
|
65
|
+
description: 'Health check endpoint (edit path/method as needed)',
|
|
66
|
+
method: 'GET',
|
|
67
|
+
path: '/health',
|
|
68
|
+
params: {}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return renderConfig(starter);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function tryOpenApiCandidates({ candidates, name, version }) {
|
|
77
|
+
const failures = [];
|
|
78
|
+
|
|
79
|
+
for (const candidate of candidates) {
|
|
80
|
+
try {
|
|
81
|
+
const config = await loadConfigFromOpenApi({
|
|
82
|
+
specInput: candidate,
|
|
83
|
+
name,
|
|
84
|
+
version
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
config,
|
|
89
|
+
discoveredSpec: candidate,
|
|
90
|
+
failures
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
failures.push({
|
|
94
|
+
candidate,
|
|
95
|
+
message: error.message
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
config: null,
|
|
102
|
+
discoveredSpec: null,
|
|
103
|
+
failures
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function init(flags) {
|
|
108
|
+
if (!flags.url) {
|
|
109
|
+
throw new Error('Missing required flag: --url <api-base-url>');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const baseUrl = String(flags.url);
|
|
113
|
+
const outputPath = path.resolve(process.cwd(), String(flags.output || './api-to-cli.config.js'));
|
|
114
|
+
const name = String(flags.name || inferNameFromUrl(baseUrl));
|
|
115
|
+
const version = String(flags.version || '1.0.0');
|
|
116
|
+
|
|
117
|
+
const candidates = buildSpecCandidates(baseUrl);
|
|
118
|
+
const result = await tryOpenApiCandidates({ candidates, name, version });
|
|
119
|
+
|
|
120
|
+
let mode = 'starter';
|
|
121
|
+
let discoveredSpec = null;
|
|
122
|
+
let fileText = renderStarterConfig({
|
|
123
|
+
name,
|
|
124
|
+
version,
|
|
125
|
+
apiBase: normalizeBaseUrl(baseUrl)
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (result.config) {
|
|
129
|
+
mode = 'openapi';
|
|
130
|
+
discoveredSpec = result.discoveredSpec;
|
|
131
|
+
fileText = renderConfig(result.config);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(outputPath, fileText, 'utf8');
|
|
135
|
+
|
|
136
|
+
console.log(
|
|
137
|
+
JSON.stringify({
|
|
138
|
+
ok: true,
|
|
139
|
+
command: 'init',
|
|
140
|
+
mode,
|
|
141
|
+
baseUrl: normalizeBaseUrl(baseUrl),
|
|
142
|
+
outputPath,
|
|
143
|
+
discoveredSpec,
|
|
144
|
+
attemptedSpecCandidates: candidates,
|
|
145
|
+
hint: mode === 'openapi'
|
|
146
|
+
? 'OpenAPI discovered. You can run: api-to-cli generate --config <outputPath> --output <dir>'
|
|
147
|
+
: 'No OpenAPI discovered. Edit the generated config and then run generate.'
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
init
|
|
154
|
+
};
|
package/src/commands/scaffold.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { loadConfig } = require('../lib/load-config');
|
|
4
3
|
const { generateCliProject } = require('../lib/generate-cli');
|
|
5
4
|
const { writeSkillPackage } = require('../lib/generate-skill');
|
|
6
5
|
const { buildManifest, writeManifest } = require('../lib/generate-manifest');
|
|
6
|
+
const { resolveConfigInput } = require('../lib/resolve-config-input');
|
|
7
7
|
|
|
8
8
|
function hasOwn(obj, key) {
|
|
9
9
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
@@ -31,7 +31,8 @@ function writeScaffoldReadme(outputPath, data) {
|
|
|
31
31
|
'# AgentBridge Scaffold Output',
|
|
32
32
|
'',
|
|
33
33
|
'## Contents',
|
|
34
|
-
`- CLI project: ${data.cliProjectPath}
|
|
34
|
+
`- CLI project: ${data.cliProjectPath}`,
|
|
35
|
+
`- Input source: ${data.inputType} (${data.inputValue})`
|
|
35
36
|
];
|
|
36
37
|
|
|
37
38
|
if (data.skillPath) {
|
|
@@ -48,10 +49,6 @@ function writeScaffoldReadme(outputPath, data) {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async function scaffold(flags) {
|
|
51
|
-
if (!flags.config) {
|
|
52
|
-
throw new Error('Missing required flag: --config <path>');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
52
|
if (!flags.output) {
|
|
56
53
|
throw new Error('Missing required flag: --output <dir>');
|
|
57
54
|
}
|
|
@@ -61,11 +58,10 @@ async function scaffold(flags) {
|
|
|
61
58
|
const skillRelativePath = './skill/SKILL.md';
|
|
62
59
|
const manifestRelativePath = './agentbridge.manifest.json';
|
|
63
60
|
|
|
64
|
-
const configPath = path.resolve(process.cwd(), String(flags.config));
|
|
65
61
|
const outputPath = path.resolve(process.cwd(), String(flags.output));
|
|
66
62
|
const cliProjectPath = path.join(outputPath, 'cli');
|
|
63
|
+
const { config, source } = await resolveConfigInput(flags);
|
|
67
64
|
|
|
68
|
-
const config = loadConfig(configPath);
|
|
69
65
|
fs.mkdirSync(outputPath, { recursive: true });
|
|
70
66
|
|
|
71
67
|
generateCliProject({
|
|
@@ -89,7 +85,9 @@ async function scaffold(flags) {
|
|
|
89
85
|
writeScaffoldReadme(outputPath, {
|
|
90
86
|
cliProjectPath: cliRelativePath,
|
|
91
87
|
skillPath,
|
|
92
|
-
manifestPath
|
|
88
|
+
manifestPath,
|
|
89
|
+
inputType: source.type,
|
|
90
|
+
inputValue: source.value
|
|
93
91
|
});
|
|
94
92
|
|
|
95
93
|
console.log(
|
|
@@ -97,6 +95,8 @@ async function scaffold(flags) {
|
|
|
97
95
|
ok: true,
|
|
98
96
|
command: 'scaffold',
|
|
99
97
|
outputPath,
|
|
98
|
+
inputType: source.type,
|
|
99
|
+
input: source.value,
|
|
100
100
|
cliProjectPath: cliRelativePath,
|
|
101
101
|
skillPath,
|
|
102
102
|
manifestPath,
|
package/src/commands/validate.js
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
const
|
|
2
|
-
const { loadConfig } = require('../lib/load-config');
|
|
1
|
+
const { resolveConfigInput } = require('../lib/resolve-config-input');
|
|
3
2
|
|
|
4
3
|
async function validate(flags) {
|
|
5
|
-
|
|
6
|
-
throw new Error('Missing required flag: --config <path>');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const configPath = path.resolve(process.cwd(), String(flags.config));
|
|
10
|
-
const config = loadConfig(configPath);
|
|
4
|
+
const { config, source } = await resolveConfigInput(flags);
|
|
11
5
|
|
|
12
6
|
console.log(
|
|
13
7
|
JSON.stringify({
|
|
14
8
|
ok: true,
|
|
15
9
|
command: 'validate',
|
|
16
|
-
|
|
10
|
+
inputType: source.type,
|
|
11
|
+
input: source.value,
|
|
17
12
|
summary: {
|
|
18
13
|
name: config.name,
|
|
19
14
|
version: config.version,
|
|
20
15
|
apiBase: config.apiBase,
|
|
21
16
|
commandCount: config.commands.length,
|
|
22
|
-
hasAuth: Boolean(config.auth && config.auth.credentials && config.auth.credentials.length)
|
|
17
|
+
hasAuth: Boolean(config.auth && config.auth.credentials && config.auth.credentials.length),
|
|
18
|
+
methods: [...new Set(config.commands.map((command) => command.method))]
|
|
23
19
|
}
|
|
24
20
|
})
|
|
25
21
|
);
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
const { doctor } = require('./commands/doctor');
|
|
1
2
|
const { generate } = require('./commands/generate');
|
|
3
|
+
const { init } = require('./commands/init');
|
|
2
4
|
const { validate } = require('./commands/validate');
|
|
3
5
|
const { scaffold } = require('./commands/scaffold');
|
|
4
6
|
|
|
@@ -6,13 +8,17 @@ function printUsage() {
|
|
|
6
8
|
console.error(
|
|
7
9
|
[
|
|
8
10
|
'Usage:',
|
|
9
|
-
' api-to-cli
|
|
10
|
-
' api-to-cli
|
|
11
|
-
' api-to-cli
|
|
11
|
+
' api-to-cli doctor [--config <path>] [--spec <path-or-url>] [--url <api-base-url>]',
|
|
12
|
+
' api-to-cli init --url <api-base-url> [--output <path>] [--name <cli-name>] [--version <semver>]',
|
|
13
|
+
' api-to-cli generate (--config <path> | --spec <path-or-url>) --output <dir> [--name <cli-name>]',
|
|
14
|
+
' api-to-cli validate (--config <path> | --spec <path-or-url>) [--name <cli-name>]',
|
|
15
|
+
' api-to-cli scaffold (--config <path> | --spec <path-or-url>) --output <dir> [--name <cli-name>] [--with-skill] [--with-manifest]',
|
|
12
16
|
'',
|
|
13
17
|
'Commands:',
|
|
14
|
-
'
|
|
15
|
-
'
|
|
18
|
+
' doctor Run environment and input diagnostics',
|
|
19
|
+
' init Discover OpenAPI and generate starter config',
|
|
20
|
+
' generate Generate a CLI from config/spec',
|
|
21
|
+
' validate Validate config/spec only',
|
|
16
22
|
' scaffold Generate CLI + optional skill/manifest bundle'
|
|
17
23
|
].join('\n')
|
|
18
24
|
);
|
|
@@ -54,6 +60,16 @@ async function run(argv) {
|
|
|
54
60
|
try {
|
|
55
61
|
const flags = parseFlags(rest);
|
|
56
62
|
|
|
63
|
+
if (command === 'doctor') {
|
|
64
|
+
await doctor(flags);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (command === 'init') {
|
|
69
|
+
await init(flags);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
if (command === 'generate') {
|
|
58
74
|
await generate(flags);
|
|
59
75
|
return;
|