crewos 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.
Files changed (64) hide show
  1. package/app/.env.example +1 -0
  2. package/app/index.html +50 -0
  3. package/app/package.json +25 -0
  4. package/app/public/favicon.svg +1 -0
  5. package/app/public/images/cursor-ide-guiiding.png +0 -0
  6. package/app/public/images/gpt.jpg +0 -0
  7. package/app/src/app.jsx +22 -0
  8. package/app/src/components/ConfirmModal.jsx +50 -0
  9. package/app/src/components/Icons.jsx +377 -0
  10. package/app/src/components/RedirectRoute.jsx +14 -0
  11. package/app/src/components/SplashScreen.jsx +15 -0
  12. package/app/src/hooks/useAuth.js +28 -0
  13. package/app/src/index.css +268 -0
  14. package/app/src/main.jsx +5 -0
  15. package/app/src/navigations/AuthRoutes.jsx +15 -0
  16. package/app/src/navigations/MainRoutes.jsx +15 -0
  17. package/app/src/navigations/OnboardingRoutes.jsx +15 -0
  18. package/app/src/navigations/index.jsx +37 -0
  19. package/app/src/pages/Home/index.jsx +2095 -0
  20. package/app/src/pages/Login/index.jsx +118 -0
  21. package/app/src/pages/Onboarding/index.jsx +550 -0
  22. package/app/src/services/api.js +46 -0
  23. package/app/src/services/auth.service.js +3 -0
  24. package/app/src/services/config.service.js +13 -0
  25. package/app/src/services/member.service.js +7 -0
  26. package/app/src/services/onboarding.service.js +17 -0
  27. package/app/src/services/role.service.js +6 -0
  28. package/app/src/services/task.service.js +22 -0
  29. package/app/src/stores/auth.store.js +7 -0
  30. package/app/src/utils/environments.js +5 -0
  31. package/app/vite.config.js +10 -0
  32. package/app/yarn.lock +1337 -0
  33. package/backend/package-lock.json +918 -0
  34. package/backend/package.json +18 -0
  35. package/backend/src/configs/db.config.js +40 -0
  36. package/backend/src/controllers/auth.controller.js +19 -0
  37. package/backend/src/controllers/config.controller.js +23 -0
  38. package/backend/src/controllers/member.controller.js +30 -0
  39. package/backend/src/controllers/models.controller.js +25 -0
  40. package/backend/src/controllers/onboarding.controller.js +49 -0
  41. package/backend/src/controllers/role.controller.js +17 -0
  42. package/backend/src/controllers/task.controller.js +63 -0
  43. package/backend/src/index.js +36 -0
  44. package/backend/src/middlewares/onboarding.guard.js +14 -0
  45. package/backend/src/routes/auth.route.js +8 -0
  46. package/backend/src/routes/config.route.js +11 -0
  47. package/backend/src/routes/index.js +22 -0
  48. package/backend/src/routes/member.route.js +11 -0
  49. package/backend/src/routes/models.route.js +8 -0
  50. package/backend/src/routes/onboarding.route.js +13 -0
  51. package/backend/src/routes/role.route.js +9 -0
  52. package/backend/src/routes/task.route.js +20 -0
  53. package/backend/src/services/auth.service.js +14 -0
  54. package/backend/src/services/config.service.js +176 -0
  55. package/backend/src/services/data/roles.json +474 -0
  56. package/backend/src/services/member.service.js +77 -0
  57. package/backend/src/services/onboarding.service.js +328 -0
  58. package/backend/src/services/role.service.js +23 -0
  59. package/backend/src/services/task.service.js +665 -0
  60. package/backend/src/utils/catcher.js +9 -0
  61. package/backend/src/utils/sanitize.js +13 -0
  62. package/backend/yarn.lock +513 -0
  63. package/bin/crewos.js +307 -0
  64. package/package.json +11 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "crewos-backend",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=24.0.0"
7
+ },
8
+ "dependencies": {
9
+ "@faker-js/faker": "^10.4.0",
10
+ "cors": "^2.8.6",
11
+ "express": "^5.2.1",
12
+ "lowdb": "^7.0.1"
13
+ },
14
+ "scripts": {
15
+ "dev": "node --watch src/index.js",
16
+ "start": "node src/index.js"
17
+ }
18
+ }
@@ -0,0 +1,40 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import { JSONFileSyncPreset } from 'lowdb/node';
4
+
5
+ const DB_PATH = path.join(os.homedir(), '.crewos', 'db.json');
6
+
7
+ const defaultData = {
8
+ members: [],
9
+ tasks: [],
10
+ meta: {
11
+ password: '',
12
+ baseURL: '',
13
+ apiKey: '',
14
+ model: 'auto',
15
+ models: [],
16
+ onboardingCompleted: false,
17
+ projectKnowledge: {
18
+ folderStructure: '',
19
+ techStack: '',
20
+ codingConventions: '',
21
+ databaseDesign: '',
22
+ apiDesign: '',
23
+ configSystem: '',
24
+ keyPatterns: '',
25
+ lastAnalyzed: null,
26
+ },
27
+ },
28
+ };
29
+
30
+ let db;
31
+
32
+ export function initDB() {
33
+ db = JSONFileSyncPreset(DB_PATH, defaultData);
34
+ return db;
35
+ }
36
+
37
+ export function getDB() {
38
+ if (!db) throw new Error('Database not initialized. Call initDB() first.');
39
+ return db;
40
+ }
@@ -0,0 +1,19 @@
1
+ import * as authService from '../services/auth.service.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+
4
+ export const me = controllerCatcher((req, res) => {
5
+ const header = req.headers.authorization;
6
+
7
+ if (!header || !header.startsWith('Bearer '))
8
+ throw new Error('Bad credential');
9
+
10
+ const token = header.slice(7);
11
+
12
+ const verifyResult = authService.verifyPassword(token);
13
+ if (!verifyResult) throw new Error('Bad credential');
14
+
15
+ return res.json({
16
+ authenticated: true,
17
+ onboardingCompleted: verifyResult.onboardingCompleted || false,
18
+ });
19
+ });
@@ -0,0 +1,23 @@
1
+ import * as configService from '../services/config.service.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+
4
+ export const getConfig = controllerCatcher((_req, res) => {
5
+ return res.json(configService.getConfig());
6
+ });
7
+
8
+ export const updateConfig = controllerCatcher(async (req, res) => {
9
+ const { password, baseURL, apiKey, model } = req.body;
10
+
11
+ const config = await configService.updateConfig({ password, baseURL, apiKey, model });
12
+ return res.json(config);
13
+ });
14
+
15
+ export const startReanalysis = controllerCatcher((_req, res) => {
16
+ const result = configService.startReanalysis();
17
+ if (result.error) return res.status(409).json({ error: result.error });
18
+ return res.json(result);
19
+ });
20
+
21
+ export const getReanalysisProgress = controllerCatcher((_req, res) => {
22
+ return res.json(configService.getReanalysisProgress());
23
+ });
@@ -0,0 +1,30 @@
1
+ import * as memberService from '../services/member.service.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+
4
+ export const getAll = controllerCatcher((_req, res) => {
5
+ const members = memberService.getAll();
6
+ return res.json(members);
7
+ });
8
+
9
+ export const getById = controllerCatcher((req, res) => {
10
+ const member = memberService.getById(req.params.id);
11
+ if (!member) return res.status(404).json({ error: 'Member not found' });
12
+ return res.json(member);
13
+ });
14
+
15
+ export const create = controllerCatcher((req, res) => {
16
+ const member = memberService.create(req.body);
17
+ return res.status(201).json(member);
18
+ });
19
+
20
+ export const update = controllerCatcher((req, res) => {
21
+ const member = memberService.update(req.params.id, req.body);
22
+ if (!member) return res.status(404).json({ error: 'Member not found' });
23
+ return res.json(member);
24
+ });
25
+
26
+ export const remove = controllerCatcher((req, res) => {
27
+ const removed = memberService.remove(req.params.id);
28
+ if (!removed) return res.status(404).json({ error: 'Member not found' });
29
+ return res.sendStatus(204);
30
+ });
@@ -0,0 +1,25 @@
1
+ import { getDB } from '../configs/db.config.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+ import { fetchModelsFromProvider } from '../services/config.service.js';
4
+
5
+ export const fetchModels = controllerCatcher(async (req, res) => {
6
+ const db = getDB();
7
+ const { baseURL, apiKey } = req.body;
8
+
9
+ const url = baseURL || db.data.meta.baseURL;
10
+ const key = apiKey || db.data.meta.apiKey;
11
+
12
+ if (!url) {
13
+ return res.status(400).json({ message: 'Base URL is required' });
14
+ }
15
+ if (!key) {
16
+ return res.status(400).json({ message: 'API Key is required' });
17
+ }
18
+
19
+ try {
20
+ const models = await fetchModelsFromProvider(url, key);
21
+ return res.json(models);
22
+ } catch (err) {
23
+ return res.status(502).json({ message: err.message });
24
+ }
25
+ });
@@ -0,0 +1,49 @@
1
+ import * as onboardingService from '../services/onboarding.service.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+
4
+ export const getStatus = controllerCatcher((_req, res) => {
5
+ return res.json(onboardingService.getOnboardingStatus());
6
+ });
7
+
8
+ export const validate = controllerCatcher(async (req, res) => {
9
+ const { baseURL, apiKey } = req.body;
10
+
11
+ if (!baseURL) return res.status(400).json({ error: 'Base URL is required' });
12
+ if (!apiKey) return res.status(400).json({ error: 'API Key is required' });
13
+
14
+ try {
15
+ await onboardingService.validateCredentials(baseURL, apiKey);
16
+ return res.json({ valid: true });
17
+ } catch (err) {
18
+ return res.status(400).json({ error: err.message });
19
+ }
20
+ });
21
+
22
+ export const saveCredentials = controllerCatcher((req, res) => {
23
+ const { baseURL, apiKey } = req.body;
24
+
25
+ if (!baseURL) return res.status(400).json({ error: 'Base URL is required' });
26
+ if (!apiKey) return res.status(400).json({ error: 'API Key is required' });
27
+
28
+ onboardingService.saveCredentials(baseURL, apiKey);
29
+ return res.json({ success: true });
30
+ });
31
+
32
+ export const startAnalysis = controllerCatcher((_req, res) => {
33
+ const result = onboardingService.startAnalysis();
34
+ if (result.error) return res.status(409).json({ error: result.error });
35
+ return res.json(result);
36
+ });
37
+
38
+ export const getAnalysisProgress = controllerCatcher((_req, res) => {
39
+ return res.json({
40
+ lines: onboardingService.getAnalysisProgress(),
41
+ isAnalyzing: onboardingService.isAnalyzing(),
42
+ });
43
+ });
44
+
45
+ export const complete = controllerCatcher((_req, res) => {
46
+ const result = onboardingService.completeOnboarding();
47
+ if (result.error) return res.status(400).json({ error: result.error });
48
+ return res.json(result);
49
+ });
@@ -0,0 +1,17 @@
1
+ import * as roleService from '../services/role.service.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+
4
+ export const getRoles = controllerCatcher((_req, res) => {
5
+ const roles = roleService.getRoles();
6
+ return res.json(roles);
7
+ });
8
+
9
+ export const pullRole = controllerCatcher((req, res) => {
10
+ const result = roleService.pullRole(req.params.roleId);
11
+
12
+ if (result.error) {
13
+ return res.status(400).json({ error: result.error });
14
+ }
15
+
16
+ return res.status(201).json(result);
17
+ });
@@ -0,0 +1,63 @@
1
+ import * as taskService from '../services/task.service.js';
2
+ import { controllerCatcher } from '../utils/catcher.js';
3
+
4
+ export const getAll = controllerCatcher((req, res) => {
5
+ const tasks = taskService.get({ status: req.query.status });
6
+ return res.json(tasks);
7
+ });
8
+
9
+ export const getById = controllerCatcher((req, res) => {
10
+ const task = taskService.getById(req.params.id);
11
+ if (!task) return res.status(404).json({ error: 'Task not found' });
12
+ return res.json(task);
13
+ });
14
+
15
+ export const create = controllerCatcher((req, res) => {
16
+ const result = taskService.create(req.body);
17
+ if (result.error) return res.status(400).json({ error: result.error });
18
+ return res.status(201).json(result);
19
+ });
20
+
21
+ export const update = controllerCatcher((req, res) => {
22
+ const task = taskService.update(req.params.id, req.body);
23
+ if (!task) return res.status(404).json({ error: 'Task not found' });
24
+ return res.json(task);
25
+ });
26
+
27
+ export const remove = controllerCatcher((req, res) => {
28
+ const removed = taskService.remove(req.params.id);
29
+ if (!removed) return res.status(404).json({ error: 'Task not found' });
30
+ return res.sendStatus(204);
31
+ });
32
+
33
+ export const start = controllerCatcher((req, res) => {
34
+ const result = taskService.startTask(req.params.id);
35
+ if (result.error) return res.status(409).json({ error: result.error });
36
+ return res.json(result);
37
+ });
38
+
39
+ export const getProgress = controllerCatcher((_req, res) => {
40
+ return res.json(taskService.getProgress());
41
+ });
42
+
43
+ export const stop = controllerCatcher((_req, res) => {
44
+ const result = taskService.stopTask();
45
+ if (result.error) return res.status(409).json({ error: result.error });
46
+ return res.json(result);
47
+ });
48
+
49
+ export const autoStart = controllerCatcher((req, res) => {
50
+ const result = taskService.startAutoDo(req.body.taskIds);
51
+ if (result.error) return res.status(409).json({ error: result.error });
52
+ return res.json(result);
53
+ });
54
+
55
+ export const autoStop = controllerCatcher((_req, res) => {
56
+ const result = taskService.stopAutoDo();
57
+ if (result.error) return res.status(409).json({ error: result.error });
58
+ return res.json(result);
59
+ });
60
+
61
+ export const autoStatus = controllerCatcher((_req, res) => {
62
+ return res.json(taskService.getAutoStatus());
63
+ });
@@ -0,0 +1,36 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import express from 'express';
6
+ import cors from 'cors';
7
+ import routes from './routes/index.js';
8
+ import { initDB } from './configs/db.config.js';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ const db = initDB();
13
+
14
+ const configPath = path.join(os.homedir(), '.crewos', 'config.json');
15
+ let appConfig = { port: 3000 };
16
+ if (fs.existsSync(configPath)) {
17
+ appConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
18
+ }
19
+
20
+ const app = express();
21
+
22
+ app.use(cors());
23
+ app.use(express.json());
24
+ app.use('/api', routes);
25
+
26
+ app.get('/', (req, res) => {
27
+ return res.sendStatus(200);
28
+ });
29
+
30
+ const PORT = process.env.PORT || appConfig.backendPort || 3000;
31
+
32
+ app.listen(PORT, () => {
33
+ console.log(`crewOS running at http://localhost:${PORT}`);
34
+ });
35
+
36
+ export default app;
@@ -0,0 +1,14 @@
1
+ import { getDB } from '../configs/db.config.js';
2
+
3
+ export const onboardingGuard = (_req, res, next) => {
4
+ const db = getDB();
5
+
6
+ if (!db.data.meta.onboardingCompleted) {
7
+ return res.status(403).json({
8
+ error: 'Onboarding not completed',
9
+ onboardingRequired: true,
10
+ });
11
+ }
12
+
13
+ next();
14
+ };
@@ -0,0 +1,8 @@
1
+ import { Router } from 'express';
2
+ import * as authController from '../controllers/auth.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/me', authController.me);
7
+
8
+ export default router;
@@ -0,0 +1,11 @@
1
+ import { Router } from 'express';
2
+ import * as configController from '../controllers/config.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/', configController.getConfig);
7
+ router.put('/', configController.updateConfig);
8
+ router.post('/analyze', configController.startReanalysis);
9
+ router.get('/analyze/progress', configController.getReanalysisProgress);
10
+
11
+ export default router;
@@ -0,0 +1,22 @@
1
+ import { Router } from 'express';
2
+ import taskRoute from './task.route.js';
3
+ import memberRoute from './member.route.js';
4
+ import roleRoute from './role.route.js';
5
+ import configRoute from './config.route.js';
6
+ import modelsRoute from './models.route.js';
7
+ import authRoute from './auth.route.js';
8
+ import onboardingRoute from './onboarding.route.js';
9
+ import { onboardingGuard } from '../middlewares/onboarding.guard.js';
10
+
11
+ const router = Router();
12
+
13
+ router.use('/v1/auth', authRoute);
14
+ router.use('/v1/onboarding', onboardingRoute);
15
+
16
+ router.use('/v1/tasks', onboardingGuard, taskRoute);
17
+ router.use('/v1/members', onboardingGuard, memberRoute);
18
+ router.use('/v1/roles', onboardingGuard, roleRoute);
19
+ router.use('/v1/config', onboardingGuard, configRoute);
20
+ router.use('/v1/models', onboardingGuard, modelsRoute);
21
+
22
+ export default router;
@@ -0,0 +1,11 @@
1
+ import { Router } from 'express';
2
+ import * as memberController from '../controllers/member.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/', memberController.getAll);
7
+ router.get('/:id', memberController.getById);
8
+ router.put('/:id', memberController.update);
9
+ router.delete('/:id', memberController.remove);
10
+
11
+ export default router;
@@ -0,0 +1,8 @@
1
+ import { Router } from 'express';
2
+ import { fetchModels } from '../controllers/models.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.post('/', fetchModels);
7
+
8
+ export default router;
@@ -0,0 +1,13 @@
1
+ import { Router } from 'express';
2
+ import * as onboardingController from '../controllers/onboarding.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/status', onboardingController.getStatus);
7
+ router.post('/validate', onboardingController.validate);
8
+ router.post('/credentials', onboardingController.saveCredentials);
9
+ router.post('/analyze', onboardingController.startAnalysis);
10
+ router.get('/analyze/progress', onboardingController.getAnalysisProgress);
11
+ router.post('/complete', onboardingController.complete);
12
+
13
+ export default router;
@@ -0,0 +1,9 @@
1
+ import { Router } from 'express';
2
+ import * as roleController from '../controllers/role.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/', roleController.getRoles);
7
+ router.post('/:roleId/pull', roleController.pullRole);
8
+
9
+ export default router;
@@ -0,0 +1,20 @@
1
+ import { Router } from 'express';
2
+ import * as taskController from '../controllers/task.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/', taskController.getAll);
7
+
8
+ router.get('/auto/status', taskController.autoStatus);
9
+ router.post('/auto/start', taskController.autoStart);
10
+ router.post('/auto/stop', taskController.autoStop);
11
+
12
+ router.get('/progress', taskController.getProgress);
13
+ router.get('/:id', taskController.getById);
14
+ router.post('/', taskController.create);
15
+ router.put('/:id', taskController.update);
16
+ router.delete('/:id', taskController.remove);
17
+ router.post('/stop', taskController.stop);
18
+ router.post('/:id/start', taskController.start);
19
+
20
+ export default router;
@@ -0,0 +1,14 @@
1
+ import { getDB } from '../configs/db.config.js';
2
+
3
+ export const verifyPassword = (token) => {
4
+ const db = getDB();
5
+ const password = db.data.meta.password;
6
+
7
+ if (!password) return false;
8
+ if (token !== password) return false;
9
+
10
+ return {
11
+ authenticated: true,
12
+ onboardingCompleted: db.data.meta.onboardingCompleted || false,
13
+ };
14
+ };
@@ -0,0 +1,176 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import fs from 'fs';
4
+ import { getDB } from '../configs/db.config.js';
5
+ import { startAnalysis, getAnalysisProgress, isAnalyzing } from './onboarding.service.js';
6
+
7
+ const CONFIG_PATH = path.join(os.homedir(), '.crewos', 'config.json');
8
+ const OPENCODE_CONFIG_PATH = path.join(
9
+ os.homedir(),
10
+ '.config',
11
+ 'opencode',
12
+ 'opencode.json',
13
+ );
14
+
15
+ export const getCachedModels = () => {
16
+ const db = getDB();
17
+ return db.data.meta.models || [];
18
+ };
19
+
20
+ export const updateModelCache = (models) => {
21
+ const db = getDB();
22
+ db.data.meta.models = models || [];
23
+ db.write();
24
+ syncOpenCodeConfig();
25
+ };
26
+
27
+ const isModelValid = (model) => {
28
+ if (!model || model === 'auto') return true;
29
+ const cached = getCachedModels();
30
+ if (cached.length === 0) return true;
31
+ return cached.some((m) => m.id === model);
32
+ };
33
+
34
+ function readConfigFile() {
35
+ if (fs.existsSync(CONFIG_PATH)) {
36
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
37
+ }
38
+ return {};
39
+ }
40
+
41
+ function readOpenCodeConfig() {
42
+ if (fs.existsSync(OPENCODE_CONFIG_PATH)) {
43
+ try {
44
+ return JSON.parse(fs.readFileSync(OPENCODE_CONFIG_PATH, 'utf-8'));
45
+ } catch {
46
+ return {};
47
+ }
48
+ }
49
+ return {};
50
+ }
51
+
52
+ function writeOpenCodeConfig(config) {
53
+ const dir = path.dirname(OPENCODE_CONFIG_PATH);
54
+ if (!fs.existsSync(dir)) {
55
+ fs.mkdirSync(dir, { recursive: true });
56
+ }
57
+ fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
58
+ }
59
+
60
+ export const syncOpenCodeConfig = () => {
61
+ const db = getDB();
62
+ const { baseURL, apiKey, model } = db.data.meta;
63
+ const cached = getCachedModels();
64
+
65
+ if (!baseURL || !apiKey) return;
66
+
67
+ const effectiveModel = model && model !== 'auto' ? model : null;
68
+ const resolvedModelId =
69
+ effectiveModel || (cached.length > 0 ? cached[0].id : null);
70
+ if (!resolvedModelId) return;
71
+
72
+ const modelsMap = {};
73
+ for (const m of cached) {
74
+ modelsMap[m.id] = { id: m.id, name: m.name || m.id };
75
+ }
76
+
77
+ const existing = readOpenCodeConfig();
78
+ const providers = existing.provider || {};
79
+ providers.crewos = {
80
+ name: 'crewOS',
81
+ options: { baseURL, apiKey },
82
+ models: modelsMap,
83
+ };
84
+
85
+ writeOpenCodeConfig({
86
+ ...existing,
87
+ $schema: existing.$schema || 'https://opencode.ai/config.json',
88
+ provider: providers,
89
+ model: `crewos/${resolvedModelId}`,
90
+ });
91
+ };
92
+
93
+ export const fetchModelsFromProvider = async (baseURL, apiKey) => {
94
+ const normalizedBase = baseURL.replace(/\/+$/, '');
95
+ const modelsUrl = `${normalizedBase}/models`;
96
+
97
+ const response = await fetch(modelsUrl, {
98
+ headers: {
99
+ Authorization: `Bearer ${apiKey}`,
100
+ 'Content-Type': 'application/json',
101
+ },
102
+ });
103
+
104
+ if (!response.ok) {
105
+ throw new Error(`Failed to fetch models: ${response.status}`);
106
+ }
107
+
108
+ const data = await response.json();
109
+ const models = (data.data || data).map((m) => ({
110
+ id: m.id || m.name || String(m),
111
+ name: m.id || m.name || String(m),
112
+ }));
113
+
114
+ updateModelCache(models);
115
+ return models;
116
+ };
117
+
118
+ export const getConfig = () => {
119
+ const db = getDB();
120
+ const fileConfig = readConfigFile();
121
+ return {
122
+ workingDir: fileConfig.workingDir || process.cwd(),
123
+ baseURL: db.data.meta.baseURL || '',
124
+ apiKey: db.data.meta.apiKey || '',
125
+ model: db.data.meta.model || 'auto',
126
+ password: db.data.meta.password || '',
127
+ models: getCachedModels(),
128
+ opencodePort: fileConfig.opencodePort,
129
+ onboardingCompleted: db.data.meta.onboardingCompleted || false,
130
+ projectKnowledge: db.data.meta.projectKnowledge || {},
131
+ };
132
+ };
133
+
134
+ export const updateConfig = async ({ password, baseURL, apiKey, model }) => {
135
+ const db = getDB();
136
+
137
+ if (password !== undefined) {
138
+ db.data.meta.password = password;
139
+ }
140
+ if (baseURL !== undefined) {
141
+ db.data.meta.baseURL = baseURL;
142
+ }
143
+ if (apiKey !== undefined) {
144
+ db.data.meta.apiKey = apiKey;
145
+ }
146
+ if (model !== undefined) {
147
+ db.data.meta.model = isModelValid(model) ? model : 'auto';
148
+ }
149
+
150
+ db.write();
151
+
152
+ const curBaseURL = baseURL !== undefined ? baseURL : db.data.meta.baseURL;
153
+ const curApiKey = apiKey !== undefined ? apiKey : db.data.meta.apiKey;
154
+
155
+ if (curBaseURL && curApiKey) {
156
+ try {
157
+ await fetchModelsFromProvider(curBaseURL, curApiKey);
158
+ } catch {
159
+ // non-fatal — models may already be in DB from a previous fetch
160
+ }
161
+ }
162
+
163
+ syncOpenCodeConfig();
164
+ return getConfig();
165
+ };
166
+
167
+ export const startReanalysis = () => {
168
+ return startAnalysis();
169
+ };
170
+
171
+ export const getReanalysisProgress = () => {
172
+ return {
173
+ lines: getAnalysisProgress(),
174
+ isAnalyzing: isAnalyzing(),
175
+ };
176
+ };