@xcanwin/manyoyo 7.0.25 → 7.1.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/lib/web/AGENTS.md +8 -8
- package/lib/web/frontend/shadcn.html +91 -88
- package/lib/web/server.js +55 -171
- package/package.json +2 -21
- package/lib/web/frontend/app.css +0 -2269
- package/lib/web/frontend/app.html +0 -394
- package/lib/web/frontend/app.js +0 -5352
- package/lib/web/frontend/chat-behavior.js +0 -107
- package/lib/web/frontend/codemirror-entry.js +0 -111
- package/lib/web/frontend/codemirror.bundle.js +0 -31
- package/lib/web/frontend/file-browser.js +0 -800
- package/lib/web/frontend/login.css +0 -208
- package/lib/web/frontend/login.html +0 -25
- package/lib/web/frontend/login.js +0 -74
- package/lib/web/frontend/markdown-renderer.js +0 -316
- package/lib/web/frontend/markdown.css +0 -84
package/lib/web/server.js
CHANGED
|
@@ -42,6 +42,12 @@ const WEB_TERMINAL_DEFAULT_COLS = 120;
|
|
|
42
42
|
const WEB_TERMINAL_DEFAULT_ROWS = 36;
|
|
43
43
|
const WEB_TERMINAL_MIN_COLS = 40;
|
|
44
44
|
const WEB_TERMINAL_MIN_ROWS = 12;
|
|
45
|
+
// 终端 WebSocket 空闲心跳:纯交互式终端可能长时间没有任何数据,
|
|
46
|
+
// 反向代理 / 移动网络的空闲超时(常见 60s)会悄悄掐断连接,这里定期发 ping
|
|
47
|
+
// 帧制造流量。判死放宽到连续 3 次没回 pong(约 90s),手机切后台导致连接被
|
|
48
|
+
// 系统短暂挂起时不至于被误杀——误杀意味着容器里的 shell 直接没了
|
|
49
|
+
const WEB_TERMINAL_PING_INTERVAL_MS = 30000;
|
|
50
|
+
const WEB_TERMINAL_MAX_MISSED_PONGS = 3;
|
|
45
51
|
const WEB_AGENT_CONTEXT_MAX_MESSAGES = 24;
|
|
46
52
|
const WEB_AGENT_CONTEXT_MAX_CHARS = 6000;
|
|
47
53
|
const WEB_AGENT_CONTEXT_PER_MESSAGE_MAX_CHARS = 600;
|
|
@@ -96,36 +102,6 @@ const DEFAULT_WEB_CONFIG_TEMPLATE = `{
|
|
|
96
102
|
}
|
|
97
103
|
`;
|
|
98
104
|
|
|
99
|
-
let XTERM_JS_FILE = null;
|
|
100
|
-
let XTERM_CSS_FILE = null;
|
|
101
|
-
let XTERM_ADDON_FIT_JS_FILE = null;
|
|
102
|
-
let MARKED_MIN_JS_FILE = null;
|
|
103
|
-
try {
|
|
104
|
-
const xtermPackageDir = path.dirname(require.resolve('@xterm/xterm/package.json'));
|
|
105
|
-
XTERM_JS_FILE = path.join(xtermPackageDir, 'lib', 'xterm.js');
|
|
106
|
-
XTERM_CSS_FILE = path.join(xtermPackageDir, 'css', 'xterm.css');
|
|
107
|
-
} catch (e) {
|
|
108
|
-
XTERM_JS_FILE = null;
|
|
109
|
-
XTERM_CSS_FILE = null;
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const xtermAddonFitPackageDir = path.dirname(require.resolve('@xterm/addon-fit/package.json'));
|
|
113
|
-
XTERM_ADDON_FIT_JS_FILE = path.join(xtermAddonFitPackageDir, 'lib', 'addon-fit.js');
|
|
114
|
-
} catch (e) {
|
|
115
|
-
XTERM_ADDON_FIT_JS_FILE = null;
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const markedPackageDir = path.dirname(require.resolve('marked/package.json'));
|
|
119
|
-
MARKED_MIN_JS_FILE = path.join(markedPackageDir, 'lib', 'marked.umd.js');
|
|
120
|
-
} catch (e) {
|
|
121
|
-
MARKED_MIN_JS_FILE = null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const MIME_TYPES = {
|
|
125
|
-
'.css': 'text/css; charset=utf-8',
|
|
126
|
-
'.js': 'application/javascript; charset=utf-8',
|
|
127
|
-
'.html': 'text/html; charset=utf-8'
|
|
128
|
-
};
|
|
129
105
|
const FILE_LANGUAGE_MAP = {
|
|
130
106
|
'.cjs': 'javascript',
|
|
131
107
|
'.css': 'css',
|
|
@@ -4183,49 +4159,6 @@ function resolveStaticAsset(name) {
|
|
|
4183
4159
|
return fs.existsSync(fullPath) ? fullPath : null;
|
|
4184
4160
|
}
|
|
4185
4161
|
|
|
4186
|
-
function resolveVendorAsset(name) {
|
|
4187
|
-
if (!isSafeStaticAssetName(name)) {
|
|
4188
|
-
return null;
|
|
4189
|
-
}
|
|
4190
|
-
if (name === 'xterm.js') {
|
|
4191
|
-
return XTERM_JS_FILE && fs.existsSync(XTERM_JS_FILE) ? XTERM_JS_FILE : null;
|
|
4192
|
-
}
|
|
4193
|
-
if (name === 'xterm.css') {
|
|
4194
|
-
return XTERM_CSS_FILE && fs.existsSync(XTERM_CSS_FILE) ? XTERM_CSS_FILE : null;
|
|
4195
|
-
}
|
|
4196
|
-
if (name === 'xterm-addon-fit.js') {
|
|
4197
|
-
return XTERM_ADDON_FIT_JS_FILE && fs.existsSync(XTERM_ADDON_FIT_JS_FILE) ? XTERM_ADDON_FIT_JS_FILE : null;
|
|
4198
|
-
}
|
|
4199
|
-
if (name === 'marked.min.js') {
|
|
4200
|
-
return MARKED_MIN_JS_FILE && fs.existsSync(MARKED_MIN_JS_FILE) ? MARKED_MIN_JS_FILE : null;
|
|
4201
|
-
}
|
|
4202
|
-
return null;
|
|
4203
|
-
}
|
|
4204
|
-
|
|
4205
|
-
function sendFileAsset(res, filePath) {
|
|
4206
|
-
if (!filePath || !fs.existsSync(filePath)) {
|
|
4207
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
4208
|
-
return;
|
|
4209
|
-
}
|
|
4210
|
-
|
|
4211
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
4212
|
-
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
4213
|
-
const content = fs.readFileSync(filePath);
|
|
4214
|
-
res.writeHead(200, {
|
|
4215
|
-
'Content-Type': contentType,
|
|
4216
|
-
'Cache-Control': 'no-store'
|
|
4217
|
-
});
|
|
4218
|
-
res.end(content);
|
|
4219
|
-
}
|
|
4220
|
-
|
|
4221
|
-
function sendStaticAsset(res, assetName) {
|
|
4222
|
-
sendFileAsset(res, resolveStaticAsset(assetName));
|
|
4223
|
-
}
|
|
4224
|
-
|
|
4225
|
-
function sendVendorAsset(res, assetName) {
|
|
4226
|
-
sendFileAsset(res, resolveVendorAsset(assetName));
|
|
4227
|
-
}
|
|
4228
|
-
|
|
4229
4162
|
function loadTemplate(name) {
|
|
4230
4163
|
const filePath = resolveStaticAsset(name);
|
|
4231
4164
|
if (!filePath) {
|
|
@@ -4241,42 +4174,20 @@ function escapeHtmlText(value) {
|
|
|
4241
4174
|
.replace(/>/g, '>');
|
|
4242
4175
|
}
|
|
4243
4176
|
|
|
4244
|
-
// JSON.stringify 不转义 "</script>",且作为 String.replace 的第二个参数时 $&/$'/$` 等会被
|
|
4245
|
-
// 当成特殊替换模式重新解释;用替换函数 + 转义 "<" 双重防护,避免 serveTitle 里的内容
|
|
4246
|
-
// 提前闭合 <script> 标签或拼接进原始 HTML。
|
|
4247
|
-
function jsonForInlineScript(value) {
|
|
4248
|
-
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
4249
|
-
}
|
|
4250
|
-
|
|
4251
4177
|
function applyServeTitle(html, ctx) {
|
|
4252
4178
|
if (typeof ctx.serveTitle !== 'string') {
|
|
4253
4179
|
return html;
|
|
4254
4180
|
}
|
|
4255
|
-
// 正则匹配任意 <title> 内容而非硬编码 "MANYOYO Web"
|
|
4181
|
+
// 正则匹配任意 <title> 内容而非硬编码 "MANYOYO Web",前端换了默认标题也不用改这里
|
|
4256
4182
|
return html.replace(/<title>[^<]*<\/title>/, () => `<title>${escapeHtmlText(ctx.serveTitle)}</title>`);
|
|
4257
4183
|
}
|
|
4258
4184
|
|
|
4259
|
-
function injectServeTitleFlag(html, ctx) {
|
|
4260
|
-
if (typeof ctx.serveTitle !== 'string') {
|
|
4261
|
-
return html;
|
|
4262
|
-
}
|
|
4263
|
-
// 告知 app.js 跳过按会话动态改写 document.title
|
|
4264
|
-
return html.replace(
|
|
4265
|
-
'</head>',
|
|
4266
|
-
() => ` <script>window.__MANYOYO_SERVE_TITLE__ = ${jsonForInlineScript(ctx.serveTitle)};</script>\n</head>`
|
|
4267
|
-
);
|
|
4268
|
-
}
|
|
4269
|
-
|
|
4270
|
-
function renderIndexHtml(ctx) {
|
|
4271
|
-
return injectServeTitleFlag(applyServeTitle(loadTemplate('app.html'), ctx), ctx);
|
|
4272
|
-
}
|
|
4273
|
-
|
|
4274
4185
|
function renderShadcnHtml(ctx) {
|
|
4275
4186
|
// shadcn.html 是 vite singlefile 打包产物,全部依赖内联成一个 <script>,
|
|
4276
|
-
// 用 .replace('</head>', ...)
|
|
4277
|
-
// "</head>"
|
|
4278
|
-
//
|
|
4279
|
-
//
|
|
4187
|
+
// 用 .replace('</head>', ...) 注入标记有极小概率命中某个库源码里字面量出现的
|
|
4188
|
+
// "</head>",把注入内容插进内联脚本中间导致整页解析错乱;所以改成跟
|
|
4189
|
+
// applyServeTitle 同款更窄、更安全的 <title> 定位,插一个 meta 标记
|
|
4190
|
+
// 告知前端跳过动态改写 document.title
|
|
4280
4191
|
let html = applyServeTitle(loadTemplate('shadcn.html'), ctx);
|
|
4281
4192
|
if (typeof ctx.serveTitle === 'string') {
|
|
4282
4193
|
html = html.replace(
|
|
@@ -4287,10 +4198,6 @@ function renderShadcnHtml(ctx) {
|
|
|
4287
4198
|
return html;
|
|
4288
4199
|
}
|
|
4289
4200
|
|
|
4290
|
-
function renderLoginHtml(ctx) {
|
|
4291
|
-
return applyServeTitle(loadTemplate('login.html'), ctx);
|
|
4292
|
-
}
|
|
4293
|
-
|
|
4294
4201
|
function toPositiveInt(value, fallback) {
|
|
4295
4202
|
const parsed = Number.parseInt(value, 10);
|
|
4296
4203
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -4406,11 +4313,32 @@ function bindTerminalWebSocket(ctx, state, ws, containerName, cols, rows) {
|
|
|
4406
4313
|
rows
|
|
4407
4314
|
});
|
|
4408
4315
|
|
|
4316
|
+
let missedPongs = 0;
|
|
4317
|
+
ws.on('pong', () => {
|
|
4318
|
+
missedPongs = 0;
|
|
4319
|
+
});
|
|
4320
|
+
const pingTimer = setInterval(() => {
|
|
4321
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
if (missedPongs >= WEB_TERMINAL_MAX_MISSED_PONGS) {
|
|
4325
|
+
ws.terminate();
|
|
4326
|
+
return;
|
|
4327
|
+
}
|
|
4328
|
+
missedPongs += 1;
|
|
4329
|
+
try {
|
|
4330
|
+
ws.ping();
|
|
4331
|
+
} catch (e) {
|
|
4332
|
+
// ping 失败说明链路已经不可用,交给 close/error 事件收尾
|
|
4333
|
+
}
|
|
4334
|
+
}, WEB_TERMINAL_PING_INTERVAL_MS);
|
|
4335
|
+
|
|
4409
4336
|
const cleanup = () => {
|
|
4410
4337
|
if (session.closing) {
|
|
4411
4338
|
return;
|
|
4412
4339
|
}
|
|
4413
4340
|
session.closing = true;
|
|
4341
|
+
clearInterval(pingTimer);
|
|
4414
4342
|
state.terminalSessions.delete(sessionId);
|
|
4415
4343
|
if (ptyProcess && !ptyProcess.killed) {
|
|
4416
4344
|
ptyProcess.kill('SIGTERM');
|
|
@@ -4472,6 +4400,12 @@ function bindTerminalWebSocket(ctx, state, ws, containerName, cols, rows) {
|
|
|
4472
4400
|
return;
|
|
4473
4401
|
}
|
|
4474
4402
|
|
|
4403
|
+
if (payload.type === 'ping') {
|
|
4404
|
+
// 浏览器 JS 发不出 WebSocket ping 帧,前端改用应用层消息保活上行链路
|
|
4405
|
+
sendTerminalEvent(ws, 'pong');
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4475
4409
|
if (payload.type === 'close') {
|
|
4476
4410
|
ws.close();
|
|
4477
4411
|
}
|
|
@@ -4488,27 +4422,16 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
|
|
|
4488
4422
|
return true;
|
|
4489
4423
|
}
|
|
4490
4424
|
|
|
4491
|
-
//
|
|
4492
|
-
|
|
4493
|
-
sendHtml(res, 200, renderLoginHtml(ctx));
|
|
4494
|
-
return true;
|
|
4495
|
-
}
|
|
4496
|
-
|
|
4497
|
-
// 默认主题(shadcn/ui 版)的登录页:与 / 、/shadcn 复用同一份构建产物,
|
|
4498
|
-
// 由前端按 pathname 自行渲染登录表单,登录逻辑仍复用 /auth/login 接口
|
|
4425
|
+
// 登录页:与 / 、/shadcn 复用同一份构建产物,由前端按 pathname 自行渲染
|
|
4426
|
+
// 登录表单,登录逻辑走下面的 POST /auth/login 接口
|
|
4499
4427
|
if (req.method === 'GET' && pathname === '/shadcn/auth/login') {
|
|
4500
4428
|
sendHtml(res, 200, applyServeTitle(loadTemplate('shadcn.html'), ctx));
|
|
4501
4429
|
return true;
|
|
4502
4430
|
}
|
|
4503
4431
|
|
|
4504
|
-
|
|
4505
|
-
if (req.method === 'GET' &&
|
|
4506
|
-
|
|
4507
|
-
if (!(assetName === 'login.css' || assetName === 'login.js')) {
|
|
4508
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
4509
|
-
return true;
|
|
4510
|
-
}
|
|
4511
|
-
sendStaticAsset(res, assetName);
|
|
4432
|
+
// GET /auth/login 曾经是独立的登录页面,现在只留一个跳转,接住老书签
|
|
4433
|
+
if (req.method === 'GET' && pathname === '/auth/login') {
|
|
4434
|
+
sendRedirect(res, 302, '/shadcn/auth/login');
|
|
4512
4435
|
return true;
|
|
4513
4436
|
}
|
|
4514
4437
|
|
|
@@ -4553,25 +4476,14 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
|
|
|
4553
4476
|
return false;
|
|
4554
4477
|
}
|
|
4555
4478
|
|
|
4556
|
-
function sendWebUnauthorized(res, pathname
|
|
4479
|
+
function sendWebUnauthorized(res, pathname) {
|
|
4557
4480
|
if (pathname.startsWith('/api/') || pathname.startsWith('/auth/')) {
|
|
4558
4481
|
sendJson(res, 401, { error: 'UNAUTHORIZED' });
|
|
4559
4482
|
return;
|
|
4560
4483
|
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
}
|
|
4565
|
-
if (pathname === '/legacy') {
|
|
4566
|
-
sendRedirect(res, 302, '/auth/login', { 'Set-Cookie': getWebAuthClearCookie() });
|
|
4567
|
-
return;
|
|
4568
|
-
}
|
|
4569
|
-
sendHtml(
|
|
4570
|
-
res,
|
|
4571
|
-
401,
|
|
4572
|
-
renderLoginHtml(ctx),
|
|
4573
|
-
{ 'Set-Cookie': getWebAuthClearCookie() }
|
|
4574
|
-
);
|
|
4484
|
+
// 其余都是页面请求:一律打回登录页。登录页本身在认证网关之前就被
|
|
4485
|
+
// handleWebAuthRoutes 放行,不会在这里形成重定向循环
|
|
4486
|
+
sendRedirect(res, 302, '/shadcn/auth/login', { 'Set-Cookie': getWebAuthClearCookie() });
|
|
4575
4487
|
}
|
|
4576
4488
|
|
|
4577
4489
|
// 运行日志查看接口的支撑函数。日志目录以 serve 实际使用的 logger.path 为准,
|
|
@@ -5912,7 +5824,7 @@ async function startWebServer(options) {
|
|
|
5912
5824
|
|
|
5913
5825
|
const authSession = getWebAuthSession(state, req);
|
|
5914
5826
|
if (!authSession) {
|
|
5915
|
-
sendWebUnauthorized(res, pathname
|
|
5827
|
+
sendWebUnauthorized(res, pathname);
|
|
5916
5828
|
return;
|
|
5917
5829
|
}
|
|
5918
5830
|
|
|
@@ -5928,40 +5840,6 @@ async function startWebServer(options) {
|
|
|
5928
5840
|
return;
|
|
5929
5841
|
}
|
|
5930
5842
|
|
|
5931
|
-
// 旧版默认主题:不再是默认路由,移到 /legacy 继续保留一段时间作为兜底
|
|
5932
|
-
if (req.method === 'GET' && pathname === '/legacy') {
|
|
5933
|
-
sendHtml(res, 200, renderIndexHtml(ctx));
|
|
5934
|
-
return;
|
|
5935
|
-
}
|
|
5936
|
-
|
|
5937
|
-
const appFrontendMatch = pathname.match(/^\/app\/frontend\/([A-Za-z0-9._-]+)$/);
|
|
5938
|
-
if (req.method === 'GET' && appFrontendMatch) {
|
|
5939
|
-
const assetName = appFrontendMatch[1];
|
|
5940
|
-
if (!(assetName === 'app.css'
|
|
5941
|
-
|| assetName === 'app.js'
|
|
5942
|
-
|| assetName === 'markdown.css'
|
|
5943
|
-
|| assetName === 'markdown-renderer.js'
|
|
5944
|
-
|| assetName === 'chat-behavior.js'
|
|
5945
|
-
|| assetName === 'file-browser.js'
|
|
5946
|
-
|| assetName === 'codemirror.bundle.js')) {
|
|
5947
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
5948
|
-
return;
|
|
5949
|
-
}
|
|
5950
|
-
sendStaticAsset(res, assetName);
|
|
5951
|
-
return;
|
|
5952
|
-
}
|
|
5953
|
-
|
|
5954
|
-
const appVendorMatch = pathname.match(/^\/app\/vendor\/([A-Za-z0-9._-]+)$/);
|
|
5955
|
-
if (req.method === 'GET' && appVendorMatch) {
|
|
5956
|
-
const assetName = appVendorMatch[1];
|
|
5957
|
-
if (!(assetName === 'xterm.css' || assetName === 'xterm.js' || assetName === 'xterm-addon-fit.js' || assetName === 'marked.min.js')) {
|
|
5958
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
5959
|
-
return;
|
|
5960
|
-
}
|
|
5961
|
-
sendVendorAsset(res, assetName);
|
|
5962
|
-
return;
|
|
5963
|
-
}
|
|
5964
|
-
|
|
5965
5843
|
if (pathname === '/healthz') {
|
|
5966
5844
|
sendJson(res, 200, { ok: true });
|
|
5967
5845
|
return;
|
|
@@ -6061,6 +5939,12 @@ async function startWebServer(options) {
|
|
|
6061
5939
|
url.searchParams.get('rows')
|
|
6062
5940
|
);
|
|
6063
5941
|
|
|
5942
|
+
// 升级成 WebSocket 后这条连接是长连接,不能再受 HTTP 请求的空闲超时约束;
|
|
5943
|
+
// 同时打开 TCP keepalive,避免中间设备(NAT / 代理)回收看起来空闲的连接
|
|
5944
|
+
socket.setTimeout(0);
|
|
5945
|
+
socket.setNoDelay(true);
|
|
5946
|
+
socket.setKeepAlive(true, WEB_TERMINAL_PING_INTERVAL_MS);
|
|
5947
|
+
|
|
6064
5948
|
ensureWebContainer(ctx, state, sessionRef.containerName)
|
|
6065
5949
|
.then(() => {
|
|
6066
5950
|
wsServer.handleUpgrade(req, socket, head, ws => {
|
|
@@ -6091,7 +5975,7 @@ async function startWebServer(options) {
|
|
|
6091
5975
|
const { GREEN, CYAN, YELLOW, NC } = ctx.colors;
|
|
6092
5976
|
const listenHost = formatUrlHost(ctx.serverHost);
|
|
6093
5977
|
console.log(`${GREEN}✅ MANYOYO Web 服务已启动: http://${listenHost}:${listenPort}${NC}`);
|
|
6094
|
-
console.log(`${CYAN}提示: 左侧是 manyoyo
|
|
5978
|
+
console.log(`${CYAN}提示: 左侧是 manyoyo 容器与 AGENT 列表,右侧是聊天/终端/文件/详情/配置/检查工作台。${NC}`);
|
|
6095
5979
|
if (ctx.serverHost === '0.0.0.0') {
|
|
6096
5980
|
console.log(`${CYAN}提示: 当前监听全部网卡,请用本机局域网 IP 访问。${NC}`);
|
|
6097
5981
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"imageVersion": "1.9.1-common",
|
|
5
5
|
"playwrightCliVersion": "0.1.18",
|
|
6
6
|
"description": "AI Agent CLI Security Sandbox for Docker and Podman",
|
|
@@ -31,12 +31,10 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"dev:release": "node scripts/dev-release.js",
|
|
34
|
-
"build:web-editor": "node scripts/build-web-code-editor.js",
|
|
35
34
|
"build:web-shadcn": "node scripts/build-web-shadcn.js",
|
|
36
35
|
"dev:web-shadcn": "node scripts/dev-web-shadcn.js",
|
|
37
36
|
"test:web-shadcn": "node scripts/test-web-shadcn.js",
|
|
38
|
-
"prepack": "npm run build:web-
|
|
39
|
-
"prepare": "npm run build:web-editor",
|
|
37
|
+
"prepack": "npm run build:web-shadcn",
|
|
40
38
|
"install-link": "npm link",
|
|
41
39
|
"test": "jest --coverage && npm run test:web-shadcn",
|
|
42
40
|
"test:unit": "jest test/ && npm run test:web-shadcn",
|
|
@@ -63,29 +61,12 @@
|
|
|
63
61
|
],
|
|
64
62
|
"dependencies": {
|
|
65
63
|
"@playwright/mcp": "0.0.79",
|
|
66
|
-
"@xterm/addon-fit": "^0.11.0",
|
|
67
|
-
"@xterm/xterm": "^6.0.0",
|
|
68
64
|
"commander": "^15.0.0",
|
|
69
65
|
"json5": "^2.2.3",
|
|
70
|
-
"marked": "^18.0.9",
|
|
71
66
|
"playwright": "1.62.1",
|
|
72
67
|
"ws": "^8.21.3"
|
|
73
68
|
},
|
|
74
69
|
"devDependencies": {
|
|
75
|
-
"@codemirror/commands": "^6.10.4",
|
|
76
|
-
"@codemirror/lang-css": "^6.3.1",
|
|
77
|
-
"@codemirror/lang-html": "^6.4.12",
|
|
78
|
-
"@codemirror/lang-javascript": "^6.2.5",
|
|
79
|
-
"@codemirror/lang-json": "^6.0.2",
|
|
80
|
-
"@codemirror/lang-markdown": "^6.5.2",
|
|
81
|
-
"@codemirror/lang-python": "^6.2.1",
|
|
82
|
-
"@codemirror/lang-yaml": "^6.1.3",
|
|
83
|
-
"@codemirror/language": "^6.12.4",
|
|
84
|
-
"@codemirror/search": "^6.7.1",
|
|
85
|
-
"@codemirror/state": "^6.7.1",
|
|
86
|
-
"@codemirror/view": "^6.43.8",
|
|
87
|
-
"codemirror": "^6.0.2",
|
|
88
|
-
"esbuild": "^0.28.2",
|
|
89
70
|
"jest": "^30.4.2",
|
|
90
71
|
"vitepress": "^1.6.4"
|
|
91
72
|
},
|