hermes-web-ui 0.3.2 → 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 (66) hide show
  1. package/dist/client/assets/{Add-CYFVC6GK.js → Add-CIZmky1Q.js} +1 -1
  2. package/dist/client/assets/{Button-Bls2FkVh.js → Button-Cpfqx5bj.js} +1 -1
  3. package/dist/client/assets/{ChannelsView-hQeWN-ve.js → ChannelsView-CqgEMY6f.js} +1 -1
  4. package/dist/client/assets/{ChatView-C67INFi3.js → ChatView-o_KHRYai.js} +1 -1
  5. package/dist/client/assets/{Close-CkAmZwHA.js → Close-Dx71Ggz9.js} +1 -1
  6. package/dist/client/assets/{FormItem-CO8nEllS.js → FormItem-D0pABOgK.js} +1 -1
  7. package/dist/client/assets/{Input-CmTdqaW1.js → Input-DmGT4Mu3.js} +1 -1
  8. package/dist/client/assets/{InputNumber-BVqiFQ5W.js → InputNumber-CQVOZP6a.js} +1 -1
  9. package/dist/client/assets/{JobsView-B0LZ2Ep0.js → JobsView-BwxWBXo3.js} +2 -2
  10. package/dist/client/assets/{LoginView-BjqBXdDl.js → LoginView-C4Id2mSY.js} +1 -1
  11. package/dist/client/assets/{LogsView-CXiJ_mte.js → LogsView-H_gfkSSN.js} +1 -1
  12. package/dist/client/assets/{MarkdownRenderer-CtI0yse1.js → MarkdownRenderer-DJK1b6T2.js} +1 -1
  13. package/dist/client/assets/{MemoryView-CjPn1pVR.js → MemoryView-DO4Y2le5.js} +1 -1
  14. package/dist/client/assets/{Modal-apmj2qGK.js → Modal-fhZL63EZ.js} +1 -1
  15. package/dist/client/assets/ModelsView-9CSt8tCa.js +1 -0
  16. package/dist/client/assets/{ModelsView-BhNEZwC0.css → ModelsView-D1p_2trx.css} +1 -1
  17. package/dist/client/assets/{Popconfirm-B9XSMaYA.js → Popconfirm-CLvbZW3I.js} +1 -1
  18. package/dist/client/assets/{Popover-CjCb_fJl.js → Popover-Dwi5NArH.js} +1 -1
  19. package/dist/client/assets/{ProfilesView-CmMfNg5X.js → ProfilesView-R_u1mtsl.js} +1 -1
  20. package/dist/client/assets/{Scrollbar-v6Ok23iG.js → Scrollbar-zYdleApO.js} +1 -1
  21. package/dist/client/assets/{Select-CDF2CEro.js → Select-0jFWg4Lf.js} +1 -1
  22. package/dist/client/assets/{SettingRow-CSjwu__h.js → SettingRow-LTP_cMLl.js} +1 -1
  23. package/dist/client/assets/{SettingsView-BYedQhU5.js → SettingsView-5o2GfsQf.js} +1 -1
  24. package/dist/client/assets/{SkillsView-B_MA4How.js → SkillsView-Cez6jHIn.js} +1 -1
  25. package/dist/client/assets/{Spin-UqA8uGuf.js → Spin-CfKqXT5a.js} +1 -1
  26. package/dist/client/assets/{Suffix-iRME2DUp.js → Suffix-jKQO068E.js} +1 -1
  27. package/dist/client/assets/{Switch-B4za8e-x.js → Switch-tk7Pkw6L.js} +1 -1
  28. package/dist/client/assets/{TerminalView-C0xlWgsR.js → TerminalView-C-oFmyFh.js} +1 -1
  29. package/dist/client/assets/{Tooltip-C9kFaGpi.js → Tooltip-Dc4tc92D.js} +1 -1
  30. package/dist/client/assets/{UsageView-Df6TKjYG.js → UsageView-SSxSWAFO.js} +1 -1
  31. package/dist/client/assets/{Warning-C--exCXL.js → Warning-BPlnDrmx.js} +1 -1
  32. package/dist/client/assets/{_plugin-vue_export-helper-BPCSidQI.js → _plugin-vue_export-helper-CcX4e_Is.js} +1 -1
  33. package/dist/client/assets/{app-BEBfTlZP.js → app-CcLe99Xa.js} +1 -1
  34. package/dist/client/assets/app-ML9hRyDO.js +1 -0
  35. package/dist/client/assets/{browser-OtgUKGth.js → browser-BfJIjfY3.js} +1 -1
  36. package/dist/client/assets/{chat-B8_g1FhX.js → chat-CuVG21Hv.js} +2 -2
  37. package/dist/client/assets/composables-BTIEu8ZB.js +1 -0
  38. package/dist/client/assets/{fade-in-scale-up.cssr-BbYEGcju.js → fade-in-scale-up.cssr-e30I2JjN.js} +1 -1
  39. package/dist/client/assets/index-XufFb2mL.js +284 -0
  40. package/dist/client/assets/{jobs-BRLhuQpK.js → jobs-LBrAH2Mz.js} +1 -1
  41. package/dist/client/assets/{light-Bmu6b0jg.js → light-BF--Dz10.js} +1 -1
  42. package/dist/client/assets/{light-B3QvGN97.js → light-BJ4xZyd0.js} +1 -1
  43. package/dist/client/assets/{light-DDEUT7ek.js → light-BLbGAFtS.js} +1 -1
  44. package/dist/client/assets/{light-B83Z4vA9.js → light-CCZMDqco.js} +1 -1
  45. package/dist/client/assets/{light-BtLlDltU.js → light-CDuRPTUd.js} +1 -1
  46. package/dist/client/assets/{light-8G-BZ0JM.js → light-DTBcb4qq.js} +1 -1
  47. package/dist/client/assets/{pinia-F-uLzgMS.js → pinia-D2g49IcO.js} +1 -1
  48. package/dist/client/assets/{profiles-DIJT1P0i.js → profiles-DbCu_blp.js} +1 -1
  49. package/dist/client/assets/{router-Drg4uUER.js → router-0X4x3p8e.js} +2 -2
  50. package/dist/client/assets/{sessions-BwtYroiN.js → sessions-SzjWXR9A.js} +1 -1
  51. package/dist/client/assets/{skills-BLhNwqgu.js → skills-CsjvGVsY.js} +1 -1
  52. package/dist/client/assets/use-compitable-DY4l-k3U.js +1 -0
  53. package/dist/client/assets/{use-message-xhQoRXQV.js → use-message-4kASja7X.js} +1 -1
  54. package/dist/client/assets/{useTheme-Dd5YNF4O.js → useTheme-Dh7NTo_V.js} +1 -1
  55. package/dist/client/index.html +27 -27
  56. package/dist/server/routes/hermes/codex-auth.d.ts +2 -0
  57. package/dist/server/routes/hermes/codex-auth.js +302 -0
  58. package/dist/server/routes/hermes/filesystem.js +67 -13
  59. package/dist/server/routes/hermes/index.js +2 -0
  60. package/dist/server/shared/providers.js +62 -18
  61. package/package.json +1 -1
  62. package/dist/client/assets/ModelsView-DXvahVC_.js +0 -1
  63. package/dist/client/assets/app-DoAgSphx.js +0 -1
  64. package/dist/client/assets/composables-VZZHak9F.js +0 -1
  65. package/dist/client/assets/index-DY7SeRWD.js +0 -284
  66. package/dist/client/assets/use-compitable-BgSV79Sl.js +0 -1
@@ -1 +1 @@
1
- import{o as e}from"./router-Drg4uUER.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-Drg4uUER.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-Drg4uUER.js";import{$ as t,Y as n}from"./browser-OtgUKGth.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-Drg4uUER.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-DY7SeRWD.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/router-Drg4uUER.js">
11
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BPCSidQI.js">
12
- <link rel="modulepreload" crossorigin href="/assets/browser-OtgUKGth.js">
13
- <link rel="modulepreload" crossorigin href="/assets/Scrollbar-v6Ok23iG.js">
14
- <link rel="modulepreload" crossorigin href="/assets/use-compitable-BgSV79Sl.js">
15
- <link rel="modulepreload" crossorigin href="/assets/Popover-CjCb_fJl.js">
16
- <link rel="modulepreload" crossorigin href="/assets/Close-CkAmZwHA.js">
17
- <link rel="modulepreload" crossorigin href="/assets/Button-Bls2FkVh.js">
18
- <link rel="modulepreload" crossorigin href="/assets/Suffix-iRME2DUp.js">
19
- <link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-BbYEGcju.js">
20
- <link rel="modulepreload" crossorigin href="/assets/light-Bmu6b0jg.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-CDF2CEro.js">
23
- <link rel="modulepreload" crossorigin href="/assets/Warning-C--exCXL.js">
24
- <link rel="modulepreload" crossorigin href="/assets/Modal-apmj2qGK.js">
25
- <link rel="modulepreload" crossorigin href="/assets/pinia-F-uLzgMS.js">
26
- <link rel="modulepreload" crossorigin href="/assets/profiles-DIJT1P0i.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-BwtYroiN.js">
29
- <link rel="modulepreload" crossorigin href="/assets/app-BEBfTlZP.js">
30
- <link rel="modulepreload" crossorigin href="/assets/chat-B8_g1FhX.js">
31
- <link rel="modulepreload" crossorigin href="/assets/light-BtLlDltU.js">
32
- <link rel="modulepreload" crossorigin href="/assets/light-8G-BZ0JM.js">
33
- <link rel="modulepreload" crossorigin href="/assets/use-message-xhQoRXQV.js">
34
- <link rel="modulepreload" crossorigin href="/assets/light-B83Z4vA9.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-DDEUT7ek.js">
37
- <link rel="modulepreload" crossorigin href="/assets/light-B3QvGN97.js">
38
- <link rel="modulepreload" crossorigin href="/assets/useTheme-Dd5YNF4O.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>
@@ -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
+ });
@@ -48,7 +48,9 @@ const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
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();
@@ -507,7 +509,52 @@ exports.fsRoutes.get('/api/hermes/available-models', async (ctx) => {
507
509
  dedupedGroups.push(g);
508
510
  }
509
511
  }
510
- // Fallback: if no providers returned models, fall back to config.yaml parsing
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
511
558
  if (dedupedGroups.length === 0) {
512
559
  const fallback = buildModelGroups(config);
513
560
  ctx.body = fallback;
@@ -644,19 +691,26 @@ exports.fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) =>
644
691
  ctx.body = { error: 'Cannot delete the last provider' };
645
692
  return;
646
693
  }
694
+ // Case-insensitive key lookup: normalize poolKey to match credential_pool
695
+ let resolvedKey = poolKey;
647
696
  if (!(poolKey in auth.credential_pool)) {
648
- ctx.status = 404;
649
- ctx.body = { error: `Provider "${poolKey}" not found` };
650
- 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;
651
705
  }
652
706
  // Check if this is the current active provider
653
707
  const config = await readConfigYaml();
654
708
  const currentProvider = config.model?.provider;
655
- const isCurrent = currentProvider === poolKey;
709
+ const isCurrent = currentProvider === poolKey || currentProvider === resolvedKey;
656
710
  // Save base_url before deleting
657
- const deletedBaseUrl = auth.credential_pool[poolKey]?.[0]?.base_url;
711
+ const deletedBaseUrl = auth.credential_pool[resolvedKey]?.[0]?.base_url;
658
712
  // 1. Delete from auth.json
659
- delete auth.credential_pool[poolKey];
713
+ delete auth.credential_pool[resolvedKey];
660
714
  await saveAuthJson(auth);
661
715
  // 2. Remove matching entry from config.yaml custom_providers
662
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());