@codecora/cli 0.0.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 +242 -0
- package/bin/cora.js +183 -0
- package/dist/api/client.js +221 -0
- package/dist/api/types.js +33 -0
- package/dist/commands/auth.js +255 -0
- package/dist/commands/hook.js +160 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/review.js +215 -0
- package/dist/config/storage.js +166 -0
- package/dist/git/diff.js +162 -0
- package/dist/index.js +12 -0
- package/dist/utils/exec.js +76 -0
- package/dist/version.js +1 -0
- package/package.json +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# CORA CLI
|
|
2
|
+
|
|
3
|
+
> AI-powered code review before you commit.
|
|
4
|
+
|
|
5
|
+
CORA CLI brings AI code review to your local development workflow. Get instant feedback on your code changes before they leave your machine.
|
|
6
|
+
|
|
7
|
+
## ✅ Verified & Tested
|
|
8
|
+
|
|
9
|
+
All CLI features have been tested and verified working:
|
|
10
|
+
|
|
11
|
+
| Feature | Status | Description |
|
|
12
|
+
|---------|--------|-------------|
|
|
13
|
+
| Authentication | ✅ | `cora auth status` shows user info, session validity, expiration |
|
|
14
|
+
| Code Review | ✅ | Reviews staged/unstaged changes with token usage tracking |
|
|
15
|
+
| Output Formats | ✅ | Pretty (default), JSON, Compact formats |
|
|
16
|
+
| Git Hooks | ✅ | Automatic pre-commit review on every commit |
|
|
17
|
+
| Mock Mode | ✅ | Testing without using API credits |
|
|
18
|
+
| Error Handling | ✅ | Graceful handling of network errors and invalid input |
|
|
19
|
+
|
|
20
|
+
**Response includes:**
|
|
21
|
+
- Issues with severity (critical, major, minor, info)
|
|
22
|
+
- Token breakdown (input/output/execution time)
|
|
23
|
+
- Quota information
|
|
24
|
+
- AI-generated summaries and suggested fixes
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Pre-commit Review** - Review code changes before committing
|
|
29
|
+
- **Instant Feedback** - Get AI analysis in seconds
|
|
30
|
+
- **Privacy-first** - Your code diffs are analyzed directly by your AI provider
|
|
31
|
+
- **Git Hooks** - Automatic review on every commit
|
|
32
|
+
- **Cross-platform** - Works on macOS, Linux, and Windows
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### Option 1: Using npm/bun (Requires Node.js 20+)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Using npm
|
|
40
|
+
npm install -g @codecora/cli
|
|
41
|
+
|
|
42
|
+
# Using bun
|
|
43
|
+
bun install -g @cora/cli
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Option 2: Standalone Binary (No Node.js Required)
|
|
47
|
+
|
|
48
|
+
Download the appropriate binary for your platform from the [releases page](https://github.com/ajianaz/cora/releases):
|
|
49
|
+
|
|
50
|
+
| Platform | Download |
|
|
51
|
+
|----------|----------|
|
|
52
|
+
| macOS (Intel) | `cora-macos-x64` |
|
|
53
|
+
| macOS (Apple Silicon) | `cora-macos-arm64` |
|
|
54
|
+
| Linux (x64) | `cora-linux-x64` |
|
|
55
|
+
| Windows (x64) | `cora-win-x64.exe` |
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Example for macOS
|
|
59
|
+
curl -L https://github.com/ajianaz/cora/releases/latest/download/cora-macos-arm64 -o cora
|
|
60
|
+
chmod +x cora
|
|
61
|
+
sudo mv cora /usr/local/bin/
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 1. Login to CORA
|
|
68
|
+
cora auth login
|
|
69
|
+
|
|
70
|
+
# 2. Review staged changes
|
|
71
|
+
cora review
|
|
72
|
+
|
|
73
|
+
# 3. Enable automatic pre-commit review (optional)
|
|
74
|
+
cora enable
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
### Authentication
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Login via GitHub OAuth
|
|
83
|
+
cora auth login
|
|
84
|
+
|
|
85
|
+
# Check authentication status
|
|
86
|
+
cora auth status
|
|
87
|
+
|
|
88
|
+
# Logout
|
|
89
|
+
cora auth logout
|
|
90
|
+
|
|
91
|
+
# View/set configuration
|
|
92
|
+
cora auth config [key] [value]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Important:** For CLI authentication to work, you must add `http://localhost:4200/callback` to your GitHub App's redirect URIs:
|
|
96
|
+
|
|
97
|
+
1. Go to your GitHub App settings
|
|
98
|
+
2. Navigate to **General** → **Homepage URL**
|
|
99
|
+
3. Add `http://localhost:4200/callback` to the redirect URIs
|
|
100
|
+
|
|
101
|
+
### Review Code
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Review staged changes (default)
|
|
105
|
+
cora review
|
|
106
|
+
|
|
107
|
+
# Review unstaged changes
|
|
108
|
+
cora review --unstaged
|
|
109
|
+
|
|
110
|
+
# Review specific files
|
|
111
|
+
cora review --files src/app.ts src/utils.ts
|
|
112
|
+
|
|
113
|
+
# Mock mode for testing
|
|
114
|
+
cora review --mock
|
|
115
|
+
|
|
116
|
+
# JSON output for CI/CD
|
|
117
|
+
cora review --format json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Git Hooks
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Install pre-commit hook
|
|
124
|
+
cora hook install
|
|
125
|
+
|
|
126
|
+
# Uninstall hook
|
|
127
|
+
cora hook uninstall
|
|
128
|
+
|
|
129
|
+
# List installed hooks
|
|
130
|
+
cora hook list
|
|
131
|
+
|
|
132
|
+
# Quick enable/disable
|
|
133
|
+
cora enable # Install pre-commit hook
|
|
134
|
+
cora disable # Uninstall pre-commit hook
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Options
|
|
138
|
+
|
|
139
|
+
| Option | Description |
|
|
140
|
+
|--------|-------------|
|
|
141
|
+
| `-w, --workspace <id>` | Workspace ID |
|
|
142
|
+
| `-r, --repository <name>` | Repository name (owner/repo) |
|
|
143
|
+
| `-b, --branch <name>` | Branch name |
|
|
144
|
+
| `-s, --staged` | Review staged changes (default) |
|
|
145
|
+
| `-u, --unstaged` | Review unstaged changes |
|
|
146
|
+
| `-f, --files <files...>` | Review specific files |
|
|
147
|
+
| `-m, --max-tokens <number>` | Maximum tokens to use |
|
|
148
|
+
| `-i, --include-walkthrough` | Include full walkthrough |
|
|
149
|
+
| `--mock` | Mock mode for testing |
|
|
150
|
+
| `--format <format>` | Output format (pretty, json, compact) |
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
Configuration is stored in `~/.codecora/`:
|
|
155
|
+
|
|
156
|
+
- `auth.json` - Session token and user info
|
|
157
|
+
- `config.json` - Preferences and settings
|
|
158
|
+
|
|
159
|
+
### Environment Variables
|
|
160
|
+
|
|
161
|
+
| Variable | Description |
|
|
162
|
+
|----------|-------------|
|
|
163
|
+
| `CORA_SKIP` | Skip pre-commit hook when set to 1 |
|
|
164
|
+
| `CORA_SERVER` | Override server URL |
|
|
165
|
+
| `CORA_WORKSPACE` | Override workspace ID |
|
|
166
|
+
| `CORA_API_KEY` | API key for CI/CD authentication (see [API Keys Documentation](../../../docs/API_KEYS.md)) |
|
|
167
|
+
|
|
168
|
+
## CI/CD Integration
|
|
169
|
+
|
|
170
|
+
### Using API Keys (Recommended)
|
|
171
|
+
|
|
172
|
+
For CI/CD workflows, use API Keys instead of session tokens:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
# Example: GitHub Actions
|
|
176
|
+
- name: Run CORA Review
|
|
177
|
+
run: |
|
|
178
|
+
npm install -g @codecora/cli
|
|
179
|
+
cora review --format json > review-results.json
|
|
180
|
+
env:
|
|
181
|
+
CORA_API_KEY: ${{ secrets.CORA_API_KEY }}
|
|
182
|
+
CORA_SERVER_URL: https://codecora.dev
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Benefits of API Keys:**
|
|
186
|
+
- No interactive login required
|
|
187
|
+
- Can be scoped to specific permissions
|
|
188
|
+
- Can have expiration dates
|
|
189
|
+
- Revocable without affecting other workflows
|
|
190
|
+
|
|
191
|
+
**Create API Key:** Visit Dashboard → API Keys to generate a key for CI/CD.
|
|
192
|
+
|
|
193
|
+
See [API Keys Documentation](../../../docs/API_KEYS.md) for complete guide.
|
|
194
|
+
|
|
195
|
+
## Troubleshooting
|
|
196
|
+
|
|
197
|
+
### Authentication Errors
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Check your authentication status
|
|
201
|
+
cora auth status
|
|
202
|
+
|
|
203
|
+
# Re-authenticate if needed
|
|
204
|
+
cora auth login
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Hook Not Running
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Verify hook is installed
|
|
211
|
+
cora hook list
|
|
212
|
+
|
|
213
|
+
# Reinstall hook
|
|
214
|
+
cora hook uninstall && cora hook install
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Skip Hook Temporarily
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Skip for one commit
|
|
221
|
+
git commit --no-verify -m "message"
|
|
222
|
+
|
|
223
|
+
# Or use environment variable
|
|
224
|
+
CORA_SKIP=1 git commit -m "message"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Requirements
|
|
228
|
+
|
|
229
|
+
- **Node.js 20+** (if installing via npm/bun)
|
|
230
|
+
- **OR** standalone binary (no dependencies)
|
|
231
|
+
- A Codecora account (sign up at https://codecora.dev)
|
|
232
|
+
- An OpenAI-compatible API key configured in your workspace
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
MIT © [CORA](https://codecora.dev)
|
|
237
|
+
|
|
238
|
+
## Support
|
|
239
|
+
|
|
240
|
+
- [Documentation](https://codecora.dev/docs/cli)
|
|
241
|
+
- [GitHub Issues](https://github.com/ajianaz/cora/issues)
|
|
242
|
+
- [GitHub Discussions](https://github.com/ajianaz/cora/discussions)
|
package/bin/cora.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CORA CLI Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Main entry point for the cora command-line tool.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { login, logout, status, config as authConfig } from '../dist/commands/auth.js';
|
|
10
|
+
import { review, autoReview } from '../dist/commands/review.js';
|
|
11
|
+
import { installHook, uninstallHook, listHooks, enable, disable } from '../dist/commands/hook.js';
|
|
12
|
+
import { VERSION } from '../dist/version.js';
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('cora')
|
|
18
|
+
.description('CORA - AI-powered code review for git commits')
|
|
19
|
+
.version(VERSION);
|
|
20
|
+
|
|
21
|
+
// Auth commands
|
|
22
|
+
const authCmd = program
|
|
23
|
+
.command('auth')
|
|
24
|
+
.description('Authentication commands');
|
|
25
|
+
|
|
26
|
+
authCmd
|
|
27
|
+
.command('login')
|
|
28
|
+
.description('Login to CORA via GitHub OAuth')
|
|
29
|
+
.option('-s, --server <url>', 'Server URL')
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
try {
|
|
32
|
+
await login(options.server);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
authCmd
|
|
40
|
+
.command('logout')
|
|
41
|
+
.description('Logout from CORA')
|
|
42
|
+
.action(async () => {
|
|
43
|
+
try {
|
|
44
|
+
await logout();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
authCmd
|
|
52
|
+
.command('status')
|
|
53
|
+
.description('Show authentication status')
|
|
54
|
+
.action(async () => {
|
|
55
|
+
try {
|
|
56
|
+
await status();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
authCmd
|
|
64
|
+
.command('config')
|
|
65
|
+
.description('View or set configuration')
|
|
66
|
+
.argument('[key]', 'Configuration key')
|
|
67
|
+
.argument('[value]', 'Configuration value')
|
|
68
|
+
.action(async (key, value) => {
|
|
69
|
+
try {
|
|
70
|
+
await authConfig(key, value);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Review command
|
|
78
|
+
program
|
|
79
|
+
.command('review')
|
|
80
|
+
.description('Review code changes')
|
|
81
|
+
.option('-w, --workspace <id>', 'Workspace ID')
|
|
82
|
+
.option('-r, --repository <name>', 'Repository name (owner/repo)')
|
|
83
|
+
.option('-b, --branch <name>', 'Branch name')
|
|
84
|
+
.option('-s, --staged', 'Review staged changes (default: true)')
|
|
85
|
+
.option('-u, --unstaged', 'Review unstaged changes')
|
|
86
|
+
.option('-f, --files <files...>', 'Review specific files')
|
|
87
|
+
.option('-m, --max-tokens <number>', 'Maximum tokens to use', parseInt)
|
|
88
|
+
.option('-i, --include-walkthrough', 'Include full walkthrough')
|
|
89
|
+
.option('--mock', 'Mock mode for testing')
|
|
90
|
+
.option('--format <format>', 'Output format (pretty, json, compact)', 'pretty')
|
|
91
|
+
.action(async (options) => {
|
|
92
|
+
try {
|
|
93
|
+
const exitCode = await review({
|
|
94
|
+
workspace: options.workspace,
|
|
95
|
+
repository: options.repository,
|
|
96
|
+
branch: options.branch,
|
|
97
|
+
staged: options.unstaged ? false : options.staged !== false,
|
|
98
|
+
files: options.files,
|
|
99
|
+
maxTokens: options.maxTokens,
|
|
100
|
+
includeWalkthrough: options.includeWalkthrough,
|
|
101
|
+
mock: options.mock,
|
|
102
|
+
format: options.format,
|
|
103
|
+
});
|
|
104
|
+
process.exit(exitCode);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Hook commands
|
|
112
|
+
const hookCmd = program
|
|
113
|
+
.command('hook')
|
|
114
|
+
.description('Git hook management');
|
|
115
|
+
|
|
116
|
+
hookCmd
|
|
117
|
+
.command('install')
|
|
118
|
+
.description('Install git hook for automatic review')
|
|
119
|
+
.option('-t, --type <type>', 'Hook type (pre-commit, pre-push)', 'pre-commit')
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
try {
|
|
122
|
+
await installHook(options.type);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
hookCmd
|
|
130
|
+
.command('uninstall')
|
|
131
|
+
.description('Uninstall git hook')
|
|
132
|
+
.option('-t, --type <type>', 'Hook type (pre-commit, pre-push)', 'pre-commit')
|
|
133
|
+
.action(async (options) => {
|
|
134
|
+
try {
|
|
135
|
+
await uninstallHook(options.type);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
hookCmd
|
|
143
|
+
.command('list')
|
|
144
|
+
.description('List installed hooks')
|
|
145
|
+
.action(async () => {
|
|
146
|
+
try {
|
|
147
|
+
await listHooks();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Enable/disable aliases
|
|
155
|
+
program
|
|
156
|
+
.command('enable')
|
|
157
|
+
.description('Enable automatic code review (install pre-commit hook)')
|
|
158
|
+
.action(async () => {
|
|
159
|
+
try {
|
|
160
|
+
await enable();
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
program
|
|
168
|
+
.command('disable')
|
|
169
|
+
.description('Disable automatic code review (uninstall pre-commit hook)')
|
|
170
|
+
.action(async () => {
|
|
171
|
+
try {
|
|
172
|
+
await disable();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Parse arguments
|
|
180
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
181
|
+
console.error(error);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client for CLI → Server communication
|
|
3
|
+
*
|
|
4
|
+
* Handles all API calls from CLI to CORA server.
|
|
5
|
+
* Uses ORPC fetch transport for type-safe API calls.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Custom error for API failures
|
|
9
|
+
*/
|
|
10
|
+
export class CLIApiError extends Error {
|
|
11
|
+
statusCode;
|
|
12
|
+
responseBody;
|
|
13
|
+
constructor(message, statusCode, responseBody) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.responseBody = responseBody;
|
|
17
|
+
this.name = 'CLIApiError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* API Client for CLI
|
|
22
|
+
*
|
|
23
|
+
* Provides type-safe methods to call CORA server endpoints.
|
|
24
|
+
*/
|
|
25
|
+
export class CLIApiClient {
|
|
26
|
+
config;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = {
|
|
29
|
+
timeout: 30000, // 30 seconds default
|
|
30
|
+
...config,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get base URL for API calls
|
|
35
|
+
*/
|
|
36
|
+
getBaseUrl() {
|
|
37
|
+
const { serverUrl } = this.config;
|
|
38
|
+
// Remove trailing slash
|
|
39
|
+
return serverUrl.replace(/\/$/, '');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get authorization headers
|
|
43
|
+
*
|
|
44
|
+
* Uses X-API-Key header for API key authentication (CI/CD)
|
|
45
|
+
* Uses Authorization: Bearer header for session token authentication (local CLI)
|
|
46
|
+
*/
|
|
47
|
+
getAuthHeaders() {
|
|
48
|
+
const headers = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
};
|
|
51
|
+
// API Key authentication (CI/CD)
|
|
52
|
+
if (this.config.apiKey) {
|
|
53
|
+
headers['X-API-Key'] = this.config.apiKey;
|
|
54
|
+
}
|
|
55
|
+
// Session token authentication (local CLI)
|
|
56
|
+
else if (this.config.sessionToken) {
|
|
57
|
+
headers['Authorization'] = `Bearer ${this.config.sessionToken}`;
|
|
58
|
+
}
|
|
59
|
+
return headers;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Make HTTP request with timeout
|
|
63
|
+
*/
|
|
64
|
+
async fetch(endpoint, options = {}) {
|
|
65
|
+
const url = `${this.getBaseUrl()}${endpoint}`;
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
// Set timeout
|
|
68
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(url, {
|
|
71
|
+
...options,
|
|
72
|
+
headers: {
|
|
73
|
+
...this.getAuthHeaders(),
|
|
74
|
+
...options.headers,
|
|
75
|
+
},
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
});
|
|
78
|
+
clearTimeout(timeoutId);
|
|
79
|
+
// Parse response
|
|
80
|
+
const contentType = response.headers.get('content-type');
|
|
81
|
+
let data;
|
|
82
|
+
if (contentType?.includes('application/json')) {
|
|
83
|
+
const raw = await response.json();
|
|
84
|
+
// ORPC wraps response in {"json": {...}}
|
|
85
|
+
data = raw.json !== undefined ? raw.json : raw;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
data = await response.text();
|
|
89
|
+
}
|
|
90
|
+
// Handle errors
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new CLIApiError(`API request failed: ${response.status} ${response.statusText}`, response.status, data);
|
|
93
|
+
}
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
clearTimeout(timeoutId);
|
|
98
|
+
if (error instanceof CLIApiError) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
102
|
+
throw new CLIApiError(`Request timeout after ${this.config.timeout}ms`);
|
|
103
|
+
}
|
|
104
|
+
throw new CLIApiError(`Network error: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* POST request helper
|
|
109
|
+
*/
|
|
110
|
+
post(endpoint, data) {
|
|
111
|
+
return this.fetch(endpoint, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: JSON.stringify(data),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Authenticate via OAuth code exchange
|
|
118
|
+
*
|
|
119
|
+
* Called after user completes OAuth flow in browser.
|
|
120
|
+
*/
|
|
121
|
+
async auth(request) {
|
|
122
|
+
return this.post('/rpc/cli/auth', request);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Review code diff
|
|
126
|
+
*
|
|
127
|
+
* Main endpoint for code review.
|
|
128
|
+
* Sends git diff and receives AI analysis results.
|
|
129
|
+
*/
|
|
130
|
+
async reviewDiff(request) {
|
|
131
|
+
return this.post('/api/cli/review', request);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check token quota
|
|
135
|
+
*
|
|
136
|
+
* Returns remaining quota for user/workspace.
|
|
137
|
+
*/
|
|
138
|
+
async checkQuota(workspaceId) {
|
|
139
|
+
return this.post('/rpc/cli/checkQuota', { workspaceId });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get CLI configuration
|
|
143
|
+
*
|
|
144
|
+
* Returns server capabilities and configuration.
|
|
145
|
+
*/
|
|
146
|
+
async getConfig() {
|
|
147
|
+
return this.fetch('/rpc/cli/getConfig', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
'Content-Type': 'application/json',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Verify session token
|
|
156
|
+
*
|
|
157
|
+
* Checks if current session token is still valid.
|
|
158
|
+
*/
|
|
159
|
+
async verifySession() {
|
|
160
|
+
return this.fetch('/rpc/cli/verifySession', {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: {
|
|
163
|
+
'Content-Type': 'application/json',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update session token
|
|
169
|
+
*
|
|
170
|
+
* Updates the client's session token (for refresh).
|
|
171
|
+
*/
|
|
172
|
+
setSessionToken(token) {
|
|
173
|
+
this.config.sessionToken = token;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get current session token
|
|
177
|
+
*/
|
|
178
|
+
getSessionToken() {
|
|
179
|
+
return this.config.sessionToken;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Update server URL
|
|
183
|
+
*/
|
|
184
|
+
setServerUrl(url) {
|
|
185
|
+
this.config.serverUrl = url;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get server URL
|
|
189
|
+
*/
|
|
190
|
+
getServerUrl() {
|
|
191
|
+
return this.config.serverUrl;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Create API client from local config
|
|
196
|
+
*
|
|
197
|
+
* Factory function to create API client with proper authentication.
|
|
198
|
+
*
|
|
199
|
+
* Priority:
|
|
200
|
+
* 1. CORA_API_KEY env var (CI/CD)
|
|
201
|
+
* 2. Stored session token (local CLI usage)
|
|
202
|
+
*/
|
|
203
|
+
export async function createApiClient() {
|
|
204
|
+
// 1. Check for API Key (CI/CD)
|
|
205
|
+
const apiKey = process.env.CORA_API_KEY;
|
|
206
|
+
if (apiKey) {
|
|
207
|
+
return new CLIApiClient({
|
|
208
|
+
serverUrl: process.env.CORA_SERVER_URL || 'https://codecora.dev',
|
|
209
|
+
apiKey,
|
|
210
|
+
timeout: 420000, // 7 minutes for review requests
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// 2. Fall back to session token (local CLI usage)
|
|
214
|
+
const { getLocalConfig } = await import('../config/storage.js');
|
|
215
|
+
const config = await getLocalConfig();
|
|
216
|
+
return new CLIApiClient({
|
|
217
|
+
serverUrl: config.auth?.serverUrl || 'https://codecora.dev',
|
|
218
|
+
sessionToken: config.auth?.sessionToken,
|
|
219
|
+
timeout: 420000, // 7 minutes for review requests
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types between CLI and Server
|
|
3
|
+
*
|
|
4
|
+
* This file defines the contract between CLI client and server API.
|
|
5
|
+
* Both CLI and server must agree on these types for compatibility.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Severity levels for code issues
|
|
9
|
+
*/
|
|
10
|
+
export const SEVERITY_LEVELS = ['critical', 'major', 'minor', 'info'];
|
|
11
|
+
/**
|
|
12
|
+
* Issue type categories
|
|
13
|
+
*/
|
|
14
|
+
export const ISSUE_TYPES = [
|
|
15
|
+
'potential_issue',
|
|
16
|
+
'security',
|
|
17
|
+
'performance',
|
|
18
|
+
'code_smell',
|
|
19
|
+
'best_practice',
|
|
20
|
+
'documentation',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* CLI exit codes
|
|
24
|
+
*/
|
|
25
|
+
export var CLIExitCode;
|
|
26
|
+
(function (CLIExitCode) {
|
|
27
|
+
CLIExitCode[CLIExitCode["Success"] = 0] = "Success";
|
|
28
|
+
CLIExitCode[CLIExitCode["GeneralError"] = 1] = "GeneralError";
|
|
29
|
+
CLIExitCode[CLIExitCode["AuthError"] = 2] = "AuthError";
|
|
30
|
+
CLIExitCode[CLIExitCode["QuotaExceeded"] = 3] = "QuotaExceeded";
|
|
31
|
+
CLIExitCode[CLIExitCode["BlockedByIssues"] = 4] = "BlockedByIssues";
|
|
32
|
+
CLIExitCode[CLIExitCode["NetworkError"] = 5] = "NetworkError";
|
|
33
|
+
})(CLIExitCode || (CLIExitCode = {}));
|