sdk-pilot-cli 1.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/README.md +161 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +514 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
- package/src/index.ts +584 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# sdk-pilot-cli
|
|
2
|
+
|
|
3
|
+
> CLI tool for SDK Pilot — generate SDKs & API docs, publish packages, and create MCP servers from API specs
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g sdk-pilot-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Authenticate
|
|
15
|
+
sdk-pilot auth <your-api-key>
|
|
16
|
+
|
|
17
|
+
# 2. Generate a TypeScript SDK
|
|
18
|
+
sdk-pilot generate https://petstore3.swagger.io/api/v3/openapi.json --lang typescript
|
|
19
|
+
|
|
20
|
+
# 3. Generate API docs
|
|
21
|
+
sdk-pilot docs https://petstore3.swagger.io/api/v3/openapi.json
|
|
22
|
+
|
|
23
|
+
# 4. Publish to npm
|
|
24
|
+
sdk-pilot publish ./my-sdk --registry npm
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
### `sdk-pilot auth <api-key>`
|
|
30
|
+
|
|
31
|
+
Save your SDK Pilot API key to `~/.sdk-pilot/config.json`.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
sdk-pilot auth sk-abc123...
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `sdk-pilot generate <spec-url-or-file>`
|
|
38
|
+
|
|
39
|
+
Generate an SDK from an OpenAPI/Swagger/GraphQL/gRPC/Postman spec.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
sdk-pilot generate https://example.com/openapi.json --lang typescript
|
|
43
|
+
sdk-pilot generate ./openapi.yaml --lang python --output ./my-sdk
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Options:**
|
|
47
|
+
|
|
48
|
+
| Flag | Description | Default |
|
|
49
|
+
|------|-------------|---------|
|
|
50
|
+
| `--lang <language>` | Target language (`typescript\|python\|go\|java\|csharp\|swift\|kotlin\|terraform`) | **required** |
|
|
51
|
+
| `--format <format>` | Spec format (`auto\|openapi\|graphql\|grpc\|postman`) | `auto` |
|
|
52
|
+
| `--model <model>` | LLM model to use (e.g. `gpt-4o`, `claude-3.5-sonnet`) | server default |
|
|
53
|
+
| `--zod` | Include Zod runtime validation schemas (TypeScript only) | off |
|
|
54
|
+
| `--output <dir>` | Output directory | `.` (current directory) |
|
|
55
|
+
|
|
56
|
+
**Examples:**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Auto-detect format, generate Go SDK
|
|
60
|
+
sdk-pilot generate https://api.example.com/spec.yaml --lang go
|
|
61
|
+
|
|
62
|
+
# Force GraphQL format, use Claude model
|
|
63
|
+
sdk-pilot generate ./schema.graphql --lang typescript --format graphql --model claude-3.5-sonnet
|
|
64
|
+
|
|
65
|
+
# TypeScript SDK with Zod schemas
|
|
66
|
+
sdk-pilot generate https://api.example.com/openapi.json --lang typescript --zod --output ./sdk
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `sdk-pilot docs <spec-url-or-file>`
|
|
70
|
+
|
|
71
|
+
Generate API documentation from a spec.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
sdk-pilot docs https://petstore3.swagger.io/api/v3/openapi.json
|
|
75
|
+
sdk-pilot docs ./openapi.yaml --output ./docs
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Options:**
|
|
79
|
+
|
|
80
|
+
| Flag | Description | Default |
|
|
81
|
+
|------|-------------|---------|
|
|
82
|
+
| `--format <format>` | Spec format (`auto\|openapi\|graphql\|grpc\|postman`) | `auto` |
|
|
83
|
+
| `--model <model>` | LLM model to use | server default |
|
|
84
|
+
| `--output <dir>` | Output directory | `.` (current directory) |
|
|
85
|
+
|
|
86
|
+
### `sdk-pilot publish <dir>`
|
|
87
|
+
|
|
88
|
+
Publish an SDK directory to a package registry.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
sdk-pilot publish ./my-sdk --registry npm
|
|
92
|
+
sdk-pilot publish ./my-python-sdk --registry pypi
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Options:**
|
|
96
|
+
|
|
97
|
+
| Flag | Description | Default |
|
|
98
|
+
|------|-------------|---------|
|
|
99
|
+
| `--registry <registry>` | Package registry (`npm\|pypi`) | `npm` |
|
|
100
|
+
|
|
101
|
+
### `sdk-pilot mcp <spec-url>`
|
|
102
|
+
|
|
103
|
+
Generate an MCP (Model Context Protocol) server from an OpenAPI spec.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
sdk-pilot mcp https://petstore3.swagger.io/api/v3/openapi.json --name petstore-mcp
|
|
107
|
+
sdk-pilot mcp https://api.example.com/openapi.json --lang python --name my-api-mcp
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Options:**
|
|
111
|
+
|
|
112
|
+
| Flag | Description | Default |
|
|
113
|
+
|------|-------------|---------|
|
|
114
|
+
| `--name <name>` | MCP server name | `my-mcp-server` |
|
|
115
|
+
| `--lang <language>` | Language (`typescript\|python`) | `typescript` |
|
|
116
|
+
|
|
117
|
+
### `sdk-pilot config`
|
|
118
|
+
|
|
119
|
+
Show current configuration.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
sdk-pilot config
|
|
123
|
+
sdk-pilot config get apiKey
|
|
124
|
+
sdk-pilot config set defaultLang typescript
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Supported Languages
|
|
128
|
+
|
|
129
|
+
- TypeScript
|
|
130
|
+
- Python
|
|
131
|
+
- Go
|
|
132
|
+
- Java
|
|
133
|
+
- C#
|
|
134
|
+
- Swift
|
|
135
|
+
- Kotlin
|
|
136
|
+
- Terraform
|
|
137
|
+
|
|
138
|
+
## Supported Spec Formats
|
|
139
|
+
|
|
140
|
+
- **OpenAPI / Swagger** (JSON & YAML)
|
|
141
|
+
- **GraphQL** (schema files)
|
|
142
|
+
- **gRPC** (proto files)
|
|
143
|
+
- **Postman** (collection JSON)
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
Config is stored at `~/.sdk-pilot/config.json`:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"apiKey": "sk-...",
|
|
152
|
+
"defaultLang": "typescript",
|
|
153
|
+
"defaultOutput": ".",
|
|
154
|
+
"defaultFormat": "auto",
|
|
155
|
+
"defaultModel": "gpt-4o"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
// ── Config helpers ──────────────────────────────────────────────────────
|
|
14
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.sdk-pilot');
|
|
15
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
|
|
16
|
+
function loadConfig() {
|
|
17
|
+
try {
|
|
18
|
+
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
19
|
+
return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch { /* ignore */ }
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
function saveConfig(cfg) {
|
|
26
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
27
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
function getApiKey() {
|
|
32
|
+
const cfg = loadConfig();
|
|
33
|
+
if (!cfg.apiKey) {
|
|
34
|
+
console.error(chalk_1.default.red('Error: API key not set. Run `sdk-pilot auth <api-key>` first.'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
return cfg.apiKey;
|
|
38
|
+
}
|
|
39
|
+
// ── Constants ───────────────────────────────────────────────────────────
|
|
40
|
+
const BASE_URL = 'https://us-central1-mage-ai-601b3.cloudfunctions.net';
|
|
41
|
+
const SUPPORTED_LANGS = ['typescript', 'python', 'go', 'java', 'csharp', 'swift', 'kotlin', 'terraform'];
|
|
42
|
+
const SPEC_FORMATS = ['auto', 'openapi', 'graphql', 'grpc', 'postman'];
|
|
43
|
+
// ── SSE stream reader ──────────────────────────────────────────────────
|
|
44
|
+
async function* readSSE(response) {
|
|
45
|
+
const reader = response.body?.getReader();
|
|
46
|
+
if (!reader)
|
|
47
|
+
throw new Error('No response body');
|
|
48
|
+
const decoder = new TextDecoder();
|
|
49
|
+
let buffer = '';
|
|
50
|
+
while (true) {
|
|
51
|
+
const { done, value } = await reader.read();
|
|
52
|
+
if (done)
|
|
53
|
+
break;
|
|
54
|
+
buffer += decoder.decode(value, { stream: true });
|
|
55
|
+
const lines = buffer.split('\n');
|
|
56
|
+
buffer = lines.pop() || '';
|
|
57
|
+
let currentEvent;
|
|
58
|
+
let currentData = '';
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (line.startsWith('event:')) {
|
|
61
|
+
currentEvent = line.slice(6).trim();
|
|
62
|
+
}
|
|
63
|
+
else if (line.startsWith('data:')) {
|
|
64
|
+
currentData += line.slice(5).trim();
|
|
65
|
+
}
|
|
66
|
+
else if (line === '') {
|
|
67
|
+
if (currentData) {
|
|
68
|
+
yield { event: currentEvent, data: currentData };
|
|
69
|
+
currentEvent = undefined;
|
|
70
|
+
currentData = '';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (buffer.trim()) {
|
|
76
|
+
const m = buffer.match(/^data:\s*(.+)$/s);
|
|
77
|
+
if (m)
|
|
78
|
+
yield { data: m[1] };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function generateCommand(specUrlOrFile, options) {
|
|
82
|
+
const apiKey = getApiKey();
|
|
83
|
+
if (!SUPPORTED_LANGS.includes(options.lang)) {
|
|
84
|
+
console.error(chalk_1.default.red(`Unsupported language: ${options.lang}`));
|
|
85
|
+
console.error(chalk_1.default.yellow(`Supported: ${SUPPORTED_LANGS.join(', ')}`));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
if (!SPEC_FORMATS.includes(options.format)) {
|
|
89
|
+
console.error(chalk_1.default.red(`Unsupported format: ${options.format}`));
|
|
90
|
+
console.error(chalk_1.default.yellow(`Supported: ${SPEC_FORMATS.join(', ')}`));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const lang = options.lang;
|
|
94
|
+
const format = options.format;
|
|
95
|
+
const outputDir = path_1.default.resolve(options.output || '.');
|
|
96
|
+
// Determine if specUrlOrFile is a file path or URL
|
|
97
|
+
let specUrl = specUrlOrFile;
|
|
98
|
+
let specContent = null;
|
|
99
|
+
if (fs_1.default.existsSync(specUrlOrFile)) {
|
|
100
|
+
specContent = fs_1.default.readFileSync(specUrlOrFile, 'utf-8');
|
|
101
|
+
specUrl = '';
|
|
102
|
+
}
|
|
103
|
+
else if (!specUrlOrFile.startsWith('http://') && !specUrlOrFile.startsWith('https://')) {
|
|
104
|
+
console.error(chalk_1.default.red('Error: spec-url-or-file must be a URL or a path to an existing file.'));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const spinner = (0, ora_1.default)('Generating SDK...').start();
|
|
108
|
+
try {
|
|
109
|
+
const body = { language: lang };
|
|
110
|
+
if (specUrl)
|
|
111
|
+
body.specUrl = specUrl;
|
|
112
|
+
if (specContent)
|
|
113
|
+
body.specContent = specContent;
|
|
114
|
+
if (format !== 'auto')
|
|
115
|
+
body.specFormat = format;
|
|
116
|
+
if (options.model)
|
|
117
|
+
body.model = options.model;
|
|
118
|
+
if (options.zod)
|
|
119
|
+
body.includeZodSchemas = true;
|
|
120
|
+
const response = await fetch(`${BASE_URL}/generateSdk`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
});
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const errText = await response.text();
|
|
130
|
+
spinner.fail(`API error: ${response.status} ${response.statusText}`);
|
|
131
|
+
console.error(chalk_1.default.red(errText));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
const contentType = response.headers.get('content-type') || '';
|
|
135
|
+
if (contentType.includes('text/event-stream')) {
|
|
136
|
+
// SSE stream
|
|
137
|
+
for await (const evt of readSSE(response)) {
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(evt.data);
|
|
140
|
+
if (parsed.progress !== undefined) {
|
|
141
|
+
spinner.text = `Generating SDK... ${parsed.progress}%${parsed.message ? ' — ' + parsed.message : ''}`;
|
|
142
|
+
}
|
|
143
|
+
if (parsed.status === 'complete' || parsed.files) {
|
|
144
|
+
spinner.succeed('SDK generated successfully!');
|
|
145
|
+
writeFiles(parsed.files || {}, outputDir);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (parsed.error) {
|
|
149
|
+
spinner.fail(`Error: ${parsed.error}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Non-JSON SSE data — skip
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
spinner.warn('Stream ended without completion signal.');
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Regular JSON response
|
|
161
|
+
const result = await response.json();
|
|
162
|
+
if (result.error) {
|
|
163
|
+
spinner.fail(`Error: ${result.error}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
spinner.succeed('SDK generated successfully!');
|
|
167
|
+
if (result.files) {
|
|
168
|
+
writeFiles(result.files, outputDir);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log(chalk_1.default.green(JSON.stringify(result, null, 2)));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
spinner.fail('Generation failed');
|
|
177
|
+
console.error(chalk_1.default.red(err.message || err));
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function docsCommand(specUrlOrFile, options) {
|
|
182
|
+
const apiKey = getApiKey();
|
|
183
|
+
if (!SPEC_FORMATS.includes(options.format)) {
|
|
184
|
+
console.error(chalk_1.default.red(`Unsupported format: ${options.format}`));
|
|
185
|
+
console.error(chalk_1.default.yellow(`Supported: ${SPEC_FORMATS.join(', ')}`));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const format = options.format;
|
|
189
|
+
const outputDir = path_1.default.resolve(options.output || '.');
|
|
190
|
+
// Determine if specUrlOrFile is a file path or URL
|
|
191
|
+
let specUrl = specUrlOrFile;
|
|
192
|
+
let specContent = null;
|
|
193
|
+
if (fs_1.default.existsSync(specUrlOrFile)) {
|
|
194
|
+
specContent = fs_1.default.readFileSync(specUrlOrFile, 'utf-8');
|
|
195
|
+
specUrl = '';
|
|
196
|
+
}
|
|
197
|
+
else if (!specUrlOrFile.startsWith('http://') && !specUrlOrFile.startsWith('https://')) {
|
|
198
|
+
console.error(chalk_1.default.red('Error: spec-url-or-file must be a URL or a path to an existing file.'));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const spinner = (0, ora_1.default)('Generating API docs...').start();
|
|
202
|
+
try {
|
|
203
|
+
const body = {};
|
|
204
|
+
if (specUrl)
|
|
205
|
+
body.specUrl = specUrl;
|
|
206
|
+
if (specContent)
|
|
207
|
+
body.specContent = specContent;
|
|
208
|
+
if (format !== 'auto')
|
|
209
|
+
body.specFormat = format;
|
|
210
|
+
if (options.model)
|
|
211
|
+
body.model = options.model;
|
|
212
|
+
const response = await fetch(`${BASE_URL}/generateDocs`, {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: {
|
|
215
|
+
'Content-Type': 'application/json',
|
|
216
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
217
|
+
},
|
|
218
|
+
body: JSON.stringify(body),
|
|
219
|
+
});
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
const errText = await response.text();
|
|
222
|
+
spinner.fail(`API error: ${response.status} ${response.statusText}`);
|
|
223
|
+
console.error(chalk_1.default.red(errText));
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
const contentType = response.headers.get('content-type') || '';
|
|
227
|
+
if (contentType.includes('text/event-stream')) {
|
|
228
|
+
for await (const evt of readSSE(response)) {
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(evt.data);
|
|
231
|
+
if (parsed.progress !== undefined) {
|
|
232
|
+
spinner.text = `Generating docs... ${parsed.progress}%${parsed.message ? ' — ' + parsed.message : ''}`;
|
|
233
|
+
}
|
|
234
|
+
if (parsed.status === 'complete' || parsed.files) {
|
|
235
|
+
spinner.succeed('API docs generated successfully!');
|
|
236
|
+
writeFiles(parsed.files || {}, outputDir);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (parsed.error) {
|
|
240
|
+
spinner.fail(`Error: ${parsed.error}`);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch { /* skip */ }
|
|
245
|
+
}
|
|
246
|
+
spinner.warn('Stream ended without completion signal.');
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const result = await response.json();
|
|
250
|
+
if (result.error) {
|
|
251
|
+
spinner.fail(`Error: ${result.error}`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
spinner.succeed('API docs generated successfully!');
|
|
255
|
+
if (result.files) {
|
|
256
|
+
writeFiles(result.files, outputDir);
|
|
257
|
+
}
|
|
258
|
+
else if (result.html || result.markdown) {
|
|
259
|
+
// Write single-file output
|
|
260
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
261
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
if (result.html) {
|
|
264
|
+
const outPath = path_1.default.join(outputDir, 'api-docs.html');
|
|
265
|
+
fs_1.default.writeFileSync(outPath, result.html, 'utf-8');
|
|
266
|
+
console.log(chalk_1.default.cyan(' Created: ') + 'api-docs.html');
|
|
267
|
+
console.log(chalk_1.default.green(`\n✅ API docs written to ${outPath}`));
|
|
268
|
+
}
|
|
269
|
+
if (result.markdown) {
|
|
270
|
+
const outPath = path_1.default.join(outputDir, 'api-docs.md');
|
|
271
|
+
fs_1.default.writeFileSync(outPath, result.markdown, 'utf-8');
|
|
272
|
+
console.log(chalk_1.default.cyan(' Created: ') + 'api-docs.md');
|
|
273
|
+
console.log(chalk_1.default.green(`\n✅ API docs written to ${outPath}`));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
console.log(chalk_1.default.green(JSON.stringify(result, null, 2)));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
spinner.fail('Docs generation failed');
|
|
283
|
+
console.error(chalk_1.default.red(err.message || err));
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// ── File writer ─────────────────────────────────────────────────────────
|
|
288
|
+
function writeFiles(files, outputDir) {
|
|
289
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
290
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
291
|
+
}
|
|
292
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
293
|
+
const fullPath = path_1.default.join(outputDir, filePath);
|
|
294
|
+
const dir = path_1.default.dirname(fullPath);
|
|
295
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
296
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
297
|
+
}
|
|
298
|
+
fs_1.default.writeFileSync(fullPath, content, 'utf-8');
|
|
299
|
+
console.log(chalk_1.default.cyan(' Created: ') + filePath);
|
|
300
|
+
}
|
|
301
|
+
console.log(chalk_1.default.green(`\n✅ ${Object.keys(files).length} file(s) written to ${outputDir}`));
|
|
302
|
+
}
|
|
303
|
+
// ── Publish command ────────────────────────────────────────────────────
|
|
304
|
+
async function publishCommand(dir, options) {
|
|
305
|
+
const apiKey = getApiKey();
|
|
306
|
+
const registry = options.registry || 'npm';
|
|
307
|
+
if (!['npm', 'pypi'].includes(registry)) {
|
|
308
|
+
console.error(chalk_1.default.red(`Unsupported registry: ${registry}. Use 'npm' or 'pypi'.`));
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
const absDir = path_1.default.resolve(dir);
|
|
312
|
+
if (!fs_1.default.existsSync(absDir)) {
|
|
313
|
+
console.error(chalk_1.default.red(`Directory not found: ${absDir}`));
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
const spinner = (0, ora_1.default)(`Publishing to ${registry}...`).start();
|
|
317
|
+
try {
|
|
318
|
+
const response = await fetch(`${BASE_URL}/publishToRegistry`, {
|
|
319
|
+
method: 'POST',
|
|
320
|
+
headers: {
|
|
321
|
+
'Content-Type': 'application/json',
|
|
322
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
directory: absDir,
|
|
326
|
+
registry,
|
|
327
|
+
}),
|
|
328
|
+
});
|
|
329
|
+
if (!response.ok) {
|
|
330
|
+
const errText = await response.text();
|
|
331
|
+
spinner.fail(`API error: ${response.status}`);
|
|
332
|
+
console.error(chalk_1.default.red(errText));
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
const result = await response.json();
|
|
336
|
+
if (result.error) {
|
|
337
|
+
spinner.fail(`Error: ${result.error}`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
spinner.succeed(`Published to ${registry} successfully!`);
|
|
341
|
+
console.log(chalk_1.default.green(JSON.stringify(result, null, 2)));
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
spinner.fail('Publish failed');
|
|
345
|
+
console.error(chalk_1.default.red(err.message || err));
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// ── MCP command ─────────────────────────────────────────────────────────
|
|
350
|
+
async function mcpCommand(specUrl, options) {
|
|
351
|
+
const apiKey = getApiKey();
|
|
352
|
+
const lang = (options.lang || 'typescript');
|
|
353
|
+
if (!['typescript', 'python'].includes(lang)) {
|
|
354
|
+
console.error(chalk_1.default.red(`MCP server only supports: typescript, python`));
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
const name = options.name || 'my-mcp-server';
|
|
358
|
+
const spinner = (0, ora_1.default)(`Generating MCP server "${name}"...`).start();
|
|
359
|
+
try {
|
|
360
|
+
const response = await fetch(`${BASE_URL}/generateMcpServer`, {
|
|
361
|
+
method: 'POST',
|
|
362
|
+
headers: {
|
|
363
|
+
'Content-Type': 'application/json',
|
|
364
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
365
|
+
},
|
|
366
|
+
body: JSON.stringify({
|
|
367
|
+
specUrl,
|
|
368
|
+
name,
|
|
369
|
+
language: lang,
|
|
370
|
+
}),
|
|
371
|
+
});
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
const errText = await response.text();
|
|
374
|
+
spinner.fail(`API error: ${response.status}`);
|
|
375
|
+
console.error(chalk_1.default.red(errText));
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
const contentType = response.headers.get('content-type') || '';
|
|
379
|
+
if (contentType.includes('text/event-stream')) {
|
|
380
|
+
for await (const evt of readSSE(response)) {
|
|
381
|
+
try {
|
|
382
|
+
const parsed = JSON.parse(evt.data);
|
|
383
|
+
if (parsed.progress !== undefined) {
|
|
384
|
+
spinner.text = `Generating MCP server... ${parsed.progress}%${parsed.message ? ' — ' + parsed.message : ''}`;
|
|
385
|
+
}
|
|
386
|
+
if (parsed.status === 'complete' || parsed.files) {
|
|
387
|
+
spinner.succeed('MCP server generated successfully!');
|
|
388
|
+
writeFiles(parsed.files || {}, `./${name}`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (parsed.error) {
|
|
392
|
+
spinner.fail(`Error: ${parsed.error}`);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch { /* skip */ }
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
const result = await response.json();
|
|
401
|
+
if (result.error) {
|
|
402
|
+
spinner.fail(`Error: ${result.error}`);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
spinner.succeed('MCP server generated successfully!');
|
|
406
|
+
if (result.files) {
|
|
407
|
+
writeFiles(result.files, `./${name}`);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log(chalk_1.default.green(JSON.stringify(result, null, 2)));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
spinner.fail('MCP generation failed');
|
|
416
|
+
console.error(chalk_1.default.red(err.message || err));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// ── Auth command ────────────────────────────────────────────────────────
|
|
421
|
+
function authCommand(apiKey) {
|
|
422
|
+
const cfg = loadConfig();
|
|
423
|
+
cfg.apiKey = apiKey;
|
|
424
|
+
saveConfig(cfg);
|
|
425
|
+
console.log(chalk_1.default.green('✅ API key saved to ' + CONFIG_FILE));
|
|
426
|
+
}
|
|
427
|
+
// ── Config command ──────────────────────────────────────────────────────
|
|
428
|
+
function configCommand(action, key, value) {
|
|
429
|
+
const cfg = loadConfig();
|
|
430
|
+
// Handle get/set subcommands
|
|
431
|
+
if (action === 'get' && key) {
|
|
432
|
+
if (cfg[key] !== undefined) {
|
|
433
|
+
const val = cfg[key];
|
|
434
|
+
if (key === 'apiKey' && typeof val === 'string') {
|
|
435
|
+
console.log(`${key}: ${val.slice(0, 8)}${'*'.repeat(Math.max(0, val.length - 8))}`);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
console.log(`${key}: ${val}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
console.log(chalk_1.default.yellow(`Key '${key}' not set.`));
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (action === 'set' && key && value !== undefined) {
|
|
447
|
+
cfg[key] = value;
|
|
448
|
+
saveConfig(cfg);
|
|
449
|
+
console.log(chalk_1.default.green(`✅ Set ${key}`));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (Object.keys(cfg).length === 0) {
|
|
453
|
+
console.log(chalk_1.default.yellow('No configuration found.'));
|
|
454
|
+
console.log(chalk_1.default.gray(`Config file: ${CONFIG_FILE}`));
|
|
455
|
+
console.log(chalk_1.default.gray('Run `sdk-pilot auth <api-key>` to set your API key.'));
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
console.log(chalk_1.default.bold('SDK Pilot Configuration'));
|
|
459
|
+
console.log(chalk_1.default.gray(`Config file: ${CONFIG_FILE}\n`));
|
|
460
|
+
for (const [key, value] of Object.entries(cfg)) {
|
|
461
|
+
if (key === 'apiKey' && typeof value === 'string') {
|
|
462
|
+
console.log(` ${chalk_1.default.cyan(key)}: ${value.slice(0, 8)}${'*'.repeat(Math.max(0, value.length - 8))}`);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
console.log(` ${chalk_1.default.cyan(key)}: ${value}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// ── Main program ───────────────────────────────────────────────────────
|
|
470
|
+
const program = new commander_1.Command();
|
|
471
|
+
program
|
|
472
|
+
.name('sdk-pilot')
|
|
473
|
+
.description('SDK Pilot CLI — Generate SDKs, API docs, publish packages, and create MCP servers from API specs')
|
|
474
|
+
.version('1.1.0');
|
|
475
|
+
program
|
|
476
|
+
.command('generate <spec-url-or-file>')
|
|
477
|
+
.description('Generate an SDK from an API spec URL or local file')
|
|
478
|
+
.requiredOption('--lang <language>', `Target language (${SUPPORTED_LANGS.join('|')})`)
|
|
479
|
+
.option('--format <spec-format>', `Spec format (${SPEC_FORMATS.join('|')})`, 'auto')
|
|
480
|
+
.option('--model <llm-model>', 'LLM model to use (e.g. gpt-4o, claude-3.5-sonnet)')
|
|
481
|
+
.option('--zod', 'Include Zod runtime validation schemas (TypeScript only)')
|
|
482
|
+
.option('--output <dir>', 'Output directory', '.')
|
|
483
|
+
.action(generateCommand);
|
|
484
|
+
program
|
|
485
|
+
.command('docs <spec-url-or-file>')
|
|
486
|
+
.description('Generate API documentation from an API spec URL or local file')
|
|
487
|
+
.option('--format <spec-format>', `Spec format (${SPEC_FORMATS.join('|')})`, 'auto')
|
|
488
|
+
.option('--model <llm-model>', 'LLM model to use (e.g. gpt-4o, claude-3.5-sonnet)')
|
|
489
|
+
.option('--output <dir>', 'Output directory', '.')
|
|
490
|
+
.action(docsCommand);
|
|
491
|
+
program
|
|
492
|
+
.command('publish <dir>')
|
|
493
|
+
.description('Publish an SDK to a package registry')
|
|
494
|
+
.option('--registry <registry>', 'Package registry (npm|pypi)', 'npm')
|
|
495
|
+
.action(publishCommand);
|
|
496
|
+
program
|
|
497
|
+
.command('mcp <spec-url>')
|
|
498
|
+
.description('Generate an MCP server from an OpenAPI spec')
|
|
499
|
+
.option('--name <server-name>', 'MCP server name', 'my-mcp-server')
|
|
500
|
+
.option('--lang <language>', 'Language (typescript|python)', 'typescript')
|
|
501
|
+
.action(mcpCommand);
|
|
502
|
+
program
|
|
503
|
+
.command('auth <api-key>')
|
|
504
|
+
.description('Save your SDK Pilot API key')
|
|
505
|
+
.action(authCommand);
|
|
506
|
+
program
|
|
507
|
+
.command('config')
|
|
508
|
+
.description('Show current configuration, or get/set individual values')
|
|
509
|
+
.argument('[action]', 'get or set')
|
|
510
|
+
.argument('[key]', 'config key')
|
|
511
|
+
.argument('[value]', 'config value (for set)')
|
|
512
|
+
.action(configCommand);
|
|
513
|
+
program.parse(process.argv);
|
|
514
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sdk-pilot-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "CLI tool for SDK Pilot \u2014 generate SDKs & API docs, publish packages, and create MCP servers from API specs",
|
|
5
|
+
"bin": {
|
|
6
|
+
"sdk-pilot": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.1.0",
|
|
15
|
+
"chalk": "^4.1.2",
|
|
16
|
+
"ora": "^5.4.1",
|
|
17
|
+
"node-fetch": "^2.7.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.4.5",
|
|
21
|
+
"@types/node": "^20.14.2"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"sdk",
|
|
25
|
+
"openapi",
|
|
26
|
+
"swagger",
|
|
27
|
+
"cli",
|
|
28
|
+
"generator",
|
|
29
|
+
"mcp"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT"
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
|
|
11
|
+
// ── Config helpers ──────────────────────────────────────────────────────
|
|
12
|
+
const CONFIG_DIR = path.join(os.homedir(), '.sdk-pilot');
|
|
13
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
14
|
+
|
|
15
|
+
interface Config {
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
defaultLang?: string;
|
|
18
|
+
defaultOutput?: string;
|
|
19
|
+
defaultFormat?: string;
|
|
20
|
+
defaultModel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function loadConfig(): Config {
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
26
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
} catch { /* ignore */ }
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function saveConfig(cfg: Config): void {
|
|
33
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
34
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getApiKey(): string {
|
|
40
|
+
const cfg = loadConfig();
|
|
41
|
+
if (!cfg.apiKey) {
|
|
42
|
+
console.error(chalk.red('Error: API key not set. Run `sdk-pilot auth <api-key>` first.'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
return cfg.apiKey;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Constants ───────────────────────────────────────────────────────────
|
|
49
|
+
const BASE_URL = 'https://us-central1-mage-ai-601b3.cloudfunctions.net';
|
|
50
|
+
|
|
51
|
+
const SUPPORTED_LANGS = ['typescript', 'python', 'go', 'java', 'csharp', 'swift', 'kotlin', 'terraform'] as const;
|
|
52
|
+
type Lang = typeof SUPPORTED_LANGS[number];
|
|
53
|
+
|
|
54
|
+
const SPEC_FORMATS = ['auto', 'openapi', 'graphql', 'grpc', 'postman'] as const;
|
|
55
|
+
type SpecFormat = typeof SPEC_FORMATS[number];
|
|
56
|
+
|
|
57
|
+
// ── SSE stream reader ──────────────────────────────────────────────────
|
|
58
|
+
async function* readSSE(response: Response): AsyncGenerator<{ event?: string; data: string }> {
|
|
59
|
+
const reader = response.body?.getReader();
|
|
60
|
+
if (!reader) throw new Error('No response body');
|
|
61
|
+
|
|
62
|
+
const decoder = new TextDecoder();
|
|
63
|
+
let buffer = '';
|
|
64
|
+
|
|
65
|
+
while (true) {
|
|
66
|
+
const { done, value } = await reader.read();
|
|
67
|
+
if (done) break;
|
|
68
|
+
|
|
69
|
+
buffer += decoder.decode(value, { stream: true });
|
|
70
|
+
const lines = buffer.split('\n');
|
|
71
|
+
buffer = lines.pop() || '';
|
|
72
|
+
|
|
73
|
+
let currentEvent: string | undefined;
|
|
74
|
+
let currentData = '';
|
|
75
|
+
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
if (line.startsWith('event:')) {
|
|
78
|
+
currentEvent = line.slice(6).trim();
|
|
79
|
+
} else if (line.startsWith('data:')) {
|
|
80
|
+
currentData += line.slice(5).trim();
|
|
81
|
+
} else if (line === '') {
|
|
82
|
+
if (currentData) {
|
|
83
|
+
yield { event: currentEvent, data: currentData };
|
|
84
|
+
currentEvent = undefined;
|
|
85
|
+
currentData = '';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (buffer.trim()) {
|
|
92
|
+
const m = buffer.match(/^data:\s*(.+)$/s);
|
|
93
|
+
if (m) yield { data: m[1] };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Generate command ───────────────────────────────────────────────────
|
|
98
|
+
interface GenerateOptions {
|
|
99
|
+
lang: string;
|
|
100
|
+
output: string;
|
|
101
|
+
format: string;
|
|
102
|
+
model: string;
|
|
103
|
+
zod: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function generateCommand(specUrlOrFile: string, options: GenerateOptions) {
|
|
107
|
+
const apiKey = getApiKey();
|
|
108
|
+
|
|
109
|
+
if (!SUPPORTED_LANGS.includes(options.lang as Lang)) {
|
|
110
|
+
console.error(chalk.red(`Unsupported language: ${options.lang}`));
|
|
111
|
+
console.error(chalk.yellow(`Supported: ${SUPPORTED_LANGS.join(', ')}`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!SPEC_FORMATS.includes(options.format as SpecFormat)) {
|
|
116
|
+
console.error(chalk.red(`Unsupported format: ${options.format}`));
|
|
117
|
+
console.error(chalk.yellow(`Supported: ${SPEC_FORMATS.join(', ')}`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const lang = options.lang as Lang;
|
|
122
|
+
const format = options.format as SpecFormat;
|
|
123
|
+
const outputDir = path.resolve(options.output || '.');
|
|
124
|
+
|
|
125
|
+
// Determine if specUrlOrFile is a file path or URL
|
|
126
|
+
let specUrl = specUrlOrFile;
|
|
127
|
+
let specContent: string | null = null;
|
|
128
|
+
|
|
129
|
+
if (fs.existsSync(specUrlOrFile)) {
|
|
130
|
+
specContent = fs.readFileSync(specUrlOrFile, 'utf-8');
|
|
131
|
+
specUrl = '';
|
|
132
|
+
} else if (!specUrlOrFile.startsWith('http://') && !specUrlOrFile.startsWith('https://')) {
|
|
133
|
+
console.error(chalk.red('Error: spec-url-or-file must be a URL or a path to an existing file.'));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const spinner = ora('Generating SDK...').start();
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const body: Record<string, unknown> = { language: lang };
|
|
141
|
+
if (specUrl) body.specUrl = specUrl;
|
|
142
|
+
if (specContent) body.specContent = specContent;
|
|
143
|
+
if (format !== 'auto') body.specFormat = format;
|
|
144
|
+
if (options.model) body.model = options.model;
|
|
145
|
+
if (options.zod) body.includeZodSchemas = true;
|
|
146
|
+
|
|
147
|
+
const response = await fetch(`${BASE_URL}/generateSdk`, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
'Content-Type': 'application/json',
|
|
151
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify(body),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
const errText = await response.text();
|
|
158
|
+
spinner.fail(`API error: ${response.status} ${response.statusText}`);
|
|
159
|
+
console.error(chalk.red(errText));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const contentType = response.headers.get('content-type') || '';
|
|
164
|
+
|
|
165
|
+
if (contentType.includes('text/event-stream')) {
|
|
166
|
+
// SSE stream
|
|
167
|
+
for await (const evt of readSSE(response)) {
|
|
168
|
+
try {
|
|
169
|
+
const parsed = JSON.parse(evt.data);
|
|
170
|
+
|
|
171
|
+
if (parsed.progress !== undefined) {
|
|
172
|
+
spinner.text = `Generating SDK... ${parsed.progress}%${parsed.message ? ' — ' + parsed.message : ''}`;
|
|
173
|
+
}
|
|
174
|
+
if (parsed.status === 'complete' || parsed.files) {
|
|
175
|
+
spinner.succeed('SDK generated successfully!');
|
|
176
|
+
writeFiles(parsed.files || {}, outputDir);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (parsed.error) {
|
|
180
|
+
spinner.fail(`Error: ${parsed.error}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// Non-JSON SSE data — skip
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
spinner.warn('Stream ended without completion signal.');
|
|
188
|
+
} else {
|
|
189
|
+
// Regular JSON response
|
|
190
|
+
const result = await response.json() as Record<string, any>;
|
|
191
|
+
|
|
192
|
+
if (result.error) {
|
|
193
|
+
spinner.fail(`Error: ${result.error}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
spinner.succeed('SDK generated successfully!');
|
|
198
|
+
|
|
199
|
+
if (result.files) {
|
|
200
|
+
writeFiles(result.files, outputDir);
|
|
201
|
+
} else {
|
|
202
|
+
console.log(chalk.green(JSON.stringify(result, null, 2)));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (err: any) {
|
|
206
|
+
spinner.fail('Generation failed');
|
|
207
|
+
console.error(chalk.red(err.message || err));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Docs command ────────────────────────────────────────────────────────
|
|
213
|
+
interface DocsOptions {
|
|
214
|
+
format: string;
|
|
215
|
+
model: string;
|
|
216
|
+
output: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function docsCommand(specUrlOrFile: string, options: DocsOptions) {
|
|
220
|
+
const apiKey = getApiKey();
|
|
221
|
+
|
|
222
|
+
if (!SPEC_FORMATS.includes(options.format as SpecFormat)) {
|
|
223
|
+
console.error(chalk.red(`Unsupported format: ${options.format}`));
|
|
224
|
+
console.error(chalk.yellow(`Supported: ${SPEC_FORMATS.join(', ')}`));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const format = options.format as SpecFormat;
|
|
229
|
+
const outputDir = path.resolve(options.output || '.');
|
|
230
|
+
|
|
231
|
+
// Determine if specUrlOrFile is a file path or URL
|
|
232
|
+
let specUrl = specUrlOrFile;
|
|
233
|
+
let specContent: string | null = null;
|
|
234
|
+
|
|
235
|
+
if (fs.existsSync(specUrlOrFile)) {
|
|
236
|
+
specContent = fs.readFileSync(specUrlOrFile, 'utf-8');
|
|
237
|
+
specUrl = '';
|
|
238
|
+
} else if (!specUrlOrFile.startsWith('http://') && !specUrlOrFile.startsWith('https://')) {
|
|
239
|
+
console.error(chalk.red('Error: spec-url-or-file must be a URL or a path to an existing file.'));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const spinner = ora('Generating API docs...').start();
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const body: Record<string, unknown> = {};
|
|
247
|
+
if (specUrl) body.specUrl = specUrl;
|
|
248
|
+
if (specContent) body.specContent = specContent;
|
|
249
|
+
if (format !== 'auto') body.specFormat = format;
|
|
250
|
+
if (options.model) body.model = options.model;
|
|
251
|
+
|
|
252
|
+
const response = await fetch(`${BASE_URL}/generateDocs`, {
|
|
253
|
+
method: 'POST',
|
|
254
|
+
headers: {
|
|
255
|
+
'Content-Type': 'application/json',
|
|
256
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
257
|
+
},
|
|
258
|
+
body: JSON.stringify(body),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
const errText = await response.text();
|
|
263
|
+
spinner.fail(`API error: ${response.status} ${response.statusText}`);
|
|
264
|
+
console.error(chalk.red(errText));
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const contentType = response.headers.get('content-type') || '';
|
|
269
|
+
|
|
270
|
+
if (contentType.includes('text/event-stream')) {
|
|
271
|
+
for await (const evt of readSSE(response)) {
|
|
272
|
+
try {
|
|
273
|
+
const parsed = JSON.parse(evt.data);
|
|
274
|
+
if (parsed.progress !== undefined) {
|
|
275
|
+
spinner.text = `Generating docs... ${parsed.progress}%${parsed.message ? ' — ' + parsed.message : ''}`;
|
|
276
|
+
}
|
|
277
|
+
if (parsed.status === 'complete' || parsed.files) {
|
|
278
|
+
spinner.succeed('API docs generated successfully!');
|
|
279
|
+
writeFiles(parsed.files || {}, outputDir);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (parsed.error) {
|
|
283
|
+
spinner.fail(`Error: ${parsed.error}`);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
} catch { /* skip */ }
|
|
287
|
+
}
|
|
288
|
+
spinner.warn('Stream ended without completion signal.');
|
|
289
|
+
} else {
|
|
290
|
+
const result = await response.json() as Record<string, any>;
|
|
291
|
+
|
|
292
|
+
if (result.error) {
|
|
293
|
+
spinner.fail(`Error: ${result.error}`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
spinner.succeed('API docs generated successfully!');
|
|
298
|
+
|
|
299
|
+
if (result.files) {
|
|
300
|
+
writeFiles(result.files, outputDir);
|
|
301
|
+
} else if (result.html || result.markdown) {
|
|
302
|
+
// Write single-file output
|
|
303
|
+
if (!fs.existsSync(outputDir)) {
|
|
304
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
305
|
+
}
|
|
306
|
+
if (result.html) {
|
|
307
|
+
const outPath = path.join(outputDir, 'api-docs.html');
|
|
308
|
+
fs.writeFileSync(outPath, result.html, 'utf-8');
|
|
309
|
+
console.log(chalk.cyan(' Created: ') + 'api-docs.html');
|
|
310
|
+
console.log(chalk.green(`\n✅ API docs written to ${outPath}`));
|
|
311
|
+
}
|
|
312
|
+
if (result.markdown) {
|
|
313
|
+
const outPath = path.join(outputDir, 'api-docs.md');
|
|
314
|
+
fs.writeFileSync(outPath, result.markdown, 'utf-8');
|
|
315
|
+
console.log(chalk.cyan(' Created: ') + 'api-docs.md');
|
|
316
|
+
console.log(chalk.green(`\n✅ API docs written to ${outPath}`));
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
console.log(chalk.green(JSON.stringify(result, null, 2)));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (err: any) {
|
|
323
|
+
spinner.fail('Docs generation failed');
|
|
324
|
+
console.error(chalk.red(err.message || err));
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── File writer ─────────────────────────────────────────────────────────
|
|
330
|
+
function writeFiles(files: Record<string, string>, outputDir: string): void {
|
|
331
|
+
if (!fs.existsSync(outputDir)) {
|
|
332
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
336
|
+
const fullPath = path.join(outputDir, filePath);
|
|
337
|
+
const dir = path.dirname(fullPath);
|
|
338
|
+
if (!fs.existsSync(dir)) {
|
|
339
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
340
|
+
}
|
|
341
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
342
|
+
console.log(chalk.cyan(' Created: ') + filePath);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log(chalk.green(`\n✅ ${Object.keys(files).length} file(s) written to ${outputDir}`));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Publish command ────────────────────────────────────────────────────
|
|
349
|
+
async function publishCommand(dir: string, options: { registry: string }) {
|
|
350
|
+
const apiKey = getApiKey();
|
|
351
|
+
const registry = options.registry || 'npm';
|
|
352
|
+
|
|
353
|
+
if (!['npm', 'pypi'].includes(registry)) {
|
|
354
|
+
console.error(chalk.red(`Unsupported registry: ${registry}. Use 'npm' or 'pypi'.`));
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const absDir = path.resolve(dir);
|
|
359
|
+
if (!fs.existsSync(absDir)) {
|
|
360
|
+
console.error(chalk.red(`Directory not found: ${absDir}`));
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const spinner = ora(`Publishing to ${registry}...`).start();
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const response = await fetch(`${BASE_URL}/publishToRegistry`, {
|
|
368
|
+
method: 'POST',
|
|
369
|
+
headers: {
|
|
370
|
+
'Content-Type': 'application/json',
|
|
371
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
directory: absDir,
|
|
375
|
+
registry,
|
|
376
|
+
}),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (!response.ok) {
|
|
380
|
+
const errText = await response.text();
|
|
381
|
+
spinner.fail(`API error: ${response.status}`);
|
|
382
|
+
console.error(chalk.red(errText));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const result = await response.json() as Record<string, any>;
|
|
387
|
+
|
|
388
|
+
if (result.error) {
|
|
389
|
+
spinner.fail(`Error: ${result.error}`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
spinner.succeed(`Published to ${registry} successfully!`);
|
|
394
|
+
console.log(chalk.green(JSON.stringify(result, null, 2)));
|
|
395
|
+
} catch (err: any) {
|
|
396
|
+
spinner.fail('Publish failed');
|
|
397
|
+
console.error(chalk.red(err.message || err));
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── MCP command ─────────────────────────────────────────────────────────
|
|
403
|
+
async function mcpCommand(specUrl: string, options: { name: string; lang: string }) {
|
|
404
|
+
const apiKey = getApiKey();
|
|
405
|
+
const lang = (options.lang || 'typescript') as string;
|
|
406
|
+
|
|
407
|
+
if (!['typescript', 'python'].includes(lang)) {
|
|
408
|
+
console.error(chalk.red(`MCP server only supports: typescript, python`));
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const name = options.name || 'my-mcp-server';
|
|
413
|
+
const spinner = ora(`Generating MCP server "${name}"...`).start();
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
const response = await fetch(`${BASE_URL}/generateMcpServer`, {
|
|
417
|
+
method: 'POST',
|
|
418
|
+
headers: {
|
|
419
|
+
'Content-Type': 'application/json',
|
|
420
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
421
|
+
},
|
|
422
|
+
body: JSON.stringify({
|
|
423
|
+
specUrl,
|
|
424
|
+
name,
|
|
425
|
+
language: lang,
|
|
426
|
+
}),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (!response.ok) {
|
|
430
|
+
const errText = await response.text();
|
|
431
|
+
spinner.fail(`API error: ${response.status}`);
|
|
432
|
+
console.error(chalk.red(errText));
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const contentType = response.headers.get('content-type') || '';
|
|
437
|
+
|
|
438
|
+
if (contentType.includes('text/event-stream')) {
|
|
439
|
+
for await (const evt of readSSE(response)) {
|
|
440
|
+
try {
|
|
441
|
+
const parsed = JSON.parse(evt.data);
|
|
442
|
+
if (parsed.progress !== undefined) {
|
|
443
|
+
spinner.text = `Generating MCP server... ${parsed.progress}%${parsed.message ? ' — ' + parsed.message : ''}`;
|
|
444
|
+
}
|
|
445
|
+
if (parsed.status === 'complete' || parsed.files) {
|
|
446
|
+
spinner.succeed('MCP server generated successfully!');
|
|
447
|
+
writeFiles(parsed.files || {}, `./${name}`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (parsed.error) {
|
|
451
|
+
spinner.fail(`Error: ${parsed.error}`);
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
} catch { /* skip */ }
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
const result = await response.json() as Record<string, any>;
|
|
458
|
+
|
|
459
|
+
if (result.error) {
|
|
460
|
+
spinner.fail(`Error: ${result.error}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
spinner.succeed('MCP server generated successfully!');
|
|
465
|
+
|
|
466
|
+
if (result.files) {
|
|
467
|
+
writeFiles(result.files, `./${name}`);
|
|
468
|
+
} else {
|
|
469
|
+
console.log(chalk.green(JSON.stringify(result, null, 2)));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} catch (err: any) {
|
|
473
|
+
spinner.fail('MCP generation failed');
|
|
474
|
+
console.error(chalk.red(err.message || err));
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── Auth command ────────────────────────────────────────────────────────
|
|
480
|
+
function authCommand(apiKey: string) {
|
|
481
|
+
const cfg = loadConfig();
|
|
482
|
+
cfg.apiKey = apiKey;
|
|
483
|
+
saveConfig(cfg);
|
|
484
|
+
console.log(chalk.green('✅ API key saved to ' + CONFIG_FILE));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── Config command ──────────────────────────────────────────────────────
|
|
488
|
+
function configCommand(action?: string, key?: string, value?: string) {
|
|
489
|
+
const cfg = loadConfig();
|
|
490
|
+
|
|
491
|
+
// Handle get/set subcommands
|
|
492
|
+
if (action === 'get' && key) {
|
|
493
|
+
if (cfg[key as keyof Config] !== undefined) {
|
|
494
|
+
const val = cfg[key as keyof Config];
|
|
495
|
+
if (key === 'apiKey' && typeof val === 'string') {
|
|
496
|
+
console.log(`${key}: ${val.slice(0, 8)}${'*'.repeat(Math.max(0, val.length - 8))}`);
|
|
497
|
+
} else {
|
|
498
|
+
console.log(`${key}: ${val}`);
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
console.log(chalk.yellow(`Key '${key}' not set.`));
|
|
502
|
+
}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (action === 'set' && key && value !== undefined) {
|
|
507
|
+
(cfg as any)[key] = value;
|
|
508
|
+
saveConfig(cfg);
|
|
509
|
+
console.log(chalk.green(`✅ Set ${key}`));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (Object.keys(cfg).length === 0) {
|
|
514
|
+
console.log(chalk.yellow('No configuration found.'));
|
|
515
|
+
console.log(chalk.gray(`Config file: ${CONFIG_FILE}`));
|
|
516
|
+
console.log(chalk.gray('Run `sdk-pilot auth <api-key>` to set your API key.'));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
console.log(chalk.bold('SDK Pilot Configuration'));
|
|
521
|
+
console.log(chalk.gray(`Config file: ${CONFIG_FILE}\n`));
|
|
522
|
+
|
|
523
|
+
for (const [key, value] of Object.entries(cfg)) {
|
|
524
|
+
if (key === 'apiKey' && typeof value === 'string') {
|
|
525
|
+
console.log(` ${chalk.cyan(key)}: ${value.slice(0, 8)}${'*'.repeat(Math.max(0, value.length - 8))}`);
|
|
526
|
+
} else {
|
|
527
|
+
console.log(` ${chalk.cyan(key)}: ${value}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ── Main program ───────────────────────────────────────────────────────
|
|
533
|
+
const program = new Command();
|
|
534
|
+
|
|
535
|
+
program
|
|
536
|
+
.name('sdk-pilot')
|
|
537
|
+
.description('SDK Pilot CLI — Generate SDKs, API docs, publish packages, and create MCP servers from API specs')
|
|
538
|
+
.version('1.1.0');
|
|
539
|
+
|
|
540
|
+
program
|
|
541
|
+
.command('generate <spec-url-or-file>')
|
|
542
|
+
.description('Generate an SDK from an API spec URL or local file')
|
|
543
|
+
.requiredOption('--lang <language>', `Target language (${SUPPORTED_LANGS.join('|')})`)
|
|
544
|
+
.option('--format <spec-format>', `Spec format (${SPEC_FORMATS.join('|')})`, 'auto')
|
|
545
|
+
.option('--model <llm-model>', 'LLM model to use (e.g. gpt-4o, claude-3.5-sonnet)')
|
|
546
|
+
.option('--zod', 'Include Zod runtime validation schemas (TypeScript only)')
|
|
547
|
+
.option('--output <dir>', 'Output directory', '.')
|
|
548
|
+
.action(generateCommand);
|
|
549
|
+
|
|
550
|
+
program
|
|
551
|
+
.command('docs <spec-url-or-file>')
|
|
552
|
+
.description('Generate API documentation from an API spec URL or local file')
|
|
553
|
+
.option('--format <spec-format>', `Spec format (${SPEC_FORMATS.join('|')})`, 'auto')
|
|
554
|
+
.option('--model <llm-model>', 'LLM model to use (e.g. gpt-4o, claude-3.5-sonnet)')
|
|
555
|
+
.option('--output <dir>', 'Output directory', '.')
|
|
556
|
+
.action(docsCommand);
|
|
557
|
+
|
|
558
|
+
program
|
|
559
|
+
.command('publish <dir>')
|
|
560
|
+
.description('Publish an SDK to a package registry')
|
|
561
|
+
.option('--registry <registry>', 'Package registry (npm|pypi)', 'npm')
|
|
562
|
+
.action(publishCommand);
|
|
563
|
+
|
|
564
|
+
program
|
|
565
|
+
.command('mcp <spec-url>')
|
|
566
|
+
.description('Generate an MCP server from an OpenAPI spec')
|
|
567
|
+
.option('--name <server-name>', 'MCP server name', 'my-mcp-server')
|
|
568
|
+
.option('--lang <language>', 'Language (typescript|python)', 'typescript')
|
|
569
|
+
.action(mcpCommand);
|
|
570
|
+
|
|
571
|
+
program
|
|
572
|
+
.command('auth <api-key>')
|
|
573
|
+
.description('Save your SDK Pilot API key')
|
|
574
|
+
.action(authCommand);
|
|
575
|
+
|
|
576
|
+
program
|
|
577
|
+
.command('config')
|
|
578
|
+
.description('Show current configuration, or get/set individual values')
|
|
579
|
+
.argument('[action]', 'get or set')
|
|
580
|
+
.argument('[key]', 'config key')
|
|
581
|
+
.argument('[value]', 'config value (for set)')
|
|
582
|
+
.action(configCommand);
|
|
583
|
+
|
|
584
|
+
program.parse(process.argv);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|