crowgent-openapi 1.0.0 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +122 -32
  2. package/package.json +19 -5
package/dist/cli.js CHANGED
@@ -1,57 +1,147 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
- import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
3
+ import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs';
4
4
  import { join, extname, relative } from 'path';
5
5
  import OpenAI from 'openai';
6
+ import * as p from '@clack/prompts';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
6
9
  const EXTENSIONS = ['.ts', '.js', '.tsx', '.jsx', '.py', '.rb', '.go', '.java', '.kt', '.rs'];
7
10
  const IGNORE = ['node_modules', '.git', 'dist', 'build', '__pycache__', '.next', 'coverage'];
8
11
  program
9
12
  .name('crowgent-openapi')
10
13
  .description('Generate OpenAPI specs from your backend code using AI')
11
- .argument('<directory>', 'Backend directory to scan')
12
- .option('-o, --output <file>', 'Output file', 'openapi.yaml')
14
+ .argument('[directory]', 'Backend directory to scan')
15
+ .option('-o, --output <file>', 'Output file')
13
16
  .option('-k, --api-key <key>', 'OpenAI API key (or set OPENAI_API_KEY)')
14
17
  .option('-m, --model <model>', 'Model to use', 'gpt-4o-mini')
15
- .option('--base-url <url>', 'API base URL', 'http://localhost:3000')
18
+ .option('--base-url <url>', 'API base URL')
19
+ .option('--yes', 'Skip prompts and use defaults', false)
16
20
  .action(async (directory, opts) => {
21
+ // Welcome
22
+ console.log();
23
+ p.intro(chalk.bgCyan.black(' 🐦 Crowgent OpenAPI Generator '));
17
24
  const apiKey = opts.apiKey || process.env.OPENAI_API_KEY;
25
+ // Check API key
18
26
  if (!apiKey) {
19
- console.error('Missing API key. Set OPENAI_API_KEY or use --api-key');
27
+ p.log.error('Missing OpenAI API key');
28
+ p.log.info('Set OPENAI_API_KEY environment variable or use --api-key flag');
29
+ p.outro(chalk.red('Setup incomplete'));
20
30
  process.exit(1);
21
31
  }
22
- console.log('📂 Scanning files...');
23
- const files = collectFiles(directory);
24
- console.log(` Found ${files.length} source files`);
32
+ // Ask for consent
33
+ if (!opts.yes) {
34
+ const consent = await p.confirm({
35
+ message: 'This will send your code to OpenAI to generate an API spec. Continue?',
36
+ initialValue: true,
37
+ });
38
+ if (p.isCancel(consent) || !consent) {
39
+ p.outro(chalk.yellow('Cancelled'));
40
+ process.exit(0);
41
+ }
42
+ }
43
+ // Get directory
44
+ let targetDir = directory;
45
+ if (!targetDir && !opts.yes) {
46
+ const dirInput = await p.text({
47
+ message: 'Which directory contains your backend code?',
48
+ placeholder: './src or ./routes',
49
+ defaultValue: '.',
50
+ validate: (value) => {
51
+ if (!existsSync(value))
52
+ return 'Directory not found';
53
+ },
54
+ });
55
+ if (p.isCancel(dirInput)) {
56
+ p.outro(chalk.yellow('Cancelled'));
57
+ process.exit(0);
58
+ }
59
+ targetDir = dirInput;
60
+ }
61
+ targetDir = targetDir || '.';
62
+ // Validate directory
63
+ if (!existsSync(targetDir)) {
64
+ p.log.error(`Directory not found: ${targetDir}`);
65
+ p.outro(chalk.red('Failed'));
66
+ process.exit(1);
67
+ }
68
+ // Get output file
69
+ let outputFile = opts.output;
70
+ if (!outputFile && !opts.yes) {
71
+ const outputInput = await p.text({
72
+ message: 'Where should we save the OpenAPI spec?',
73
+ placeholder: 'openapi.yaml',
74
+ defaultValue: 'openapi.yaml',
75
+ });
76
+ if (p.isCancel(outputInput)) {
77
+ p.outro(chalk.yellow('Cancelled'));
78
+ process.exit(0);
79
+ }
80
+ outputFile = outputInput;
81
+ }
82
+ outputFile = outputFile || 'openapi.yaml';
83
+ // Get base URL
84
+ let baseUrl = opts.baseUrl;
85
+ if (!baseUrl && !opts.yes) {
86
+ const urlInput = await p.text({
87
+ message: 'What is your API base URL?',
88
+ placeholder: 'http://localhost:3000',
89
+ defaultValue: 'http://localhost:3000',
90
+ });
91
+ if (p.isCancel(urlInput)) {
92
+ p.outro(chalk.yellow('Cancelled'));
93
+ process.exit(0);
94
+ }
95
+ baseUrl = urlInput;
96
+ }
97
+ baseUrl = baseUrl || 'http://localhost:3000';
98
+ // Scan files
99
+ const scanSpinner = ora('Scanning files...').start();
100
+ const files = collectFiles(targetDir);
101
+ scanSpinner.succeed(`Found ${files.length} source files`);
25
102
  if (files.length === 0) {
26
- console.error('No source files found');
103
+ p.log.error('No source files found');
104
+ p.outro(chalk.red('Failed'));
27
105
  process.exit(1);
28
106
  }
29
- console.log('🤖 Generating OpenAPI spec...');
30
- const openai = new OpenAI({ apiKey });
31
- const codeContext = files
32
- .map(f => `### ${f.path}\n\`\`\`\n${f.content}\n\`\`\``)
33
- .join('\n\n');
34
- const response = await openai.chat.completions.create({
35
- model: opts.model,
36
- messages: [{
37
- role: 'system',
38
- content: `You are an expert at generating OpenAPI 3.0 specifications.
107
+ // Generate spec
108
+ const genSpinner = ora('Generating OpenAPI spec with AI...').start();
109
+ try {
110
+ const openai = new OpenAI({ apiKey });
111
+ const codeContext = files
112
+ .map(f => `### ${f.path}\n\`\`\`\n${f.content}\n\`\`\``)
113
+ .join('\n\n');
114
+ const response = await openai.chat.completions.create({
115
+ model: opts.model,
116
+ messages: [{
117
+ role: 'system',
118
+ content: `You are an expert at generating OpenAPI 3.0 specifications.
39
119
  Analyze the provided backend code and generate a complete, valid OpenAPI 3.0.3 YAML spec.
40
120
  Include: all endpoints, HTTP methods, path/query parameters, request bodies, response schemas with properties.
41
121
  Use descriptive summaries. Infer types from the code. Return ONLY valid YAML, no markdown or explanation.`
42
- }, {
43
- role: 'user',
44
- content: `Generate an OpenAPI spec for this backend code. Base URL: ${opts.baseUrl}\n\n${codeContext}`
45
- }],
46
- max_tokens: 16000,
47
- temperature: 0.2,
48
- });
49
- let yaml = response.choices[0].message.content || '';
50
- // Strip markdown fences if present
51
- yaml = yaml.replace(/^```ya?ml\n?/i, '').replace(/\n?```$/i, '').trim();
52
- writeFileSync(opts.output, yaml);
53
- console.log(`✅ Saved to ${opts.output}`);
54
- console.log(` ${response.usage?.total_tokens || '?'} tokens used`);
122
+ }, {
123
+ role: 'user',
124
+ content: `Generate an OpenAPI spec for this backend code. Base URL: ${baseUrl}\n\n${codeContext}`
125
+ }],
126
+ max_tokens: 16000,
127
+ temperature: 0.2,
128
+ });
129
+ let yaml = response.choices[0].message.content || '';
130
+ yaml = yaml.replace(/^```ya?ml\n?/i, '').replace(/\n?```$/i, '').trim();
131
+ writeFileSync(outputFile, yaml);
132
+ const tokens = response.usage?.total_tokens || 0;
133
+ const cost = (tokens * 0.00015 / 1000).toFixed(4);
134
+ genSpinner.succeed('OpenAPI spec generated');
135
+ p.log.success(`Saved to ${chalk.cyan(outputFile)}`);
136
+ p.log.info(`${tokens} tokens used (~$${cost})`);
137
+ p.outro(chalk.green('✨ Done!'));
138
+ }
139
+ catch (error) {
140
+ genSpinner.fail('Generation failed');
141
+ p.log.error(error.message || 'Unknown error');
142
+ p.outro(chalk.red('Failed'));
143
+ process.exit(1);
144
+ }
55
145
  });
56
146
  function collectFiles(dir, root = dir) {
57
147
  const files = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crowgent-openapi",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Generate OpenAPI specs from your backend code using AI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,10 +12,16 @@
12
12
  "dev": "tsx src/cli.ts",
13
13
  "prepublishOnly": "npm run build"
14
14
  },
15
- "files": ["dist", "README.md"],
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
16
19
  "dependencies": {
20
+ "@clack/prompts": "^0.7.0",
21
+ "chalk": "^5.3.0",
17
22
  "commander": "^12.0.0",
18
- "openai": "^4.0.0"
23
+ "openai": "^4.0.0",
24
+ "ora": "^8.0.0"
19
25
  },
20
26
  "devDependencies": {
21
27
  "@types/node": "^20.0.0",
@@ -25,7 +31,16 @@
25
31
  "engines": {
26
32
  "node": ">=18"
27
33
  },
28
- "keywords": ["openapi", "swagger", "api", "generator", "ai", "llm", "gpt", "documentation"],
34
+ "keywords": [
35
+ "openapi",
36
+ "swagger",
37
+ "api",
38
+ "generator",
39
+ "ai",
40
+ "llm",
41
+ "gpt",
42
+ "documentation"
43
+ ],
29
44
  "repository": {
30
45
  "type": "git",
31
46
  "url": "https://github.com/usecrow/crowgent-openapi"
@@ -33,4 +48,3 @@
33
48
  "author": "Crow",
34
49
  "license": "MIT"
35
50
  }
36
-