@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 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 = {}));