@kodus/cli 0.0.11 → 0.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 +387 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth/index.d.ts +3 -0
- package/dist/commands/auth/index.d.ts.map +1 -0
- package/dist/commands/auth/index.js +36 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/auth/login.d.ts +7 -0
- package/dist/commands/auth/login.d.ts.map +1 -0
- package/dist/commands/auth/login.js +97 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +2 -0
- package/dist/commands/auth/logout.d.ts.map +1 -0
- package/dist/commands/auth/logout.js +24 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth/signup.d.ts +2 -0
- package/dist/commands/auth/signup.d.ts.map +1 -0
- package/dist/commands/auth/signup.js +74 -0
- package/dist/commands/auth/signup.js.map +1 -0
- package/dist/commands/auth/status.d.ts +2 -0
- package/dist/commands/auth/status.d.ts.map +1 -0
- package/dist/commands/auth/status.js +91 -0
- package/dist/commands/auth/status.js.map +1 -0
- package/dist/commands/auth/team-key.d.ts +5 -0
- package/dist/commands/auth/team-key.d.ts.map +1 -0
- package/dist/commands/auth/team-key.js +59 -0
- package/dist/commands/auth/team-key.js.map +1 -0
- package/dist/commands/auth/token.d.ts +2 -0
- package/dist/commands/auth/token.d.ts.map +1 -0
- package/dist/commands/auth/token.js +31 -0
- package/dist/commands/auth/token.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +47 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/pr.d.ts +3 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +75 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/review.d.ts +3 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +245 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/telemetry.d.ts +3 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +76 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +35 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -0
- package/dist/formatters/json.d.ts +7 -0
- package/dist/formatters/json.d.ts.map +1 -0
- package/dist/formatters/json.js +7 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/markdown.d.ts +7 -0
- package/dist/formatters/markdown.d.ts.map +1 -0
- package/dist/formatters/markdown.js +93 -0
- package/dist/formatters/markdown.js.map +1 -0
- package/dist/formatters/prompt.d.ts +12 -0
- package/dist/formatters/prompt.d.ts.map +1 -0
- package/dist/formatters/prompt.js +90 -0
- package/dist/formatters/prompt.js.map +1 -0
- package/dist/formatters/terminal.d.ts +7 -0
- package/dist/formatters/terminal.d.ts.map +1 -0
- package/dist/formatters/terminal.js +127 -0
- package/dist/formatters/terminal.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/services/api/api.interface.d.ts +46 -0
- package/dist/services/api/api.interface.d.ts.map +1 -0
- package/dist/services/api/api.interface.js +2 -0
- package/dist/services/api/api.interface.js.map +1 -0
- package/dist/services/api/api.mock.d.ts +8 -0
- package/dist/services/api/api.mock.d.ts.map +1 -0
- package/dist/services/api/api.mock.js +249 -0
- package/dist/services/api/api.mock.js.map +1 -0
- package/dist/services/api/api.real.d.ts +8 -0
- package/dist/services/api/api.real.d.ts.map +1 -0
- package/dist/services/api/api.real.js +253 -0
- package/dist/services/api/api.real.js.map +1 -0
- package/dist/services/api/index.d.ts +4 -0
- package/dist/services/api/index.d.ts.map +1 -0
- package/dist/services/api/index.js +9 -0
- package/dist/services/api/index.js.map +1 -0
- package/dist/services/auth.service.d.ts +19 -0
- package/dist/services/auth.service.d.ts.map +1 -0
- package/dist/services/auth.service.js +90 -0
- package/dist/services/auth.service.js.map +1 -0
- package/dist/services/context.service.d.ts +22 -0
- package/dist/services/context.service.d.ts.map +1 -0
- package/dist/services/context.service.js +104 -0
- package/dist/services/context.service.js.map +1 -0
- package/dist/services/fix.service.d.ts +31 -0
- package/dist/services/fix.service.d.ts.map +1 -0
- package/dist/services/fix.service.js +120 -0
- package/dist/services/fix.service.js.map +1 -0
- package/dist/services/git.service.d.ts +32 -0
- package/dist/services/git.service.d.ts.map +1 -0
- package/dist/services/git.service.js +261 -0
- package/dist/services/git.service.js.map +1 -0
- package/dist/services/review.service.d.ts +26 -0
- package/dist/services/review.service.d.ts.map +1 -0
- package/dist/services/review.service.js +82 -0
- package/dist/services/review.service.js.map +1 -0
- package/dist/services/telemetry.service.d.ts +73 -0
- package/dist/services/telemetry.service.d.ts.map +1 -0
- package/dist/services/telemetry.service.js +229 -0
- package/dist/services/telemetry.service.js.map +1 -0
- package/dist/types/index.d.ts +143 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +15 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/interactive.d.ts +26 -0
- package/dist/ui/interactive.d.ts.map +1 -0
- package/dist/ui/interactive.js +365 -0
- package/dist/ui/interactive.js.map +1 -0
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +54 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/credentials.d.ts +6 -0
- package/dist/utils/credentials.d.ts.map +1 -0
- package/dist/utils/credentials.js +54 -0
- package/dist/utils/credentials.js.map +1 -0
- package/dist/utils/rate-limit.d.ts +5 -0
- package/dist/utils/rate-limit.d.ts.map +1 -0
- package/dist/utils/rate-limit.js +47 -0
- package/dist/utils/rate-limit.js.map +1 -0
- package/dist/utils/review-loading.d.ts +19 -0
- package/dist/utils/review-loading.d.ts.map +1 -0
- package/dist/utils/review-loading.js +140 -0
- package/dist/utils/review-loading.js.map +1 -0
- package/package.json +39 -33
- package/index.js +0 -17
- package/license.md +0 -21
- package/readme.md +0 -128
- package/scripts/setup-db.sh +0 -77
- package/src/commands/install.js +0 -324
- package/src/config/default.js +0 -66
- package/src/utils/helpers.js +0 -118
- package/templates/docker-compose.yml +0 -139
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { ApiError } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates and returns the API base URL
|
|
4
|
+
* Prevents URL injection attacks by validating custom API URLs
|
|
5
|
+
*/
|
|
6
|
+
function getApiBaseUrl() {
|
|
7
|
+
const customUrl = process.env.KODUS_API_URL;
|
|
8
|
+
const defaultUrl = 'https://api.kodus.io';
|
|
9
|
+
if (!customUrl) {
|
|
10
|
+
return defaultUrl;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(customUrl);
|
|
14
|
+
// Only allow HTTPS (except localhost for development)
|
|
15
|
+
const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
16
|
+
if (url.protocol !== 'https:' && !isLocalhost) {
|
|
17
|
+
console.error('Security Error: KODUS_API_URL must use HTTPS protocol');
|
|
18
|
+
console.error(`Falling back to default: ${defaultUrl}`);
|
|
19
|
+
return defaultUrl;
|
|
20
|
+
}
|
|
21
|
+
// Warn about non-standard API URLs
|
|
22
|
+
const standardDomains = ['api.kodus.io', 'localhost', '127.0.0.1'];
|
|
23
|
+
const isStandard = standardDomains.some(domain => url.hostname === domain || url.hostname.endsWith(`.${domain}`));
|
|
24
|
+
if (!isStandard && process.env.KODUS_VERBOSE) {
|
|
25
|
+
console.warn(`Warning: Using non-standard API URL: ${url.hostname}`);
|
|
26
|
+
}
|
|
27
|
+
return customUrl;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error('Invalid KODUS_API_URL format:', customUrl);
|
|
31
|
+
console.error(`Falling back to default: ${defaultUrl}`);
|
|
32
|
+
return defaultUrl;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const API_BASE_URL = getApiBaseUrl();
|
|
36
|
+
async function request(endpoint, options = {}) {
|
|
37
|
+
const url = `${API_BASE_URL}${endpoint}`;
|
|
38
|
+
const response = await fetch(url, {
|
|
39
|
+
...options,
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
...options.headers,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const errorData = await response.json().catch(() => ({ message: 'Request failed' }));
|
|
47
|
+
const errorMessage = errorData.message || `Request failed with status ${response.status}`;
|
|
48
|
+
if (process.env.KODUS_VERBOSE) {
|
|
49
|
+
console.error('API Error:', { status: response.status, url, errorData });
|
|
50
|
+
}
|
|
51
|
+
throw new ApiError(response.status, errorMessage);
|
|
52
|
+
}
|
|
53
|
+
const json = await response.json();
|
|
54
|
+
// API retorna { data: {...}, statusCode, type }
|
|
55
|
+
// Extrair apenas o .data se existir
|
|
56
|
+
if (json && typeof json === 'object' && 'data' in json) {
|
|
57
|
+
return json.data;
|
|
58
|
+
}
|
|
59
|
+
return json;
|
|
60
|
+
}
|
|
61
|
+
class RealAuthApi {
|
|
62
|
+
async login(email, password) {
|
|
63
|
+
const response = await request('/auth/login', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
body: JSON.stringify({ email, password }),
|
|
66
|
+
});
|
|
67
|
+
// Mapear resposta da API para formato esperado pelo CLI
|
|
68
|
+
return {
|
|
69
|
+
accessToken: response.accessToken,
|
|
70
|
+
refreshToken: response.refreshToken,
|
|
71
|
+
expiresIn: 3600, // Default: 1 hora
|
|
72
|
+
user: {
|
|
73
|
+
id: 'unknown', // API não retorna user info no login
|
|
74
|
+
email,
|
|
75
|
+
orgs: [],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async signup(email, password) {
|
|
80
|
+
// Signup não é permitido via CLI - só via app.kodus.io
|
|
81
|
+
throw new Error('Signup is not available via CLI. Please sign up at https://app.kodus.io');
|
|
82
|
+
}
|
|
83
|
+
async refresh(refreshToken) {
|
|
84
|
+
return request('/auth/refresh', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
body: JSON.stringify({ refreshToken }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async logout(accessToken) {
|
|
90
|
+
await request('/auth/logout', {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${accessToken}`,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async generateCIToken(accessToken) {
|
|
98
|
+
const response = await request('/auth/ci-token', {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: `Bearer ${accessToken}`,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
return response.token;
|
|
105
|
+
}
|
|
106
|
+
async verify(accessToken) {
|
|
107
|
+
// SECURITY NOTE: This performs basic client-side JWT validation without signature verification.
|
|
108
|
+
// This is acceptable for a CLI client where:
|
|
109
|
+
// 1. The token is securely stored locally and only accessed by the user
|
|
110
|
+
// 2. The API validates the token signature on every request
|
|
111
|
+
// 3. We only check format and expiration to avoid unnecessary API calls
|
|
112
|
+
//
|
|
113
|
+
// For production security, all authorization decisions MUST be made by the API
|
|
114
|
+
// after validating the token signature.
|
|
115
|
+
if (!accessToken || !accessToken.startsWith('eyJ')) {
|
|
116
|
+
return { valid: false };
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
// Decode JWT payload (without signature validation)
|
|
120
|
+
const parts = accessToken.split('.');
|
|
121
|
+
if (parts.length !== 3) {
|
|
122
|
+
return { valid: false };
|
|
123
|
+
}
|
|
124
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
125
|
+
// Check expiration
|
|
126
|
+
if (payload.exp && payload.exp * 1000 < Date.now()) {
|
|
127
|
+
return { valid: false };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
valid: true,
|
|
131
|
+
user: {
|
|
132
|
+
id: payload.sub || 'unknown',
|
|
133
|
+
email: payload.email || 'unknown',
|
|
134
|
+
orgs: [],
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (process.env.KODUS_VERBOSE) {
|
|
140
|
+
console.error('Token verification failed:', error);
|
|
141
|
+
}
|
|
142
|
+
return { valid: false };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
class RealReviewApi {
|
|
147
|
+
async analyze(diff, accessToken, config) {
|
|
148
|
+
const isTeamKey = accessToken.startsWith('kodus_');
|
|
149
|
+
if (isTeamKey) {
|
|
150
|
+
return request('/cli/review', {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: {
|
|
153
|
+
'X-Team-Key': accessToken,
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({ diff, config }),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
let teamId;
|
|
159
|
+
try {
|
|
160
|
+
const payload = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString());
|
|
161
|
+
teamId = payload.organizationId;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// Ignore if cannot decode
|
|
165
|
+
}
|
|
166
|
+
const endpoint = teamId ? `/cli/review?teamId=${encodeURIComponent(teamId)}` : '/cli/review';
|
|
167
|
+
return request(endpoint, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: {
|
|
170
|
+
Authorization: `Bearer ${accessToken}`,
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({ diff, config }),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async analyzeWithMetrics(diff, accessToken, config, metrics) {
|
|
176
|
+
const isTeamKey = accessToken.startsWith('kodus_');
|
|
177
|
+
if (isTeamKey) {
|
|
178
|
+
return request('/cli/review', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {
|
|
181
|
+
'X-Team-Key': accessToken,
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
diff,
|
|
185
|
+
config,
|
|
186
|
+
...metrics,
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return this.analyze(diff, accessToken, config);
|
|
191
|
+
}
|
|
192
|
+
async getPullRequestSuggestions(accessToken, params) {
|
|
193
|
+
const query = new URLSearchParams();
|
|
194
|
+
if (params.prUrl) {
|
|
195
|
+
query.set('prUrl', params.prUrl);
|
|
196
|
+
}
|
|
197
|
+
if (params.prNumber !== undefined) {
|
|
198
|
+
query.set('prNumber', params.prNumber.toString());
|
|
199
|
+
}
|
|
200
|
+
if (params.repositoryId) {
|
|
201
|
+
query.set('repositoryId', params.repositoryId);
|
|
202
|
+
}
|
|
203
|
+
if (params.format) {
|
|
204
|
+
query.set('format', params.format);
|
|
205
|
+
}
|
|
206
|
+
if (params.severity) {
|
|
207
|
+
query.set('severity', params.severity);
|
|
208
|
+
}
|
|
209
|
+
if (params.category) {
|
|
210
|
+
query.set('category', params.category);
|
|
211
|
+
}
|
|
212
|
+
const queryString = query.toString();
|
|
213
|
+
const endpoint = `/pull-requests/suggestions${queryString ? `?${queryString}` : ''}`;
|
|
214
|
+
return request(endpoint, {
|
|
215
|
+
headers: {
|
|
216
|
+
Authorization: `Bearer ${accessToken}`,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async trialAnalyze(diff, fingerprint) {
|
|
221
|
+
return request('/cli/trial/review', {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
body: JSON.stringify({ diff, fingerprint }),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
class RealConfigApi {
|
|
228
|
+
async get(accessToken, org, repo) {
|
|
229
|
+
const params = new URLSearchParams();
|
|
230
|
+
if (org)
|
|
231
|
+
params.set('org', org);
|
|
232
|
+
if (repo)
|
|
233
|
+
params.set('repo', repo);
|
|
234
|
+
const query = params.toString() ? `?${params.toString()}` : '';
|
|
235
|
+
return request(`/cli/config${query}`, {
|
|
236
|
+
headers: {
|
|
237
|
+
Authorization: `Bearer ${accessToken}`,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
class RealTrialApi {
|
|
243
|
+
async getStatus(fingerprint) {
|
|
244
|
+
return request(`/cli/trial/status?fingerprint=${fingerprint}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export class RealApi {
|
|
248
|
+
auth = new RealAuthApi();
|
|
249
|
+
review = new RealReviewApi();
|
|
250
|
+
config = new RealConfigApi();
|
|
251
|
+
trial = new RealTrialApi();
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=api.real.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.real.js","sourceRoot":"","sources":["../../../src/services/api/api.real.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAIpD,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAUpD,eAAO,MAAM,GAAG,WAAc,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MockApi } from './api.mock.js';
|
|
2
|
+
import { RealApi } from './api.real.js';
|
|
3
|
+
function createApi() {
|
|
4
|
+
const useMock = process.env.KODUS_MOCK === 'true' ||
|
|
5
|
+
(!process.env.KODUS_API_URL && process.env.NODE_ENV !== 'production');
|
|
6
|
+
return useMock ? new MockApi() : new RealApi();
|
|
7
|
+
}
|
|
8
|
+
export const api = createApi();
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/api/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAIxC,SAAS,SAAS;IAChB,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,MAAM;QACjC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC;IAExE,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StoredCredentials } from '../types/index.js';
|
|
2
|
+
declare class AuthService {
|
|
3
|
+
private cachedCredentials;
|
|
4
|
+
login(email: string, password: string): Promise<void>;
|
|
5
|
+
signup(email: string, password: string): Promise<void>;
|
|
6
|
+
logout(): Promise<void>;
|
|
7
|
+
isAuthenticated(): Promise<boolean>;
|
|
8
|
+
getCredentials(): Promise<StoredCredentials | null>;
|
|
9
|
+
getValidToken(): Promise<string>;
|
|
10
|
+
generateCIToken(): Promise<string>;
|
|
11
|
+
verifyToken(): Promise<{
|
|
12
|
+
valid: boolean;
|
|
13
|
+
user?: any;
|
|
14
|
+
}>;
|
|
15
|
+
private storeAuthResponse;
|
|
16
|
+
}
|
|
17
|
+
export declare const authService: AuthService;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=auth.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAgB,MAAM,mBAAmB,CAAC;AAKzE,cAAM,WAAW;IACf,OAAO,CAAC,iBAAiB,CAAkC;IAErD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAevB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAQnC,cAAc,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IASnD,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IA6BhC,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAKlC,WAAW,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;YAU9C,iBAAiB;CAWhC;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { api } from './api/index.js';
|
|
2
|
+
import { loadCredentials, saveCredentials, clearCredentials, } from '../utils/credentials.js';
|
|
3
|
+
import { loadConfig } from '../utils/config.js';
|
|
4
|
+
import { AuthError } from '../types/index.js';
|
|
5
|
+
const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
6
|
+
class AuthService {
|
|
7
|
+
cachedCredentials = null;
|
|
8
|
+
async login(email, password) {
|
|
9
|
+
const response = await api.auth.login(email, password);
|
|
10
|
+
await this.storeAuthResponse(response);
|
|
11
|
+
}
|
|
12
|
+
async signup(email, password) {
|
|
13
|
+
const response = await api.auth.signup(email, password);
|
|
14
|
+
await this.storeAuthResponse(response);
|
|
15
|
+
}
|
|
16
|
+
async logout() {
|
|
17
|
+
const credentials = await this.getCredentials();
|
|
18
|
+
if (credentials) {
|
|
19
|
+
try {
|
|
20
|
+
await api.auth.logout(credentials.accessToken);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Ignore logout errors
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
await clearCredentials();
|
|
27
|
+
this.cachedCredentials = null;
|
|
28
|
+
}
|
|
29
|
+
async isAuthenticated() {
|
|
30
|
+
const credentials = await this.getCredentials();
|
|
31
|
+
if (credentials !== null)
|
|
32
|
+
return true;
|
|
33
|
+
const config = await loadConfig();
|
|
34
|
+
return !!config?.teamKey;
|
|
35
|
+
}
|
|
36
|
+
async getCredentials() {
|
|
37
|
+
if (this.cachedCredentials) {
|
|
38
|
+
return this.cachedCredentials;
|
|
39
|
+
}
|
|
40
|
+
this.cachedCredentials = await loadCredentials();
|
|
41
|
+
return this.cachedCredentials;
|
|
42
|
+
}
|
|
43
|
+
async getValidToken() {
|
|
44
|
+
const config = await loadConfig();
|
|
45
|
+
if (config?.teamKey) {
|
|
46
|
+
return config.teamKey;
|
|
47
|
+
}
|
|
48
|
+
const credentials = await this.getCredentials();
|
|
49
|
+
if (!credentials) {
|
|
50
|
+
throw new AuthError('Not authenticated. Run: kodus auth login or kodus auth team-key --key <your-key>');
|
|
51
|
+
}
|
|
52
|
+
const isExpired = Date.now() > credentials.expiresAt - TOKEN_REFRESH_BUFFER_MS;
|
|
53
|
+
if (isExpired) {
|
|
54
|
+
try {
|
|
55
|
+
const response = await api.auth.refresh(credentials.refreshToken);
|
|
56
|
+
await this.storeAuthResponse(response);
|
|
57
|
+
return response.accessToken;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await clearCredentials();
|
|
61
|
+
this.cachedCredentials = null;
|
|
62
|
+
throw new AuthError('Session expired. Run: kodus auth login');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return credentials.accessToken;
|
|
66
|
+
}
|
|
67
|
+
async generateCIToken() {
|
|
68
|
+
const token = await this.getValidToken();
|
|
69
|
+
return api.auth.generateCIToken(token);
|
|
70
|
+
}
|
|
71
|
+
async verifyToken() {
|
|
72
|
+
const credentials = await this.getCredentials();
|
|
73
|
+
if (!credentials) {
|
|
74
|
+
return { valid: false };
|
|
75
|
+
}
|
|
76
|
+
return api.auth.verify(credentials.accessToken);
|
|
77
|
+
}
|
|
78
|
+
async storeAuthResponse(response) {
|
|
79
|
+
const credentials = {
|
|
80
|
+
accessToken: response.accessToken,
|
|
81
|
+
refreshToken: response.refreshToken,
|
|
82
|
+
expiresAt: Date.now() + response.expiresIn * 1000,
|
|
83
|
+
user: response.user,
|
|
84
|
+
};
|
|
85
|
+
await saveCredentials(credentials);
|
|
86
|
+
this.cachedCredentials = credentials;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export const authService = new AuthService();
|
|
90
|
+
//# sourceMappingURL=auth.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProjectContext } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Context Service - Reads project context files
|
|
4
|
+
* Similar to CodeRabbit CLI reading .cursorrules, claude.md, etc.
|
|
5
|
+
*/
|
|
6
|
+
declare class ContextService {
|
|
7
|
+
private fileExists;
|
|
8
|
+
private readFile;
|
|
9
|
+
private getRepoRoot;
|
|
10
|
+
readProjectContext(customContextPath?: string): Promise<ProjectContext>;
|
|
11
|
+
/**
|
|
12
|
+
* Formats project context for inclusion in review requests
|
|
13
|
+
*/
|
|
14
|
+
formatContextForReview(context: ProjectContext): string;
|
|
15
|
+
/**
|
|
16
|
+
* Enriches diff with project context
|
|
17
|
+
*/
|
|
18
|
+
enrichDiffWithContext(diff: string, customContextPath?: string): Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
export declare const contextService: ContextService;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=context.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.service.d.ts","sourceRoot":"","sources":["../../src/services/context.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;GAGG;AACH,cAAM,cAAc;YACJ,UAAU;YASV,QAAQ;YAYR,WAAW;IAWnB,kBAAkB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA6B7E;;OAEG;IACH,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM;IA8BvD;;OAEG;IACG,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAUvF;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { simpleGit } from 'simple-git';
|
|
4
|
+
/**
|
|
5
|
+
* Context Service - Reads project context files
|
|
6
|
+
* Similar to CodeRabbit CLI reading .cursorrules, claude.md, etc.
|
|
7
|
+
*/
|
|
8
|
+
class ContextService {
|
|
9
|
+
async fileExists(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
await fs.access(filePath);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async readFile(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const exists = await this.fileExists(filePath);
|
|
21
|
+
if (!exists)
|
|
22
|
+
return undefined;
|
|
23
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
24
|
+
return content.trim();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async getRepoRoot() {
|
|
31
|
+
try {
|
|
32
|
+
const git = simpleGit();
|
|
33
|
+
const root = await git.revparse(['--show-toplevel']);
|
|
34
|
+
return root.trim();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Fallback to current directory
|
|
38
|
+
return process.cwd();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async readProjectContext(customContextPath) {
|
|
42
|
+
const repoRoot = await this.getRepoRoot();
|
|
43
|
+
const context = {};
|
|
44
|
+
// Read .cursorrules
|
|
45
|
+
const cursorRulesPath = path.join(repoRoot, '.cursorrules');
|
|
46
|
+
context.cursorRules = await this.readFile(cursorRulesPath);
|
|
47
|
+
// Read claude.md or .claude.md
|
|
48
|
+
const claudeMdPath = path.join(repoRoot, 'claude.md');
|
|
49
|
+
const dotClaudeMdPath = path.join(repoRoot, '.claude.md');
|
|
50
|
+
context.claudeRules = await this.readFile(claudeMdPath) || await this.readFile(dotClaudeMdPath);
|
|
51
|
+
// Read .kodus.md or .kodus/rules.md
|
|
52
|
+
const kodusMdPath = path.join(repoRoot, '.kodus.md');
|
|
53
|
+
const kodusRulesPath = path.join(repoRoot, '.kodus', 'rules.md');
|
|
54
|
+
context.kodusRules = await this.readFile(kodusMdPath) || await this.readFile(kodusRulesPath);
|
|
55
|
+
// Read custom context file if specified
|
|
56
|
+
if (customContextPath) {
|
|
57
|
+
const customPath = path.isAbsolute(customContextPath)
|
|
58
|
+
? customContextPath
|
|
59
|
+
: path.join(repoRoot, customContextPath);
|
|
60
|
+
context.customContext = await this.readFile(customPath);
|
|
61
|
+
}
|
|
62
|
+
return context;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Formats project context for inclusion in review requests
|
|
66
|
+
*/
|
|
67
|
+
formatContextForReview(context) {
|
|
68
|
+
const parts = [];
|
|
69
|
+
if (context.cursorRules) {
|
|
70
|
+
parts.push('=== Cursor Rules (.cursorrules) ===');
|
|
71
|
+
parts.push(context.cursorRules);
|
|
72
|
+
parts.push('');
|
|
73
|
+
}
|
|
74
|
+
if (context.claudeRules) {
|
|
75
|
+
parts.push('=== Claude Rules (claude.md) ===');
|
|
76
|
+
parts.push(context.claudeRules);
|
|
77
|
+
parts.push('');
|
|
78
|
+
}
|
|
79
|
+
if (context.kodusRules) {
|
|
80
|
+
parts.push('=== Kodus Rules (.kodus.md) ===');
|
|
81
|
+
parts.push(context.kodusRules);
|
|
82
|
+
parts.push('');
|
|
83
|
+
}
|
|
84
|
+
if (context.customContext) {
|
|
85
|
+
parts.push('=== Custom Context ===');
|
|
86
|
+
parts.push(context.customContext);
|
|
87
|
+
parts.push('');
|
|
88
|
+
}
|
|
89
|
+
return parts.join('\n');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Enriches diff with project context
|
|
93
|
+
*/
|
|
94
|
+
async enrichDiffWithContext(diff, customContextPath) {
|
|
95
|
+
const context = await this.readProjectContext(customContextPath);
|
|
96
|
+
const formattedContext = this.formatContextForReview(context);
|
|
97
|
+
if (!formattedContext) {
|
|
98
|
+
return diff;
|
|
99
|
+
}
|
|
100
|
+
return `${formattedContext}\n=== Code Changes ===\n${diff}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export const contextService = new ContextService();
|
|
104
|
+
//# sourceMappingURL=context.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.service.js","sourceRoot":"","sources":["../../src/services/context.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReviewIssue } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fix Service - Applies code fixes to files
|
|
4
|
+
*/
|
|
5
|
+
declare class FixService {
|
|
6
|
+
/**
|
|
7
|
+
* Applies a single fix to a file
|
|
8
|
+
*/
|
|
9
|
+
applyFix(issue: ReviewIssue): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Applies multiple fixes to files
|
|
12
|
+
*/
|
|
13
|
+
applyFixes(issues: ReviewIssue[]): Promise<{
|
|
14
|
+
applied: number;
|
|
15
|
+
failed: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Generates a preview diff for a fix
|
|
19
|
+
*/
|
|
20
|
+
generatePreview(issue: ReviewIssue): string;
|
|
21
|
+
private applyReplace;
|
|
22
|
+
private applyInsert;
|
|
23
|
+
private applyDelete;
|
|
24
|
+
/**
|
|
25
|
+
* Checks if a fix can be safely applied
|
|
26
|
+
*/
|
|
27
|
+
canApplyFix(issue: ReviewIssue): Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
export declare const fixService: FixService;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=fix.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix.service.d.ts","sourceRoot":"","sources":["../../src/services/fix.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,mBAAmB,CAAC;AAE9D;;GAEG;AACH,cAAM,UAAU;IACd;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBjD;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAmCrF;;OAEG;IACH,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;YAoB7B,YAAY;YAcZ,WAAW;YASX,WAAW;IASzB;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAaxD;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
|