archicore 0.3.3 → 0.3.5

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.
@@ -32,7 +32,14 @@ export async function getCurrentUser() {
32
32
  return null;
33
33
  }
34
34
  const data = await response.json();
35
- return data.user;
35
+ if (!data.user)
36
+ return null;
37
+ return {
38
+ id: data.user.id,
39
+ email: data.user.email,
40
+ name: data.user.name || data.user.username,
41
+ tier: data.user.tier || 'free'
42
+ };
36
43
  }
37
44
  catch {
38
45
  return null;
@@ -693,7 +693,7 @@ async function handleIndexCommand() {
693
693
  });
694
694
  if (reRegisterResponse.ok) {
695
695
  const reData = await reRegisterResponse.json();
696
- state.projectId = reData.id || reData.project?.id ?? null;
696
+ state.projectId = reData.id || (reData.project && reData.project.id) || null;
697
697
  printInfo('Project re-registered. Run /index again to complete indexing.');
698
698
  // Обновляем локальный конфиг
699
699
  const localProjectRetry = await getLocalProject(state.projectPath);
@@ -244,7 +244,7 @@ export function createSimpleAutocomplete(rl, commands, prompt) {
244
244
  }
245
245
  return [[], line];
246
246
  };
247
- // Return the completer function
247
+ // Return the completer function (accessing internal Node.js readline property)
248
248
  rl._completer = completer;
249
249
  }
250
250
  //# sourceMappingURL=autocomplete.js.map
@@ -22,7 +22,7 @@ export async function fetchUserProjects() {
22
22
  if (!response.ok)
23
23
  return [];
24
24
  const data = await response.json();
25
- return (data.projects || data || []).map((p) => ({
25
+ return (data.projects || []).map((p) => ({
26
26
  id: p.id,
27
27
  name: p.name,
28
28
  path: p.path,
@@ -50,12 +50,14 @@ if (process.env.GITHUB_OAUTH_CLIENT_ID && process.env.GITHUB_OAUTH_CLIENT_SECRET
50
50
  try {
51
51
  Logger.info(`[OAuth] GitHub authentication for user: ${profile.username}`);
52
52
  // GitHub might not provide email in profile, check emails array
53
- const email = profile.emails?.[0]?.value || profile._json?.email || '';
53
+ // _json contains raw GitHub API response (not typed in passport-github2)
54
+ const rawProfile = profile;
55
+ const email = profile.emails?.[0]?.value || rawProfile._json?.email || '';
54
56
  const oauthProfile = {
55
57
  id: profile.id,
56
58
  email,
57
59
  displayName: profile.displayName || profile.username || '',
58
- avatar: profile.photos?.[0]?.value || profile._json?.avatar_url,
60
+ avatar: profile.photos?.[0]?.value || rawProfile._json?.avatar_url,
59
61
  provider: 'github',
60
62
  };
61
63
  // Validate email
@@ -172,7 +172,7 @@ export class ArchiCoreServer {
172
172
  scriptSrcAttr: ["'unsafe-inline'", "'unsafe-hashes'"],
173
173
  imgSrc: ["'self'", "data:", "https:"],
174
174
  fontSrc: ["'self'", "https://fonts.gstatic.com"],
175
- connectSrc: ["'self'", "https://api.jina.ai", "https://api.openai.com", "https://api.anthropic.com"],
175
+ connectSrc: ["'self'", "https://api.jina.ai", "https://api.openai.com", "https://api.anthropic.com", "https://cdn.jsdelivr.net"],
176
176
  },
177
177
  },
178
178
  crossOriginResourcePolicy: { policy: 'cross-origin' },
@@ -90,6 +90,7 @@ adminRouter.get('/users/:id', async (req, res) => {
90
90
  res.status(404).json({ error: 'User not found' });
91
91
  return;
92
92
  }
93
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
93
94
  const { passwordHash, ...sanitized } = user;
94
95
  res.json({ user: sanitized });
95
96
  }
@@ -434,6 +435,7 @@ adminRouter.get('/export/all', async (_req, res) => {
434
435
  const auditLogs = await auditService.query({ limit: 1000, offset: 0 });
435
436
  // Sanitize user data (remove passwords)
436
437
  const sanitizedUsers = users.map(u => {
438
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
437
439
  const { passwordHash, ...safe } = u;
438
440
  return safe;
439
441
  });
@@ -333,7 +333,7 @@ apiRouter.post('/projects/:id/upload-index/finalize', authMiddleware, checkProje
333
333
  }
334
334
  Logger.info(`Finalizing chunked upload ${uploadId}: ${upload.asts.length} ASTs, ${upload.symbols.length} symbols`);
335
335
  // Собираем все данные и отправляем в projectService
336
- // Type cast because chunked data comes as unknown from JSON
336
+ // Type cast needed: chunked data comes as serialized JSON, types match at runtime
337
337
  const result = await projectService.uploadIndexedData(id, {
338
338
  asts: upload.asts,
339
339
  symbols: upload.symbols,
@@ -21,6 +21,152 @@ export const gitlabRouter = Router();
21
21
  const gitlabService = new GitLabService();
22
22
  const projectService = new ProjectService();
23
23
  const authService = AuthService.getInstance();
24
+ // ===== SIMPLIFIED CONNECT (for frontend) =====
25
+ /**
26
+ * POST /api/gitlab/connect
27
+ * Simplified endpoint to connect a GitLab repository in one step
28
+ * Creates instance if needed, then connects repository
29
+ */
30
+ gitlabRouter.post('/connect', authMiddleware, async (req, res) => {
31
+ try {
32
+ if (!req.user) {
33
+ res.status(401).json({ error: 'Not authenticated' });
34
+ return;
35
+ }
36
+ const { gitlabUrl, repoPath, branch, token, autoAnalyze, analyzeMRs } = req.body;
37
+ if (!repoPath) {
38
+ res.status(400).json({ error: 'Repository path is required (e.g., owner/repo)' });
39
+ return;
40
+ }
41
+ const instanceUrl = gitlabUrl || 'https://gitlab.com';
42
+ // Check if we have an existing instance for this URL
43
+ let instances = await gitlabService.getInstances(req.user.id);
44
+ let instance = instances.find(i => i.instanceUrl === instanceUrl);
45
+ // If no instance and no token provided, return error
46
+ if (!instance && !token) {
47
+ res.status(400).json({
48
+ error: 'GitLab access token required for first connection',
49
+ message: 'Please provide a Personal Access Token with api scope'
50
+ });
51
+ return;
52
+ }
53
+ // Create instance if needed
54
+ if (!instance && token) {
55
+ try {
56
+ instance = await gitlabService.addInstance(req.user.id, instanceUrl, token, { name: instanceUrl.replace('https://', '').replace('http://', '') });
57
+ Logger.info(`Created GitLab instance for ${instanceUrl}`);
58
+ }
59
+ catch (e) {
60
+ Logger.error('Failed to create GitLab instance:', e);
61
+ res.status(400).json({
62
+ error: 'Failed to connect to GitLab',
63
+ message: e instanceof Error ? e.message : 'Invalid token or GitLab URL'
64
+ });
65
+ return;
66
+ }
67
+ }
68
+ if (!instance) {
69
+ res.status(400).json({ error: 'No GitLab instance available' });
70
+ return;
71
+ }
72
+ // Find the project by path - try direct fetch first (works for private repos)
73
+ let project;
74
+ try {
75
+ // GitLab API accepts URL-encoded path like "owner%2Frepo"
76
+ project = await gitlabService.getProject(req.user.id, instance.id, repoPath);
77
+ }
78
+ catch (e) {
79
+ // If direct fetch fails, try searching
80
+ Logger.debug(`Direct project fetch failed, trying search: ${e}`);
81
+ const projects = await gitlabService.listProjects(req.user.id, instance.id, {
82
+ search: repoPath.split('/').pop(), // Search by repo name only
83
+ membership: false // Include all accessible repos
84
+ });
85
+ project = projects.find(p => p.path_with_namespace === repoPath);
86
+ }
87
+ if (!project) {
88
+ res.status(404).json({
89
+ error: 'Repository not found',
90
+ message: `Could not find repository "${repoPath}" on ${instanceUrl}. Make sure the path is correct (e.g., "owner/repo") and your token has access to it.`
91
+ });
92
+ return;
93
+ }
94
+ // Check usage limit
95
+ const usageResult = await authService.checkAndUpdateUsage(req.user.id, 'project');
96
+ if (!usageResult.allowed) {
97
+ res.status(429).json({
98
+ error: 'Project limit reached',
99
+ message: `You have reached your daily project limit (${usageResult.limit})`,
100
+ usage: { used: usageResult.limit, limit: usageResult.limit, remaining: 0 }
101
+ });
102
+ return;
103
+ }
104
+ // Connect repository
105
+ const connectedRepo = await gitlabService.connectRepository(req.user.id, instance.id, project.id, { autoAnalyze: autoAnalyze !== false, analyzeMRs: analyzeMRs !== false });
106
+ // Download and create ArchiCore project
107
+ const targetBranch = branch || project.default_branch || 'main';
108
+ Logger.progress(`Downloading GitLab repository: ${project.path_with_namespace} (branch: ${targetBranch})`);
109
+ const zipBuffer = await gitlabService.downloadRepository(req.user.id, instance.id, project.id, targetBranch);
110
+ // Create projects directory
111
+ const projectsDir = process.env.PROJECTS_DIR || join('.archicore', 'projects');
112
+ await mkdir(projectsDir, { recursive: true });
113
+ // Extract to project directory
114
+ const projectPath = join(projectsDir, project.name);
115
+ await mkdir(projectPath, { recursive: true });
116
+ const zip = new AdmZip(zipBuffer);
117
+ const zipEntries = zip.getEntries();
118
+ // Extract files
119
+ for (const entry of zipEntries) {
120
+ if (entry.isDirectory) {
121
+ const dirPath = join(projectPath, entry.entryName);
122
+ await mkdir(dirPath, { recursive: true });
123
+ }
124
+ else {
125
+ const filePath = join(projectPath, entry.entryName);
126
+ const fileDir = join(projectPath, entry.entryName.split('/').slice(0, -1).join('/'));
127
+ await mkdir(fileDir, { recursive: true });
128
+ const content = entry.getData();
129
+ const { writeFile } = await import('fs/promises');
130
+ await writeFile(filePath, content);
131
+ }
132
+ }
133
+ // GitLab creates a folder like "project-branch" inside, find it
134
+ const { readdir, stat } = await import('fs/promises');
135
+ const contents = await readdir(projectPath);
136
+ let actualPath = projectPath;
137
+ if (contents.length === 1) {
138
+ const innerPath = join(projectPath, contents[0]);
139
+ const stats = await stat(innerPath);
140
+ if (stats.isDirectory()) {
141
+ actualPath = innerPath;
142
+ }
143
+ }
144
+ Logger.success(`Downloaded and extracted to: ${actualPath}`);
145
+ // Create ArchiCore project
146
+ const archiProject = await projectService.createProject(project.name, actualPath, req.user.id);
147
+ // Update connected repo with project ID
148
+ await gitlabService.updateRepositoryProjectId(connectedRepo.id, archiProject.id);
149
+ await gitlabService.updateRepositoryStatus(connectedRepo.id, 'active');
150
+ res.json({
151
+ success: true,
152
+ project: {
153
+ id: archiProject.id,
154
+ name: archiProject.name,
155
+ path: actualPath
156
+ },
157
+ repository: {
158
+ id: connectedRepo.id,
159
+ fullPath: project.path_with_namespace,
160
+ branch: targetBranch
161
+ }
162
+ });
163
+ }
164
+ catch (error) {
165
+ Logger.error('GitLab connect error:', error);
166
+ const message = error instanceof Error ? error.message : 'Failed to connect GitLab repository';
167
+ res.status(500).json({ error: message });
168
+ }
169
+ });
24
170
  // ===== INSTANCE MANAGEMENT =====
25
171
  /**
26
172
  * POST /api/gitlab/instances
@@ -536,7 +536,7 @@ export class ProjectService {
536
536
  graphNodes.set(key, {
537
537
  id: node.id,
538
538
  name: node.name,
539
- type: node.type || 'file',
539
+ type: (node.type || 'file'),
540
540
  filePath: node.filePath,
541
541
  metadata: node.metadata || {}
542
542
  });
@@ -630,7 +630,7 @@ export class ProjectService {
630
630
  graphNodes.set(key, {
631
631
  id: node.id,
632
632
  name: node.name,
633
- type: node.type || 'file',
633
+ type: (node.type || 'file'),
634
634
  filePath: node.filePath,
635
635
  metadata: node.metadata || {}
636
636
  });
@@ -282,5 +282,92 @@ export interface UserProfileResponse {
282
282
  };
283
283
  error?: string;
284
284
  }
285
+ /** Generic error response from API */
286
+ export interface ErrorResponse {
287
+ error?: string;
288
+ message?: string;
289
+ success?: boolean;
290
+ }
291
+ /** Project data from API */
292
+ export interface ProjectData {
293
+ id: string;
294
+ name: string;
295
+ path: string;
296
+ status?: string;
297
+ indexed?: boolean;
298
+ lastAccess?: string;
299
+ updatedAt?: string;
300
+ lastIndexed?: string;
301
+ statistics?: {
302
+ codeIndex?: {
303
+ totalFiles?: number;
304
+ totalSymbols?: number;
305
+ };
306
+ };
307
+ stats?: {
308
+ filesCount?: number;
309
+ symbolsCount?: number;
310
+ };
311
+ }
312
+ /** Projects list response */
313
+ export interface ProjectsListData {
314
+ projects?: ProjectData[];
315
+ success?: boolean;
316
+ error?: string;
317
+ }
318
+ /** Single project response */
319
+ export interface ProjectDetailData {
320
+ project?: ProjectData;
321
+ success?: boolean;
322
+ error?: string;
323
+ }
324
+ /** User response from profile endpoint */
325
+ export interface UserData {
326
+ id: string;
327
+ email: string;
328
+ username?: string;
329
+ tier?: string;
330
+ provider?: string;
331
+ avatar?: string;
332
+ }
333
+ /** User profile response */
334
+ export interface UserProfileData {
335
+ user?: UserData;
336
+ success?: boolean;
337
+ error?: string;
338
+ }
339
+ /** Export response */
340
+ export interface ExportData {
341
+ success?: boolean;
342
+ export?: unknown;
343
+ error?: string;
344
+ }
345
+ /** Graph node for visualization */
346
+ export interface GraphNodeData {
347
+ id: string;
348
+ label?: string;
349
+ type?: string;
350
+ filePath?: string;
351
+ kind?: string;
352
+ size?: number;
353
+ group?: string;
354
+ }
355
+ /** Graph edge for visualization */
356
+ export interface GraphEdgeData {
357
+ from: string;
358
+ to: string;
359
+ type?: string;
360
+ }
361
+ /** Symbol data */
362
+ export interface SymbolData {
363
+ id: string;
364
+ name: string;
365
+ kind: string;
366
+ filePath: string;
367
+ startLine: number;
368
+ endLine: number;
369
+ references?: string[];
370
+ modifiers?: string[];
371
+ }
285
372
  export declare const DEFAULT_RATE_LIMITS: Record<string, RateLimitConfig>;
286
373
  //# sourceMappingURL=api.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archicore",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "AI Software Architect - code analysis, impact prediction, semantic search",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",