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.
- package/app/.env.example +1 -0
- package/app/index.html +50 -0
- package/app/package.json +25 -0
- package/app/public/favicon.svg +1 -0
- package/app/public/images/cursor-ide-guiiding.png +0 -0
- package/app/public/images/gpt.jpg +0 -0
- package/app/src/app.jsx +22 -0
- package/app/src/components/ConfirmModal.jsx +50 -0
- package/app/src/components/Icons.jsx +377 -0
- package/app/src/components/RedirectRoute.jsx +14 -0
- package/app/src/components/SplashScreen.jsx +15 -0
- package/app/src/hooks/useAuth.js +28 -0
- package/app/src/index.css +268 -0
- package/app/src/main.jsx +5 -0
- package/app/src/navigations/AuthRoutes.jsx +15 -0
- package/app/src/navigations/MainRoutes.jsx +15 -0
- package/app/src/navigations/OnboardingRoutes.jsx +15 -0
- package/app/src/navigations/index.jsx +37 -0
- package/app/src/pages/Home/index.jsx +2095 -0
- package/app/src/pages/Login/index.jsx +118 -0
- package/app/src/pages/Onboarding/index.jsx +550 -0
- package/app/src/services/api.js +46 -0
- package/app/src/services/auth.service.js +3 -0
- package/app/src/services/config.service.js +13 -0
- package/app/src/services/member.service.js +7 -0
- package/app/src/services/onboarding.service.js +17 -0
- package/app/src/services/role.service.js +6 -0
- package/app/src/services/task.service.js +22 -0
- package/app/src/stores/auth.store.js +7 -0
- package/app/src/utils/environments.js +5 -0
- package/app/vite.config.js +10 -0
- package/app/yarn.lock +1337 -0
- package/backend/package-lock.json +918 -0
- package/backend/package.json +18 -0
- package/backend/src/configs/db.config.js +40 -0
- package/backend/src/controllers/auth.controller.js +19 -0
- package/backend/src/controllers/config.controller.js +23 -0
- package/backend/src/controllers/member.controller.js +30 -0
- package/backend/src/controllers/models.controller.js +25 -0
- package/backend/src/controllers/onboarding.controller.js +49 -0
- package/backend/src/controllers/role.controller.js +17 -0
- package/backend/src/controllers/task.controller.js +63 -0
- package/backend/src/index.js +36 -0
- package/backend/src/middlewares/onboarding.guard.js +14 -0
- package/backend/src/routes/auth.route.js +8 -0
- package/backend/src/routes/config.route.js +11 -0
- package/backend/src/routes/index.js +22 -0
- package/backend/src/routes/member.route.js +11 -0
- package/backend/src/routes/models.route.js +8 -0
- package/backend/src/routes/onboarding.route.js +13 -0
- package/backend/src/routes/role.route.js +9 -0
- package/backend/src/routes/task.route.js +20 -0
- package/backend/src/services/auth.service.js +14 -0
- package/backend/src/services/config.service.js +176 -0
- package/backend/src/services/data/roles.json +474 -0
- package/backend/src/services/member.service.js +77 -0
- package/backend/src/services/onboarding.service.js +328 -0
- package/backend/src/services/role.service.js +23 -0
- package/backend/src/services/task.service.js +665 -0
- package/backend/src/utils/catcher.js +9 -0
- package/backend/src/utils/sanitize.js +13 -0
- package/backend/yarn.lock +513 -0
- package/bin/crewos.js +307 -0
- package/package.json +11 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
import { getDB } from '../configs/db.config.js';
|
|
7
|
+
import { fetchModelsFromProvider } from './config.service.js';
|
|
8
|
+
|
|
9
|
+
const CREWOS_CONFIG_PATH = path.join(os.homedir(), '.crewos', 'config.json');
|
|
10
|
+
const ANALYSIS_TIMEOUT_MS = 15 * 60 * 1_000;
|
|
11
|
+
|
|
12
|
+
const SECTION_MARKERS = [
|
|
13
|
+
'FOLDER_STRUCTURE',
|
|
14
|
+
'TECH_STACK',
|
|
15
|
+
'CODING_CONVENTIONS',
|
|
16
|
+
'DATABASE_DESIGN',
|
|
17
|
+
'API_DESIGN',
|
|
18
|
+
'CONFIG_SYSTEM',
|
|
19
|
+
'KEY_PATTERNS',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const FIELD_MAP = {
|
|
23
|
+
FOLDER_STRUCTURE: 'folderStructure',
|
|
24
|
+
TECH_STACK: 'techStack',
|
|
25
|
+
CODING_CONVENTIONS: 'codingConventions',
|
|
26
|
+
DATABASE_DESIGN: 'databaseDesign',
|
|
27
|
+
API_DESIGN: 'apiDesign',
|
|
28
|
+
CONFIG_SYSTEM: 'configSystem',
|
|
29
|
+
KEY_PATTERNS: 'keyPatterns',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let analysisProcess = null;
|
|
33
|
+
let analysisProgress = [];
|
|
34
|
+
let analysisSettled = false;
|
|
35
|
+
|
|
36
|
+
export const isAnalyzing = () => analysisProcess !== null;
|
|
37
|
+
|
|
38
|
+
export const getAnalysisProgress = () => analysisProgress;
|
|
39
|
+
|
|
40
|
+
export const validateCredentials = async (baseURL, apiKey) => {
|
|
41
|
+
const normalizedBase = baseURL.replace(/\/+$/, '');
|
|
42
|
+
const modelsUrl = `${normalizedBase}/models`;
|
|
43
|
+
|
|
44
|
+
const response = await fetch(modelsUrl, {
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Connection failed (HTTP ${response.status}). Check your Base URL and API Key.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const getOnboardingStatus = () => {
|
|
61
|
+
const db = getDB();
|
|
62
|
+
const pk = db.data.meta.projectKnowledge || {};
|
|
63
|
+
const contentFields = [
|
|
64
|
+
'folderStructure',
|
|
65
|
+
'techStack',
|
|
66
|
+
'codingConventions',
|
|
67
|
+
'databaseDesign',
|
|
68
|
+
'apiDesign',
|
|
69
|
+
'configSystem',
|
|
70
|
+
'keyPatterns',
|
|
71
|
+
];
|
|
72
|
+
const hasKnowledge = contentFields.some(
|
|
73
|
+
(f) => typeof pk[f] === 'string' && pk[f].trim().length > 0,
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
onboardingCompleted: db.data.meta.onboardingCompleted || false,
|
|
77
|
+
baseURL: db.data.meta.baseURL || '',
|
|
78
|
+
apiKey: db.data.meta.apiKey || '',
|
|
79
|
+
projectKnowledge: pk,
|
|
80
|
+
hasCredentials: !!(db.data.meta.baseURL && db.data.meta.apiKey),
|
|
81
|
+
hasKnowledge,
|
|
82
|
+
isAnalyzing: isAnalyzing(),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const saveCredentials = (baseURL, apiKey) => {
|
|
87
|
+
const db = getDB();
|
|
88
|
+
db.data.meta.baseURL = baseURL;
|
|
89
|
+
db.data.meta.apiKey = apiKey;
|
|
90
|
+
db.write();
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
fetchModelsFromProvider(baseURL, apiKey);
|
|
94
|
+
} catch {
|
|
95
|
+
// non-fatal
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function parseAnalysisOutput(raw) {
|
|
100
|
+
const result = {};
|
|
101
|
+
let currentSection = null;
|
|
102
|
+
const lines = raw.split('\n');
|
|
103
|
+
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
for (const marker of SECTION_MARKERS) {
|
|
106
|
+
const tag = `===${marker}===`;
|
|
107
|
+
if (line.includes(tag)) {
|
|
108
|
+
currentSection = marker;
|
|
109
|
+
result[FIELD_MAP[marker]] = '';
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
currentSection &&
|
|
116
|
+
!SECTION_MARKERS.some((m) => line.includes(`===${m}===`))
|
|
117
|
+
) {
|
|
118
|
+
const field = FIELD_MAP[currentSection];
|
|
119
|
+
if (result[field] !== undefined) {
|
|
120
|
+
result[field] += (result[field] ? '\n' : '') + line;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const marker of SECTION_MARKERS) {
|
|
126
|
+
const field = FIELD_MAP[marker];
|
|
127
|
+
if (!result[field]) result[field] = '';
|
|
128
|
+
else result[field] = result[field].trim();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const startAnalysis = () => {
|
|
135
|
+
const db = getDB();
|
|
136
|
+
|
|
137
|
+
if (analysisProcess) {
|
|
138
|
+
return { error: 'Analysis is already running' };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let workingDir = process.cwd();
|
|
142
|
+
if (fs.existsSync(CREWOS_CONFIG_PATH)) {
|
|
143
|
+
try {
|
|
144
|
+
const cfg = JSON.parse(fs.readFileSync(CREWOS_CONFIG_PATH, 'utf-8'));
|
|
145
|
+
workingDir = cfg.workingDir || process.cwd();
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`workingDir`, err);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(`analyze working directory`, workingDir);
|
|
152
|
+
|
|
153
|
+
const sectionInstructions = SECTION_MARKERS.map((m) => {
|
|
154
|
+
const label = m.replace(/_/g, ' ').replace(/\b\w/g, (c) => c);
|
|
155
|
+
return `===${m}===\n[content about ${label.toLowerCase()}]\n===END_${m}===`;
|
|
156
|
+
}).join('\n\n');
|
|
157
|
+
|
|
158
|
+
const prompt = [
|
|
159
|
+
`You are analyzing the codebase at: ${workingDir}`,
|
|
160
|
+
'',
|
|
161
|
+
'IMPORTANT: Explore EVERY directory and subdirectory in this project. Use ls, find (or equivalents) to discover the full folder tree. Read files from ALL top-level and nested directories — do NOT focus on a single subfolder.',
|
|
162
|
+
'',
|
|
163
|
+
'Produce a structured knowledge report using EXACTLY the section markers shown below. Each section MUST start with its marker on its own line and end with its END_ marker.',
|
|
164
|
+
'Include specific file paths, function names, and concrete examples. If a section does not apply, write "N/A". Do not skip any section.',
|
|
165
|
+
'',
|
|
166
|
+
sectionInstructions,
|
|
167
|
+
'',
|
|
168
|
+
'SECTIONS:',
|
|
169
|
+
'FOLDER_STRUCTURE — List EVERY top-level directory and file with its purpose. Show the full tree.',
|
|
170
|
+
'TECH_STACK — Languages, frameworks, dependencies, build tools (check all package.json, requirements.txt, go.mod, Cargo.toml, etc. across every subdirectory)',
|
|
171
|
+
'CODING_CONVENTIONS — Naming patterns, file organization, styling approach, code style across the whole project',
|
|
172
|
+
'DATABASE_DESIGN — Schemas, models, relationships, storage mechanism',
|
|
173
|
+
'API_DESIGN — Route structure, auth approach, request/response patterns',
|
|
174
|
+
'CONFIG_SYSTEM — Config files, environment variables, how the app bootstraps',
|
|
175
|
+
'KEY_PATTERNS — Design patterns, architectural decisions, notable idioms',
|
|
176
|
+
'',
|
|
177
|
+
`Remember: explore the ENTIRE directory tree at ${workingDir} — do not limit your analysis to any single folder.`,
|
|
178
|
+
].join('\n');
|
|
179
|
+
|
|
180
|
+
const args = ['run', '--dangerously-skip-permissions', prompt];
|
|
181
|
+
|
|
182
|
+
const child = spawn('opencode', args, {
|
|
183
|
+
cwd: workingDir,
|
|
184
|
+
env: process.env,
|
|
185
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
analysisProcess = child;
|
|
189
|
+
analysisProgress = [];
|
|
190
|
+
analysisSettled = false;
|
|
191
|
+
|
|
192
|
+
let settled = false;
|
|
193
|
+
let timeout = null;
|
|
194
|
+
|
|
195
|
+
timeout = setTimeout(() => {
|
|
196
|
+
if (settled) return;
|
|
197
|
+
settled = true;
|
|
198
|
+
analysisSettled = true;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
child.kill('SIGTERM');
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
if (!child.killed) {
|
|
204
|
+
try {
|
|
205
|
+
child.kill('SIGKILL');
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error(`timeout hit`, err);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}, 5000);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error(`kill`, err);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
analysisProcess = null;
|
|
216
|
+
}, ANALYSIS_TIMEOUT_MS);
|
|
217
|
+
|
|
218
|
+
child.stdout.on('data', (data) => {
|
|
219
|
+
const lines = data.toString().split('\n').filter(Boolean);
|
|
220
|
+
analysisProgress.push(...lines);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
child.stderr.on('data', (data) => {
|
|
224
|
+
const lines = data.toString().split('\n').filter(Boolean);
|
|
225
|
+
analysisProgress.push(...lines);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
child.on('close', (code) => {
|
|
229
|
+
if (settled) return;
|
|
230
|
+
if (timeout) clearTimeout(timeout);
|
|
231
|
+
settled = true;
|
|
232
|
+
analysisSettled = true;
|
|
233
|
+
|
|
234
|
+
if (code === 0) {
|
|
235
|
+
const raw = analysisProgress.join('\n');
|
|
236
|
+
const structured = parseAnalysisOutput(raw);
|
|
237
|
+
const hasAny = Object.values(structured).some((v) => v && v.trim());
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const freshDb = getDB();
|
|
241
|
+
freshDb.data.meta.projectKnowledge = hasAny
|
|
242
|
+
? structured
|
|
243
|
+
: fallbackParse(raw);
|
|
244
|
+
freshDb.data.meta.projectKnowledge.lastAnalyzed =
|
|
245
|
+
new Date().toISOString();
|
|
246
|
+
freshDb.write();
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.error(`db write`, err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
analysisProcess = null;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
child.on('error', () => {
|
|
256
|
+
if (settled) return;
|
|
257
|
+
if (timeout) clearTimeout(timeout);
|
|
258
|
+
settled = true;
|
|
259
|
+
analysisSettled = true;
|
|
260
|
+
analysisProcess = null;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return { success: true };
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
function fallbackParse(raw) {
|
|
267
|
+
const result = {};
|
|
268
|
+
const sections = {
|
|
269
|
+
'## Project Structure': 'folderStructure',
|
|
270
|
+
'## Tech Stack': 'techStack',
|
|
271
|
+
'## Coding Conventions': 'codingConventions',
|
|
272
|
+
'## Database Design': 'databaseDesign',
|
|
273
|
+
'## API Design': 'apiDesign',
|
|
274
|
+
'## Configuration System': 'configSystem',
|
|
275
|
+
'## Key Patterns': 'keyPatterns',
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
let current = null;
|
|
279
|
+
for (const line of raw.split('\n')) {
|
|
280
|
+
const trimmed = line.trim();
|
|
281
|
+
const match = Object.entries(sections).find(([h]) =>
|
|
282
|
+
trimmed.toLowerCase().startsWith(h.toLowerCase()),
|
|
283
|
+
);
|
|
284
|
+
if (match) {
|
|
285
|
+
current = match[1];
|
|
286
|
+
result[current] = '';
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (current && result[current] !== undefined) {
|
|
290
|
+
result[current] += (result[current] ? '\n' : '') + line;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const field of Object.values(sections)) {
|
|
295
|
+
if (!result[field]) result[field] = '';
|
|
296
|
+
else result[field] = result[field].trim();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const completeOnboarding = () => {
|
|
303
|
+
const db = getDB();
|
|
304
|
+
const pk = db.data.meta.projectKnowledge || {};
|
|
305
|
+
const contentFields = [
|
|
306
|
+
'folderStructure',
|
|
307
|
+
'techStack',
|
|
308
|
+
'codingConventions',
|
|
309
|
+
'databaseDesign',
|
|
310
|
+
'apiDesign',
|
|
311
|
+
'configSystem',
|
|
312
|
+
'keyPatterns',
|
|
313
|
+
];
|
|
314
|
+
const hasKnowledge = contentFields.some(
|
|
315
|
+
(f) => typeof pk[f] === 'string' && pk[f].trim().length > 0,
|
|
316
|
+
);
|
|
317
|
+
if (!hasKnowledge) {
|
|
318
|
+
return { error: 'Project analysis must be completed first' };
|
|
319
|
+
}
|
|
320
|
+
db.data.meta.onboardingCompleted = true;
|
|
321
|
+
db.write();
|
|
322
|
+
return { success: true };
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export const getProjectKnowledge = () => {
|
|
326
|
+
const db = getDB();
|
|
327
|
+
return db.data.meta.projectKnowledge || {};
|
|
328
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { create as createMember } from './member.service.js';
|
|
2
|
+
import roles from './data/roles.json' with { type: 'json' };
|
|
3
|
+
|
|
4
|
+
export const getRoles = () => {
|
|
5
|
+
return roles;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getRoleById = (id) => {
|
|
9
|
+
return roles.find((r) => r.id === id) || null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const pullRole = (roleId) => {
|
|
13
|
+
const role = getRoleById(roleId);
|
|
14
|
+
if (!role) return { error: 'Role not found' };
|
|
15
|
+
|
|
16
|
+
return createMember({
|
|
17
|
+
role: role.title,
|
|
18
|
+
roleId: role.id,
|
|
19
|
+
skills: role.skills.join(', '),
|
|
20
|
+
rules: role.rules.join('\n'),
|
|
21
|
+
memory: role.memory.join('\n'),
|
|
22
|
+
});
|
|
23
|
+
};
|