hermes-web-ui 0.3.1 → 0.3.3

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 (84) hide show
  1. package/dist/client/assets/{Add-cXqy3eJz.js → Add-CIZmky1Q.js} +1 -1
  2. package/dist/client/assets/{Button-BeWnBoDR.js → Button-Cpfqx5bj.js} +1 -1
  3. package/dist/client/assets/{ChannelsView-Do43S3tP.css → ChannelsView-CSo2o-P4.css} +1 -1
  4. package/dist/client/assets/ChannelsView-CqgEMY6f.js +1 -0
  5. package/dist/client/assets/{ChatView-CqA3Wo54.js → ChatView-o_KHRYai.js} +1 -1
  6. package/dist/client/assets/{Close-ySsuLJTC.js → Close-Dx71Ggz9.js} +1 -1
  7. package/dist/client/assets/{FormItem-EcbFnIOF.js → FormItem-D0pABOgK.js} +1 -1
  8. package/dist/client/assets/{Input-DP5LDvGa.js → Input-DmGT4Mu3.js} +1 -1
  9. package/dist/client/assets/{InputNumber-CU7XKbbt.js → InputNumber-CQVOZP6a.js} +1 -1
  10. package/dist/client/assets/JobsView-BwxWBXo3.js +2 -0
  11. package/dist/client/assets/{JobsView-CvuV9mZY.css → JobsView-CVx2Yv-y.css} +1 -1
  12. package/dist/client/assets/{LoginView-CDJXoOEZ.js → LoginView-C4Id2mSY.js} +1 -1
  13. package/dist/client/assets/{LogsView-DS_E9xkh.js → LogsView-H_gfkSSN.js} +1 -1
  14. package/dist/client/assets/{MarkdownRenderer-BntkPd0f.js → MarkdownRenderer-DJK1b6T2.js} +1 -1
  15. package/dist/client/assets/{MemoryView-CCCXW1tv.js → MemoryView-DO4Y2le5.js} +1 -1
  16. package/dist/client/assets/{Modal-D_vF9k8c.js → Modal-fhZL63EZ.js} +1 -1
  17. package/dist/client/assets/ModelsView-9CSt8tCa.js +1 -0
  18. package/dist/client/assets/{ModelsView-BhNEZwC0.css → ModelsView-D1p_2trx.css} +1 -1
  19. package/dist/client/assets/{Popconfirm-IKYJxM7n.js → Popconfirm-CLvbZW3I.js} +1 -1
  20. package/dist/client/assets/{Popover-BLBLYanQ.js → Popover-Dwi5NArH.js} +1 -1
  21. package/dist/client/assets/{ProfilesView-qt3NXito.js → ProfilesView-R_u1mtsl.js} +1 -1
  22. package/dist/client/assets/{Scrollbar-DZGif2x4.js → Scrollbar-zYdleApO.js} +1 -1
  23. package/dist/client/assets/{Select-3KS3TLPL.js → Select-0jFWg4Lf.js} +1 -1
  24. package/dist/client/assets/{SettingRow-D26AUezg.js → SettingRow-LTP_cMLl.js} +1 -1
  25. package/dist/client/assets/{SettingsView-30x1265A.js → SettingsView-5o2GfsQf.js} +2 -2
  26. package/dist/client/assets/{SettingsView-C78xbLXK.css → SettingsView-BIEQOPzq.css} +1 -1
  27. package/dist/client/assets/{SkillsView-qq8Oz7fo.js → SkillsView-Cez6jHIn.js} +1 -1
  28. package/dist/client/assets/{Spin-C9izy3tZ.js → Spin-CfKqXT5a.js} +1 -1
  29. package/dist/client/assets/{Suffix-CIX5CF-j.js → Suffix-jKQO068E.js} +1 -1
  30. package/dist/client/assets/{Switch-D9x35tOv.js → Switch-tk7Pkw6L.js} +1 -1
  31. package/dist/client/assets/{TerminalView-zaRNn2aX.js → TerminalView-C-oFmyFh.js} +1 -1
  32. package/dist/client/assets/{Tooltip-A5--U35v.js → Tooltip-Dc4tc92D.js} +1 -1
  33. package/dist/client/assets/{UsageView-DQ3t8CHE.js → UsageView-SSxSWAFO.js} +1 -1
  34. package/dist/client/assets/{Warning-BZfjWCrB.js → Warning-BPlnDrmx.js} +1 -1
  35. package/dist/client/assets/{_plugin-vue_export-helper-B4hqCVU_.js → _plugin-vue_export-helper-CcX4e_Is.js} +1 -1
  36. package/dist/client/assets/{app-DG_zFxqi.js → app-CcLe99Xa.js} +1 -1
  37. package/dist/client/assets/app-ML9hRyDO.js +1 -0
  38. package/dist/client/assets/{browser-BanYmQkE.js → browser-BfJIjfY3.js} +1 -1
  39. package/dist/client/assets/{chat-C6pq-bYQ.js → chat-CuVG21Hv.js} +2 -2
  40. package/dist/client/assets/composables-BTIEu8ZB.js +1 -0
  41. package/dist/client/assets/{fade-in-scale-up.cssr-B100njI2.js → fade-in-scale-up.cssr-e30I2JjN.js} +1 -1
  42. package/dist/client/assets/index-XufFb2mL.js +284 -0
  43. package/dist/client/assets/{jobs-DK_GVjqW.js → jobs-LBrAH2Mz.js} +1 -1
  44. package/dist/client/assets/{light-B8HxMQKs.js → light-BF--Dz10.js} +1 -1
  45. package/dist/client/assets/{light-BT2gqEar.js → light-BJ4xZyd0.js} +1 -1
  46. package/dist/client/assets/{light-CDN7aXQb.js → light-BLbGAFtS.js} +1 -1
  47. package/dist/client/assets/{light-Dav3NshO.js → light-CCZMDqco.js} +1 -1
  48. package/dist/client/assets/{light-BfPLur6t.js → light-CDuRPTUd.js} +1 -1
  49. package/dist/client/assets/{light-CeMZM90j.js → light-DTBcb4qq.js} +1 -1
  50. package/dist/client/assets/{pinia-DEKB7D30.js → pinia-D2g49IcO.js} +1 -1
  51. package/dist/client/assets/{profiles-BSAOLUpt.js → profiles-DbCu_blp.js} +1 -1
  52. package/dist/client/assets/{router-Zpu1Tghg.js → router-0X4x3p8e.js} +2 -2
  53. package/dist/client/assets/{sessions-1rFjfO8M.js → sessions-SzjWXR9A.js} +1 -1
  54. package/dist/client/assets/{skills-Bu5JdqX-.js → skills-CsjvGVsY.js} +1 -1
  55. package/dist/client/assets/use-compitable-DY4l-k3U.js +1 -0
  56. package/dist/client/assets/{use-message-CFWjMwGX.js → use-message-4kASja7X.js} +1 -1
  57. package/dist/client/assets/{useTheme-CHkzsMnk.js → useTheme-Dh7NTo_V.js} +1 -1
  58. package/dist/client/index.html +27 -27
  59. package/dist/server/index.js +2 -2
  60. package/dist/server/routes/hermes/codex-auth.d.ts +2 -0
  61. package/dist/server/routes/hermes/codex-auth.js +302 -0
  62. package/dist/server/routes/hermes/config.js +15 -3
  63. package/dist/server/routes/hermes/filesystem.js +92 -18
  64. package/dist/server/routes/hermes/index.js +2 -0
  65. package/dist/server/routes/hermes/logs.js +1 -1
  66. package/dist/server/routes/hermes/profiles.js +1 -1
  67. package/dist/server/routes/hermes/sessions.js +1 -1
  68. package/dist/server/routes/hermes/weixin.js +2 -2
  69. package/dist/server/routes/webhook.js +1 -1
  70. package/dist/server/services/hermes/hermes-cli.d.ts +125 -0
  71. package/dist/server/services/hermes/hermes-cli.js +488 -0
  72. package/dist/server/services/hermes/hermes-profile.d.ts +22 -0
  73. package/dist/server/services/hermes/hermes-profile.js +60 -0
  74. package/dist/server/services/hermes/hermes.d.ts +40 -0
  75. package/dist/server/services/hermes/hermes.js +118 -0
  76. package/dist/server/shared/providers.js +64 -20
  77. package/package.json +4 -1
  78. package/dist/client/assets/ChannelsView-tUAkDoSF.js +0 -1
  79. package/dist/client/assets/JobsView-g3zcs8_m.js +0 -2
  80. package/dist/client/assets/ModelsView-C8rPRq6p.js +0 -1
  81. package/dist/client/assets/app-DVQEsGHs.js +0 -1
  82. package/dist/client/assets/composables-xsQwqT-T.js +0 -1
  83. package/dist/client/assets/index-DtDTIaVj.js +0 -284
  84. package/dist/client/assets/use-compitable-Dl0AqAGv.js +0 -1
@@ -1 +1 @@
1
- import{o as e}from"./router-Zpu1Tghg.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-0X4x3p8e.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-Zpu1Tghg.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-0X4x3p8e.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-0X4x3p8e.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-Zpu1Tghg.js";import{$ as t,Y as n}from"./browser-BanYmQkE.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};
1
+ import{F as e}from"./router-0X4x3p8e.js";import{$ as t,Y as n}from"./browser-BfJIjfY3.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};
@@ -1 +1 @@
1
- import{X as e,ct as t}from"./router-Zpu1Tghg.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};
1
+ import{X as e,ct as t}from"./router-0X4x3p8e.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};
@@ -6,36 +6,36 @@
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-DtDTIaVj.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/router-Zpu1Tghg.js">
11
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-B4hqCVU_.js">
12
- <link rel="modulepreload" crossorigin href="/assets/browser-BanYmQkE.js">
13
- <link rel="modulepreload" crossorigin href="/assets/Scrollbar-DZGif2x4.js">
14
- <link rel="modulepreload" crossorigin href="/assets/use-compitable-Dl0AqAGv.js">
15
- <link rel="modulepreload" crossorigin href="/assets/Popover-BLBLYanQ.js">
16
- <link rel="modulepreload" crossorigin href="/assets/Close-ySsuLJTC.js">
17
- <link rel="modulepreload" crossorigin href="/assets/Button-BeWnBoDR.js">
18
- <link rel="modulepreload" crossorigin href="/assets/Suffix-CIX5CF-j.js">
19
- <link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-B100njI2.js">
20
- <link rel="modulepreload" crossorigin href="/assets/light-B8HxMQKs.js">
9
+ <script type="module" crossorigin src="/assets/index-XufFb2mL.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/router-0X4x3p8e.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-CcX4e_Is.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/browser-BfJIjfY3.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/Scrollbar-zYdleApO.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/use-compitable-DY4l-k3U.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/Popover-Dwi5NArH.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/Close-Dx71Ggz9.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/Button-Cpfqx5bj.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/Suffix-jKQO068E.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-e30I2JjN.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/light-BF--Dz10.js">
21
21
  <link rel="modulepreload" crossorigin href="/assets/create-5zWq3BEB.js">
22
- <link rel="modulepreload" crossorigin href="/assets/Select-3KS3TLPL.js">
23
- <link rel="modulepreload" crossorigin href="/assets/Warning-BZfjWCrB.js">
24
- <link rel="modulepreload" crossorigin href="/assets/Modal-D_vF9k8c.js">
25
- <link rel="modulepreload" crossorigin href="/assets/pinia-DEKB7D30.js">
26
- <link rel="modulepreload" crossorigin href="/assets/profiles-BSAOLUpt.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/Select-0jFWg4Lf.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/Warning-BPlnDrmx.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/Modal-fhZL63EZ.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/pinia-D2g49IcO.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/profiles-DbCu_blp.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/omit-1BRB6K75.js">
28
- <link rel="modulepreload" crossorigin href="/assets/sessions-1rFjfO8M.js">
29
- <link rel="modulepreload" crossorigin href="/assets/app-DG_zFxqi.js">
30
- <link rel="modulepreload" crossorigin href="/assets/chat-C6pq-bYQ.js">
31
- <link rel="modulepreload" crossorigin href="/assets/light-BfPLur6t.js">
32
- <link rel="modulepreload" crossorigin href="/assets/light-CeMZM90j.js">
33
- <link rel="modulepreload" crossorigin href="/assets/use-message-CFWjMwGX.js">
34
- <link rel="modulepreload" crossorigin href="/assets/light-Dav3NshO.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/sessions-SzjWXR9A.js">
29
+ <link rel="modulepreload" crossorigin href="/assets/app-CcLe99Xa.js">
30
+ <link rel="modulepreload" crossorigin href="/assets/chat-CuVG21Hv.js">
31
+ <link rel="modulepreload" crossorigin href="/assets/light-CDuRPTUd.js">
32
+ <link rel="modulepreload" crossorigin href="/assets/light-DTBcb4qq.js">
33
+ <link rel="modulepreload" crossorigin href="/assets/use-message-4kASja7X.js">
34
+ <link rel="modulepreload" crossorigin href="/assets/light-CCZMDqco.js">
35
35
  <link rel="modulepreload" crossorigin href="/assets/_common-DgdkN_d5.js">
36
- <link rel="modulepreload" crossorigin href="/assets/light-CDN7aXQb.js">
37
- <link rel="modulepreload" crossorigin href="/assets/light-BT2gqEar.js">
38
- <link rel="modulepreload" crossorigin href="/assets/useTheme-CHkzsMnk.js">
36
+ <link rel="modulepreload" crossorigin href="/assets/light-BLbGAFtS.js">
37
+ <link rel="modulepreload" crossorigin href="/assets/light-BJ4xZyd0.js">
38
+ <link rel="modulepreload" crossorigin href="/assets/useTheme-Dh7NTo_V.js">
39
39
  <link rel="modulepreload" crossorigin href="/assets/logo-Cd-t_oGE.js">
40
40
  <link rel="stylesheet" crossorigin href="/assets/index-JeutuTe0.css">
41
41
  </head>
@@ -49,7 +49,7 @@ const config_1 = require("./config");
49
49
  const hermes_1 = require("./routes/hermes");
50
50
  const upload_1 = require("./routes/upload");
51
51
  const webhook_1 = require("./routes/webhook");
52
- const hermesCli = __importStar(require("./services/hermes-cli"));
52
+ const hermesCli = __importStar(require("./services/hermes/hermes-cli"));
53
53
  const auth_1 = require("./services/auth");
54
54
  function getLocalVersion() {
55
55
  // production: dist/server → ../../package.json
@@ -241,7 +241,7 @@ function bindShutdown() {
241
241
  async function ensureApiServerConfig() {
242
242
  const { readFileSync, writeFileSync, existsSync, copyFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
243
243
  const yaml = (await Promise.resolve().then(() => __importStar(require('js-yaml')))).default;
244
- const { getActiveConfigPath } = await Promise.resolve().then(() => __importStar(require('./services/hermes-profile')));
244
+ const { getActiveConfigPath } = await Promise.resolve().then(() => __importStar(require('./services/hermes/hermes-profile')));
245
245
  const configPath = getActiveConfigPath();
246
246
  const defaults = {
247
247
  enabled: true,
@@ -0,0 +1,2 @@
1
+ import Router from '@koa/router';
2
+ export declare const codexAuthRoutes: Router<import("koa").DefaultState, import("koa").DefaultContext>;
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.codexAuthRoutes = void 0;
7
+ const router_1 = __importDefault(require("@koa/router"));
8
+ const crypto_1 = require("crypto");
9
+ const path_1 = require("path");
10
+ const os_1 = require("os");
11
+ const fs_1 = require("fs");
12
+ const hermes_profile_1 = require("../../services/hermes/hermes-profile");
13
+ // --- OAuth Constants ---
14
+ const CODEX_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
15
+ const CODEX_DEVICE_AUTH_URL = 'https://auth.openai.com/api/accounts/deviceauth/usercode';
16
+ const CODEX_DEVICE_TOKEN_URL = 'https://auth.openai.com/api/accounts/deviceauth/token';
17
+ const CODEX_OAUTH_TOKEN_URL = 'https://auth.openai.com/oauth/token';
18
+ const CODEX_DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
19
+ const CODEX_REDIRECT_URI = 'https://auth.openai.com/deviceauth/callback';
20
+ const CODEX_VERIFICATION_URL = 'https://auth.openai.com/codex/device';
21
+ const CODEX_HOME = (0, path_1.join)((0, os_1.homedir)(), '.codex');
22
+ const POLL_MAX_DURATION = 15 * 60 * 1000; // 15 minutes
23
+ const POLL_DEFAULT_INTERVAL = 5000; // 5 seconds
24
+ const sessions = new Map();
25
+ function cleanupExpiredSessions() {
26
+ const now = Date.now();
27
+ sessions.forEach((session, id) => {
28
+ if (now - session.createdAt > POLL_MAX_DURATION + 60000) {
29
+ sessions.delete(id);
30
+ }
31
+ });
32
+ }
33
+ function loadAuthJson(authPath) {
34
+ try {
35
+ const raw = (0, fs_1.readFileSync)(authPath, 'utf-8');
36
+ return JSON.parse(raw);
37
+ }
38
+ catch {
39
+ return { version: 1 };
40
+ }
41
+ }
42
+ function saveAuthJson(authPath, data) {
43
+ data.updated_at = new Date().toISOString();
44
+ const dir = authPath.substring(0, authPath.lastIndexOf('/'));
45
+ if (!(0, fs_1.existsSync)(dir))
46
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
47
+ (0, fs_1.writeFileSync)(authPath, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
48
+ }
49
+ function saveCodexCliTokens(accessToken, refreshToken) {
50
+ const codexHome = process.env.CODEX_HOME || CODEX_HOME;
51
+ const codexAuthPath = (0, path_1.join)(codexHome, 'auth.json');
52
+ const dir = codexAuthPath.substring(0, codexAuthPath.lastIndexOf('/'));
53
+ if (!(0, fs_1.existsSync)(dir))
54
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
55
+ const data = {
56
+ tokens: { access_token: accessToken, refresh_token: refreshToken },
57
+ last_refresh: new Date().toISOString(),
58
+ };
59
+ (0, fs_1.writeFileSync)(codexAuthPath, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
60
+ }
61
+ function decodeJwtExp(token) {
62
+ try {
63
+ const parts = token.split('.');
64
+ if (parts.length !== 3)
65
+ return null;
66
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
67
+ const claims = JSON.parse(payload);
68
+ return typeof claims.exp === 'number' ? claims.exp : null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ // --- Background login worker ---
75
+ async function codexLoginWorker(session, authPath) {
76
+ const startTime = Date.now();
77
+ const interval = POLL_DEFAULT_INTERVAL;
78
+ while (Date.now() - startTime < POLL_MAX_DURATION) {
79
+ await new Promise(resolve => setTimeout(resolve, interval));
80
+ if (session.status !== 'pending')
81
+ return;
82
+ try {
83
+ // Step 3: Poll for authorization
84
+ const pollRes = await fetch(CODEX_DEVICE_TOKEN_URL, {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({
88
+ device_auth_id: session.deviceAuthId,
89
+ user_code: session.userCode,
90
+ }),
91
+ signal: AbortSignal.timeout(10000),
92
+ });
93
+ if (pollRes.status === 200) {
94
+ const pollData = await pollRes.json();
95
+ // Step 4: Exchange authorization code for tokens
96
+ const tokenRes = await fetch(CODEX_OAUTH_TOKEN_URL, {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
99
+ body: new URLSearchParams({
100
+ grant_type: 'authorization_code',
101
+ code: pollData.authorization_code,
102
+ redirect_uri: CODEX_REDIRECT_URI,
103
+ client_id: CODEX_CLIENT_ID,
104
+ code_verifier: pollData.code_verifier,
105
+ }).toString(),
106
+ signal: AbortSignal.timeout(15000),
107
+ });
108
+ if (!tokenRes.ok) {
109
+ const errText = await tokenRes.text();
110
+ console.error('[Codex Auth] Token exchange failed:', tokenRes.status, errText);
111
+ session.status = 'error';
112
+ session.error = `Token exchange failed: ${tokenRes.status}`;
113
+ return;
114
+ }
115
+ const tokenData = await tokenRes.json();
116
+ const refreshToken = tokenData.refresh_token || '';
117
+ session.accessToken = tokenData.access_token;
118
+ session.refreshToken = refreshToken;
119
+ session.status = 'approved';
120
+ // Save to auth.json
121
+ const auth = loadAuthJson(authPath);
122
+ if (!auth.providers)
123
+ auth.providers = {};
124
+ auth.providers['openai-codex'] = {
125
+ tokens: {
126
+ access_token: tokenData.access_token,
127
+ refresh_token: refreshToken,
128
+ },
129
+ last_refresh: new Date().toISOString(),
130
+ auth_mode: 'chatgpt',
131
+ };
132
+ // Add to credential pool
133
+ if (!auth.credential_pool)
134
+ auth.credential_pool = {};
135
+ auth.credential_pool['openai-codex'] = [{
136
+ id: `openai-codex-${Date.now()}`,
137
+ label: 'OpenAI Codex',
138
+ base_url: CODEX_DEFAULT_BASE_URL,
139
+ access_token: tokenData.access_token,
140
+ last_status: null,
141
+ }];
142
+ saveAuthJson(authPath, auth);
143
+ // Save to ~/.codex/auth.json for CLI sync
144
+ saveCodexCliTokens(tokenData.access_token, refreshToken);
145
+ console.log('[Codex Auth] Login successful');
146
+ return;
147
+ }
148
+ if (pollRes.status === 403 || pollRes.status === 404) {
149
+ // Not yet authorized, keep polling
150
+ continue;
151
+ }
152
+ // Other error status
153
+ console.error('[Codex Auth] Poll failed:', pollRes.status);
154
+ session.status = 'error';
155
+ session.error = `Poll failed: ${pollRes.status}`;
156
+ return;
157
+ }
158
+ catch (err) {
159
+ if (err.name === 'TimeoutError' || err.name === 'AbortError') {
160
+ continue;
161
+ }
162
+ console.error('[Codex Auth] Poll error:', err.message);
163
+ session.status = 'error';
164
+ session.error = err.message;
165
+ return;
166
+ }
167
+ }
168
+ // Timeout
169
+ session.status = 'expired';
170
+ }
171
+ // --- Routes ---
172
+ exports.codexAuthRoutes = new router_1.default();
173
+ exports.codexAuthRoutes.post('/api/hermes/auth/codex/start', async (ctx) => {
174
+ try {
175
+ cleanupExpiredSessions();
176
+ // Step 1: Request device code
177
+ const res = await fetch(CODEX_DEVICE_AUTH_URL, {
178
+ method: 'POST',
179
+ headers: {
180
+ 'Content-Type': 'application/json',
181
+ 'Accept': 'application/json',
182
+ 'User-Agent': 'node-fetch',
183
+ },
184
+ body: JSON.stringify({ client_id: CODEX_CLIENT_ID }),
185
+ signal: AbortSignal.timeout(10000),
186
+ });
187
+ if (!res.ok) {
188
+ let errorBody = null;
189
+ try {
190
+ errorBody = await res.json();
191
+ }
192
+ catch { /* ignore */ }
193
+ console.error(`[codex-auth] Device code request failed: ${res.status}`, errorBody);
194
+ let errorMessage = `Device code request failed: ${res.status}`;
195
+ if (errorBody?.error?.code === 'unsupported_country_region_territory') {
196
+ errorMessage = 'OpenAI does not support your region. You may need to use a proxy or VPN to access Codex.';
197
+ }
198
+ ctx.status = 502;
199
+ ctx.body = { error: errorMessage, code: errorBody?.error?.code };
200
+ return;
201
+ }
202
+ const data = await res.json();
203
+ const sessionId = (0, crypto_1.randomUUID)();
204
+ const session = {
205
+ id: sessionId,
206
+ userCode: data.user_code,
207
+ deviceAuthId: data.device_auth_id,
208
+ status: 'pending',
209
+ createdAt: Date.now(),
210
+ };
211
+ sessions.set(sessionId, session);
212
+ // Start background worker
213
+ const authPath = (0, hermes_profile_1.getActiveAuthPath)();
214
+ codexLoginWorker(session, authPath).catch(err => {
215
+ console.error('[Codex Auth] Worker error:', err);
216
+ session.status = 'error';
217
+ session.error = err.message;
218
+ });
219
+ ctx.body = {
220
+ session_id: sessionId,
221
+ user_code: data.user_code,
222
+ verification_url: CODEX_VERIFICATION_URL,
223
+ expires_in: 900, // 15 minutes
224
+ };
225
+ }
226
+ catch (err) {
227
+ ctx.status = 500;
228
+ ctx.body = { error: err.message };
229
+ }
230
+ });
231
+ exports.codexAuthRoutes.get('/api/hermes/auth/codex/poll/:sessionId', async (ctx) => {
232
+ const session = sessions.get(ctx.params.sessionId);
233
+ if (!session) {
234
+ ctx.status = 404;
235
+ ctx.body = { error: 'Session not found' };
236
+ return;
237
+ }
238
+ ctx.body = {
239
+ status: session.status,
240
+ error: session.error || null,
241
+ };
242
+ });
243
+ exports.codexAuthRoutes.get('/api/hermes/auth/codex/status', async (ctx) => {
244
+ try {
245
+ const authPath = (0, hermes_profile_1.getActiveAuthPath)();
246
+ const auth = loadAuthJson(authPath);
247
+ const tokens = auth.providers?.['openai-codex']?.tokens;
248
+ if (!tokens?.access_token || !auth.providers) {
249
+ ctx.body = { authenticated: false };
250
+ return;
251
+ }
252
+ const codexProvider = auth.providers['openai-codex'];
253
+ // Check if token is expired
254
+ const exp = decodeJwtExp(tokens.access_token);
255
+ if (exp && exp <= Date.now() / 1000 + 120) {
256
+ // Try refresh
257
+ if (tokens.refresh_token) {
258
+ try {
259
+ const refreshRes = await fetch(CODEX_OAUTH_TOKEN_URL, {
260
+ method: 'POST',
261
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
262
+ body: new URLSearchParams({
263
+ grant_type: 'refresh_token',
264
+ refresh_token: tokens.refresh_token,
265
+ client_id: CODEX_CLIENT_ID,
266
+ }).toString(),
267
+ signal: AbortSignal.timeout(15000),
268
+ });
269
+ if (refreshRes.ok) {
270
+ const newTokens = await refreshRes.json();
271
+ codexProvider.tokens.access_token = newTokens.access_token;
272
+ if (newTokens.refresh_token) {
273
+ codexProvider.tokens.refresh_token = newTokens.refresh_token;
274
+ }
275
+ codexProvider.last_refresh = new Date().toISOString();
276
+ saveAuthJson(authPath, auth);
277
+ saveCodexCliTokens(newTokens.access_token, newTokens.refresh_token || tokens.refresh_token);
278
+ // Update credential pool too
279
+ if (auth.credential_pool?.['openai-codex']?.[0]) {
280
+ auth.credential_pool['openai-codex'][0].access_token = newTokens.access_token;
281
+ saveAuthJson(authPath, auth);
282
+ }
283
+ ctx.body = { authenticated: true, last_refresh: codexProvider.last_refresh };
284
+ return;
285
+ }
286
+ }
287
+ catch {
288
+ // Refresh failed
289
+ }
290
+ }
291
+ ctx.body = { authenticated: false };
292
+ return;
293
+ }
294
+ ctx.body = {
295
+ authenticated: true,
296
+ last_refresh: codexProvider.last_refresh,
297
+ };
298
+ }
299
+ catch {
300
+ ctx.body = { authenticated: false };
301
+ }
302
+ });
@@ -8,8 +8,8 @@ const router_1 = __importDefault(require("@koa/router"));
8
8
  const promises_1 = require("fs/promises");
9
9
  const promises_2 = require("fs/promises");
10
10
  const js_yaml_1 = __importDefault(require("js-yaml"));
11
- const hermes_cli_1 = require("../../services/hermes-cli");
12
- const hermes_profile_1 = require("../../services/hermes-profile");
11
+ const hermes_cli_1 = require("../../services/hermes/hermes-cli");
12
+ const hermes_profile_1 = require("../../services/hermes/hermes-profile");
13
13
  // Platform sections that require gateway restart after config change
14
14
  const PLATFORM_SECTIONS = new Set([
15
15
  'telegram', 'discord', 'slack', 'whatsapp', 'matrix',
@@ -82,6 +82,18 @@ function getNested(obj, path) {
82
82
  }
83
83
  return cur;
84
84
  }
85
+ function deepMerge(target, source) {
86
+ for (const key of Object.keys(source)) {
87
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&
88
+ target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
89
+ target[key] = deepMerge(target[key], source[key]);
90
+ }
91
+ else {
92
+ target[key] = source[key];
93
+ }
94
+ }
95
+ return target;
96
+ }
85
97
  async function readEnvPlatforms() {
86
98
  try {
87
99
  const raw = await (0, promises_1.readFile)(envPath(), 'utf-8');
@@ -218,7 +230,7 @@ exports.configRoutes.put('/api/hermes/config', async (ctx) => {
218
230
  }
219
231
  try {
220
232
  const config = await readConfig();
221
- config[section] = { ...(config[section] || {}), ...values };
233
+ config[section] = deepMerge(config[section] || {}, values);
222
234
  await writeConfig(config);
223
235
  // Restart gateway for platform/channel config changes
224
236
  if (PLATFORM_SECTIONS.has(section)) {
@@ -41,14 +41,16 @@ const router_1 = __importDefault(require("@koa/router"));
41
41
  const promises_1 = require("fs/promises");
42
42
  const path_1 = require("path");
43
43
  const js_yaml_1 = __importDefault(require("js-yaml"));
44
- const hermes_profile_1 = require("../../services/hermes-profile");
45
- const hermesCli = __importStar(require("../../services/hermes-cli"));
44
+ const hermes_profile_1 = require("../../services/hermes/hermes-profile");
45
+ const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
46
46
  // --- Provider env var mapping (from hermes providers.py HERMES_OVERLAYS + config.py) ---
47
47
  // Maps provider key → { api_key_envs: all env var aliases for API key, base_url_env: env var for base URL }
48
48
  const PROVIDER_ENV_MAP = {
49
49
  openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: 'OPENROUTER_BASE_URL' },
50
50
  zai: { api_key_env: 'ZAI_API_KEY', base_url_env: '' },
51
- 'kimi-for-coding': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
51
+ 'kimi-coding': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
52
+ 'kimi-coding-cn': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
53
+ moonshot: { api_key_env: 'MOONSHOT_API_KEY', base_url_env: 'MOONSHOT_BASE_URL' },
52
54
  minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' },
53
55
  'minimax-cn': { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' },
54
56
  deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
@@ -57,11 +59,13 @@ const PROVIDER_ENV_MAP = {
57
59
  xai: { api_key_env: 'XAI_API_KEY', base_url_env: 'XAI_BASE_URL' },
58
60
  xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: 'XIAOMI_BASE_URL' },
59
61
  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' },
62
+ kilocode: { api_key_env: 'KILO_API_KEY', base_url_env: 'KILOCODE_BASE_URL' },
63
+ 'ai-gateway': { api_key_env: 'AI_GATEWAY_API_KEY', base_url_env: '' },
64
+ 'opencode-zen': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_ZEN_BASE_URL' },
63
65
  'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_GO_BASE_URL' },
64
66
  huggingface: { api_key_env: 'HF_TOKEN', base_url_env: 'HF_BASE_URL' },
67
+ arcee: { api_key_env: 'ARCEE_API_KEY', base_url_env: '' },
68
+ 'openai-codex': { api_key_env: '', base_url_env: 'HERMES_CODEX_BASE_URL' },
65
69
  };
66
70
  async function saveEnvValue(key, value) {
67
71
  const envPath = (0, hermes_profile_1.getActiveEnvPath)();
@@ -390,14 +394,12 @@ exports.fsRoutes.post('/api/hermes/memory', async (ctx) => {
390
394
  // Build model list from user's actual config.yaml using js-yaml
391
395
  function buildModelGroups(config) {
392
396
  let defaultModel = '';
393
- let defaultProvider = '';
394
397
  const groups = [];
395
398
  const allModelIds = new Set();
396
399
  // 1. Extract current model
397
400
  const modelSection = config.model;
398
401
  if (typeof modelSection === 'object' && modelSection !== null) {
399
402
  defaultModel = String(modelSection.default || '').trim();
400
- defaultProvider = String(modelSection.provider || '').trim();
401
403
  }
402
404
  else if (typeof modelSection === 'string') {
403
405
  defaultModel = modelSection.trim();
@@ -480,20 +482,85 @@ exports.fsRoutes.get('/api/hermes/available-models', async (ctx) => {
480
482
  for (const result of results) {
481
483
  if (result.status === 'fulfilled' && result.value.models.length > 0) {
482
484
  const { key, label, base_url, models } = result.value;
483
- groups.push({ provider: key, label, base_url, models });
485
+ groups.push({ provider: key, label, base_url, models: Array.from(new Set(models)) });
484
486
  }
485
487
  else if (result.status === 'rejected') {
486
488
  console.error(`[available-models] Failed: ${result.reason?.message || result.reason}`);
487
489
  }
488
490
  }
489
491
  }
490
- // Fallback: if no providers returned models, fall back to config.yaml parsing
491
- if (groups.length === 0) {
492
+ // Deduplicate models within each group and merge groups with the same provider key
493
+ const dedupedGroups = [];
494
+ const seenProviders = new Map();
495
+ for (const g of groups) {
496
+ g.models = Array.from(new Set(g.models));
497
+ const existingIdx = seenProviders.get(g.provider);
498
+ if (existingIdx !== undefined) {
499
+ // Merge models into existing group
500
+ const existing = dedupedGroups[existingIdx];
501
+ const existingSet = new Set(existing.models);
502
+ for (const m of g.models) {
503
+ if (!existingSet.has(m))
504
+ existing.models.push(m);
505
+ }
506
+ }
507
+ else {
508
+ seenProviders.set(g.provider, dedupedGroups.length);
509
+ dedupedGroups.push(g);
510
+ }
511
+ }
512
+ // Merge custom_providers from config.yaml (ensures manually-input model names appear)
513
+ const customProviders = Array.isArray(config.custom_providers)
514
+ ? config.custom_providers
515
+ : [];
516
+ for (const cp of customProviders) {
517
+ if (!cp.base_url || !cp.model)
518
+ continue;
519
+ const baseUrl = cp.base_url.replace(/\/+$/, '');
520
+ // Check if we already have a group for this base_url
521
+ const existing = dedupedGroups.find(g => g.base_url.replace(/\/+$/, '') === baseUrl);
522
+ if (existing) {
523
+ if (!existing.models.includes(cp.model)) {
524
+ existing.models.push(cp.model);
525
+ }
526
+ }
527
+ else {
528
+ dedupedGroups.push({
529
+ provider: `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`,
530
+ label: cp.name,
531
+ base_url: baseUrl,
532
+ models: [cp.model],
533
+ });
534
+ }
535
+ }
536
+ // Ensure config's current default model appears in the model list
537
+ if (currentDefault) {
538
+ const currentProvider = typeof config.model === 'object' ? String(config.model.provider || '').trim() : '';
539
+ if (currentProvider) {
540
+ const targetGroup = dedupedGroups.find(g => g.provider === currentProvider);
541
+ if (targetGroup && !targetGroup.models.includes(currentDefault)) {
542
+ targetGroup.models.unshift(currentDefault);
543
+ }
544
+ }
545
+ else {
546
+ // No provider specified — add to the first group that matches via base_url
547
+ // or just prepend to all groups
548
+ let found = false;
549
+ for (const g of dedupedGroups) {
550
+ if (!found && !g.models.includes(currentDefault)) {
551
+ g.models.unshift(currentDefault);
552
+ found = true;
553
+ }
554
+ }
555
+ }
556
+ }
557
+ // Fallback: if still no providers, fall back to config.yaml parsing
558
+ if (dedupedGroups.length === 0) {
492
559
  const fallback = buildModelGroups(config);
493
560
  ctx.body = fallback;
494
561
  return;
495
562
  }
496
- ctx.body = { default: currentDefault, groups };
563
+ ctx.body = { default: currentDefault, groups: dedupedGroups };
497
564
  }
498
565
  catch (err) {
499
566
  ctx.status = 500;
@@ -624,19 +691,26 @@ exports.fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) =>
624
691
  ctx.body = { error: 'Cannot delete the last provider' };
625
692
  return;
626
693
  }
694
+ // Case-insensitive key lookup: normalize poolKey to match credential_pool
695
+ let resolvedKey = poolKey;
627
696
  if (!(poolKey in auth.credential_pool)) {
628
- ctx.status = 404;
629
- ctx.body = { error: `Provider "${poolKey}" not found` };
630
- return;
697
+ const normalized = poolKey.toLowerCase();
698
+ const match = Object.keys(auth.credential_pool).find(k => k.toLowerCase() === normalized);
699
+ if (!match) {
700
+ ctx.status = 404;
701
+ ctx.body = { error: `Provider "${poolKey}" not found` };
702
+ return;
703
+ }
704
+ resolvedKey = match;
631
705
  }
632
706
  // Check if this is the current active provider
633
707
  const config = await readConfigYaml();
634
708
  const currentProvider = config.model?.provider;
635
- const isCurrent = currentProvider === poolKey;
709
+ const isCurrent = currentProvider === poolKey || currentProvider === resolvedKey;
636
710
  // Save base_url before deleting
637
- const deletedBaseUrl = auth.credential_pool[poolKey]?.[0]?.base_url;
711
+ const deletedBaseUrl = auth.credential_pool[resolvedKey]?.[0]?.base_url;
638
712
  // 1. Delete from auth.json
639
- delete auth.credential_pool[poolKey];
713
+ delete auth.credential_pool[resolvedKey];
640
714
  await saveAuthJson(auth);
641
715
  // 2. Remove matching entry from config.yaml custom_providers
642
716
  if (deletedBaseUrl && Array.isArray(config.custom_providers)) {
@@ -11,6 +11,7 @@ const config_1 = require("./config");
11
11
  const filesystem_1 = require("./filesystem");
12
12
  const logs_1 = require("./logs");
13
13
  const weixin_1 = require("./weixin");
14
+ const codex_auth_1 = require("./codex-auth");
14
15
  const proxy_1 = require("./proxy");
15
16
  Object.defineProperty(exports, "proxyMiddleware", { enumerable: true, get: function () { return proxy_1.proxyMiddleware; } });
16
17
  const terminal_1 = require("./terminal");
@@ -22,4 +23,5 @@ exports.hermesRoutes.use(config_1.configRoutes.routes());
22
23
  exports.hermesRoutes.use(filesystem_1.fsRoutes.routes());
23
24
  exports.hermesRoutes.use(logs_1.logRoutes.routes());
24
25
  exports.hermesRoutes.use(weixin_1.weixinRoutes.routes());
26
+ exports.hermesRoutes.use(codex_auth_1.codexAuthRoutes.routes());
25
27
  exports.hermesRoutes.use(proxy_1.proxyRoutes.routes());