hermes-web-ui 0.2.8 → 0.3.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/README.md +26 -0
- package/dist/client/assets/Add-HK51AOcE.js +1 -0
- package/dist/client/assets/{Button-Cxh6x3Um.js → Button-sy1PzWPU.js} +14 -14
- package/dist/client/assets/ChannelsView-B3Iv1qGF.js +1 -0
- package/dist/client/assets/ChannelsView-Do43S3tP.css +1 -0
- package/dist/client/assets/ChatView-BZljWOsk.js +127 -0
- package/dist/client/assets/ChatView-DWJ1E8rg.css +1 -0
- package/dist/client/assets/{Close-hb0eNgAb.js → Close-h76fWaJi.js} +11 -11
- package/dist/client/assets/FormItem-DAkggFjx.js +110 -0
- package/dist/client/assets/Input-BgeYIOdH.js +234 -0
- package/dist/client/assets/{InputNumber-qFTlSmmT.js → InputNumber-CP0Q1JN_.js} +4 -4
- package/dist/client/assets/{JobsView-rFyONi-b.js → JobsView-C3MddyvL.js} +2 -2
- package/dist/client/assets/JobsView-CvuV9mZY.css +1 -0
- package/dist/client/assets/LoginView-CA9w3oDH.css +1 -0
- package/dist/client/assets/{LoginView-C6C3nX0Y.js → LoginView-DR64ehSS.js} +1 -1
- package/dist/client/assets/LogsView-BG7Bro4j.css +1 -0
- package/dist/client/assets/{LogsView-CO8aslfu.js → LogsView-De8hT0ry.js} +1 -1
- package/dist/client/assets/{MarkdownRenderer-Cy_O_tQS.js → MarkdownRenderer-CgO4HMET.js} +1 -1
- package/dist/client/assets/MarkdownRenderer-Ct6cRT2D.css +1 -0
- package/dist/client/assets/MemoryView-DEyIgPS8.css +1 -0
- package/dist/client/assets/{MemoryView-DZjJTdI6.js → MemoryView-vo6uIeGe.js} +1 -1
- package/dist/client/assets/Modal-D0bHwidv.js +232 -0
- package/dist/client/assets/ModelsView-BhNEZwC0.css +1 -0
- package/dist/client/assets/{ModelsView-CvHz1aiJ.js → ModelsView-Czh0QZMT.js} +1 -1
- package/dist/client/assets/Popconfirm-C4L6j7Ed.js +16 -0
- package/dist/client/assets/Popover-CSVNytD9.js +117 -0
- package/dist/client/assets/ProfilesView-5N8GD-OY.js +440 -0
- package/dist/client/assets/ProfilesView-Chffv0-D.css +1 -0
- package/dist/client/assets/{Scrollbar-BHJ682ve.js → Scrollbar-CP3UrDPW.js} +17 -17
- package/dist/client/assets/Select-BX8K2ITf.js +340 -0
- package/dist/client/assets/SettingRow-CwidKv2Q.css +1 -0
- package/dist/client/assets/{SettingRow-pvADoOth.js → SettingRow-Dj1WGKUP.js} +1 -1
- package/dist/client/assets/SettingsView-8WKddMDg.js +352 -0
- package/dist/client/assets/SettingsView-C78xbLXK.css +1 -0
- package/dist/client/assets/SkillsView-D5bivF7Z.css +1 -0
- package/dist/client/assets/{SkillsView-Bl8tqv9V.js → SkillsView-huWlVVSd.js} +1 -1
- package/dist/client/assets/{Spin-BbaC-FmW.js → Spin-7qVct-fX.js} +10 -10
- package/dist/client/assets/{Suffix-yRSbGVfA.js → Suffix-CTB2TVbZ.js} +7 -7
- package/dist/client/assets/{Switch-Svs0Rnhi.js → Switch-DznAKpSq.js} +15 -15
- package/dist/client/assets/{TerminalView-XDDNp69h.js → TerminalView-D8h6hUON.js} +2 -2
- package/dist/client/assets/TerminalView-DPdn9YA7.css +1 -0
- package/dist/client/assets/Tooltip-hZdU3QDl.js +1 -0
- package/dist/client/assets/UsageView-CnADqqzf.css +1 -0
- package/dist/client/assets/{UsageView-xqsO8W9W.js → UsageView-c67PH6UD.js} +1 -1
- package/dist/client/assets/{Warning-Srffo82k.js → Warning-ajVqmXLg.js} +1 -1
- package/dist/client/assets/_common-DgdkN_d5.js +1 -0
- package/dist/client/assets/{_plugin-vue_export-helper-TyZ5GTTn.js → _plugin-vue_export-helper-Cla2Sei2.js} +1 -1
- package/dist/client/assets/{app-DTyari-k.js → app-Dn_xFNgc.js} +1 -1
- package/dist/client/assets/app-DuW413U3.js +1 -0
- package/dist/client/assets/browser-Bj_pylgC.js +47 -0
- package/dist/client/assets/chat-oRYxV4zZ.js +6 -0
- package/dist/client/assets/composables-NK3hMwZN.js +1 -0
- package/dist/client/assets/dance-dark-BBm98kyn.mp4 +0 -0
- package/dist/client/assets/dance-light-424rxYpN.mp4 +0 -0
- package/dist/client/assets/{fade-in-scale-up.cssr-CiZdBCR3.js → fade-in-scale-up.cssr-A5vydVRa.js} +1 -1
- package/dist/client/assets/index-8Iu2oDQ9.css +1 -0
- package/dist/client/assets/index-CNVL9pSf.js +284 -0
- package/dist/client/assets/{jobs-DxRPm5Sf.js → jobs-D6sYt3p2.js} +1 -1
- package/dist/client/assets/light-2H7dwk18.js +1 -0
- package/dist/client/assets/light-BRnwbni5.js +71 -0
- package/dist/client/assets/light-BlRycys6.js +1 -0
- package/dist/client/assets/light-DTze2Byq.js +1 -0
- package/dist/client/assets/light-DyxaUrhN.js +1 -0
- package/dist/client/assets/light-s39H4IqC.js +1 -0
- package/dist/client/assets/{pinia-BTE-WqCz.js → pinia-0T1SpgZT.js} +1 -1
- package/dist/client/assets/profiles-CXj2WAET.js +23 -0
- package/dist/client/assets/{router-mPoLLYeq.js → router-k7Mt11r-.js} +2 -2
- package/dist/client/assets/{sessions-Bv5JsX3U.js → sessions-C04iLbID.js} +1 -1
- package/dist/client/assets/{skills-CXFhBwRj.js → skills-CrJMmxLv.js} +1 -1
- package/dist/client/assets/thinking-dark-yYkYRZLs.mp4 +0 -0
- package/dist/client/assets/thinking-light-BjeGd2gJ.mp4 +0 -0
- package/dist/client/assets/use-compitable-CBi_h52X.js +1 -0
- package/dist/client/assets/{use-message-B3svE98N.js → use-message-kLSX34Jy.js} +1 -1
- package/dist/client/assets/useTheme-OKrDn5ib.js +1 -0
- package/dist/client/index.html +30 -24
- package/dist/server/index.js +17 -3
- package/dist/server/routes/hermes/profiles.js +52 -5
- package/dist/server/routes/hermes/proxy-handler.js +45 -17
- package/dist/server/routes/hermes/terminal.js +32 -4
- package/dist/server/services/hermes-cli.js +41 -20
- package/package.json +9 -2
- package/dist/client/assets/ChannelsView-CQJ1fX8O.js +0 -1
- package/dist/client/assets/ChannelsView-D168bwSq.css +0 -1
- package/dist/client/assets/ChatView-Ds8Q3Yrv.css +0 -1
- package/dist/client/assets/ChatView-nxyDDB73.js +0 -127
- package/dist/client/assets/FormItem-dHtHjgtl.js +0 -110
- package/dist/client/assets/Input-CZZ9TfBw.js +0 -234
- package/dist/client/assets/JobsView-Bbg7Yvse.css +0 -1
- package/dist/client/assets/LoginView-Bt3yT3yN.css +0 -1
- package/dist/client/assets/LogsView-D9Es8tZb.css +0 -1
- package/dist/client/assets/MarkdownRenderer-Bd-nS4jK.css +0 -1
- package/dist/client/assets/MemoryView-hIfyUkwz.css +0 -1
- package/dist/client/assets/Modal-BLinjj9c.js +0 -232
- package/dist/client/assets/ModelsView-HlT7QxLF.css +0 -1
- package/dist/client/assets/Popconfirm-JTALkPda.js +0 -16
- package/dist/client/assets/Popover-0__1CUa3.js +0 -117
- package/dist/client/assets/ProfilesView-Dv1-Qa9-.js +0 -1
- package/dist/client/assets/ProfilesView-oIk8yBVG.css +0 -1
- package/dist/client/assets/Select-DqPBeTT3.js +0 -340
- package/dist/client/assets/SettingRow-DsjrPlmy.css +0 -1
- package/dist/client/assets/SettingsView-Y522req-.js +0 -352
- package/dist/client/assets/SettingsView-vVdZjtPc.css +0 -1
- package/dist/client/assets/SkillsView-l8q6J0Ov.css +0 -1
- package/dist/client/assets/Tag-DdwPhG9T.js +0 -71
- package/dist/client/assets/TerminalView-CuqATvpq.css +0 -1
- package/dist/client/assets/Tooltip-CHbB9ntH.js +0 -1
- package/dist/client/assets/UsageView-BgU-h2NT.css +0 -1
- package/dist/client/assets/app-CpUVwd9n.js +0 -1
- package/dist/client/assets/browser-DbKCW2k6.js +0 -47
- package/dist/client/assets/chat-BmXwfHmM.js +0 -6
- package/dist/client/assets/composables-BoU_Wud6.js +0 -1
- package/dist/client/assets/dance-gGRu7JHG.mp4 +0 -0
- package/dist/client/assets/get-DUDt1SRf.js +0 -1
- package/dist/client/assets/index-BT19Bivl.css +0 -1
- package/dist/client/assets/index-WIPEA9ik.js +0 -306
- package/dist/client/assets/profiles-C9UmdPLd.js +0 -1
- package/dist/client/assets/thinking-Cwsva50h.mp4 +0 -0
- package/dist/client/assets/use-compitable-aofta5ri.js +0 -1
- /package/dist/client/assets/{omit-DKSz8IbY.js → omit-1BRB6K75.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as e}from"./router-
|
|
1
|
+
import{o as e}from"./router-k7Mt11r-.js";async function t(t,n){let r=new URLSearchParams;t&&r.set(`source`,t),n&&r.set(`limit`,String(n));let i=r.toString();return(await e(`/api/hermes/sessions${i?`?${i}`:``}`)).sessions}async function n(t){try{return(await e(`/api/hermes/sessions/${t}`)).session}catch{return null}}async function r(t){try{return await e(`/api/hermes/sessions/${t}`,{method:`DELETE`}),!0}catch{return!1}}async function i(t,n){try{return await e(`/api/hermes/sessions/${t}/rename`,{method:`POST`,body:JSON.stringify({title:n})}),!0}catch{return!1}}export{i,n,t as r,r as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as e}from"./router-
|
|
1
|
+
import{o as e}from"./router-k7Mt11r-.js";async function t(){return(await e(`/api/hermes/skills`)).categories}async function n(t){return(await e(`/api/hermes/skills/${t}`)).content}async function r(t,n){return(await e(`/api/hermes/skills/${t}/${n}/files`)).files}async function i(){return e(`/api/hermes/memory`)}async function a(t,n){await e(`/api/hermes/memory`,{method:`POST`,body:JSON.stringify({section:t,content:n})})}async function o(t,n){await e(`/api/hermes/skills/toggle`,{method:`PUT`,body:JSON.stringify({name:t,enabled:n})})}export{a,t as i,n,o,r,i as t};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{C as e}from"./router-k7Mt11r-.js";function t(t,n){return e(()=>{for(let e of n)if(t[e]!==void 0)return t[e];return t[n[n.length-1]]})}export{t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{F as e}from"./router-
|
|
1
|
+
import{F as e}from"./router-k7Mt11r-.js";import{$ as t,Y as n}from"./browser-Bj_pylgC.js";var r=t(`n-message-api`),i=t(`n-message-provider`);function a(){let t=e(r,null);return t===null&&n(`use-message`,"No outer <n-message-provider /> founded. See prerequisite in https://www.naiveui.com/en-US/os-theme/components/message for more details. If you want to use `useMessage` outside setup, please check https://www.naiveui.com/zh-CN/os-theme/components/message#Q-&-A."),t}export{r as n,i as r,a as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{X as e,ct as t}from"./router-k7Mt11r-.js";var n=`hermes_theme`,r=t(localStorage.getItem(n)||`system`),i=t(!1);function a(e){i.value=e,document.documentElement.classList.toggle(`dark`,e)}function o(e){return e===`system`?window.matchMedia(`(prefers-color-scheme: dark)`).matches:e===`dark`}a(o(r.value)),window.matchMedia(`(prefers-color-scheme: dark)`).addEventListener(`change`,()=>{r.value===`system`&&a(o(`system`))}),e(r,e=>{localStorage.setItem(n,e),a(o(e))});function s(){function e(e){r.value=e}function t(){r.value=i.value?`light`:`dark`}return{mode:r,isDark:i,setMode:e,toggleTheme:t}}export{s as t};
|
package/dist/client/index.html
CHANGED
|
@@ -6,32 +6,38 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>Hermes</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/router-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/browser-
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/Scrollbar-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/assets/Tag-DdwPhG9T.js">
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CNVL9pSf.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/router-k7Mt11r-.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-Cla2Sei2.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/browser-Bj_pylgC.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/Scrollbar-CP3UrDPW.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/use-compitable-CBi_h52X.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/Popover-CSVNytD9.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/Close-h76fWaJi.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/Button-sy1PzWPU.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/Suffix-CTB2TVbZ.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-A5vydVRa.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/light-BRnwbni5.js">
|
|
22
21
|
<link rel="modulepreload" crossorigin href="/assets/create-5zWq3BEB.js">
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/Select-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/Warning-
|
|
25
|
-
<link rel="modulepreload" crossorigin href="/assets/Modal-
|
|
26
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
27
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
28
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
29
|
-
<link rel="modulepreload" crossorigin href="/assets/sessions-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/assets/app-
|
|
31
|
-
<link rel="modulepreload" crossorigin href="/assets/chat-
|
|
32
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/Select-BX8K2ITf.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/Warning-ajVqmXLg.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/Modal-D0bHwidv.js">
|
|
25
|
+
<link rel="modulepreload" crossorigin href="/assets/pinia-0T1SpgZT.js">
|
|
26
|
+
<link rel="modulepreload" crossorigin href="/assets/profiles-CXj2WAET.js">
|
|
27
|
+
<link rel="modulepreload" crossorigin href="/assets/omit-1BRB6K75.js">
|
|
28
|
+
<link rel="modulepreload" crossorigin href="/assets/sessions-C04iLbID.js">
|
|
29
|
+
<link rel="modulepreload" crossorigin href="/assets/app-Dn_xFNgc.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/assets/chat-oRYxV4zZ.js">
|
|
31
|
+
<link rel="modulepreload" crossorigin href="/assets/light-DyxaUrhN.js">
|
|
32
|
+
<link rel="modulepreload" crossorigin href="/assets/light-s39H4IqC.js">
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/use-message-kLSX34Jy.js">
|
|
34
|
+
<link rel="modulepreload" crossorigin href="/assets/light-2H7dwk18.js">
|
|
35
|
+
<link rel="modulepreload" crossorigin href="/assets/_common-DgdkN_d5.js">
|
|
36
|
+
<link rel="modulepreload" crossorigin href="/assets/light-DTze2Byq.js">
|
|
37
|
+
<link rel="modulepreload" crossorigin href="/assets/light-BlRycys6.js">
|
|
38
|
+
<link rel="modulepreload" crossorigin href="/assets/useTheme-OKrDn5ib.js">
|
|
33
39
|
<link rel="modulepreload" crossorigin href="/assets/logo-Cd-t_oGE.js">
|
|
34
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
40
|
+
<link rel="stylesheet" crossorigin href="/assets/index-8Iu2oDQ9.css">
|
|
35
41
|
</head>
|
|
36
42
|
|
|
37
43
|
<body>
|
package/dist/server/index.js
CHANGED
|
@@ -272,6 +272,19 @@ async function ensureApiServerConfig() {
|
|
|
272
272
|
}
|
|
273
273
|
async function ensureGatewayRunning() {
|
|
274
274
|
const upstream = config_1.config.upstream.replace(/\/$/, '');
|
|
275
|
+
const waitForGatewayReady = async (timeoutMs = 15000) => {
|
|
276
|
+
const deadline = Date.now() + timeoutMs;
|
|
277
|
+
while (Date.now() < deadline) {
|
|
278
|
+
try {
|
|
279
|
+
const res = await fetch(`${upstream}/health`, { signal: AbortSignal.timeout(2000) });
|
|
280
|
+
if (res.ok)
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
await new Promise(r => setTimeout(r, 300));
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
};
|
|
275
288
|
try {
|
|
276
289
|
const res = await fetch(`${upstream}/health`, { signal: AbortSignal.timeout(5000) });
|
|
277
290
|
if (res.ok)
|
|
@@ -282,11 +295,12 @@ async function ensureGatewayRunning() {
|
|
|
282
295
|
try {
|
|
283
296
|
// 👉 关键:保存 PID
|
|
284
297
|
gatewayPid = await startGatewayBackground();
|
|
285
|
-
|
|
286
|
-
const res = await fetch(`${upstream}/health`, { signal: AbortSignal.timeout(5000) });
|
|
287
|
-
if (res.ok) {
|
|
298
|
+
if (await waitForGatewayReady()) {
|
|
288
299
|
console.log(`✓ Gateway started (PID: ${gatewayPid})`);
|
|
289
300
|
}
|
|
301
|
+
else {
|
|
302
|
+
console.error('gateway start failed: timed out waiting for health');
|
|
303
|
+
}
|
|
290
304
|
}
|
|
291
305
|
catch (err) {
|
|
292
306
|
console.error('gateway start failed:', err.message);
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.profileRoutes = void 0;
|
|
40
40
|
const router_1 = __importDefault(require("@koa/router"));
|
|
41
41
|
const fs_1 = require("fs");
|
|
42
|
+
const promises_1 = require("fs/promises");
|
|
42
43
|
const path_1 = require("path");
|
|
43
44
|
const os_1 = require("os");
|
|
44
45
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
@@ -278,19 +279,65 @@ exports.profileRoutes.post('/api/hermes/profiles/:name/export', async (ctx) => {
|
|
|
278
279
|
ctx.body = { error: err.message };
|
|
279
280
|
}
|
|
280
281
|
});
|
|
281
|
-
// POST /api/profiles/import - Import profile from archive
|
|
282
|
+
// POST /api/profiles/import - Import profile from uploaded archive
|
|
282
283
|
exports.profileRoutes.post('/api/hermes/profiles/import', async (ctx) => {
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
284
|
+
const contentType = ctx.get('content-type') || '';
|
|
285
|
+
if (!contentType.startsWith('multipart/form-data')) {
|
|
285
286
|
ctx.status = 400;
|
|
286
|
-
ctx.body = { error: '
|
|
287
|
+
ctx.body = { error: 'Expected multipart/form-data' };
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const boundary = '--' + contentType.split('boundary=')[1];
|
|
291
|
+
if (!boundary || boundary === '--undefined') {
|
|
292
|
+
ctx.status = 400;
|
|
293
|
+
ctx.body = { error: 'Missing boundary' };
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const tmpDir = (0, path_1.join)((0, os_1.tmpdir)(), 'hermes-import');
|
|
297
|
+
await (0, promises_1.mkdir)(tmpDir, { recursive: true });
|
|
298
|
+
// Read raw body and parse multipart
|
|
299
|
+
const chunks = [];
|
|
300
|
+
for await (const chunk of ctx.req)
|
|
301
|
+
chunks.push(chunk);
|
|
302
|
+
const body = Buffer.concat(chunks).toString('latin1');
|
|
303
|
+
const parts = body.split(boundary).slice(1, -1);
|
|
304
|
+
let archivePath = '';
|
|
305
|
+
for (const part of parts) {
|
|
306
|
+
const headerEnd = part.indexOf('\r\n\r\n');
|
|
307
|
+
if (headerEnd === -1)
|
|
308
|
+
continue;
|
|
309
|
+
const header = part.substring(0, headerEnd);
|
|
310
|
+
const data = part.substring(headerEnd + 4, part.length - 2);
|
|
311
|
+
const filenameMatch = header.match(/filename="([^"]+)"/);
|
|
312
|
+
if (!filenameMatch)
|
|
313
|
+
continue;
|
|
314
|
+
const filename = filenameMatch[1];
|
|
315
|
+
const ext = filename.includes('.') ? '.' + filename.split('.').pop() : '';
|
|
316
|
+
if (!['.gz', '.tar.gz', '.zip', '.tgz'].includes(ext))
|
|
317
|
+
continue;
|
|
318
|
+
archivePath = (0, path_1.join)(tmpDir, filename);
|
|
319
|
+
await (0, promises_1.writeFile)(archivePath, Buffer.from(data, 'binary'));
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
if (!archivePath) {
|
|
323
|
+
ctx.status = 400;
|
|
324
|
+
ctx.body = { error: 'No archive file found (.gz, .zip, .tgz)' };
|
|
287
325
|
return;
|
|
288
326
|
}
|
|
289
327
|
try {
|
|
290
|
-
const result = await hermesCli.importProfile(
|
|
328
|
+
const result = await hermesCli.importProfile(archivePath);
|
|
329
|
+
// Clean up temp file
|
|
330
|
+
try {
|
|
331
|
+
(0, fs_1.unlinkSync)(archivePath);
|
|
332
|
+
}
|
|
333
|
+
catch { }
|
|
291
334
|
ctx.body = { success: true, message: result.trim() };
|
|
292
335
|
}
|
|
293
336
|
catch (err) {
|
|
337
|
+
try {
|
|
338
|
+
(0, fs_1.unlinkSync)(archivePath);
|
|
339
|
+
}
|
|
340
|
+
catch { }
|
|
294
341
|
ctx.status = 500;
|
|
295
342
|
ctx.body = { error: err.message };
|
|
296
343
|
}
|
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.proxy = proxy;
|
|
4
4
|
const config_1 = require("../../config");
|
|
5
|
+
function isTransientGatewayError(err) {
|
|
6
|
+
const msg = String(err?.message || '');
|
|
7
|
+
const causeCode = String(err?.cause?.code || '');
|
|
8
|
+
return (causeCode === 'ECONNREFUSED' ||
|
|
9
|
+
causeCode === 'ECONNRESET' ||
|
|
10
|
+
/ECONNREFUSED|ECONNRESET|fetch failed|socket hang up/i.test(msg));
|
|
11
|
+
}
|
|
12
|
+
async function waitForGatewayReady(upstream, timeoutMs = 5000) {
|
|
13
|
+
const deadline = Date.now() + timeoutMs;
|
|
14
|
+
const healthUrl = `${upstream}/health`;
|
|
15
|
+
while (Date.now() < deadline) {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(healthUrl, {
|
|
18
|
+
method: 'GET',
|
|
19
|
+
signal: AbortSignal.timeout(1200),
|
|
20
|
+
});
|
|
21
|
+
if (res.ok)
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
5
29
|
async function proxy(ctx) {
|
|
6
30
|
const upstream = config_1.config.upstream.replace(/\/$/, '');
|
|
7
31
|
// Rewrite path for upstream gateway:
|
|
@@ -9,8 +33,7 @@ async function proxy(ctx) {
|
|
|
9
33
|
// /api/hermes/* -> /api/* (upstream uses /api/ prefix)
|
|
10
34
|
const upstreamPath = ctx.path.replace(/^\/api\/hermes\/v1/, '/v1').replace(/^\/api\/hermes/, '/api');
|
|
11
35
|
const url = `${upstream}${upstreamPath}${ctx.search || ''}`;
|
|
12
|
-
|
|
13
|
-
// Build headers — forward most, strip browser-specific ones
|
|
36
|
+
// Build headers — forward most, strip browser/web-ui specific ones
|
|
14
37
|
const headers = {};
|
|
15
38
|
for (const [key, value] of Object.entries(ctx.headers)) {
|
|
16
39
|
if (value == null)
|
|
@@ -19,42 +42,47 @@ async function proxy(ctx) {
|
|
|
19
42
|
if (lower === 'host') {
|
|
20
43
|
headers['host'] = new URL(upstream).host;
|
|
21
44
|
}
|
|
22
|
-
else if (lower
|
|
45
|
+
else if (lower === 'authorization' || lower === 'origin' || lower === 'referer' || lower === 'connection') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
23
49
|
const v = Array.isArray(value) ? value[0] : value;
|
|
24
50
|
if (v)
|
|
25
51
|
headers[key] = v;
|
|
26
52
|
}
|
|
27
53
|
}
|
|
28
|
-
// Add SSE-friendly headers
|
|
29
|
-
if (ctx.path.match(/\/events$/)) {
|
|
30
|
-
headers['x-accel-buffering'] = 'no';
|
|
31
|
-
headers['cache-control'] = 'no-cache';
|
|
32
|
-
}
|
|
33
54
|
try {
|
|
34
55
|
// Build request body from raw body
|
|
35
56
|
let body;
|
|
36
57
|
if (ctx.req.method !== 'GET' && ctx.req.method !== 'HEAD') {
|
|
37
58
|
body = ctx.request.rawBody;
|
|
38
59
|
}
|
|
39
|
-
const
|
|
60
|
+
const requestInit = {
|
|
40
61
|
method: ctx.req.method,
|
|
41
62
|
headers,
|
|
42
63
|
body,
|
|
43
|
-
}
|
|
64
|
+
};
|
|
65
|
+
let res;
|
|
66
|
+
try {
|
|
67
|
+
res = await fetch(url, requestInit);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// Gateway may be restarting; wait briefly and retry once.
|
|
71
|
+
if (isTransientGatewayError(err) && await waitForGatewayReady(upstream)) {
|
|
72
|
+
res = await fetch(url, requestInit);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
44
78
|
// Set response headers
|
|
45
|
-
const resHeaders = {};
|
|
46
79
|
res.headers.forEach((value, key) => {
|
|
47
80
|
const lower = key.toLowerCase();
|
|
48
81
|
if (lower !== 'transfer-encoding' && lower !== 'connection') {
|
|
49
|
-
|
|
82
|
+
ctx.set(key, value);
|
|
50
83
|
}
|
|
51
84
|
});
|
|
52
|
-
if (ctx.path.match(/\/events$/)) {
|
|
53
|
-
resHeaders['x-accel-buffering'] = 'no';
|
|
54
|
-
resHeaders['cache-control'] = 'no-cache';
|
|
55
|
-
}
|
|
56
85
|
ctx.status = res.status;
|
|
57
|
-
ctx.set(resHeaders);
|
|
58
86
|
// Stream response body
|
|
59
87
|
if (res.body) {
|
|
60
88
|
const reader = res.body.getReader();
|
|
@@ -3,14 +3,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.setupTerminalWebSocket = setupTerminalWebSocket;
|
|
4
4
|
const ws_1 = require("ws");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
6
7
|
const auth_1 = require("../../services/auth");
|
|
7
8
|
let pty = null;
|
|
9
|
+
function ensureNodePtySpawnHelperExecutable() {
|
|
10
|
+
if (process.platform !== 'darwin')
|
|
11
|
+
return;
|
|
12
|
+
try {
|
|
13
|
+
const nodePtyRoot = (0, path_1.dirname)(require.resolve('node-pty/package.json'));
|
|
14
|
+
const helperCandidates = [
|
|
15
|
+
(0, path_1.join)(nodePtyRoot, 'build', 'Release', 'spawn-helper'),
|
|
16
|
+
(0, path_1.join)(nodePtyRoot, 'build', 'Debug', 'spawn-helper'),
|
|
17
|
+
(0, path_1.join)(nodePtyRoot, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper'),
|
|
18
|
+
];
|
|
19
|
+
for (const helperPath of helperCandidates) {
|
|
20
|
+
if (!(0, fs_1.existsSync)(helperPath))
|
|
21
|
+
continue;
|
|
22
|
+
try {
|
|
23
|
+
(0, fs_1.accessSync)(helperPath, fs_1.constants.X_OK);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
(0, fs_1.chmodSync)(helperPath, 0o755);
|
|
27
|
+
console.log(`[Terminal] Restored execute bit for node-pty helper: ${helperPath}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.warn(`[Terminal] Could not normalize node-pty helper permissions: ${err?.message || err}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
8
35
|
try {
|
|
36
|
+
ensureNodePtySpawnHelperExecutable();
|
|
9
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
10
38
|
pty = require('node-pty');
|
|
11
39
|
}
|
|
12
|
-
catch {
|
|
13
|
-
console.warn(
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.warn(`[Terminal] node-pty failed to load, terminal feature disabled (${err?.message || 'unknown error'})`);
|
|
14
42
|
}
|
|
15
43
|
// ─── Shell detection ────────────────────────────────────────────
|
|
16
44
|
function findShell() {
|
|
@@ -46,7 +74,7 @@ function createSession(shell) {
|
|
|
46
74
|
});
|
|
47
75
|
}
|
|
48
76
|
catch (err) {
|
|
49
|
-
throw new Error(`Failed to spawn shell "${shell}": ${err.message}
|
|
77
|
+
throw new Error(`Failed to spawn shell "${shell}": ${err.message}`);
|
|
50
78
|
}
|
|
51
79
|
const session = {
|
|
52
80
|
id,
|
|
@@ -262,5 +290,5 @@ function setupTerminalWebSocket(httpServer) {
|
|
|
262
290
|
}));
|
|
263
291
|
console.log(`[Terminal] First session created: ${firstSession.id} (${shellName(defaultShell)}, pid ${firstSession.pid})`);
|
|
264
292
|
});
|
|
265
|
-
console.log(`[Terminal] WebSocket ready at /terminal (shell: ${defaultShell})`);
|
|
293
|
+
console.log(`[Terminal] WebSocket ready at /terminal (shell: ${defaultShell}, transport: node-pty)`);
|
|
266
294
|
}
|
|
@@ -21,9 +21,18 @@ exports.exportProfile = exportProfile;
|
|
|
21
21
|
exports.setupReset = setupReset;
|
|
22
22
|
exports.importProfile = importProfile;
|
|
23
23
|
const child_process_1 = require("child_process");
|
|
24
|
+
const fs_1 = require("fs");
|
|
24
25
|
const util_1 = require("util");
|
|
25
26
|
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
26
27
|
const execOpts = { windowsHide: true };
|
|
28
|
+
const isDocker = (0, fs_1.existsSync)('/.dockerenv');
|
|
29
|
+
function resolveHermesBin() {
|
|
30
|
+
const envBin = process.env.HERMES_BIN?.trim();
|
|
31
|
+
if (envBin)
|
|
32
|
+
return envBin;
|
|
33
|
+
return 'hermes';
|
|
34
|
+
}
|
|
35
|
+
const HERMES_BIN = resolveHermesBin();
|
|
27
36
|
/**
|
|
28
37
|
* List sessions from Hermes CLI (without messages)
|
|
29
38
|
*/
|
|
@@ -32,7 +41,7 @@ async function listSessions(source, limit) {
|
|
|
32
41
|
if (source)
|
|
33
42
|
args.push('--source', source);
|
|
34
43
|
try {
|
|
35
|
-
const { stdout } = await execFileAsync(
|
|
44
|
+
const { stdout } = await execFileAsync(HERMES_BIN, args, {
|
|
36
45
|
maxBuffer: 50 * 1024 * 1024, // 50MB
|
|
37
46
|
timeout: 30000,
|
|
38
47
|
...execOpts,
|
|
@@ -92,7 +101,7 @@ async function listSessions(source, limit) {
|
|
|
92
101
|
async function getSession(id) {
|
|
93
102
|
const args = ['sessions', 'export', '-', '--session-id', id];
|
|
94
103
|
try {
|
|
95
|
-
const { stdout } = await execFileAsync(
|
|
104
|
+
const { stdout } = await execFileAsync(HERMES_BIN, args, {
|
|
96
105
|
maxBuffer: 50 * 1024 * 1024,
|
|
97
106
|
timeout: 30000,
|
|
98
107
|
...execOpts,
|
|
@@ -138,7 +147,7 @@ async function getSession(id) {
|
|
|
138
147
|
*/
|
|
139
148
|
async function deleteSession(id) {
|
|
140
149
|
try {
|
|
141
|
-
await execFileAsync(
|
|
150
|
+
await execFileAsync(HERMES_BIN, ['sessions', 'delete', id, '--yes'], {
|
|
142
151
|
timeout: 10000,
|
|
143
152
|
...execOpts,
|
|
144
153
|
});
|
|
@@ -154,7 +163,7 @@ async function deleteSession(id) {
|
|
|
154
163
|
*/
|
|
155
164
|
async function renameSession(id, title) {
|
|
156
165
|
try {
|
|
157
|
-
await execFileAsync(
|
|
166
|
+
await execFileAsync(HERMES_BIN, ['sessions', 'rename', id, title], {
|
|
158
167
|
timeout: 10000,
|
|
159
168
|
...execOpts,
|
|
160
169
|
});
|
|
@@ -170,7 +179,7 @@ async function renameSession(id, title) {
|
|
|
170
179
|
*/
|
|
171
180
|
async function getVersion() {
|
|
172
181
|
try {
|
|
173
|
-
const { stdout } = await execFileAsync(
|
|
182
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['--version'], { timeout: 5000, ...execOpts });
|
|
174
183
|
return stdout.trim();
|
|
175
184
|
}
|
|
176
185
|
catch {
|
|
@@ -181,7 +190,11 @@ async function getVersion() {
|
|
|
181
190
|
* Start Hermes gateway (uses launchd/systemd)
|
|
182
191
|
*/
|
|
183
192
|
async function startGateway() {
|
|
184
|
-
|
|
193
|
+
if (isDocker) {
|
|
194
|
+
const pid = await startGatewayBackground();
|
|
195
|
+
return pid ? `Gateway started (PID: ${pid})` : 'Gateway start triggered';
|
|
196
|
+
}
|
|
197
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['gateway', 'start'], {
|
|
185
198
|
timeout: 30000,
|
|
186
199
|
...execOpts,
|
|
187
200
|
});
|
|
@@ -193,7 +206,7 @@ async function startGateway() {
|
|
|
193
206
|
*/
|
|
194
207
|
async function startGatewayBackground() {
|
|
195
208
|
const { spawn } = require('child_process');
|
|
196
|
-
const child = spawn(
|
|
209
|
+
const child = spawn(HERMES_BIN, ['gateway', 'run'], {
|
|
197
210
|
detached: true,
|
|
198
211
|
stdio: 'ignore',
|
|
199
212
|
windowsHide: true,
|
|
@@ -205,7 +218,15 @@ async function startGatewayBackground() {
|
|
|
205
218
|
* Restart Hermes gateway
|
|
206
219
|
*/
|
|
207
220
|
async function restartGateway() {
|
|
208
|
-
|
|
221
|
+
if (isDocker) {
|
|
222
|
+
try {
|
|
223
|
+
await stopGateway();
|
|
224
|
+
}
|
|
225
|
+
catch { }
|
|
226
|
+
const pid = await startGatewayBackground();
|
|
227
|
+
return pid ? `Gateway restarted (PID: ${pid})` : 'Gateway restart triggered';
|
|
228
|
+
}
|
|
229
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['gateway', 'restart'], {
|
|
209
230
|
timeout: 30000,
|
|
210
231
|
...execOpts,
|
|
211
232
|
});
|
|
@@ -215,7 +236,7 @@ async function restartGateway() {
|
|
|
215
236
|
* Stop Hermes gateway
|
|
216
237
|
*/
|
|
217
238
|
async function stopGateway() {
|
|
218
|
-
const { stdout, stderr } = await execFileAsync(
|
|
239
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['gateway', 'stop'], {
|
|
219
240
|
timeout: 30000,
|
|
220
241
|
...execOpts,
|
|
221
242
|
});
|
|
@@ -226,7 +247,7 @@ async function stopGateway() {
|
|
|
226
247
|
*/
|
|
227
248
|
async function listLogFiles() {
|
|
228
249
|
try {
|
|
229
|
-
const { stdout } = await execFileAsync(
|
|
250
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['logs', 'list'], {
|
|
230
251
|
timeout: 10000,
|
|
231
252
|
...execOpts,
|
|
232
253
|
});
|
|
@@ -261,7 +282,7 @@ async function readLogs(logName = 'agent', lines = 100, level, session, since) {
|
|
|
261
282
|
if (since)
|
|
262
283
|
args.push('--since', since);
|
|
263
284
|
try {
|
|
264
|
-
const { stdout } = await execFileAsync(
|
|
285
|
+
const { stdout } = await execFileAsync(HERMES_BIN, args, {
|
|
265
286
|
maxBuffer: 10 * 1024 * 1024,
|
|
266
287
|
timeout: 15000,
|
|
267
288
|
...execOpts,
|
|
@@ -278,7 +299,7 @@ async function readLogs(logName = 'agent', lines = 100, level, session, since) {
|
|
|
278
299
|
*/
|
|
279
300
|
async function listProfiles() {
|
|
280
301
|
try {
|
|
281
|
-
const { stdout } = await execFileAsync(
|
|
302
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['profile', 'list'], {
|
|
282
303
|
timeout: 10000,
|
|
283
304
|
...execOpts,
|
|
284
305
|
});
|
|
@@ -311,7 +332,7 @@ async function listProfiles() {
|
|
|
311
332
|
*/
|
|
312
333
|
async function getProfile(name) {
|
|
313
334
|
try {
|
|
314
|
-
const { stdout } = await execFileAsync(
|
|
335
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['profile', 'show', name], {
|
|
315
336
|
timeout: 10000,
|
|
316
337
|
...execOpts,
|
|
317
338
|
});
|
|
@@ -352,7 +373,7 @@ async function createProfile(name, clone) {
|
|
|
352
373
|
if (clone)
|
|
353
374
|
args.push('--clone');
|
|
354
375
|
try {
|
|
355
|
-
const { stdout, stderr } = await execFileAsync(
|
|
376
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
|
|
356
377
|
timeout: 15000,
|
|
357
378
|
...execOpts,
|
|
358
379
|
});
|
|
@@ -368,7 +389,7 @@ async function createProfile(name, clone) {
|
|
|
368
389
|
*/
|
|
369
390
|
async function deleteProfile(name) {
|
|
370
391
|
try {
|
|
371
|
-
await execFileAsync(
|
|
392
|
+
await execFileAsync(HERMES_BIN, ['profile', 'delete', name, '--yes'], {
|
|
372
393
|
timeout: 10000,
|
|
373
394
|
...execOpts,
|
|
374
395
|
});
|
|
@@ -384,7 +405,7 @@ async function deleteProfile(name) {
|
|
|
384
405
|
*/
|
|
385
406
|
async function renameProfile(oldName, newName) {
|
|
386
407
|
try {
|
|
387
|
-
await execFileAsync(
|
|
408
|
+
await execFileAsync(HERMES_BIN, ['profile', 'rename', oldName, newName], {
|
|
388
409
|
timeout: 10000,
|
|
389
410
|
...execOpts,
|
|
390
411
|
});
|
|
@@ -400,7 +421,7 @@ async function renameProfile(oldName, newName) {
|
|
|
400
421
|
*/
|
|
401
422
|
async function useProfile(name) {
|
|
402
423
|
try {
|
|
403
|
-
const { stdout, stderr } = await execFileAsync(
|
|
424
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['profile', 'use', name], {
|
|
404
425
|
timeout: 10000,
|
|
405
426
|
...execOpts,
|
|
406
427
|
});
|
|
@@ -419,7 +440,7 @@ async function exportProfile(name, outputPath) {
|
|
|
419
440
|
if (outputPath)
|
|
420
441
|
args.push('--output', outputPath);
|
|
421
442
|
try {
|
|
422
|
-
const { stdout, stderr } = await execFileAsync(
|
|
443
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
|
|
423
444
|
timeout: 60000,
|
|
424
445
|
...execOpts,
|
|
425
446
|
});
|
|
@@ -435,7 +456,7 @@ async function exportProfile(name, outputPath) {
|
|
|
435
456
|
*/
|
|
436
457
|
async function setupReset() {
|
|
437
458
|
try {
|
|
438
|
-
const { stdout, stderr } = await execFileAsync(
|
|
459
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['setup', '--non-interactive', '--reset'], {
|
|
439
460
|
timeout: 30000,
|
|
440
461
|
...execOpts,
|
|
441
462
|
});
|
|
@@ -454,7 +475,7 @@ async function importProfile(archivePath, name) {
|
|
|
454
475
|
if (name)
|
|
455
476
|
args.push('--name', name);
|
|
456
477
|
try {
|
|
457
|
-
const { stdout, stderr } = await execFileAsync(
|
|
478
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
|
|
458
479
|
timeout: 60000,
|
|
459
480
|
...execOpts,
|
|
460
481
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermes-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Web dashboard for Hermes Agent — multi-platform AI chat, session management, scheduled jobs, usage analytics & channel configuration (Telegram, Discord, Slack, WhatsApp)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"dev:client": "vite --host",
|
|
36
36
|
"dev:server": "nodemon --signal SIGTERM --watch packages/server/src -e ts,tsx --exec node -r ts-node/register packages/server/src/index.ts",
|
|
37
37
|
"build": "vue-tsc -b && vite build && tsc -p packages/server/tsconfig.json",
|
|
38
|
-
"preview": "vite preview"
|
|
38
|
+
"preview": "vite preview",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"test:coverage": "vitest run --coverage"
|
|
39
42
|
},
|
|
40
43
|
"files": [
|
|
41
44
|
"bin/",
|
|
@@ -65,6 +68,7 @@
|
|
|
65
68
|
"ws": "^8.20.0"
|
|
66
69
|
},
|
|
67
70
|
"devDependencies": {
|
|
71
|
+
"@pinia/testing": "^1.0.3",
|
|
68
72
|
"@types/js-yaml": "^4.0.9",
|
|
69
73
|
"@types/koa": "^2.15.0",
|
|
70
74
|
"@types/koa__cors": "^5.0.0",
|
|
@@ -76,13 +80,16 @@
|
|
|
76
80
|
"@types/qrcode": "^1.5.6",
|
|
77
81
|
"@types/ws": "^8.18.1",
|
|
78
82
|
"@vitejs/plugin-vue": "^6.0.5",
|
|
83
|
+
"@vue/test-utils": "^2.4.6",
|
|
79
84
|
"@vue/tsconfig": "^0.9.1",
|
|
80
85
|
"concurrently": "^9.2.1",
|
|
86
|
+
"jsdom": "^27.0.1",
|
|
81
87
|
"nodemon": "^3.1.14",
|
|
82
88
|
"sass": "^1.99.0",
|
|
83
89
|
"ts-node": "^10.9.2",
|
|
84
90
|
"typescript": "~6.0.2",
|
|
85
91
|
"vite": "^8.0.4",
|
|
92
|
+
"vitest": "^3.2.4",
|
|
86
93
|
"vue-tsc": "^3.2.6"
|
|
87
94
|
}
|
|
88
95
|
}
|