hermes-web-ui 0.2.7 → 0.2.9

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 (88) hide show
  1. package/README.md +2 -2
  2. package/bin/hermes-web-ui.mjs +50 -13
  3. package/dist/client/assets/Add-XpaL69a7.js +1 -0
  4. package/dist/client/assets/{Button-zECKCotA.js → Button-DZWlvt3_.js} +14 -14
  5. package/dist/client/assets/{ChannelsView-xCdAEZam.css → ChannelsView-D168bwSq.css} +1 -1
  6. package/dist/client/assets/ChannelsView-DsFZWVNp.js +1 -0
  7. package/dist/client/assets/ChatView-DoqDlZLC.js +127 -0
  8. package/dist/client/assets/{Close-DjLWDodG.js → Close-CynFIaWE.js} +10 -10
  9. package/dist/client/assets/FormItem-9JbgiHgw.js +110 -0
  10. package/dist/client/assets/Input-BUMpvosL.js +234 -0
  11. package/dist/client/assets/InputNumber-BOptkZXt.js +13 -0
  12. package/dist/client/assets/{JobsView-DsfGyjnT.js → JobsView-C-nRY00Q.js} +2 -2
  13. package/dist/client/assets/{LoginView-BiWaCYS0.js → LoginView-CLrTbJPK.js} +1 -1
  14. package/dist/client/assets/{LogsView-DuORyFFY.js → LogsView-BjeUz4w_.js} +1 -1
  15. package/dist/client/assets/{MarkdownRenderer-BA3oPVqT.js → MarkdownRenderer-pJXYVaJf.js} +1 -1
  16. package/dist/client/assets/MemoryView-B_b0Bw4s.js +7 -0
  17. package/dist/client/assets/MemoryView-hIfyUkwz.css +1 -0
  18. package/dist/client/assets/{Modal-WQKdvHJY.js → Modal-Gb5CDHOn.js} +56 -56
  19. package/dist/client/assets/ModelsView-BHO6JeeK.js +1 -0
  20. package/dist/client/assets/{Popconfirm-02A7vL_z.js → Popconfirm-f3hviugz.js} +4 -4
  21. package/dist/client/assets/Popover-BtQeULHo.js +117 -0
  22. package/dist/client/assets/ProfilesView-C4GoKpgi.js +440 -0
  23. package/dist/client/assets/ProfilesView-jqeO8Zv3.css +1 -0
  24. package/dist/client/assets/{Scrollbar-Bg2FeTtI.js → Scrollbar-Bui9E0cT.js} +17 -17
  25. package/dist/client/assets/Select-By6oALhZ.js +340 -0
  26. package/dist/client/assets/{SettingRow-CLLFxWP3.js → SettingRow-CWqG1TP7.js} +1 -1
  27. package/dist/client/assets/SettingsView-CzgT0dAx.js +352 -0
  28. package/dist/client/assets/{SkillsView-D6CegW4w.js → SkillsView-2xeOBpxm.js} +1 -1
  29. package/dist/client/assets/{Spin-lybeIDeX.js → Spin-DD5RgTEV.js} +4 -4
  30. package/dist/client/assets/{Suffix-Dv1UElLH.js → Suffix-CkTuXehC.js} +5 -5
  31. package/dist/client/assets/{Switch-mgeu-lpD.js → Switch-BqpUglHp.js} +15 -15
  32. package/dist/client/assets/{Tag-DWc4lbAm.js → Tag-BdqJCtwA.js} +15 -15
  33. package/dist/client/assets/{TerminalView-DmnXOHFu.js → TerminalView-DOvaJfhr.js} +1 -1
  34. package/dist/client/assets/{Tooltip-DrDTmrj3.js → Tooltip-2f394xdV.js} +1 -1
  35. package/dist/client/assets/{UsageView-C3O6-vbW.js → UsageView-Cd7Gui1x.js} +1 -1
  36. package/dist/client/assets/{Warning-t1i_4T5i.js → Warning-21tMOc2L.js} +1 -1
  37. package/dist/client/assets/{_plugin-vue_export-helper-BkT4A29V.js → _plugin-vue_export-helper-C39Y8Snr.js} +1 -1
  38. package/dist/client/assets/app-Dc1GzlQi.js +1 -0
  39. package/dist/client/assets/app-DjAPGKg7.js +1 -0
  40. package/dist/client/assets/{browser-DzAOCZtm.js → browser-DMdqvQBg.js} +9 -9
  41. package/dist/client/assets/{chat-CX1Hza8X.js → chat-1kL3Ps59.js} +2 -2
  42. package/dist/client/assets/composables-BxlGNoT-.js +1 -0
  43. package/dist/client/assets/create-5zWq3BEB.js +1 -0
  44. package/dist/client/assets/fade-in-scale-up.cssr-HXBs3oL0.js +1 -0
  45. package/dist/client/assets/index-BT19Bivl.css +1 -0
  46. package/dist/client/assets/index-CFIEnaxD.js +284 -0
  47. package/dist/client/assets/{jobs-B9NHIxuL.js → jobs-YFhNrsBu.js} +1 -1
  48. package/dist/client/assets/{pinia-bPUFQqFK.js → pinia-BPgWlB7x.js} +1 -1
  49. package/dist/client/assets/profiles-H8Bd9mDO.js +23 -0
  50. package/dist/client/assets/{router-COwpqexM.js → router-cAIqC_mO.js} +2 -2
  51. package/dist/client/assets/{sessions-B1SPfe-R.js → sessions-fFwnFvo3.js} +1 -1
  52. package/dist/client/assets/{skills-CRtljpt6.js → skills-DZWMwFIf.js} +1 -1
  53. package/dist/client/assets/use-compitable-esQP7BHf.js +1 -0
  54. package/dist/client/assets/use-message-CnDHqZf8.js +1 -0
  55. package/dist/client/index.html +24 -22
  56. package/dist/server/data/.token +1 -0
  57. package/dist/server/index.js +70 -4
  58. package/dist/server/routes/hermes/config.js +11 -11
  59. package/dist/server/routes/hermes/filesystem.js +154 -29
  60. package/dist/server/routes/hermes/profiles.js +136 -14
  61. package/dist/server/routes/hermes/terminal.js +12 -34
  62. package/dist/server/routes/hermes/weixin.js +6 -6
  63. package/dist/server/services/hermes-cli.d.ts +4 -0
  64. package/dist/server/services/hermes-cli.js +17 -0
  65. package/dist/server/services/hermes-profile.d.ts +22 -0
  66. package/dist/server/services/hermes-profile.js +60 -0
  67. package/dist/server/shared/providers.js +8 -14
  68. package/package.json +1 -1
  69. package/dist/client/assets/ChannelsView-Bvh3tnio.js +0 -1
  70. package/dist/client/assets/ChatView-CJZahJ1V.js +0 -127
  71. package/dist/client/assets/FormItem-CZ9auYA2.js +0 -110
  72. package/dist/client/assets/Input-DxJk6pT-.js +0 -234
  73. package/dist/client/assets/InputNumber-BTqp81h0.js +0 -13
  74. package/dist/client/assets/MemoryView-CU3JHweh.css +0 -1
  75. package/dist/client/assets/MemoryView-VgQFNMsP.js +0 -5
  76. package/dist/client/assets/ModelsView-QDRloKd5.js +0 -1
  77. package/dist/client/assets/Popover-2C1zuptC.js +0 -117
  78. package/dist/client/assets/Select-C_hKaXgI.js +0 -340
  79. package/dist/client/assets/SettingsView-CeScqhwO.js +0 -352
  80. package/dist/client/assets/app-BUjbfMSg.js +0 -1
  81. package/dist/client/assets/app-p59ZckFN.js +0 -1
  82. package/dist/client/assets/context-BM8ZQCOz.js +0 -1
  83. package/dist/client/assets/fade-in-scale-up.cssr-DQl-Z54c.js +0 -1
  84. package/dist/client/assets/index-95z-iQoj.js +0 -306
  85. package/dist/client/assets/index-C33O1KZm.css +0 -1
  86. package/dist/client/assets/use-compitable-BnlNjEug.js +0 -1
  87. package/dist/client/assets/use-message-g8F4Z57w.js +0 -1
  88. /package/dist/client/assets/{omit-C4dR5R2G.js → omit-1BRB6K75.js} +0 -0
@@ -1 +1 @@
1
- import{o as e}from"./router-COwpqexM.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
+ import{o as e}from"./router-cAIqC_mO.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-COwpqexM.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};
1
+ import{o as e}from"./router-cAIqC_mO.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};
@@ -0,0 +1 @@
1
+ import{C as e}from"./router-cAIqC_mO.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};
@@ -0,0 +1 @@
1
+ import{F as e}from"./router-cAIqC_mO.js";import{$ as t,Y as n}from"./browser-DMdqvQBg.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};
@@ -6,29 +6,31 @@
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-95z-iQoj.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/router-COwpqexM.js">
11
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BkT4A29V.js">
12
- <link rel="modulepreload" crossorigin href="/assets/browser-DzAOCZtm.js">
13
- <link rel="modulepreload" crossorigin href="/assets/Scrollbar-Bg2FeTtI.js">
14
- <link rel="modulepreload" crossorigin href="/assets/use-compitable-BnlNjEug.js">
15
- <link rel="modulepreload" crossorigin href="/assets/Popover-2C1zuptC.js">
16
- <link rel="modulepreload" crossorigin href="/assets/Close-DjLWDodG.js">
17
- <link rel="modulepreload" crossorigin href="/assets/Button-zECKCotA.js">
18
- <link rel="modulepreload" crossorigin href="/assets/Suffix-Dv1UElLH.js">
19
- <link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-DQl-Z54c.js">
20
- <link rel="modulepreload" crossorigin href="/assets/Tag-DWc4lbAm.js">
21
- <link rel="modulepreload" crossorigin href="/assets/Select-C_hKaXgI.js">
22
- <link rel="modulepreload" crossorigin href="/assets/Warning-t1i_4T5i.js">
23
- <link rel="modulepreload" crossorigin href="/assets/Modal-WQKdvHJY.js">
24
- <link rel="modulepreload" crossorigin href="/assets/omit-C4dR5R2G.js">
25
- <link rel="modulepreload" crossorigin href="/assets/context-BM8ZQCOz.js">
26
- <link rel="modulepreload" crossorigin href="/assets/pinia-bPUFQqFK.js">
27
- <link rel="modulepreload" crossorigin href="/assets/sessions-B1SPfe-R.js">
28
- <link rel="modulepreload" crossorigin href="/assets/app-BUjbfMSg.js">
29
- <link rel="modulepreload" crossorigin href="/assets/chat-CX1Hza8X.js">
9
+ <script type="module" crossorigin src="/assets/index-CFIEnaxD.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/router-cAIqC_mO.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-C39Y8Snr.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/browser-DMdqvQBg.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/Scrollbar-Bui9E0cT.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/use-compitable-esQP7BHf.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/Popover-BtQeULHo.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/Close-CynFIaWE.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/Button-DZWlvt3_.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/Suffix-CkTuXehC.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-HXBs3oL0.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/Tag-BdqJCtwA.js">
21
+ <link rel="modulepreload" crossorigin href="/assets/create-5zWq3BEB.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/Select-By6oALhZ.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/Warning-21tMOc2L.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/Modal-Gb5CDHOn.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/pinia-BPgWlB7x.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/profiles-H8Bd9mDO.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/omit-1BRB6K75.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/use-message-CnDHqZf8.js">
29
+ <link rel="modulepreload" crossorigin href="/assets/sessions-fFwnFvo3.js">
30
+ <link rel="modulepreload" crossorigin href="/assets/app-Dc1GzlQi.js">
31
+ <link rel="modulepreload" crossorigin href="/assets/chat-1kL3Ps59.js">
30
32
  <link rel="modulepreload" crossorigin href="/assets/logo-Cd-t_oGE.js">
31
- <link rel="stylesheet" crossorigin href="/assets/index-C33O1KZm.css">
33
+ <link rel="stylesheet" crossorigin href="/assets/index-BT19Bivl.css">
32
34
  </head>
33
35
 
34
36
  <body>
@@ -0,0 +1 @@
1
+ f56904d3b78a9bb085ba075b2827b1c29bf06d41bdddba5b758fd4e68845f5e7
@@ -44,12 +44,48 @@ const koa_static_1 = __importDefault(require("koa-static"));
44
44
  const koa_send_1 = __importDefault(require("koa-send"));
45
45
  const path_1 = require("path");
46
46
  const promises_1 = require("fs/promises");
47
+ const fs_1 = require("fs");
47
48
  const config_1 = require("./config");
48
49
  const hermes_1 = require("./routes/hermes");
49
50
  const upload_1 = require("./routes/upload");
50
51
  const webhook_1 = require("./routes/webhook");
51
52
  const hermesCli = __importStar(require("./services/hermes-cli"));
52
53
  const auth_1 = require("./services/auth");
54
+ function getLocalVersion() {
55
+ // production: dist/server → ../../package.json
56
+ // dev: packages/server/src → ../../../package.json
57
+ const candidates = [
58
+ (0, path_1.resolve)(__dirname, '../../package.json'),
59
+ (0, path_1.resolve)(__dirname, '../../../package.json'),
60
+ ];
61
+ for (const p of candidates) {
62
+ try {
63
+ return JSON.parse((0, fs_1.readFileSync)(p, 'utf-8')).version;
64
+ }
65
+ catch { }
66
+ }
67
+ return '0.0.0';
68
+ }
69
+ const LOCAL_VERSION = getLocalVersion();
70
+ let cachedLatestVersion = '';
71
+ async function checkLatestVersion() {
72
+ try {
73
+ const res = await fetch('https://registry.npmjs.org/hermes-web-ui/latest', {
74
+ signal: AbortSignal.timeout(5000),
75
+ });
76
+ if (res.ok) {
77
+ const data = await res.json();
78
+ const latest = data.version || '';
79
+ if (latest && latest !== cachedLatestVersion) {
80
+ cachedLatestVersion = latest;
81
+ if (latest !== LOCAL_VERSION) {
82
+ console.log(`⬆ New version available: v${LOCAL_VERSION} → v${latest}`);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ catch { }
88
+ }
53
89
  const app = new koa_1.default();
54
90
  const { restartGateway, startGateway, startGatewayBackground, getVersion } = hermesCli;
55
91
  let server = null;
@@ -71,13 +107,37 @@ async function bootstrap() {
71
107
  app.use((0, bodyparser_1.default)());
72
108
  app.use(webhook_1.webhookRoutes.routes());
73
109
  app.use(upload_1.uploadRoutes.routes());
110
+ // update (must be before hermesRoutes which includes proxy routes)
111
+ app.use(async (ctx, next) => {
112
+ if (ctx.path === '/api/hermes/update' && ctx.method === 'POST') {
113
+ const isWin = process.platform === 'win32';
114
+ const cmd = isWin
115
+ ? 'cmd /c hermes-web-ui update'
116
+ : 'hermes-web-ui update';
117
+ try {
118
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
119
+ const output = execSync(cmd, {
120
+ encoding: 'utf-8',
121
+ timeout: 120000,
122
+ stdio: ['pipe', 'pipe', 'pipe'],
123
+ });
124
+ ctx.body = { success: true, message: output.trim() };
125
+ }
126
+ catch (err) {
127
+ ctx.status = 500;
128
+ ctx.body = { success: false, message: err.stderr || err.message };
129
+ }
130
+ return;
131
+ }
132
+ await next();
133
+ });
74
134
  app.use(hermes_1.hermesRoutes.routes());
75
135
  app.use(hermes_1.proxyMiddleware);
76
136
  // health
77
137
  app.use(async (ctx, next) => {
78
138
  if (ctx.path === '/health') {
79
139
  const raw = await getVersion();
80
- const version = raw.split('\n')[0].replace('Hermes Agent ', '') || '';
140
+ const hermesVersion = raw.split('\n')[0].replace('Hermes Agent ', '') || '';
81
141
  let gatewayOk = false;
82
142
  try {
83
143
  const res = await fetch(`${config_1.config.upstream.replace(/\/$/, '')}/health`, {
@@ -89,8 +149,11 @@ async function bootstrap() {
89
149
  ctx.body = {
90
150
  status: gatewayOk ? 'ok' : 'error',
91
151
  platform: 'hermes-agent',
92
- version,
152
+ version: hermesVersion,
93
153
  gateway: gatewayOk ? 'running' : 'stopped',
154
+ webui_version: LOCAL_VERSION,
155
+ webui_latest: cachedLatestVersion,
156
+ webui_update_available: cachedLatestVersion && cachedLatestVersion !== LOCAL_VERSION,
94
157
  };
95
158
  return;
96
159
  }
@@ -120,6 +183,9 @@ async function bootstrap() {
120
183
  });
121
184
  // 👇 绑定退出信号
122
185
  bindShutdown();
186
+ // Check for updates every 4 hours
187
+ checkLatestVersion();
188
+ setInterval(checkLatestVersion, 4 * 60 * 60 * 1000);
123
189
  }
124
190
  // ============================
125
191
  // ✅ 统一关闭逻辑(核心)
@@ -173,10 +239,10 @@ function bindShutdown() {
173
239
  // 你的原逻辑(基本不动)
174
240
  // ============================
175
241
  async function ensureApiServerConfig() {
176
- const { homedir } = await Promise.resolve().then(() => __importStar(require('os')));
177
242
  const { readFileSync, writeFileSync, existsSync, copyFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
178
243
  const yaml = (await Promise.resolve().then(() => __importStar(require('js-yaml')))).default;
179
- const configPath = (0, path_1.resolve)(homedir(), '.hermes/config.yaml');
244
+ const { getActiveConfigPath } = await Promise.resolve().then(() => __importStar(require('./services/hermes-profile')));
245
+ const configPath = getActiveConfigPath();
180
246
  const defaults = {
181
247
  enabled: true,
182
248
  host: '127.0.0.1',
@@ -7,17 +7,16 @@ exports.configRoutes = void 0;
7
7
  const router_1 = __importDefault(require("@koa/router"));
8
8
  const promises_1 = require("fs/promises");
9
9
  const promises_2 = require("fs/promises");
10
- const path_1 = require("path");
11
- const os_1 = require("os");
12
10
  const js_yaml_1 = __importDefault(require("js-yaml"));
13
11
  const hermes_cli_1 = require("../../services/hermes-cli");
12
+ const hermes_profile_1 = require("../../services/hermes-profile");
14
13
  // Platform sections that require gateway restart after config change
15
14
  const PLATFORM_SECTIONS = new Set([
16
15
  'telegram', 'discord', 'slack', 'whatsapp', 'matrix',
17
16
  'weixin', 'wecom', 'feishu', 'dingtalk',
18
17
  ]);
19
- const configPath = (0, path_1.resolve)((0, os_1.homedir)(), '.hermes/config.yaml');
20
- const envPath = (0, path_1.resolve)((0, os_1.homedir)(), '.hermes/.env');
18
+ const configPath = () => (0, hermes_profile_1.getActiveConfigPath)();
19
+ const envPath = () => (0, hermes_profile_1.getActiveEnvPath)();
21
20
  // Env var → (platform, configPath in PlatformConfig) mapping
22
21
  // Matches hermes _apply_env_overrides() in gateway/config.py
23
22
  const envPlatformMap = {
@@ -85,7 +84,7 @@ function getNested(obj, path) {
85
84
  }
86
85
  async function readEnvPlatforms() {
87
86
  try {
88
- const raw = await (0, promises_1.readFile)(envPath, 'utf-8');
87
+ const raw = await (0, promises_1.readFile)(envPath(), 'utf-8');
89
88
  const env = parseEnv(raw);
90
89
  const platforms = {};
91
90
  for (const [envKey, [platform, cfgPath]] of Object.entries(envPlatformMap)) {
@@ -110,7 +109,7 @@ async function readEnvPlatforms() {
110
109
  async function saveEnvValue(key, value) {
111
110
  let raw;
112
111
  try {
113
- raw = await (0, promises_1.readFile)(envPath, 'utf-8');
112
+ raw = await (0, promises_1.readFile)(envPath(), 'utf-8');
114
113
  }
115
114
  catch {
116
115
  raw = '';
@@ -151,26 +150,27 @@ async function saveEnvValue(key, value) {
151
150
  }
152
151
  // Remove trailing empty lines, keep exactly one trailing newline
153
152
  let output = result.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\n+$/, '') + '\n';
154
- await (0, promises_1.writeFile)(envPath, output, 'utf-8');
153
+ await (0, promises_1.writeFile)(envPath(), output, 'utf-8');
155
154
  // Set permissions to 0600 (owner only), matching hermes behavior
156
155
  try {
157
- await (0, promises_2.chmod)(envPath, 0o600);
156
+ await (0, promises_2.chmod)(envPath(), 0o600);
158
157
  }
159
158
  catch { /* ignore */ }
160
159
  }
161
160
  async function readConfig() {
162
- const raw = await (0, promises_1.readFile)(configPath, 'utf-8');
161
+ const raw = await (0, promises_1.readFile)(configPath(), 'utf-8');
163
162
  return js_yaml_1.default.load(raw) || {};
164
163
  }
165
164
  async function writeConfig(data) {
166
- await (0, promises_1.copyFile)(configPath, configPath + '.bak');
165
+ const cp = configPath();
166
+ await (0, promises_1.copyFile)(cp, cp + '.bak');
167
167
  const yamlStr = js_yaml_1.default.dump(data, {
168
168
  lineWidth: -1,
169
169
  noRefs: true,
170
170
  quotingType: '"',
171
171
  forceQuotes: false,
172
172
  });
173
- await (0, promises_1.writeFile)(configPath, yamlStr, 'utf-8');
173
+ await (0, promises_1.writeFile)(cp, yamlStr, 'utf-8');
174
174
  }
175
175
  exports.configRoutes = new router_1.default();
176
176
  // GET /api/config — read config sections
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -7,12 +40,71 @@ exports.fsRoutes = void 0;
7
40
  const router_1 = __importDefault(require("@koa/router"));
8
41
  const promises_1 = require("fs/promises");
9
42
  const path_1 = require("path");
10
- const os_1 = require("os");
11
43
  const js_yaml_1 = __importDefault(require("js-yaml"));
12
- const authPath = (0, path_1.resolve)((0, os_1.homedir)(), '.hermes', 'auth.json');
44
+ const hermes_profile_1 = require("../../services/hermes-profile");
45
+ const hermesCli = __importStar(require("../../services/hermes-cli"));
46
+ // --- Provider env var mapping (from hermes providers.py HERMES_OVERLAYS + config.py) ---
47
+ // Maps provider key → { api_key_envs: all env var aliases for API key, base_url_env: env var for base URL }
48
+ const PROVIDER_ENV_MAP = {
49
+ openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: 'OPENROUTER_BASE_URL' },
50
+ zai: { api_key_env: 'ZAI_API_KEY', base_url_env: '' },
51
+ 'kimi-for-coding': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
52
+ minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' },
53
+ 'minimax-cn': { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' },
54
+ deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
55
+ alibaba: { api_key_env: 'DASHSCOPE_API_KEY', base_url_env: 'DASHSCOPE_BASE_URL' },
56
+ anthropic: { api_key_env: 'ANTHROPIC_API_KEY', base_url_env: '' },
57
+ xai: { api_key_env: 'XAI_API_KEY', base_url_env: 'XAI_BASE_URL' },
58
+ xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: 'XIAOMI_BASE_URL' },
59
+ gemini: { api_key_env: 'GEMINI_API_KEY', base_url_env: '' },
60
+ kilo: { api_key_env: 'KILO_API_KEY', base_url_env: 'KILOCODE_BASE_URL' },
61
+ vercel: { api_key_env: 'AI_GATEWAY_API_KEY', base_url_env: '' },
62
+ opencode: { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_ZEN_BASE_URL' },
63
+ 'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_GO_BASE_URL' },
64
+ huggingface: { api_key_env: 'HF_TOKEN', base_url_env: 'HF_BASE_URL' },
65
+ };
66
+ async function saveEnvValue(key, value) {
67
+ const envPath = (0, hermes_profile_1.getActiveEnvPath)();
68
+ let raw;
69
+ try {
70
+ raw = await (0, promises_1.readFile)(envPath, 'utf-8');
71
+ }
72
+ catch {
73
+ raw = '';
74
+ }
75
+ const remove = !value;
76
+ const lines = raw.split('\n');
77
+ let found = false;
78
+ const result = [];
79
+ for (const line of lines) {
80
+ const trimmed = line.trim();
81
+ if (trimmed.startsWith('#') && trimmed.startsWith(`# ${key}=`)) {
82
+ if (!remove)
83
+ result.push(`${key}=${value}`);
84
+ found = true;
85
+ }
86
+ else {
87
+ const eqIdx = trimmed.indexOf('=');
88
+ if (eqIdx !== -1 && trimmed.slice(0, eqIdx).trim() === key) {
89
+ if (!remove)
90
+ result.push(`${key}=${value}`);
91
+ found = true;
92
+ }
93
+ else {
94
+ result.push(line);
95
+ }
96
+ }
97
+ }
98
+ if (!found && !remove) {
99
+ result.push(`${key}=${value}`);
100
+ }
101
+ let output = result.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\n+$/, '') + '\n';
102
+ await (0, promises_1.writeFile)(envPath, output, 'utf-8');
103
+ }
104
+ const authPath = () => (0, hermes_profile_1.getActiveAuthPath)();
13
105
  async function loadAuthJson() {
14
106
  try {
15
- const raw = await (0, promises_1.readFile)(authPath, 'utf-8');
107
+ const raw = await (0, promises_1.readFile)(authPath(), 'utf-8');
16
108
  return JSON.parse(raw);
17
109
  }
18
110
  catch {
@@ -20,7 +112,7 @@ async function loadAuthJson() {
20
112
  }
21
113
  }
22
114
  async function saveAuthJson(auth) {
23
- await (0, promises_1.writeFile)(authPath, JSON.stringify(auth, null, 2) + '\n', 'utf-8');
115
+ await (0, promises_1.writeFile)(authPath(), JSON.stringify(auth, null, 2) + '\n', 'utf-8');
24
116
  }
25
117
  async function fetchProviderModels(baseUrl, apiKey) {
26
118
  try {
@@ -49,7 +141,7 @@ async function fetchProviderModels(baseUrl, apiKey) {
49
141
  const providers_1 = require("../../shared/providers");
50
142
  const PROVIDER_MODEL_CATALOG = (0, providers_1.buildProviderModelMap)();
51
143
  exports.fsRoutes = new router_1.default();
52
- const hermesDir = (0, path_1.resolve)((0, os_1.homedir)(), '.hermes');
144
+ const hermesDir = () => (0, hermes_profile_1.getActiveProfileDir)();
53
145
  // --- Helpers ---
54
146
  function extractDescription(content) {
55
147
  const lines = content.split('\n');
@@ -95,26 +187,27 @@ async function safeStat(filePath) {
95
187
  }
96
188
  }
97
189
  // --- Config YAML helpers ---
98
- const configPath = (0, path_1.resolve)((0, os_1.homedir)(), '.hermes/config.yaml');
190
+ const configPath = () => (0, hermes_profile_1.getActiveConfigPath)();
99
191
  async function readConfigYaml() {
100
- const raw = await safeReadFile(configPath);
192
+ const raw = await safeReadFile(configPath());
101
193
  if (!raw)
102
194
  return {};
103
195
  return js_yaml_1.default.load(raw) || {};
104
196
  }
105
197
  async function writeConfigYaml(config) {
106
- await (0, promises_1.copyFile)(configPath, configPath + '.bak');
198
+ const cp = configPath();
199
+ await (0, promises_1.copyFile)(cp, cp + '.bak');
107
200
  const yamlStr = js_yaml_1.default.dump(config, {
108
201
  lineWidth: -1,
109
202
  noRefs: true,
110
203
  quotingType: '"',
111
204
  });
112
- await (0, promises_1.writeFile)(configPath, yamlStr, 'utf-8');
205
+ await (0, promises_1.writeFile)(cp, yamlStr, 'utf-8');
113
206
  }
114
207
  // --- Skills Routes ---
115
208
  // List all skills grouped by category
116
209
  exports.fsRoutes.get('/api/hermes/skills', async (ctx) => {
117
- const skillsDir = (0, path_1.join)(hermesDir, 'skills');
210
+ const skillsDir = (0, path_1.join)(hermesDir(), 'skills');
118
211
  try {
119
212
  // Read disabled skills list from config.yaml
120
213
  const config = await readConfigYaml();
@@ -213,7 +306,7 @@ async function listFilesRecursive(dir, prefix) {
213
306
  }
214
307
  exports.fsRoutes.get('/api/hermes/skills/:category/:skill/files', async (ctx) => {
215
308
  const { category, skill } = ctx.params;
216
- const skillDir = (0, path_1.join)(hermesDir, 'skills', category, skill);
309
+ const skillDir = (0, path_1.join)(hermesDir(), 'skills', category, skill);
217
310
  try {
218
311
  const allFiles = await listFilesRecursive(skillDir, '');
219
312
  const files = allFiles.filter(f => f.path !== 'SKILL.md');
@@ -227,8 +320,9 @@ exports.fsRoutes.get('/api/hermes/skills/:category/:skill/files', async (ctx) =>
227
320
  // Read a specific file under skills/ (must be registered after the /files route)
228
321
  exports.fsRoutes.get('/api/hermes/skills/{*path}', async (ctx) => {
229
322
  const filePath = ctx.params.path;
230
- const fullPath = (0, path_1.resolve)((0, path_1.join)(hermesDir, 'skills', filePath));
231
- if (!fullPath.startsWith((0, path_1.join)(hermesDir, 'skills'))) {
323
+ const hd = hermesDir();
324
+ const fullPath = (0, path_1.resolve)((0, path_1.join)(hd, 'skills', filePath));
325
+ if (!fullPath.startsWith((0, path_1.join)(hd, 'skills'))) {
232
326
  ctx.status = 403;
233
327
  ctx.body = { error: 'Access denied' };
234
328
  return;
@@ -243,19 +337,25 @@ exports.fsRoutes.get('/api/hermes/skills/{*path}', async (ctx) => {
243
337
  });
244
338
  // --- Memory Routes ---
245
339
  exports.fsRoutes.get('/api/hermes/memory', async (ctx) => {
246
- const memoryPath = (0, path_1.join)(hermesDir, 'memories', 'MEMORY.md');
247
- const userPath = (0, path_1.join)(hermesDir, 'memories', 'USER.md');
248
- const [memory, user, memoryStat, userStat] = await Promise.all([
340
+ const hd = hermesDir();
341
+ const memoryPath = (0, path_1.join)(hd, 'memories', 'MEMORY.md');
342
+ const userPath = (0, path_1.join)(hd, 'memories', 'USER.md');
343
+ const soulPath = (0, path_1.join)(hd, 'SOUL.md');
344
+ const [memory, user, soul, memoryStat, userStat, soulStat] = await Promise.all([
249
345
  safeReadFile(memoryPath),
250
346
  safeReadFile(userPath),
347
+ safeReadFile(soulPath),
251
348
  safeStat(memoryPath),
252
349
  safeStat(userPath),
350
+ safeStat(soulPath),
253
351
  ]);
254
352
  ctx.body = {
255
353
  memory: memory || '',
256
354
  user: user || '',
355
+ soul: soul || '',
257
356
  memory_mtime: memoryStat?.mtime || null,
258
357
  user_mtime: userStat?.mtime || null,
358
+ soul_mtime: soulStat?.mtime || null,
259
359
  };
260
360
  });
261
361
  exports.fsRoutes.post('/api/hermes/memory', async (ctx) => {
@@ -265,13 +365,19 @@ exports.fsRoutes.post('/api/hermes/memory', async (ctx) => {
265
365
  ctx.body = { error: 'Missing section or content' };
266
366
  return;
267
367
  }
268
- if (section !== 'memory' && section !== 'user') {
368
+ if (section !== 'memory' && section !== 'user' && section !== 'soul') {
269
369
  ctx.status = 400;
270
- ctx.body = { error: 'Section must be "memory" or "user"' };
370
+ ctx.body = { error: 'Section must be "memory", "user", or "soul"' };
271
371
  return;
272
372
  }
273
- const fileName = section === 'memory' ? 'MEMORY.md' : 'USER.md';
274
- const filePath = (0, path_1.join)(hermesDir, 'memories', fileName);
373
+ let filePath;
374
+ if (section === 'soul') {
375
+ filePath = (0, path_1.join)(hermesDir(), 'SOUL.md');
376
+ }
377
+ else {
378
+ const fileName = section === 'memory' ? 'MEMORY.md' : 'USER.md';
379
+ filePath = (0, path_1.join)(hermesDir(), 'memories', fileName);
380
+ }
275
381
  try {
276
382
  await (0, promises_1.writeFile)(filePath, content, 'utf-8');
277
383
  ctx.body = { success: true };
@@ -444,16 +550,20 @@ exports.fsRoutes.post('/api/hermes/config/providers', async (ctx) => {
444
550
  return;
445
551
  }
446
552
  try {
447
- // 1. Write to config.yaml custom_providers
448
- const config = await readConfigYaml();
449
- if (!Array.isArray(config.custom_providers)) {
450
- config.custom_providers = [];
451
- }
452
- config.custom_providers.push({ name, base_url, api_key, model });
453
- await writeConfigYaml(config);
454
- // 2. Write to auth.json credential_pool
553
+ // Determine if this is a built-in provider or a custom one
455
554
  const poolKey = providerKey
456
555
  || `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`;
556
+ const isBuiltin = poolKey in PROVIDER_ENV_MAP;
557
+ if (!isBuiltin) {
558
+ // Custom provider: write to config.yaml custom_providers
559
+ const config = await readConfigYaml();
560
+ if (!Array.isArray(config.custom_providers)) {
561
+ config.custom_providers = [];
562
+ }
563
+ config.custom_providers.push({ name, base_url, api_key, model });
564
+ await writeConfigYaml(config);
565
+ }
566
+ // Write to auth.json credential_pool (all providers)
457
567
  const auth = await loadAuthJson() || { credential_pool: {} };
458
568
  if (!auth.credential_pool)
459
569
  auth.credential_pool = {};
@@ -468,7 +578,15 @@ exports.fsRoutes.post('/api/hermes/config/providers', async (ctx) => {
468
578
  last_status: null,
469
579
  });
470
580
  await saveAuthJson(auth);
471
- // 3. Auto-switch model to the newly added provider
581
+ // Write API key to .env (built-in providers only)
582
+ const envMapping = PROVIDER_ENV_MAP[poolKey] || PROVIDER_ENV_MAP[providerKey || ''];
583
+ if (envMapping) {
584
+ await saveEnvValue(envMapping.api_key_env, api_key);
585
+ if (envMapping.base_url_env) {
586
+ await saveEnvValue(envMapping.base_url_env, base_url);
587
+ }
588
+ }
589
+ // Auto-switch model to the newly added provider
472
590
  const config2 = await readConfigYaml();
473
591
  if (typeof config2.model !== 'object' || config2.model === null) {
474
592
  config2.model = {};
@@ -476,6 +594,13 @@ exports.fsRoutes.post('/api/hermes/config/providers', async (ctx) => {
476
594
  config2.model.default = model;
477
595
  config2.model.provider = poolKey;
478
596
  await writeConfigYaml(config2);
597
+ // Restart gateway to pick up .env and config.yaml changes
598
+ try {
599
+ await hermesCli.restartGateway();
600
+ }
601
+ catch (e) {
602
+ console.error('[Provider] Gateway restart failed:', e.message);
603
+ }
479
604
  ctx.body = { success: true };
480
605
  }
481
606
  catch (err) {