@kite-copilot/cli 1.0.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 +325 -0
- package/dist/analyzer.d.ts +66 -0
- package/dist/analyzer.js +333 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +139 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +283 -0
- package/dist/login.d.ts +12 -0
- package/dist/login.js +149 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +6 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# @kite/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for analyzing frontend codebases to discover API mappings. The CLI uses Claude AI to analyze your frontend code, identify routes, API calls, and generate mappings that connect your frontend to the Kite backend.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
### Claude CLI
|
|
8
|
+
|
|
9
|
+
The Kite CLI requires Claude CLI to be installed for code analysis:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @anthropic-ai/claude-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Verify installation:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
claude --version
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Node.js
|
|
22
|
+
|
|
23
|
+
Requires Node.js 18.0.0 or higher.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
### From npm (when published)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g @kite/cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### From source
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd agent/cli-npm
|
|
37
|
+
npm install
|
|
38
|
+
npm run build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then run with:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node dist/index.js <command>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or create a global link:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm link
|
|
51
|
+
kite <command>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
### `kite login`
|
|
57
|
+
|
|
58
|
+
Authenticate with the Kite backend.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
kite login [--backend-url <url>]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Options:**
|
|
65
|
+
|
|
66
|
+
| Option | Description |
|
|
67
|
+
|--------|-------------|
|
|
68
|
+
| `--backend-url <url>` | Kite backend URL (default: https://api.kite.com) |
|
|
69
|
+
|
|
70
|
+
**Environment Variables:**
|
|
71
|
+
|
|
72
|
+
| Variable | Description |
|
|
73
|
+
|----------|-------------|
|
|
74
|
+
| `KITE_BACKEND_URL` | Override default backend URL |
|
|
75
|
+
|
|
76
|
+
**Example:**
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Login to production
|
|
80
|
+
kite login
|
|
81
|
+
|
|
82
|
+
# Login to local development server
|
|
83
|
+
kite login --backend-url http://localhost:5002
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
When prompted, enter your API token from the Kite dashboard.
|
|
87
|
+
|
|
88
|
+
### `kite logout`
|
|
89
|
+
|
|
90
|
+
Clear stored credentials.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
kite logout
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `kite status`
|
|
97
|
+
|
|
98
|
+
Show current login status.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
kite status
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `kite map`
|
|
105
|
+
|
|
106
|
+
Analyze a frontend codebase to discover API mappings.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
kite map <path> [options]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Arguments:**
|
|
113
|
+
|
|
114
|
+
| Argument | Description |
|
|
115
|
+
|----------|-------------|
|
|
116
|
+
| `<path>` | Path to the frontend codebase directory |
|
|
117
|
+
|
|
118
|
+
**Options:**
|
|
119
|
+
|
|
120
|
+
| Option | Description |
|
|
121
|
+
|--------|-------------|
|
|
122
|
+
| `-o, --output <file>` | Output JSON file path (default: kite-mappings.json) |
|
|
123
|
+
| `-u, --upload` | Upload mappings to Kite backend after analysis |
|
|
124
|
+
| `-q, --quiet` | Suppress terminal visualization output |
|
|
125
|
+
| `-v, --verbose` | Show verbose debug output |
|
|
126
|
+
| `--timeout <seconds>` | Analysis timeout in seconds (default: 300) |
|
|
127
|
+
| `--no-output-file` | Don't generate output JSON file |
|
|
128
|
+
|
|
129
|
+
**Examples:**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Analyze and save to default file
|
|
133
|
+
kite map ./my-frontend-app
|
|
134
|
+
|
|
135
|
+
# Analyze with custom output file
|
|
136
|
+
kite map ./my-frontend-app -o custom-mappings.json
|
|
137
|
+
|
|
138
|
+
# Analyze and upload to backend
|
|
139
|
+
kite map ./my-frontend-app --upload
|
|
140
|
+
|
|
141
|
+
# Quiet mode - just generate the file
|
|
142
|
+
kite map ./my-frontend-app --quiet
|
|
143
|
+
|
|
144
|
+
# Verbose mode for debugging
|
|
145
|
+
kite map ./my-frontend-app --verbose
|
|
146
|
+
|
|
147
|
+
# Increase timeout for large codebases
|
|
148
|
+
kite map ./my-frontend-app --timeout 600
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Configuration
|
|
152
|
+
|
|
153
|
+
Credentials are stored in `~/.kite/config.json`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"token": "your-api-token",
|
|
158
|
+
"org_id": "organization-uuid",
|
|
159
|
+
"api_config_id": "config-uuid",
|
|
160
|
+
"email": "your@email.com",
|
|
161
|
+
"org_name": "Your Organization",
|
|
162
|
+
"backend_url": "https://api.kite.com"
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Output Format
|
|
167
|
+
|
|
168
|
+
The `kite map` command generates a JSON file with the following structure:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"version": "1.0",
|
|
173
|
+
"generated_at": "2024-01-15T10:30:00Z",
|
|
174
|
+
"codebase_path": "/path/to/frontend",
|
|
175
|
+
"routes": [
|
|
176
|
+
{
|
|
177
|
+
"route_name": "customers",
|
|
178
|
+
"file_path": "pages/customers.tsx",
|
|
179
|
+
"component_name": "CustomersPage"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"api_calls": [
|
|
183
|
+
{
|
|
184
|
+
"endpoint": "/api/customers",
|
|
185
|
+
"method": "GET",
|
|
186
|
+
"component_file": "pages/customers.tsx",
|
|
187
|
+
"action_form_type": null
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"mappings": [
|
|
191
|
+
{
|
|
192
|
+
"tool_name": "customers",
|
|
193
|
+
"api_endpoint": "/api/customers",
|
|
194
|
+
"navigation_page": "customers",
|
|
195
|
+
"action_form_type": "addCustomer",
|
|
196
|
+
"frontend_component": "CustomersPage",
|
|
197
|
+
"frontend_file_path": "pages/customers.tsx"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Workflow
|
|
204
|
+
|
|
205
|
+
### First-time Setup
|
|
206
|
+
|
|
207
|
+
1. Install the CLI and Claude CLI
|
|
208
|
+
2. Get your API token from the Kite dashboard
|
|
209
|
+
3. Login: `kite login`
|
|
210
|
+
4. Verify: `kite status`
|
|
211
|
+
|
|
212
|
+
### Analyzing a Codebase
|
|
213
|
+
|
|
214
|
+
1. Navigate to your project or provide the path
|
|
215
|
+
2. Run: `kite map ./your-frontend --upload`
|
|
216
|
+
3. Review the generated mappings
|
|
217
|
+
4. Mappings are now available in the Kite backend
|
|
218
|
+
|
|
219
|
+
### Local Development
|
|
220
|
+
|
|
221
|
+
For local development without Supabase:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# 1. Seed local test data
|
|
225
|
+
python agent/app/scripts/seed_local_dev.py
|
|
226
|
+
|
|
227
|
+
# 2. Start the backend
|
|
228
|
+
cd agent && python server.py
|
|
229
|
+
|
|
230
|
+
# 3. Login to local backend
|
|
231
|
+
kite login --backend-url http://localhost:5002
|
|
232
|
+
|
|
233
|
+
# 4. Map your codebase
|
|
234
|
+
kite map ./your-frontend --upload
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Troubleshooting
|
|
238
|
+
|
|
239
|
+
### Claude CLI not found
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Error: Claude CLI not found
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Solution:** Install Claude CLI:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
npm install -g @anthropic-ai/claude-cli
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Analysis timeout
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Error: Analysis timed out after 300 seconds
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Solution:** Increase the timeout:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
kite map ./large-codebase --timeout 600
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Authentication failed
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
Error: Invalid token
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Solution:** Re-run login:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
kite logout
|
|
273
|
+
kite login
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Not logged in
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
Error: Not logged in. Run 'kite login' first.
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Solution:** Login before uploading:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
kite login
|
|
286
|
+
kite map ./frontend --upload
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Path is not a frontend project
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
Error: Directory doesn't appear to be a frontend project
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Solution:** Ensure your path contains one of:
|
|
296
|
+
- `package.json`
|
|
297
|
+
- `tsconfig.json`
|
|
298
|
+
- `src/` directory
|
|
299
|
+
- `app/` directory
|
|
300
|
+
- `components/` directory
|
|
301
|
+
- `pages/` directory
|
|
302
|
+
|
|
303
|
+
## Development
|
|
304
|
+
|
|
305
|
+
### Building
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
npm run build
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Development mode
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npm run dev
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Running from source
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
node dist/index.js map ./test-frontend
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## License
|
|
324
|
+
|
|
325
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Analyzer Module
|
|
3
|
+
*
|
|
4
|
+
* Invokes Claude Code CLI to analyze frontend codebases.
|
|
5
|
+
* Parses JSON responses into structured analysis results.
|
|
6
|
+
*/
|
|
7
|
+
import type { FrontendAnalysisResult, FrontendRoute, FrontendApiCall, FrontendMapping, AnalyzeOptions } from "./types.js";
|
|
8
|
+
export declare class AnalyzerError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class ClaudeNotFoundError extends AnalyzerError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class ClaudeTimeoutError extends AnalyzerError {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class ClaudeParseError extends AnalyzerError {
|
|
18
|
+
constructor(message: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validate that a path looks like a frontend project
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateCodebasePath(codebasePath: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Check if Claude CLI is available
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkClaudeAvailable(claudePath?: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Parse Claude's response text into a structured object
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseClaudeResponse(responseText: string): {
|
|
32
|
+
routes: FrontendRoute[];
|
|
33
|
+
api_calls: FrontendApiCall[];
|
|
34
|
+
mappings: FrontendMapping[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Build a complete analysis result
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAnalysisResult(data: {
|
|
40
|
+
routes: FrontendRoute[];
|
|
41
|
+
api_calls: FrontendApiCall[];
|
|
42
|
+
mappings: FrontendMapping[];
|
|
43
|
+
}, codebasePath: string): FrontendAnalysisResult;
|
|
44
|
+
/**
|
|
45
|
+
* Analyze a frontend codebase using Claude Code
|
|
46
|
+
*/
|
|
47
|
+
export declare function analyzeWithClaude(codebasePath: string, options?: AnalyzeOptions): Promise<FrontendAnalysisResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Save analysis result to a JSON file
|
|
50
|
+
*/
|
|
51
|
+
export declare function saveResultToFile(result: FrontendAnalysisResult, outputPath: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Upload mappings to the backend
|
|
54
|
+
*/
|
|
55
|
+
export declare function uploadMappings(result: FrontendAnalysisResult, config: {
|
|
56
|
+
backendUrl: string;
|
|
57
|
+
token: string;
|
|
58
|
+
orgId: string;
|
|
59
|
+
apiConfigId: string;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
success: boolean;
|
|
62
|
+
mappings_uploaded: number;
|
|
63
|
+
mappings_merged: number;
|
|
64
|
+
mappings_skipped: number;
|
|
65
|
+
message: string;
|
|
66
|
+
}>;
|
package/dist/analyzer.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code Analyzer Module
|
|
4
|
+
*
|
|
5
|
+
* Invokes Claude Code CLI to analyze frontend codebases.
|
|
6
|
+
* Parses JSON responses into structured analysis results.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.ClaudeParseError = exports.ClaudeTimeoutError = exports.ClaudeNotFoundError = exports.AnalyzerError = void 0;
|
|
43
|
+
exports.validateCodebasePath = validateCodebasePath;
|
|
44
|
+
exports.checkClaudeAvailable = checkClaudeAvailable;
|
|
45
|
+
exports.parseClaudeResponse = parseClaudeResponse;
|
|
46
|
+
exports.buildAnalysisResult = buildAnalysisResult;
|
|
47
|
+
exports.analyzeWithClaude = analyzeWithClaude;
|
|
48
|
+
exports.saveResultToFile = saveResultToFile;
|
|
49
|
+
exports.uploadMappings = uploadMappings;
|
|
50
|
+
const node_child_process_1 = require("node:child_process");
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
53
|
+
// Analysis prompt for Claude Code (same as Python version)
|
|
54
|
+
const ANALYSIS_PROMPT = `Analyze this frontend codebase and extract:
|
|
55
|
+
|
|
56
|
+
1. All page routes and their component files
|
|
57
|
+
- Look for route definitions (React Router, Next.js pages, etc.)
|
|
58
|
+
- Map route names to their component files
|
|
59
|
+
|
|
60
|
+
2. All API calls (fetch, axios, etc.) with endpoints and HTTP methods
|
|
61
|
+
- Find all HTTP requests to backend APIs
|
|
62
|
+
- Identify the HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
63
|
+
- Note which component file makes each call
|
|
64
|
+
|
|
65
|
+
3. Form action types (event names like "addCustomer", "updateSettings")
|
|
66
|
+
- Find custom event dispatches or action handlers
|
|
67
|
+
- Look for form submission handlers and their action types
|
|
68
|
+
|
|
69
|
+
4. Map API endpoints to their likely tool names
|
|
70
|
+
- Extract the resource name from the endpoint (e.g., /api/customers -> customers)
|
|
71
|
+
- Associate with navigation pages and form types
|
|
72
|
+
|
|
73
|
+
Return a JSON object with this EXACT structure (no markdown, no explanation, just JSON):
|
|
74
|
+
{
|
|
75
|
+
"routes": [
|
|
76
|
+
{"route_name": "string", "file_path": "string", "component_name": "string"}
|
|
77
|
+
],
|
|
78
|
+
"api_calls": [
|
|
79
|
+
{"endpoint": "string", "method": "string", "component_file": "string", "action_form_type": "string or null"}
|
|
80
|
+
],
|
|
81
|
+
"mappings": [
|
|
82
|
+
{
|
|
83
|
+
"tool_name": "string",
|
|
84
|
+
"api_endpoint": "string",
|
|
85
|
+
"navigation_page": "string or null",
|
|
86
|
+
"action_form_type": "string or null",
|
|
87
|
+
"frontend_component": "string or null",
|
|
88
|
+
"frontend_file_path": "string or null"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Important:
|
|
94
|
+
- tool_name should be the resource name extracted from the API endpoint (e.g., "/api/customers" -> "customers")
|
|
95
|
+
- navigation_page should match a route_name if the API is used on that page
|
|
96
|
+
- action_form_type should match form handlers or custom events
|
|
97
|
+
- Return ONLY valid JSON, no markdown code blocks or explanations`;
|
|
98
|
+
class AnalyzerError extends Error {
|
|
99
|
+
constructor(message) {
|
|
100
|
+
super(message);
|
|
101
|
+
this.name = "AnalyzerError";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.AnalyzerError = AnalyzerError;
|
|
105
|
+
class ClaudeNotFoundError extends AnalyzerError {
|
|
106
|
+
constructor(message) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = "ClaudeNotFoundError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.ClaudeNotFoundError = ClaudeNotFoundError;
|
|
112
|
+
class ClaudeTimeoutError extends AnalyzerError {
|
|
113
|
+
constructor(message) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.name = "ClaudeTimeoutError";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.ClaudeTimeoutError = ClaudeTimeoutError;
|
|
119
|
+
class ClaudeParseError extends AnalyzerError {
|
|
120
|
+
constructor(message) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "ClaudeParseError";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.ClaudeParseError = ClaudeParseError;
|
|
126
|
+
/**
|
|
127
|
+
* Validate that a path looks like a frontend project
|
|
128
|
+
*/
|
|
129
|
+
function validateCodebasePath(codebasePath) {
|
|
130
|
+
if (!fs.existsSync(codebasePath)) {
|
|
131
|
+
throw new AnalyzerError(`Codebase path does not exist: ${codebasePath}`);
|
|
132
|
+
}
|
|
133
|
+
const stat = fs.statSync(codebasePath);
|
|
134
|
+
if (!stat.isDirectory()) {
|
|
135
|
+
throw new AnalyzerError(`Codebase path is not a directory: ${codebasePath}`);
|
|
136
|
+
}
|
|
137
|
+
// Check for common frontend project indicators
|
|
138
|
+
const indicators = [
|
|
139
|
+
"package.json",
|
|
140
|
+
"tsconfig.json",
|
|
141
|
+
"src",
|
|
142
|
+
"app",
|
|
143
|
+
"components",
|
|
144
|
+
"pages",
|
|
145
|
+
];
|
|
146
|
+
const foundIndicators = indicators.filter((ind) => fs.existsSync(path.join(codebasePath, ind)));
|
|
147
|
+
if (foundIndicators.length === 0) {
|
|
148
|
+
throw new AnalyzerError(`Directory doesn't appear to be a frontend project. Expected one of: ${indicators.join(", ")}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if Claude CLI is available
|
|
153
|
+
*/
|
|
154
|
+
async function checkClaudeAvailable(claudePath = "claude") {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
const proc = (0, node_child_process_1.spawn)(claudePath, ["--version"], { stdio: "pipe" });
|
|
157
|
+
proc.on("close", (code) => {
|
|
158
|
+
resolve(code === 0);
|
|
159
|
+
});
|
|
160
|
+
proc.on("error", () => {
|
|
161
|
+
resolve(false);
|
|
162
|
+
});
|
|
163
|
+
// Timeout after 5 seconds
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
proc.kill();
|
|
166
|
+
resolve(false);
|
|
167
|
+
}, 5000);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Parse Claude's response text into a structured object
|
|
172
|
+
*/
|
|
173
|
+
function parseClaudeResponse(responseText) {
|
|
174
|
+
if (!responseText || !responseText.trim()) {
|
|
175
|
+
throw new ClaudeParseError("Empty response from Claude");
|
|
176
|
+
}
|
|
177
|
+
let text = responseText.trim();
|
|
178
|
+
// Remove markdown code blocks if present
|
|
179
|
+
if (text.startsWith("```")) {
|
|
180
|
+
const lines = text.split("\n");
|
|
181
|
+
// Remove first line (```json or ```)
|
|
182
|
+
const contentLines = lines.slice(1);
|
|
183
|
+
// Find and remove closing ```
|
|
184
|
+
const endIndex = contentLines.findIndex((line) => line.trim() === "```");
|
|
185
|
+
if (endIndex !== -1) {
|
|
186
|
+
text = contentLines.slice(0, endIndex).join("\n");
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
text = contentLines.join("\n");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Try to find JSON object in the text
|
|
193
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
194
|
+
if (jsonMatch) {
|
|
195
|
+
text = jsonMatch[0];
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const data = JSON.parse(text);
|
|
199
|
+
return {
|
|
200
|
+
routes: data.routes || [],
|
|
201
|
+
api_calls: data.api_calls || [],
|
|
202
|
+
mappings: data.mappings || [],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
throw new ClaudeParseError(`Failed to parse JSON response: ${e}\nResponse: ${text.substring(0, 500)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Build a complete analysis result
|
|
211
|
+
*/
|
|
212
|
+
function buildAnalysisResult(data, codebasePath) {
|
|
213
|
+
return {
|
|
214
|
+
version: "1.0",
|
|
215
|
+
generated_at: new Date().toISOString(),
|
|
216
|
+
codebase_path: codebasePath,
|
|
217
|
+
routes: data.routes,
|
|
218
|
+
api_calls: data.api_calls,
|
|
219
|
+
mappings: data.mappings,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Analyze a frontend codebase using Claude Code
|
|
224
|
+
*/
|
|
225
|
+
async function analyzeWithClaude(codebasePath, options = {}) {
|
|
226
|
+
const { timeout = 300, claudePath = "claude" } = options;
|
|
227
|
+
// Validate path
|
|
228
|
+
validateCodebasePath(codebasePath);
|
|
229
|
+
// Get absolute path
|
|
230
|
+
const absolutePath = path.resolve(codebasePath);
|
|
231
|
+
// Check Claude is available
|
|
232
|
+
const claudeAvailable = await checkClaudeAvailable(claudePath);
|
|
233
|
+
if (!claudeAvailable) {
|
|
234
|
+
throw new ClaudeNotFoundError("Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-cli");
|
|
235
|
+
}
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
const args = ["--print", "-p", ANALYSIS_PROMPT];
|
|
238
|
+
const proc = (0, node_child_process_1.spawn)(claudePath, args, {
|
|
239
|
+
cwd: absolutePath,
|
|
240
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
241
|
+
});
|
|
242
|
+
let stdout = "";
|
|
243
|
+
let stderr = "";
|
|
244
|
+
proc.stdout.on("data", (data) => {
|
|
245
|
+
stdout += data.toString();
|
|
246
|
+
});
|
|
247
|
+
proc.stderr.on("data", (data) => {
|
|
248
|
+
stderr += data.toString();
|
|
249
|
+
});
|
|
250
|
+
// Set timeout
|
|
251
|
+
const timeoutId = setTimeout(() => {
|
|
252
|
+
proc.kill();
|
|
253
|
+
reject(new ClaudeTimeoutError(`Claude analysis timed out after ${timeout} seconds`));
|
|
254
|
+
}, timeout * 1000);
|
|
255
|
+
proc.on("close", (code) => {
|
|
256
|
+
clearTimeout(timeoutId);
|
|
257
|
+
if (code !== 0) {
|
|
258
|
+
reject(new AnalyzerError(`Claude exited with code ${code}: ${stderr}`));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const data = parseClaudeResponse(stdout);
|
|
263
|
+
const result = buildAnalysisResult(data, absolutePath);
|
|
264
|
+
resolve(result);
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
reject(e);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
proc.on("error", (err) => {
|
|
271
|
+
clearTimeout(timeoutId);
|
|
272
|
+
reject(new ClaudeNotFoundError(`Failed to spawn Claude: ${err.message}`));
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Save analysis result to a JSON file
|
|
278
|
+
*/
|
|
279
|
+
function saveResultToFile(result, outputPath) {
|
|
280
|
+
const json = JSON.stringify(result, null, 2);
|
|
281
|
+
fs.writeFileSync(outputPath, json, "utf-8");
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Upload mappings to the backend
|
|
285
|
+
*/
|
|
286
|
+
async function uploadMappings(result, config) {
|
|
287
|
+
const url = `${config.backendUrl}/api/mappings/frontend?org_id=${config.orgId}&api_config_id=${config.apiConfigId}`;
|
|
288
|
+
// Filter out mappings with null/empty api_endpoint (invalid data from Claude)
|
|
289
|
+
const validMappings = result.mappings.filter((m) => m.api_endpoint && m.tool_name);
|
|
290
|
+
const payload = {
|
|
291
|
+
mappings: validMappings.map((m) => ({
|
|
292
|
+
tool_name: m.tool_name,
|
|
293
|
+
api_endpoint: m.api_endpoint,
|
|
294
|
+
navigation_page: m.navigation_page,
|
|
295
|
+
action_form_type: m.action_form_type,
|
|
296
|
+
frontend_component: m.frontend_component,
|
|
297
|
+
frontend_file_path: m.frontend_file_path,
|
|
298
|
+
})),
|
|
299
|
+
metadata: {
|
|
300
|
+
codebase_path: result.codebase_path,
|
|
301
|
+
analyzed_at: result.generated_at,
|
|
302
|
+
version: result.version,
|
|
303
|
+
routes_count: result.routes.length,
|
|
304
|
+
api_calls_count: result.api_calls.length,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
const response = await fetch(url, {
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: {
|
|
310
|
+
"Content-Type": "application/json",
|
|
311
|
+
Authorization: `Bearer ${config.token}`,
|
|
312
|
+
},
|
|
313
|
+
body: JSON.stringify(payload),
|
|
314
|
+
});
|
|
315
|
+
if (response.status === 401) {
|
|
316
|
+
throw new Error("Authentication failed. Please run 'kite login' again.");
|
|
317
|
+
}
|
|
318
|
+
if (response.status === 403) {
|
|
319
|
+
throw new Error("Access denied for this organization.");
|
|
320
|
+
}
|
|
321
|
+
if (!response.ok) {
|
|
322
|
+
const text = await response.text();
|
|
323
|
+
throw new Error(`Upload failed: ${response.status} ${text}`);
|
|
324
|
+
}
|
|
325
|
+
const data = await response.json();
|
|
326
|
+
return {
|
|
327
|
+
success: true,
|
|
328
|
+
mappings_uploaded: data.mappings_uploaded || validMappings.length,
|
|
329
|
+
mappings_merged: data.mappings_merged || 0,
|
|
330
|
+
mappings_skipped: data.mappings_skipped || (result.mappings.length - validMappings.length),
|
|
331
|
+
message: data.message || "Mappings uploaded successfully",
|
|
332
|
+
};
|
|
333
|
+
}
|