crewx 0.8.5 → 0.8.6-rc.1
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/bin/crewx-lib.js +3 -1
- package/bin/crewx.js +6 -1
- package/dist-server/bootstrap/tls.js +57 -0
- package/dist-server/domain/auth/auth.controller.js +5 -4
- package/dist-server/domain/mcp/mcp.service.js +20 -18
- package/dist-server/main.js +13 -7
- package/package.json +6 -5
- package/packages/cli/package.json +1 -1
- package/dist-server/package.json +0 -3
package/bin/crewx-lib.js
CHANGED
|
@@ -164,7 +164,9 @@ export function ensureCrewxDirs() {
|
|
|
164
164
|
const crewxHome = join(homedir(), '.crewx'); // config/DB
|
|
165
165
|
const crewxWorkspaces = join(homedir(), 'crewx'); // workspaces collection
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
const crewxTls = join(crewxHome, 'tls');
|
|
168
|
+
|
|
169
|
+
for (const dir of [crewxHome, crewxWorkspaces, crewxTls]) {
|
|
168
170
|
try {
|
|
169
171
|
mkdirSync(dir, { recursive: true });
|
|
170
172
|
} catch (err) {
|
package/bin/crewx.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* crewx q|x|agent|... <args> → CLI subcommand (forwarded to packages/cli)
|
|
15
15
|
*/
|
|
16
16
|
import { spawn } from 'child_process';
|
|
17
|
-
import { readFileSync } from 'fs';
|
|
17
|
+
import { existsSync, readFileSync } from 'fs';
|
|
18
18
|
import { fileURLToPath } from 'url';
|
|
19
19
|
import { dirname, join } from 'path';
|
|
20
20
|
import { homedir } from 'os';
|
|
@@ -139,6 +139,11 @@ function launchWeb(serveArgs = [], { openBrowser = false } = {}) {
|
|
|
139
139
|
env.MCP_BEARER_TOKEN = content;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
const certPath = join(homedir(), '.crewx', 'tls', 'cert.pem');
|
|
143
|
+
if (existsSync(certPath)) {
|
|
144
|
+
env.NODE_EXTRA_CA_CERTS = certPath;
|
|
145
|
+
}
|
|
146
|
+
|
|
142
147
|
if (openBrowser && !noOpen) {
|
|
143
148
|
env.CREWX_OPEN_BROWSER = '1';
|
|
144
149
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CERT_PATH = void 0;
|
|
4
|
+
exports.resolveHttpsOptions = resolveHttpsOptions;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const selfsigned_1 = require("selfsigned");
|
|
9
|
+
const TLS_DIR = (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'tls');
|
|
10
|
+
const CERT_PATH = (0, path_1.join)(TLS_DIR, 'cert.pem');
|
|
11
|
+
exports.CERT_PATH = CERT_PATH;
|
|
12
|
+
const KEY_PATH = (0, path_1.join)(TLS_DIR, 'key.pem');
|
|
13
|
+
async function resolveHttpsOptions() {
|
|
14
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (process.env.TLS_CERT && process.env.TLS_KEY) {
|
|
18
|
+
return {
|
|
19
|
+
cert: (0, fs_1.readFileSync)(process.env.TLS_CERT),
|
|
20
|
+
key: (0, fs_1.readFileSync)(process.env.TLS_KEY),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if ((0, fs_1.existsSync)(CERT_PATH) && (0, fs_1.existsSync)(KEY_PATH)) {
|
|
24
|
+
return {
|
|
25
|
+
cert: (0, fs_1.readFileSync)(CERT_PATH),
|
|
26
|
+
key: (0, fs_1.readFileSync)(KEY_PATH),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return generateSelfSignedCert();
|
|
30
|
+
}
|
|
31
|
+
async function generateSelfSignedCert() {
|
|
32
|
+
const host = (0, os_1.hostname)();
|
|
33
|
+
const attrs = [{ name: 'commonName', value: 'CrewX Local Server' }];
|
|
34
|
+
const sanExt = {
|
|
35
|
+
name: 'subjectAltName',
|
|
36
|
+
altNames: [
|
|
37
|
+
{ type: 2, value: 'localhost' },
|
|
38
|
+
{ type: 2, value: host },
|
|
39
|
+
{ type: 7, ip: '127.0.0.1' },
|
|
40
|
+
{ type: 7, ip: '::1' },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
const pems = await (0, selfsigned_1.generate)(attrs, {
|
|
44
|
+
keySize: 2048,
|
|
45
|
+
extensions: [sanExt],
|
|
46
|
+
});
|
|
47
|
+
(0, fs_1.mkdirSync)(TLS_DIR, { recursive: true });
|
|
48
|
+
(0, fs_1.writeFileSync)(CERT_PATH, pems.cert, { mode: 0o644 });
|
|
49
|
+
(0, fs_1.writeFileSync)(KEY_PATH, pems.private, { mode: 0o600 });
|
|
50
|
+
console.log(`🔒 Self-signed TLS certificate generated: ~/.crewx/tls/`);
|
|
51
|
+
console.log(`⚠️ Browser will show a security warning on first access.`);
|
|
52
|
+
console.log(`💡 Install mkcert for warning-free local HTTPS.`);
|
|
53
|
+
return {
|
|
54
|
+
cert: Buffer.from(pems.cert),
|
|
55
|
+
key: Buffer.from(pems.private),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -56,7 +56,7 @@ let AuthController = AuthController_1 = class AuthController {
|
|
|
56
56
|
return { success: true, data: { authenticated: true, method } };
|
|
57
57
|
}
|
|
58
58
|
// AUTH.LOGIN: brute-force protected, @Public() skips BaseAuthGuard
|
|
59
|
-
login(body, res) {
|
|
59
|
+
login(body, req, res) {
|
|
60
60
|
const usernameOk = (0, ip_utils_js_1.safeCompare)(body.username, this.credentials.username);
|
|
61
61
|
const passwordOk = (0, ip_utils_js_1.safeCompare)(body.password, this.credentials.password);
|
|
62
62
|
if (!usernameOk || !passwordOk) {
|
|
@@ -69,7 +69,7 @@ let AuthController = AuthController_1 = class AuthController {
|
|
|
69
69
|
res.cookie(COOKIE_NAME, this.mcpToken.token, {
|
|
70
70
|
httpOnly: true,
|
|
71
71
|
sameSite: 'strict',
|
|
72
|
-
secure:
|
|
72
|
+
secure: req.secure,
|
|
73
73
|
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
74
74
|
path: '/',
|
|
75
75
|
});
|
|
@@ -97,9 +97,10 @@ __decorate([
|
|
|
97
97
|
(0, common_1.HttpCode)(200),
|
|
98
98
|
(0, throttler_1.Throttle)({ default: { limit: 5, ttl: 60000 } }),
|
|
99
99
|
__param(0, (0, common_1.Body)()),
|
|
100
|
-
__param(1, (0, common_1.
|
|
100
|
+
__param(1, (0, common_1.Req)()),
|
|
101
|
+
__param(2, (0, common_1.Res)({ passthrough: true })),
|
|
101
102
|
__metadata("design:type", Function),
|
|
102
|
-
__metadata("design:paramtypes", [LoginDto, Object]),
|
|
103
|
+
__metadata("design:paramtypes", [LoginDto, Object, Object]),
|
|
103
104
|
__metadata("design:returntype", Object)
|
|
104
105
|
], AuthController.prototype, "login", null);
|
|
105
106
|
__decorate([
|
|
@@ -429,24 +429,26 @@ let McpService = McpService_1 = class McpService {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
432
|
+
// Record only browser tool calls (agent calling a tool during execution).
|
|
433
|
+
// Server tools (queryAgent, executeAgent, etc.) are task triggers, not tool calls.
|
|
434
|
+
if (this.toolRouter.isBrowserTool(params.name, sessionId)) {
|
|
435
|
+
const duration = Date.now() - startTime;
|
|
436
|
+
const taskId = (sessionId ? this.activeTaskMap.get(sessionId) : undefined) ?? null;
|
|
437
|
+
try {
|
|
438
|
+
this.toolCallRepo.insertToolCall({
|
|
439
|
+
id: (0, sdk_1.generateId)('tcl'),
|
|
440
|
+
task_id: taskId,
|
|
441
|
+
session_id: sessionId ?? null,
|
|
442
|
+
tool_name: `mcp_${params.name}`,
|
|
443
|
+
input: JSON.stringify(params.arguments ?? {}),
|
|
444
|
+
output: JSON.stringify(response.result?.content ?? response.error ?? null),
|
|
445
|
+
duration_ms: duration,
|
|
446
|
+
timestamp: new Date().toISOString(),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
this.logger.warn(`tool_calls insert failed: ${err?.message}`);
|
|
451
|
+
}
|
|
450
452
|
}
|
|
451
453
|
return response;
|
|
452
454
|
}
|
package/dist-server/main.js
CHANGED
|
@@ -45,15 +45,20 @@ const cookie_parser_1 = __importDefault(require("cookie-parser"));
|
|
|
45
45
|
const fs_1 = __importDefault(require("fs"));
|
|
46
46
|
const path_1 = require("path");
|
|
47
47
|
const app_module_js_1 = require("./app.module.js");
|
|
48
|
+
const tls_js_1 = require("./bootstrap/tls.js");
|
|
48
49
|
const cors_js_1 = require("./common/cors.js");
|
|
49
50
|
const mcp_constants_js_1 = require("./domain/mcp/mcp.constants.js");
|
|
50
51
|
const auth_constants_js_1 = require("./domain/auth/auth.constants.js");
|
|
51
52
|
const ip_utils_js_1 = require("./domain/auth/utils/ip.utils.js");
|
|
52
53
|
const project_repository_js_1 = require("./repository/project.repository.js");
|
|
53
54
|
async function bootstrap() {
|
|
55
|
+
const httpsOptions = await (0, tls_js_1.resolveHttpsOptions)();
|
|
54
56
|
// RT-6: bodyParser disabled to preserve raw body for adapter webhook signature verification.
|
|
55
57
|
// Adapter routes (/adapters/*) need raw Buffer body for signature verification.
|
|
56
|
-
const app = await core_1.NestFactory.create(app_module_js_1.AppModule, {
|
|
58
|
+
const app = await core_1.NestFactory.create(app_module_js_1.AppModule, {
|
|
59
|
+
bodyParser: false,
|
|
60
|
+
...(httpsOptions ? { httpsOptions } : {}),
|
|
61
|
+
});
|
|
57
62
|
// trust proxy — set before other middleware so req.ip is correct behind nginx/Caddy
|
|
58
63
|
if (process.env.TRUST_PROXY) {
|
|
59
64
|
app.set('trust proxy', process.env.TRUST_PROXY);
|
|
@@ -142,8 +147,9 @@ async function bootstrap() {
|
|
|
142
147
|
const server = app.getHttpServer();
|
|
143
148
|
const address = server.address();
|
|
144
149
|
const actualPort = typeof address === 'object' ? address?.port : port;
|
|
145
|
-
|
|
146
|
-
console.log(
|
|
150
|
+
const protocol = httpsOptions ? 'https' : 'http';
|
|
151
|
+
console.log(`🚀 CrewX UI Server running on ${protocol}://localhost:${actualPort}`);
|
|
152
|
+
console.log(`📖 Swagger UI: ${protocol}://localhost:${actualPort}/api/docs`);
|
|
147
153
|
// Print MCP token info and clickable URLs
|
|
148
154
|
const tokenInfo = app.get(mcp_constants_js_1.MCP_TOKEN);
|
|
149
155
|
const tokenSource = tokenInfo.source === 'env' ? 'MCP_BEARER_TOKEN env' : 'auto-generated';
|
|
@@ -158,14 +164,14 @@ async function bootstrap() {
|
|
|
158
164
|
console.log(`🔐 Web UI auth: ${authCreds.username} / ${masked} (source: ${authCreds.source})`);
|
|
159
165
|
console.log(` Set AUTH_USERNAME / AUTH_PASSWORD in .env to customize.`);
|
|
160
166
|
}
|
|
161
|
-
console.log(`🏥 MCP Health:
|
|
162
|
-
console.log(`📡 MCP Endpoint: POST
|
|
167
|
+
console.log(`🏥 MCP Health: ${protocol}://localhost:${actualPort}/mcp/health?token=${tokenInfo.token}`);
|
|
168
|
+
console.log(`📡 MCP Endpoint: POST ${protocol}://localhost:${actualPort}/mcp`);
|
|
163
169
|
// Auto-open browser when CREWX_OPEN_BROWSER=1 (set by bin/crewx.js for web-default route)
|
|
164
170
|
if (process.env.CREWX_OPEN_BROWSER === '1') {
|
|
165
171
|
try {
|
|
166
172
|
const open = (await Promise.resolve().then(() => __importStar(require('open')))).default;
|
|
167
|
-
await open(
|
|
168
|
-
console.log(`🌐 Browser opened:
|
|
173
|
+
await open(`${protocol}://localhost:${actualPort}`);
|
|
174
|
+
console.log(`🌐 Browser opened: ${protocol}://localhost:${actualPort}`);
|
|
169
175
|
}
|
|
170
176
|
catch {
|
|
171
177
|
console.warn('⚠️ Could not open browser automatically. Please open manually.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewx",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6-rc.1",
|
|
4
4
|
"description": "CrewX — AI agent team dashboard with Electron UI and CLI (Web + Electron + Global CLI)",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"remark-parse": "^11.0.0",
|
|
61
61
|
"remark-stringify": "^11.0.0",
|
|
62
62
|
"rxjs": "^7.8.1",
|
|
63
|
+
"selfsigned": "^5.5.0",
|
|
63
64
|
"swagger-ui-express": "^5.0.1",
|
|
64
65
|
"unified": "^11.0.5",
|
|
65
66
|
"unist-util-visit": "^5.0.0",
|
|
@@ -67,12 +68,12 @@
|
|
|
67
68
|
"wink-nlp-utils": "^2.1.0",
|
|
68
69
|
"yargs": "^17.7.0",
|
|
69
70
|
"zod": "^3.22.0",
|
|
70
|
-
"@crewx/cli": "0.8.
|
|
71
|
-
"@crewx/cron": "0.1.8",
|
|
71
|
+
"@crewx/cli": "0.8.6-rc.1",
|
|
72
72
|
"@crewx/doc": "0.1.8",
|
|
73
|
-
"@crewx/
|
|
73
|
+
"@crewx/cron": "0.1.8",
|
|
74
74
|
"@crewx/memory": "0.1.10",
|
|
75
|
-
"@crewx/
|
|
75
|
+
"@crewx/knowledge-core": "0.1.6",
|
|
76
|
+
"@crewx/sdk": "0.8.6-rc.2",
|
|
76
77
|
"@crewx/shared": "0.0.5",
|
|
77
78
|
"@crewx/wbs": "0.1.9",
|
|
78
79
|
"@crewx/search": "0.1.9",
|
package/dist-server/package.json
DELETED