hermes-web-ui 0.4.2 → 0.4.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 (97) hide show
  1. package/bin/hermes-web-ui.mjs +76 -9
  2. package/dist/client/assets/Add-DiTv65Se.js +1 -0
  3. package/dist/client/assets/{Button-pgPN7RfC.js → Button-vKfgky-Y.js} +3 -3
  4. package/dist/client/assets/{ChannelsView-qFMvJ9rS.js → ChannelsView-CHWtzAkF.js} +1 -1
  5. package/dist/client/assets/ChatView--lvFbW3I.js +127 -0
  6. package/dist/client/assets/ChatView-b0_L3ucp.css +1 -0
  7. package/dist/client/assets/{Close-CXC76d7R.js → Close-Brba3ay5.js} +2 -2
  8. package/dist/client/assets/{FormItem-DIcSMjKz.js → FormItem-kXuCeSKq.js} +3 -3
  9. package/dist/client/assets/GatewaysView-DUPP1UyD.js +1 -0
  10. package/dist/client/assets/Input-BS71ZaoJ.js +234 -0
  11. package/dist/client/assets/{InputNumber-Btyn8bQo.js → InputNumber-BXy4reTI.js} +2 -2
  12. package/dist/client/assets/JobsView-CXa1h0NF.js +2 -0
  13. package/dist/client/assets/LoginView-Cpod3SAL.js +1 -0
  14. package/dist/client/assets/LoginView-DfWYTzX1.css +1 -0
  15. package/dist/client/assets/LogsView-CgxeVMn-.js +1 -0
  16. package/dist/client/assets/{MarkdownRenderer-G4nly1xR.js → MarkdownRenderer-B7t0ZEw0.js} +9 -9
  17. package/dist/client/assets/MemoryView-BQjYVDJc.js +7 -0
  18. package/dist/client/assets/{Modal-rKb1tfQv.js → Modal-6Bv1v4kO.js} +4 -4
  19. package/dist/client/assets/ModelsView-CVghPzgk.js +1 -0
  20. package/dist/client/assets/Popconfirm-Bpz_dC1j.js +16 -0
  21. package/dist/client/assets/Popover-CXrjClQw.js +117 -0
  22. package/dist/client/assets/ProfilesView-Brpyzl-Q.js +440 -0
  23. package/dist/client/assets/{Select-CpNY3UGb.js → Select-Z20Mp4nQ.js} +5 -5
  24. package/dist/client/assets/SettingRow-Cz749k-e.js +1 -0
  25. package/dist/client/assets/SettingsView-C3HbKb4w.js +352 -0
  26. package/dist/client/assets/{SettingsView-CX312EGG.css → SettingsView-Cw4iOwXj.css} +1 -1
  27. package/dist/client/assets/SkillsView-qVZkbI37.js +1 -0
  28. package/dist/client/assets/Spin-DKwUVt1F.js +43 -0
  29. package/dist/client/assets/Suffix-C1Mw6UwH.js +90 -0
  30. package/dist/client/assets/{Switch-haCJAcVn.js → Switch-DG-3EwdR.js} +2 -2
  31. package/dist/client/assets/{Tag-CKzgX6hi.js → Tag-BWD1u63m.js} +2 -2
  32. package/dist/client/assets/{TerminalView-CmSON012.js → TerminalView-DTR8bNOy.js} +6 -6
  33. package/dist/client/assets/Tooltip-Dka1-DNs.js +1 -0
  34. package/dist/client/assets/UsageView-elTUQCED.js +1 -0
  35. package/dist/client/assets/{Warning-B2AD2ulw.js → Warning-CCZES7el.js} +1 -1
  36. package/dist/client/assets/{_plugin-vue_export-helper-DXZ6IauR.js → _plugin-vue_export-helper-Bl_Pm_OI.js} +2 -2
  37. package/dist/client/assets/app-BMD_VOYs.js +1 -0
  38. package/dist/client/assets/{app-Du5RZCLa.js → app-y8cD3FPg.js} +1 -1
  39. package/dist/client/assets/auth-BPBv1nNQ.js +1 -0
  40. package/dist/client/assets/browser-DlPqZ-4M.js +47 -0
  41. package/dist/client/assets/chat-3-o9JAHE.js +6 -0
  42. package/dist/client/assets/composables-CoSqDggz.js +1 -0
  43. package/dist/client/assets/{fade-in.cssr-CkQxIGt5.js → fade-in.cssr-D0suZaLL.js} +2 -2
  44. package/dist/client/assets/index-BRXOdlZt.css +1 -0
  45. package/dist/client/assets/index-CeFFa_Wg.js +284 -0
  46. package/dist/client/assets/{jobs-BnXwbdOs.js → jobs-DMiCP2Li.js} +1 -1
  47. package/dist/client/assets/light-BNuYwJcs.js +1 -0
  48. package/dist/client/assets/{light-BZfVIZ0W.js → light-DLz3o2il.js} +1 -1
  49. package/dist/client/assets/{light-Bdfae2y3.js → light-DbUQYfY2.js} +1 -1
  50. package/dist/client/assets/{light-Jn66LOmu.js → light-DcGe0-g1.js} +1 -1
  51. package/dist/client/assets/{light-Dgzhn0jz.js → light-cIgLWDVk.js} +1 -1
  52. package/dist/client/assets/{models-mCQyOPuV.js → models-EKNn3-zC.js} +1 -1
  53. package/dist/client/assets/{pinia-Jb4QOAzE.js → pinia-C8BDM-Fc.js} +1 -1
  54. package/dist/client/assets/profiles-1vZTTlBc.js +1 -0
  55. package/dist/client/assets/{router-qWVkGgap.js → router-kR4kqS2l.js} +2 -2
  56. package/dist/client/assets/{session-browser-prefs-Bh1and1f.js → session-browser-prefs-BI-rZu2N.js} +1 -1
  57. package/dist/client/assets/sessions-CJI89wYu.js +1 -0
  58. package/dist/client/assets/{skills-BIFy9nXD.js → skills-DjdZmDZP.js} +1 -1
  59. package/dist/client/assets/{use-message-Do_vF1fs.js → use-message-sSsrvqld.js} +1 -1
  60. package/dist/client/assets/{useTheme-BFm8wMmc.js → useTheme-DgQIobZP.js} +1 -1
  61. package/dist/client/index.html +29 -30
  62. package/dist/server/index.js +1067 -329
  63. package/dist/server/index.js.map +4 -4
  64. package/package.json +2 -2
  65. package/dist/client/assets/Add-COjp_zn3.js +0 -1
  66. package/dist/client/assets/ChatView-DVQv3z6i.js +0 -127
  67. package/dist/client/assets/ChatView-w9DKSiie.css +0 -1
  68. package/dist/client/assets/GatewaysView-BeaZvbdd.js +0 -1
  69. package/dist/client/assets/Input-CxMM68Q2.js +0 -234
  70. package/dist/client/assets/JobsView-DhosFF52.js +0 -2
  71. package/dist/client/assets/LoginView-DHPxG3eC.css +0 -1
  72. package/dist/client/assets/LoginView-DrjDX7xD.js +0 -1
  73. package/dist/client/assets/LogsView-BpW_vgEs.js +0 -1
  74. package/dist/client/assets/MemoryView-CP3K8mDG.js +0 -7
  75. package/dist/client/assets/ModelsView-DdQUiiu6.js +0 -1
  76. package/dist/client/assets/Popconfirm-DnYCLqNF.js +0 -16
  77. package/dist/client/assets/Popover-DAXLiB78.js +0 -117
  78. package/dist/client/assets/ProfilesView-Bkhqt3QB.js +0 -440
  79. package/dist/client/assets/SettingRow-B4_BuJSm.js +0 -1
  80. package/dist/client/assets/SettingsView-ZwzxJZVc.js +0 -352
  81. package/dist/client/assets/SkillsView-B3X9y2kZ.js +0 -1
  82. package/dist/client/assets/Spin-CAOjab_F.js +0 -43
  83. package/dist/client/assets/Suffix-DQWmhzzW.js +0 -90
  84. package/dist/client/assets/Tooltip-U6DmMv6W.js +0 -1
  85. package/dist/client/assets/UsageView-rmIKdR1u.js +0 -1
  86. package/dist/client/assets/app-lD2ZIoa4.js +0 -1
  87. package/dist/client/assets/browser-DcLLT85r.js +0 -47
  88. package/dist/client/assets/chat-DJP53-h1.js +0 -6
  89. package/dist/client/assets/composables-CjmuafMO.js +0 -1
  90. package/dist/client/assets/index-BfU2Be-n.css +0 -1
  91. package/dist/client/assets/index-BqaBjme9.js +0 -284
  92. package/dist/client/assets/light-ZuZhSzix.js +0 -1
  93. package/dist/client/assets/light-ba4gwt1q.js +0 -1
  94. package/dist/client/assets/omit-1BRB6K75.js +0 -1
  95. package/dist/client/assets/profiles-PisKQKsd.js +0 -1
  96. package/dist/client/assets/sessions-smjenK_5.js +0 -1
  97. /package/dist/client/assets/{_common-Yp55QE79.js → _common-D_mHAddk.js} +0 -0
@@ -33,6 +33,176 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
33
33
  ));
34
34
  var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
35
35
 
36
+ // packages/server/src/db/index.ts
37
+ function isSqliteAvailable() {
38
+ return SQLITE_AVAILABLE;
39
+ }
40
+ function getDb() {
41
+ if (!SQLITE_AVAILABLE) return null;
42
+ if (!_db) {
43
+ (0, import_fs.mkdirSync)(DB_DIR, { recursive: true });
44
+ _db = new import_node_sqlite.DatabaseSync(DB_PATH);
45
+ _db.exec("PRAGMA journal_mode=WAL");
46
+ _db.exec("PRAGMA foreign_keys=ON");
47
+ }
48
+ return _db;
49
+ }
50
+ function ensureTable(tableName, schema2) {
51
+ const db = getDb();
52
+ if (!db) return;
53
+ const colDefs = Object.entries(schema2).map(([col, def]) => `"${col}" ${def}`).join(", ");
54
+ db.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (${colDefs})`);
55
+ const rows = db.prepare(`PRAGMA table_info("${tableName}")`).all();
56
+ const existingCols = new Set(rows.map((r) => r.name));
57
+ const expectedCols = new Set(Object.keys(schema2));
58
+ for (const col of expectedCols) {
59
+ if (!existingCols.has(col)) {
60
+ db.exec(`ALTER TABLE "${tableName}" ADD COLUMN "${col}" ${schema2[col]}`);
61
+ }
62
+ }
63
+ for (const col of existingCols) {
64
+ if (!expectedCols.has(col)) {
65
+ db.exec(`ALTER TABLE "${tableName}" DROP COLUMN "${col}"`);
66
+ }
67
+ }
68
+ }
69
+ function readJsonStore() {
70
+ if (!(0, import_fs.existsSync)(JSON_PATH)) return {};
71
+ try {
72
+ return JSON.parse((0, import_fs.readFileSync)(JSON_PATH, "utf-8"));
73
+ } catch {
74
+ return {};
75
+ }
76
+ }
77
+ function writeJsonStore(data) {
78
+ (0, import_fs.mkdirSync)(DB_DIR, { recursive: true });
79
+ (0, import_fs.writeFileSync)(JSON_PATH, JSON.stringify(data, null, 2), "utf-8");
80
+ }
81
+ function jsonGet(table, key) {
82
+ const data = readJsonStore();
83
+ return data[table]?.[key];
84
+ }
85
+ function jsonSet(table, key, value) {
86
+ const data = readJsonStore();
87
+ if (!data[table]) data[table] = {};
88
+ data[table][key] = value;
89
+ writeJsonStore(data);
90
+ }
91
+ function jsonGetAll(table) {
92
+ const data = readJsonStore();
93
+ return data[table] || {};
94
+ }
95
+ function jsonDelete(table, key) {
96
+ const data = readJsonStore();
97
+ if (data[table]) {
98
+ delete data[table][key];
99
+ writeJsonStore(data);
100
+ }
101
+ }
102
+ function getStoragePath() {
103
+ return SQLITE_AVAILABLE ? DB_PATH : JSON_PATH;
104
+ }
105
+ var import_node_sqlite, import_fs, import_path, import_os, DB_DIR, DB_PATH, JSON_PATH, SQLITE_AVAILABLE, _db;
106
+ var init_db = __esm({
107
+ "packages/server/src/db/index.ts"() {
108
+ "use strict";
109
+ import_node_sqlite = require("node:sqlite");
110
+ import_fs = require("fs");
111
+ import_path = require("path");
112
+ import_os = require("os");
113
+ DB_DIR = (0, import_path.resolve)((0, import_os.homedir)(), ".hermes-web-ui");
114
+ DB_PATH = (0, import_path.resolve)(DB_DIR, "hermes-web-ui.db");
115
+ JSON_PATH = (0, import_path.resolve)(DB_DIR, "hermes-web-ui.json");
116
+ SQLITE_AVAILABLE = (() => {
117
+ const [major, minor] = process.versions.node.split(".").map(Number);
118
+ return major > 22 || major === 22 && minor >= 5;
119
+ })();
120
+ _db = null;
121
+ }
122
+ });
123
+
124
+ // packages/server/src/db/hermes/usage-store.ts
125
+ var usage_store_exports = {};
126
+ __export(usage_store_exports, {
127
+ deleteUsage: () => deleteUsage,
128
+ getUsage: () => getUsage,
129
+ getUsageBatch: () => getUsageBatch,
130
+ initUsageStore: () => initUsageStore,
131
+ updateUsage: () => updateUsage
132
+ });
133
+ function initUsageStore() {
134
+ if (isSqliteAvailable()) {
135
+ ensureTable(TABLE, SCHEMA);
136
+ }
137
+ }
138
+ function updateUsage(sessionId, inputTokens, outputTokens) {
139
+ const record = { input_tokens: inputTokens, output_tokens: outputTokens, updated_at: Date.now() };
140
+ if (isSqliteAvailable()) {
141
+ const db = getDb();
142
+ db.prepare(
143
+ `INSERT INTO ${TABLE} (session_id, input_tokens, output_tokens, updated_at)
144
+ VALUES (?, ?, ?, ?)
145
+ ON CONFLICT(session_id) DO UPDATE SET
146
+ input_tokens = excluded.input_tokens,
147
+ output_tokens = excluded.output_tokens,
148
+ updated_at = excluded.updated_at`
149
+ ).run(sessionId, inputTokens, outputTokens, record.updated_at);
150
+ } else {
151
+ jsonSet(TABLE, sessionId, record);
152
+ }
153
+ }
154
+ function getUsage(sessionId) {
155
+ if (isSqliteAvailable()) {
156
+ return getDb().prepare(
157
+ `SELECT input_tokens, output_tokens FROM ${TABLE} WHERE session_id = ?`
158
+ ).get(sessionId);
159
+ }
160
+ const row = jsonGet(TABLE, sessionId);
161
+ if (!row) return void 0;
162
+ return { input_tokens: row.input_tokens ?? 0, output_tokens: row.output_tokens ?? 0 };
163
+ }
164
+ function getUsageBatch(sessionIds) {
165
+ if (sessionIds.length === 0) return {};
166
+ if (isSqliteAvailable()) {
167
+ const db = getDb();
168
+ const placeholders = sessionIds.map(() => "?").join(",");
169
+ const rows = db.prepare(
170
+ `SELECT session_id, input_tokens, output_tokens FROM ${TABLE} WHERE session_id IN (${placeholders})`
171
+ ).all(...sessionIds);
172
+ const map3 = {};
173
+ for (const r of rows) map3[r.session_id] = { input_tokens: r.input_tokens, output_tokens: r.output_tokens };
174
+ return map3;
175
+ }
176
+ const all3 = jsonGetAll(TABLE);
177
+ const map2 = {};
178
+ for (const id of sessionIds) {
179
+ const row = all3[id];
180
+ if (row) map2[id] = { input_tokens: row.input_tokens ?? 0, output_tokens: row.output_tokens ?? 0 };
181
+ }
182
+ return map2;
183
+ }
184
+ function deleteUsage(sessionId) {
185
+ if (isSqliteAvailable()) {
186
+ getDb().prepare(`DELETE FROM ${TABLE} WHERE session_id = ?`).run(sessionId);
187
+ } else {
188
+ jsonDelete(TABLE, sessionId);
189
+ }
190
+ }
191
+ var TABLE, SCHEMA;
192
+ var init_usage_store = __esm({
193
+ "packages/server/src/db/hermes/usage-store.ts"() {
194
+ "use strict";
195
+ init_db();
196
+ TABLE = "session_usage";
197
+ SCHEMA = {
198
+ session_id: "TEXT PRIMARY KEY",
199
+ input_tokens: "INTEGER NOT NULL DEFAULT 0",
200
+ output_tokens: "INTEGER NOT NULL DEFAULT 0",
201
+ updated_at: "INTEGER NOT NULL"
202
+ };
203
+ }
204
+ });
205
+
36
206
  // node_modules/es-object-atoms/index.js
37
207
  var require_es_object_atoms = __commonJS({
38
208
  "node_modules/es-object-atoms/index.js"(exports2, module2) {
@@ -1231,13 +1401,13 @@ var require_common = __commonJS({
1231
1401
  }
1232
1402
  }
1233
1403
  }
1234
- function matchesTemplate(search, template) {
1404
+ function matchesTemplate(search2, template) {
1235
1405
  let searchIndex = 0;
1236
1406
  let templateIndex = 0;
1237
1407
  let starIndex = -1;
1238
1408
  let matchIndex = 0;
1239
- while (searchIndex < search.length) {
1240
- if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
1409
+ while (searchIndex < search2.length) {
1410
+ if (templateIndex < template.length && (template[templateIndex] === search2[searchIndex] || template[templateIndex] === "*")) {
1241
1411
  if (template[templateIndex] === "*") {
1242
1412
  starIndex = templateIndex;
1243
1413
  matchIndex = searchIndex;
@@ -15038,14 +15208,14 @@ var require_parseurl = __commonJS({
15038
15208
  }
15039
15209
  var pathname = str2;
15040
15210
  var query = null;
15041
- var search = null;
15211
+ var search2 = null;
15042
15212
  for (var i = 1; i < str2.length; i++) {
15043
15213
  switch (str2.charCodeAt(i)) {
15044
15214
  case 63:
15045
- if (search === null) {
15215
+ if (search2 === null) {
15046
15216
  pathname = str2.substring(0, i);
15047
15217
  query = str2.substring(i + 1);
15048
- search = str2.substring(i);
15218
+ search2 = str2.substring(i);
15049
15219
  }
15050
15220
  break;
15051
15221
  case 9:
@@ -15069,9 +15239,9 @@ var require_parseurl = __commonJS({
15069
15239
  url3.path = str2;
15070
15240
  url3.href = str2;
15071
15241
  url3.pathname = pathname;
15072
- if (search !== null) {
15242
+ if (search2 !== null) {
15073
15243
  url3.query = query;
15074
- url3.search = search;
15244
+ url3.search = search2;
15075
15245
  }
15076
15246
  return url3;
15077
15247
  }
@@ -15788,9 +15958,9 @@ var require_co = __commonJS({
15788
15958
  function co(gen) {
15789
15959
  var ctx = this;
15790
15960
  var args2 = slice.call(arguments, 1);
15791
- return new Promise(function(resolve8, reject) {
15961
+ return new Promise(function(resolve10, reject) {
15792
15962
  if (typeof gen === "function") gen = gen.apply(ctx, args2);
15793
- if (!gen || typeof gen.next !== "function") return resolve8(gen);
15963
+ if (!gen || typeof gen.next !== "function") return resolve10(gen);
15794
15964
  onFulfilled();
15795
15965
  function onFulfilled(res) {
15796
15966
  var ret;
@@ -15811,7 +15981,7 @@ var require_co = __commonJS({
15811
15981
  next(ret);
15812
15982
  }
15813
15983
  function next(ret) {
15814
- if (ret.done) return resolve8(ret.value);
15984
+ if (ret.done) return resolve10(ret.value);
15815
15985
  var value = toPromise.call(ctx, ret.value);
15816
15986
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
15817
15987
  return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, but the following object was passed: "' + String(ret.value) + '"'));
@@ -15829,11 +15999,11 @@ var require_co = __commonJS({
15829
15999
  }
15830
16000
  function thunkToPromise(fn2) {
15831
16001
  var ctx = this;
15832
- return new Promise(function(resolve8, reject) {
16002
+ return new Promise(function(resolve10, reject) {
15833
16003
  fn2.call(ctx, function(err, res) {
15834
16004
  if (err) return reject(err);
15835
16005
  if (arguments.length > 2) res = slice.call(arguments, 1);
15836
- resolve8(res);
16006
+ resolve10(res);
15837
16007
  });
15838
16008
  });
15839
16009
  }
@@ -20211,10 +20381,10 @@ var require_raw_body = __commonJS({
20211
20381
  if (done) {
20212
20382
  return readStream2(stream4, encoding, length, limit, wrap(done));
20213
20383
  }
20214
- return new Promise(function executor(resolve8, reject) {
20384
+ return new Promise(function executor(resolve10, reject) {
20215
20385
  readStream2(stream4, encoding, length, limit, function onRead(err, buf) {
20216
20386
  if (err) return reject(err);
20217
- resolve8(buf);
20387
+ resolve10(buf);
20218
20388
  });
20219
20389
  });
20220
20390
  }
@@ -24106,10 +24276,10 @@ var require_resolve_path = __commonJS({
24106
24276
  "node_modules/resolve-path/index.js"(exports2, module2) {
24107
24277
  "use strict";
24108
24278
  var createError = require_http_errors4();
24109
- var join11 = require("path").join;
24279
+ var join14 = require("path").join;
24110
24280
  var normalize = require("path").normalize;
24111
24281
  var pathIsAbsolute = require_path_is_absolute();
24112
- var resolve8 = require("path").resolve;
24282
+ var resolve10 = require("path").resolve;
24113
24283
  var sep = require("path").sep;
24114
24284
  module2.exports = resolvePath;
24115
24285
  var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
@@ -24141,7 +24311,7 @@ var require_resolve_path = __commonJS({
24141
24311
  if (UP_PATH_REGEXP.test(normalize("." + sep + path))) {
24142
24312
  throw createError(403);
24143
24313
  }
24144
- return normalize(join11(resolve8(root), path));
24314
+ return normalize(join14(resolve10(root), path));
24145
24315
  }
24146
24316
  }
24147
24317
  });
@@ -24900,7 +25070,7 @@ var require_koa_send = __commonJS({
24900
25070
  normalize,
24901
25071
  basename: basename2,
24902
25072
  extname,
24903
- resolve: resolve8,
25073
+ resolve: resolve10,
24904
25074
  parse: parse2,
24905
25075
  sep
24906
25076
  } = require("path");
@@ -24909,7 +25079,7 @@ var require_koa_send = __commonJS({
24909
25079
  assert(ctx, "koa context required");
24910
25080
  assert(path, "pathname required");
24911
25081
  debug2('send "%s" %j', path, opts);
24912
- const root = opts.root ? normalize(resolve8(opts.root)) : "";
25082
+ const root = opts.root ? normalize(resolve10(opts.root)) : "";
24913
25083
  const trailingSlash = path[path.length - 1] === "/";
24914
25084
  path = path.substr(parse2(path).root.length);
24915
25085
  const index = opts.index;
@@ -25013,7 +25183,7 @@ var require_koa_static = __commonJS({
25013
25183
  "node_modules/koa-static/index.js"(exports2, module2) {
25014
25184
  "use strict";
25015
25185
  var debug2 = require_src2()("koa-static");
25016
- var { resolve: resolve8 } = require("path");
25186
+ var { resolve: resolve10 } = require("path");
25017
25187
  var assert = require("assert");
25018
25188
  var send2 = require_koa_send();
25019
25189
  module2.exports = serve2;
@@ -25021,7 +25191,7 @@ var require_koa_static = __commonJS({
25021
25191
  opts = Object.assign({}, opts);
25022
25192
  assert(root, "root directory is required to serve files");
25023
25193
  debug2('static "%s" %j', root, opts);
25024
- opts.root = resolve8(root);
25194
+ opts.root = resolve10(root);
25025
25195
  if (opts.index !== false) opts.index = opts.index || "index.html";
25026
25196
  if (!opts.defer) {
25027
25197
  return async function serve3(ctx, next) {
@@ -28931,7 +29101,7 @@ var require_sonic_boom = __commonJS({
28931
29101
  if (!(this instanceof SonicBoom)) {
28932
29102
  return new SonicBoom(opts);
28933
29103
  }
28934
- let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append: append2 = true, mkdir: mkdir4, retryEAGAIN, fsync, contentMode, mode } = opts || {};
29104
+ let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append: append2 = true, mkdir: mkdir5, retryEAGAIN, fsync, contentMode, mode } = opts || {};
28935
29105
  fd = fd || dest;
28936
29106
  this._len = 0;
28937
29107
  this.fd = -1;
@@ -28956,7 +29126,7 @@ var require_sonic_boom = __commonJS({
28956
29126
  this.append = append2 || false;
28957
29127
  this.mode = mode;
28958
29128
  this.retryEAGAIN = retryEAGAIN || (() => true);
28959
- this.mkdir = mkdir4 || false;
29129
+ this.mkdir = mkdir5 || false;
28960
29130
  let fsWriteSync;
28961
29131
  let fsWrite;
28962
29132
  if (contentMode === kContentModeBuffer) {
@@ -29662,7 +29832,7 @@ var require_thread_stream = __commonJS({
29662
29832
  var { version } = require_package();
29663
29833
  var { EventEmitter: EventEmitter2 } = require("events");
29664
29834
  var { Worker } = require("worker_threads");
29665
- var { join: join11 } = require("path");
29835
+ var { join: join14 } = require("path");
29666
29836
  var { pathToFileURL } = require("url");
29667
29837
  var { wait } = require_wait();
29668
29838
  var {
@@ -29698,7 +29868,7 @@ var require_thread_stream = __commonJS({
29698
29868
  function createWorker(stream4, opts) {
29699
29869
  const { filename, workerData } = opts;
29700
29870
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
29701
- const toExecute = bundlerOverrides["thread-stream-worker"] || join11(__dirname, "lib", "worker.js");
29871
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join14(__dirname, "lib", "worker.js");
29702
29872
  const worker = new Worker(toExecute, {
29703
29873
  ...opts.workerOpts,
29704
29874
  trackUnmanagedFds: false,
@@ -30087,9 +30257,9 @@ var require_transport = __commonJS({
30087
30257
  "node_modules/pino/lib/transport.js"(exports2, module2) {
30088
30258
  "use strict";
30089
30259
  var { createRequire } = require("module");
30090
- var { existsSync: existsSync11 } = require("node:fs");
30260
+ var { existsSync: existsSync14 } = require("node:fs");
30091
30261
  var getCallers = require_caller();
30092
- var { join: join11, isAbsolute, sep } = require("node:path");
30262
+ var { join: join14, isAbsolute, sep } = require("node:path");
30093
30263
  var { fileURLToPath } = require("node:url");
30094
30264
  var sleep = require_atomic_sleep();
30095
30265
  var onExit = require_on_exit_leak_free();
@@ -30161,7 +30331,7 @@ var require_transport = __commonJS({
30161
30331
  return false;
30162
30332
  }
30163
30333
  }
30164
- return isAbsolute(path) && !existsSync11(path);
30334
+ return isAbsolute(path) && !existsSync14(path);
30165
30335
  }
30166
30336
  function stripQuotes(value) {
30167
30337
  const first = value[0];
@@ -30242,7 +30412,7 @@ var require_transport = __commonJS({
30242
30412
  throw new Error("only one of target or targets can be specified");
30243
30413
  }
30244
30414
  if (targets) {
30245
- target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
30415
+ target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
30246
30416
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
30247
30417
  return {
30248
30418
  ...dest,
@@ -30260,7 +30430,7 @@ var require_transport = __commonJS({
30260
30430
  });
30261
30431
  });
30262
30432
  } else if (pipeline) {
30263
- target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
30433
+ target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
30264
30434
  options.pipelines = [pipeline.map((dest) => {
30265
30435
  return {
30266
30436
  ...dest,
@@ -30283,7 +30453,7 @@ var require_transport = __commonJS({
30283
30453
  return origin2;
30284
30454
  }
30285
30455
  if (origin2 === "pino/file") {
30286
- return join11(__dirname, "..", "file.js");
30456
+ return join14(__dirname, "..", "file.js");
30287
30457
  }
30288
30458
  let fixTarget2;
30289
30459
  for (const filePath of callers) {
@@ -31263,7 +31433,7 @@ var require_safe_stable_stringify = __commonJS({
31263
31433
  return circularValue;
31264
31434
  }
31265
31435
  let res = "";
31266
- let join11 = ",";
31436
+ let join14 = ",";
31267
31437
  const originalIndentation = indentation;
31268
31438
  if (Array.isArray(value)) {
31269
31439
  if (value.length === 0) {
@@ -31277,7 +31447,7 @@ var require_safe_stable_stringify = __commonJS({
31277
31447
  indentation += spacer;
31278
31448
  res += `
31279
31449
  ${indentation}`;
31280
- join11 = `,
31450
+ join14 = `,
31281
31451
  ${indentation}`;
31282
31452
  }
31283
31453
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -31285,13 +31455,13 @@ ${indentation}`;
31285
31455
  for (; i < maximumValuesToStringify - 1; i++) {
31286
31456
  const tmp2 = stringifyFnReplacer(String(i), value, stack2, replacer, spacer, indentation);
31287
31457
  res += tmp2 !== void 0 ? tmp2 : "null";
31288
- res += join11;
31458
+ res += join14;
31289
31459
  }
31290
31460
  const tmp = stringifyFnReplacer(String(i), value, stack2, replacer, spacer, indentation);
31291
31461
  res += tmp !== void 0 ? tmp : "null";
31292
31462
  if (value.length - 1 > maximumBreadth) {
31293
31463
  const removedKeys = value.length - maximumBreadth - 1;
31294
- res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
31464
+ res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
31295
31465
  }
31296
31466
  if (spacer !== "") {
31297
31467
  res += `
@@ -31312,7 +31482,7 @@ ${originalIndentation}`;
31312
31482
  let separator = "";
31313
31483
  if (spacer !== "") {
31314
31484
  indentation += spacer;
31315
- join11 = `,
31485
+ join14 = `,
31316
31486
  ${indentation}`;
31317
31487
  whitespace = " ";
31318
31488
  }
@@ -31326,13 +31496,13 @@ ${indentation}`;
31326
31496
  const tmp = stringifyFnReplacer(key2, value, stack2, replacer, spacer, indentation);
31327
31497
  if (tmp !== void 0) {
31328
31498
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
31329
- separator = join11;
31499
+ separator = join14;
31330
31500
  }
31331
31501
  }
31332
31502
  if (keyLength > maximumBreadth) {
31333
31503
  const removedKeys = keyLength - maximumBreadth;
31334
31504
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
31335
- separator = join11;
31505
+ separator = join14;
31336
31506
  }
31337
31507
  if (spacer !== "" && separator.length > 1) {
31338
31508
  res = `
@@ -31373,7 +31543,7 @@ ${originalIndentation}`;
31373
31543
  }
31374
31544
  const originalIndentation = indentation;
31375
31545
  let res = "";
31376
- let join11 = ",";
31546
+ let join14 = ",";
31377
31547
  if (Array.isArray(value)) {
31378
31548
  if (value.length === 0) {
31379
31549
  return "[]";
@@ -31386,7 +31556,7 @@ ${originalIndentation}`;
31386
31556
  indentation += spacer;
31387
31557
  res += `
31388
31558
  ${indentation}`;
31389
- join11 = `,
31559
+ join14 = `,
31390
31560
  ${indentation}`;
31391
31561
  }
31392
31562
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -31394,13 +31564,13 @@ ${indentation}`;
31394
31564
  for (; i < maximumValuesToStringify - 1; i++) {
31395
31565
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack2, replacer, spacer, indentation);
31396
31566
  res += tmp2 !== void 0 ? tmp2 : "null";
31397
- res += join11;
31567
+ res += join14;
31398
31568
  }
31399
31569
  const tmp = stringifyArrayReplacer(String(i), value[i], stack2, replacer, spacer, indentation);
31400
31570
  res += tmp !== void 0 ? tmp : "null";
31401
31571
  if (value.length - 1 > maximumBreadth) {
31402
31572
  const removedKeys = value.length - maximumBreadth - 1;
31403
- res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
31573
+ res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
31404
31574
  }
31405
31575
  if (spacer !== "") {
31406
31576
  res += `
@@ -31413,7 +31583,7 @@ ${originalIndentation}`;
31413
31583
  let whitespace = "";
31414
31584
  if (spacer !== "") {
31415
31585
  indentation += spacer;
31416
- join11 = `,
31586
+ join14 = `,
31417
31587
  ${indentation}`;
31418
31588
  whitespace = " ";
31419
31589
  }
@@ -31422,7 +31592,7 @@ ${indentation}`;
31422
31592
  const tmp = stringifyArrayReplacer(key2, value[key2], stack2, replacer, spacer, indentation);
31423
31593
  if (tmp !== void 0) {
31424
31594
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
31425
- separator = join11;
31595
+ separator = join14;
31426
31596
  }
31427
31597
  }
31428
31598
  if (spacer !== "" && separator.length > 1) {
@@ -31480,20 +31650,20 @@ ${originalIndentation}`;
31480
31650
  indentation += spacer;
31481
31651
  let res2 = `
31482
31652
  ${indentation}`;
31483
- const join12 = `,
31653
+ const join15 = `,
31484
31654
  ${indentation}`;
31485
31655
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
31486
31656
  let i = 0;
31487
31657
  for (; i < maximumValuesToStringify - 1; i++) {
31488
31658
  const tmp2 = stringifyIndent(String(i), value[i], stack2, spacer, indentation);
31489
31659
  res2 += tmp2 !== void 0 ? tmp2 : "null";
31490
- res2 += join12;
31660
+ res2 += join15;
31491
31661
  }
31492
31662
  const tmp = stringifyIndent(String(i), value[i], stack2, spacer, indentation);
31493
31663
  res2 += tmp !== void 0 ? tmp : "null";
31494
31664
  if (value.length - 1 > maximumBreadth) {
31495
31665
  const removedKeys = value.length - maximumBreadth - 1;
31496
- res2 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
31666
+ res2 += `${join15}"... ${getItemCount(removedKeys)} not stringified"`;
31497
31667
  }
31498
31668
  res2 += `
31499
31669
  ${originalIndentation}`;
@@ -31509,16 +31679,16 @@ ${originalIndentation}`;
31509
31679
  return '"[Object]"';
31510
31680
  }
31511
31681
  indentation += spacer;
31512
- const join11 = `,
31682
+ const join14 = `,
31513
31683
  ${indentation}`;
31514
31684
  let res = "";
31515
31685
  let separator = "";
31516
31686
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
31517
31687
  if (isTypedArrayWithEntries(value)) {
31518
- res += stringifyTypedArray(value, join11, maximumBreadth);
31688
+ res += stringifyTypedArray(value, join14, maximumBreadth);
31519
31689
  keys = keys.slice(value.length);
31520
31690
  maximumPropertiesToStringify -= value.length;
31521
- separator = join11;
31691
+ separator = join14;
31522
31692
  }
31523
31693
  if (deterministic) {
31524
31694
  keys = sort(keys, comparator);
@@ -31529,13 +31699,13 @@ ${indentation}`;
31529
31699
  const tmp = stringifyIndent(key2, value[key2], stack2, spacer, indentation);
31530
31700
  if (tmp !== void 0) {
31531
31701
  res += `${separator}${strEscape(key2)}: ${tmp}`;
31532
- separator = join11;
31702
+ separator = join14;
31533
31703
  }
31534
31704
  }
31535
31705
  if (keyLength > maximumBreadth) {
31536
31706
  const removedKeys = keyLength - maximumBreadth;
31537
31707
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
31538
- separator = join11;
31708
+ separator = join14;
31539
31709
  }
31540
31710
  if (separator !== "") {
31541
31711
  res = `
@@ -32062,28 +32232,28 @@ var require_pino = __commonJS({
32062
32232
  });
32063
32233
 
32064
32234
  // packages/server/src/services/logger.ts
32065
- var import_pino, import_path3, import_fs, import_os3, MAX_LOG_SIZE, logDir, logFile, logger;
32235
+ var import_pino, import_path4, import_fs2, import_os4, MAX_LOG_SIZE, logDir, logFile, logger;
32066
32236
  var init_logger = __esm({
32067
32237
  "packages/server/src/services/logger.ts"() {
32068
32238
  "use strict";
32069
32239
  import_pino = __toESM(require_pino());
32070
- import_path3 = require("path");
32071
- import_fs = require("fs");
32072
- import_os3 = require("os");
32240
+ import_path4 = require("path");
32241
+ import_fs2 = require("fs");
32242
+ import_os4 = require("os");
32073
32243
  MAX_LOG_SIZE = 3 * 1024 * 1024;
32074
- logDir = (0, import_path3.resolve)((0, import_os3.homedir)(), ".hermes-web-ui", "logs");
32075
- (0, import_fs.mkdirSync)(logDir, { recursive: true });
32076
- logFile = (0, import_path3.resolve)(logDir, "server.log");
32244
+ logDir = (0, import_path4.resolve)((0, import_os4.homedir)(), ".hermes-web-ui", "logs");
32245
+ (0, import_fs2.mkdirSync)(logDir, { recursive: true });
32246
+ logFile = (0, import_path4.resolve)(logDir, "server.log");
32077
32247
  try {
32078
- const stat2 = (0, import_fs.statSync)(logFile);
32248
+ const stat2 = (0, import_fs2.statSync)(logFile);
32079
32249
  if (stat2.size > MAX_LOG_SIZE) {
32080
32250
  const keepSize = Math.floor(MAX_LOG_SIZE / 2);
32081
- const fd = (0, import_fs.openSync)(logFile, "r");
32251
+ const fd = (0, import_fs2.openSync)(logFile, "r");
32082
32252
  const buf = Buffer.alloc(keepSize);
32083
- (0, import_fs.readSync)(fd, buf, 0, keepSize, stat2.size - keepSize);
32084
- (0, import_fs.closeSync)(fd);
32085
- (0, import_fs.truncateSync)(logFile, 0);
32086
- (0, import_fs.writeFileSync)(logFile, buf);
32253
+ (0, import_fs2.readSync)(fd, buf, 0, keepSize, stat2.size - keepSize);
32254
+ (0, import_fs2.closeSync)(fd);
32255
+ (0, import_fs2.truncateSync)(logFile, 0);
32256
+ (0, import_fs2.writeFileSync)(logFile, buf);
32087
32257
  }
32088
32258
  } catch {
32089
32259
  }
@@ -32101,24 +32271,24 @@ var gateway_manager_exports = {};
32101
32271
  __export(gateway_manager_exports, {
32102
32272
  GatewayManager: () => GatewayManager
32103
32273
  });
32104
- var import_child_process, import_path4, import_os4, import_fs2, import_child_process2, import_util, import_net, execFileAsync, HERMES_BASE, HERMES_BIN, isWsl, isDocker, needsRunMode, GatewayManager;
32274
+ var import_child_process, import_path5, import_os5, import_fs3, import_child_process2, import_util, import_net, execFileAsync, HERMES_BASE, HERMES_BIN, isWsl, isDocker, needsRunMode, GatewayManager;
32105
32275
  var init_gateway_manager = __esm({
32106
32276
  "packages/server/src/services/hermes/gateway-manager.ts"() {
32107
32277
  "use strict";
32108
32278
  import_child_process = require("child_process");
32109
- import_path4 = require("path");
32110
- import_os4 = require("os");
32111
- import_fs2 = require("fs");
32279
+ import_path5 = require("path");
32280
+ import_os5 = require("os");
32281
+ import_fs3 = require("fs");
32112
32282
  import_child_process2 = require("child_process");
32113
32283
  import_util = require("util");
32114
32284
  import_net = require("net");
32115
32285
  init_js_yaml();
32116
32286
  init_logger();
32117
32287
  execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
32118
- HERMES_BASE = (0, import_path4.resolve)((0, import_os4.homedir)(), ".hermes");
32288
+ HERMES_BASE = (0, import_path5.resolve)((0, import_os5.homedir)(), ".hermes");
32119
32289
  HERMES_BIN = process.env.HERMES_BIN?.trim() || "hermes";
32120
- isWsl = (0, import_fs2.existsSync)("/proc/version") && (0, import_fs2.readFileSync)("/proc/version", "utf-8").toLowerCase().includes("microsoft");
32121
- isDocker = (0, import_fs2.existsSync)("/.dockerenv");
32290
+ isWsl = (0, import_fs3.existsSync)("/proc/version") && (0, import_fs3.readFileSync)("/proc/version", "utf-8").toLowerCase().includes("microsoft");
32291
+ isDocker = (0, import_fs3.existsSync)("/.dockerenv");
32122
32292
  needsRunMode = isWsl || isDocker;
32123
32293
  GatewayManager = class {
32124
32294
  /** 已注册的网关:profile name → { pid, port, host, url } */
@@ -32136,17 +32306,17 @@ var init_gateway_manager = __esm({
32136
32306
  /** 获取 profile 的 home 目录路径 */
32137
32307
  profileDir(name) {
32138
32308
  if (name === "default") return HERMES_BASE;
32139
- return (0, import_path4.join)(HERMES_BASE, "profiles", name);
32309
+ return (0, import_path5.join)(HERMES_BASE, "profiles", name);
32140
32310
  }
32141
32311
  /**
32142
32312
  * 从 profile 的 config.yaml 读取 api_server 端口和主机
32143
32313
  * 读取路径:platforms.api_server.extra.port / extra.host
32144
32314
  */
32145
32315
  readProfilePort(name) {
32146
- const configPath3 = (0, import_path4.join)(this.profileDir(name), "config.yaml");
32147
- if (!(0, import_fs2.existsSync)(configPath3)) return { port: 8642, host: "127.0.0.1" };
32316
+ const configPath3 = (0, import_path5.join)(this.profileDir(name), "config.yaml");
32317
+ if (!(0, import_fs3.existsSync)(configPath3)) return { port: 8642, host: "127.0.0.1" };
32148
32318
  try {
32149
- const content = (0, import_fs2.readFileSync)(configPath3, "utf-8");
32319
+ const content = (0, import_fs3.readFileSync)(configPath3, "utf-8");
32150
32320
  const cfg = jsYaml.load(content) || {};
32151
32321
  const extra = cfg?.platforms?.api_server?.extra;
32152
32322
  const rawPort = extra?.port || 8642;
@@ -32159,10 +32329,10 @@ var init_gateway_manager = __esm({
32159
32329
  }
32160
32330
  /** 从 profile 的 gateway.pid 文件读取 PID(JSON 格式 { "pid": 12345 }) */
32161
32331
  readPidFile(name) {
32162
- const pidPath = (0, import_path4.join)(this.profileDir(name), "gateway.pid");
32163
- if (!(0, import_fs2.existsSync)(pidPath)) return null;
32332
+ const pidPath = (0, import_path5.join)(this.profileDir(name), "gateway.pid");
32333
+ if (!(0, import_fs3.existsSync)(pidPath)) return null;
32164
32334
  try {
32165
- const content = (0, import_fs2.readFileSync)(pidPath, "utf-8").trim();
32335
+ const content = (0, import_fs3.readFileSync)(pidPath, "utf-8").trim();
32166
32336
  const data = JSON.parse(content);
32167
32337
  return typeof data.pid === "number" ? data.pid : parseInt(data.pid, 10) || null;
32168
32338
  } catch {
@@ -32195,22 +32365,22 @@ var init_gateway_manager = __esm({
32195
32365
  /** 尝试绑定端口,检测端口是否被系统级进程占用 */
32196
32366
  checkPortAvailable(port, host) {
32197
32367
  if (port < 0 || port > 65535) return Promise.resolve(false);
32198
- return new Promise((resolve8) => {
32368
+ return new Promise((resolve10) => {
32199
32369
  const server2 = (0, import_net.createServer)();
32200
32370
  server2.once("error", () => {
32201
32371
  server2.close();
32202
- resolve8(false);
32372
+ resolve10(false);
32203
32373
  });
32204
32374
  server2.once("listening", () => {
32205
32375
  server2.close();
32206
- resolve8(true);
32376
+ resolve10(true);
32207
32377
  });
32208
32378
  server2.listen(port, host);
32209
32379
  });
32210
32380
  }
32211
32381
  /** 从 base 端口开始递增查找空闲端口(上限 65535) */
32212
32382
  findFreePort(base, host = "127.0.0.1") {
32213
- return new Promise((resolve8, reject) => {
32383
+ return new Promise((resolve10, reject) => {
32214
32384
  const tryPort = (port) => {
32215
32385
  if (port > 65535) {
32216
32386
  reject(new Error(`No free port found in range ${base}-65535`));
@@ -32223,7 +32393,7 @@ var init_gateway_manager = __esm({
32223
32393
  });
32224
32394
  server2.once("listening", () => {
32225
32395
  server2.close();
32226
- resolve8(port);
32396
+ resolve10(port);
32227
32397
  });
32228
32398
  server2.listen(port, host);
32229
32399
  };
@@ -32247,9 +32417,9 @@ var init_gateway_manager = __esm({
32247
32417
  * 同时清理旧的顶层 port/host(避免 Hermes 读取错误)
32248
32418
  */
32249
32419
  writeProfilePort(name, port, host) {
32250
- const configPath3 = (0, import_path4.join)(this.profileDir(name), "config.yaml");
32420
+ const configPath3 = (0, import_path5.join)(this.profileDir(name), "config.yaml");
32251
32421
  try {
32252
- const content = (0, import_fs2.existsSync)(configPath3) ? (0, import_fs2.readFileSync)(configPath3, "utf-8") : "";
32422
+ const content = (0, import_fs3.existsSync)(configPath3) ? (0, import_fs3.readFileSync)(configPath3, "utf-8") : "";
32253
32423
  const cfg = jsYaml.load(content) || {};
32254
32424
  if (!cfg.platforms) cfg.platforms = {};
32255
32425
  if (!cfg.platforms.api_server) cfg.platforms.api_server = {};
@@ -32265,7 +32435,7 @@ var init_gateway_manager = __esm({
32265
32435
  if (cfg.platforms.api_server.host !== void 0) {
32266
32436
  delete cfg.platforms.api_server.host;
32267
32437
  }
32268
- (0, import_fs2.writeFileSync)(configPath3, jsYaml.dump(cfg, { lineWidth: -1 }), "utf-8");
32438
+ (0, import_fs3.writeFileSync)(configPath3, jsYaml.dump(cfg, { lineWidth: -1 }), "utf-8");
32269
32439
  logger.debug("Updated %s: api_server.extra.port = %d", configPath3, port);
32270
32440
  } catch (err) {
32271
32441
  logger.error(err, 'Failed to write config for profile "%s"', name);
@@ -32324,9 +32494,9 @@ var init_gateway_manager = __esm({
32324
32494
  getApiKey(profileName) {
32325
32495
  const name = profileName || this.activeProfile;
32326
32496
  try {
32327
- const envPath3 = (0, import_path4.join)(this.profileDir(name), ".env");
32328
- if (!(0, import_fs2.existsSync)(envPath3)) return null;
32329
- const content = (0, import_fs2.readFileSync)(envPath3, "utf-8");
32497
+ const envPath3 = (0, import_path5.join)(this.profileDir(name), ".env");
32498
+ if (!(0, import_fs3.existsSync)(envPath3)) return null;
32499
+ const content = (0, import_fs3.readFileSync)(envPath3, "utf-8");
32330
32500
  const match = content.match(/^API_SERVER_KEY\s*=\s*"?([^"\n]+)"?/m);
32331
32501
  return match?.[1]?.trim() || null;
32332
32502
  } catch {
@@ -32355,10 +32525,10 @@ var init_gateway_manager = __esm({
32355
32525
  return profiles;
32356
32526
  } catch {
32357
32527
  const profiles = ["default"];
32358
- const profilesDir = (0, import_path4.join)(HERMES_BASE, "profiles");
32359
- if ((0, import_fs2.existsSync)(profilesDir)) {
32360
- for (const entry of (0, import_fs2.readdirSync)(profilesDir, { withFileTypes: true })) {
32361
- if (entry.isDirectory() && (0, import_fs2.existsSync)((0, import_path4.join)(profilesDir, entry.name, "config.yaml"))) {
32528
+ const profilesDir = (0, import_path5.join)(HERMES_BASE, "profiles");
32529
+ if ((0, import_fs3.existsSync)(profilesDir)) {
32530
+ for (const entry of (0, import_fs3.readdirSync)(profilesDir, { withFileTypes: true })) {
32531
+ if (entry.isDirectory() && (0, import_fs3.existsSync)((0, import_path5.join)(profilesDir, entry.name, "config.yaml"))) {
32362
32532
  profiles.push(entry.name);
32363
32533
  }
32364
32534
  }
@@ -32404,7 +32574,7 @@ var init_gateway_manager = __esm({
32404
32574
  const hermesHome = this.profileDir(name);
32405
32575
  const url2 = `http://${host}:${port}`;
32406
32576
  if (needsRunMode) {
32407
- return new Promise((resolve8, reject) => {
32577
+ return new Promise((resolve10, reject) => {
32408
32578
  const env2 = { ...process.env, HERMES_HOME: hermesHome };
32409
32579
  const child = (0, import_child_process.spawn)(HERMES_BIN, ["gateway", "run", "--replace"], {
32410
32580
  detached: true,
@@ -32415,7 +32585,7 @@ var init_gateway_manager = __esm({
32415
32585
  child.unref();
32416
32586
  const pid = child.pid ?? 0;
32417
32587
  logger.info('Starting gateway for profile "%s" (run mode, PID: %d, port: %d)', name, pid, port);
32418
- this.waitForReady(name, pid, port, host, url2).then(resolve8).catch(reject);
32588
+ this.waitForReady(name, pid, port, host, url2).then(resolve10).catch(reject);
32419
32589
  });
32420
32590
  }
32421
32591
  logger.info('Starting gateway for profile "%s" (start mode, port: %d)', name, port);
@@ -32579,30 +32749,30 @@ __export(hermes_profile_exports, {
32579
32749
  getProfileDir: () => getProfileDir
32580
32750
  });
32581
32751
  function getActiveProfileDir() {
32582
- const activeFile = (0, import_path5.join)(HERMES_BASE2, "active_profile");
32752
+ const activeFile = (0, import_path6.join)(HERMES_BASE2, "active_profile");
32583
32753
  try {
32584
- const name = (0, import_fs3.readFileSync)(activeFile, "utf-8").trim();
32754
+ const name = (0, import_fs4.readFileSync)(activeFile, "utf-8").trim();
32585
32755
  if (name && name !== "default") {
32586
- const dir = (0, import_path5.join)(HERMES_BASE2, "profiles", name);
32587
- if ((0, import_fs3.existsSync)(dir)) return dir;
32756
+ const dir = (0, import_path6.join)(HERMES_BASE2, "profiles", name);
32757
+ if ((0, import_fs4.existsSync)(dir)) return dir;
32588
32758
  }
32589
32759
  } catch {
32590
32760
  }
32591
32761
  return HERMES_BASE2;
32592
32762
  }
32593
32763
  function getActiveConfigPath() {
32594
- return (0, import_path5.join)(getActiveProfileDir(), "config.yaml");
32764
+ return (0, import_path6.join)(getActiveProfileDir(), "config.yaml");
32595
32765
  }
32596
32766
  function getActiveAuthPath() {
32597
- return (0, import_path5.join)(getActiveProfileDir(), "auth.json");
32767
+ return (0, import_path6.join)(getActiveProfileDir(), "auth.json");
32598
32768
  }
32599
32769
  function getActiveEnvPath() {
32600
- return (0, import_path5.join)(getActiveProfileDir(), ".env");
32770
+ return (0, import_path6.join)(getActiveProfileDir(), ".env");
32601
32771
  }
32602
32772
  function getActiveProfileName() {
32603
- const activeFile = (0, import_path5.join)(HERMES_BASE2, "active_profile");
32773
+ const activeFile = (0, import_path6.join)(HERMES_BASE2, "active_profile");
32604
32774
  try {
32605
- const name = (0, import_fs3.readFileSync)(activeFile, "utf-8").trim();
32775
+ const name = (0, import_fs4.readFileSync)(activeFile, "utf-8").trim();
32606
32776
  return name || "default";
32607
32777
  } catch {
32608
32778
  return "default";
@@ -32610,17 +32780,17 @@ function getActiveProfileName() {
32610
32780
  }
32611
32781
  function getProfileDir(name) {
32612
32782
  if (!name || name === "default") return HERMES_BASE2;
32613
- const dir = (0, import_path5.join)(HERMES_BASE2, "profiles", name);
32614
- return (0, import_fs3.existsSync)(dir) ? dir : HERMES_BASE2;
32783
+ const dir = (0, import_path6.join)(HERMES_BASE2, "profiles", name);
32784
+ return (0, import_fs4.existsSync)(dir) ? dir : HERMES_BASE2;
32615
32785
  }
32616
- var import_path5, import_os5, import_fs3, HERMES_BASE2;
32786
+ var import_path6, import_os6, import_fs4, HERMES_BASE2;
32617
32787
  var init_hermes_profile = __esm({
32618
32788
  "packages/server/src/services/hermes/hermes-profile.ts"() {
32619
32789
  "use strict";
32620
- import_path5 = require("path");
32621
- import_os5 = require("os");
32622
- import_fs3 = require("fs");
32623
- HERMES_BASE2 = (0, import_path5.resolve)((0, import_os5.homedir)(), ".hermes");
32790
+ import_path6 = require("path");
32791
+ import_os6 = require("os");
32792
+ import_fs4 = require("fs");
32793
+ HERMES_BASE2 = (0, import_path6.resolve)((0, import_os6.homedir)(), ".hermes");
32624
32794
  }
32625
32795
  });
32626
32796
 
@@ -34827,7 +34997,7 @@ var require_websocket = __commonJS({
34827
34997
  var http3 = require("http");
34828
34998
  var net = require("net");
34829
34999
  var tls = require("tls");
34830
- var { randomBytes: randomBytes3, createHash } = require("crypto");
35000
+ var { randomBytes: randomBytes4, createHash } = require("crypto");
34831
35001
  var { Duplex, Readable: Readable2 } = require("stream");
34832
35002
  var { URL: URL2 } = require("url");
34833
35003
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -35357,7 +35527,7 @@ var require_websocket = __commonJS({
35357
35527
  }
35358
35528
  }
35359
35529
  const defaultPort = isSecure ? 443 : 80;
35360
- const key = randomBytes3(16).toString("base64");
35530
+ const key = randomBytes4(16).toString("base64");
35361
35531
  const request = isSecure ? https2.request : http3.request;
35362
35532
  const protocolSet = /* @__PURE__ */ new Set();
35363
35533
  let perMessageDeflate;
@@ -38110,23 +38280,23 @@ function bodyParserWrapper(opts = {}) {
38110
38280
  }
38111
38281
 
38112
38282
  // packages/server/src/config.ts
38113
- var import_path = require("path");
38114
- var import_os = require("os");
38283
+ var import_path2 = require("path");
38284
+ var import_os2 = require("os");
38115
38285
  var config = {
38116
38286
  port: parseInt(process.env.PORT || "8648", 10),
38117
38287
  upstream: process.env.UPSTREAM || "http://127.0.0.1:8642",
38118
- uploadDir: process.env.UPLOAD_DIR || (0, import_path.resolve)((0, import_os.tmpdir)(), "hermes-uploads"),
38119
- dataDir: (0, import_path.resolve)(__dirname, "..", "data"),
38288
+ uploadDir: process.env.UPLOAD_DIR || (0, import_path2.resolve)((0, import_os2.tmpdir)(), "hermes-uploads"),
38289
+ dataDir: (0, import_path2.resolve)(__dirname, "..", "data"),
38120
38290
  corsOrigins: process.env.CORS_ORIGINS || "*"
38121
38291
  };
38122
38292
 
38123
38293
  // packages/server/src/services/auth.ts
38124
38294
  var import_promises = require("fs/promises");
38125
- var import_path2 = require("path");
38295
+ var import_path3 = require("path");
38126
38296
  var import_crypto = require("crypto");
38127
- var import_os2 = require("os");
38128
- var APP_HOME = (0, import_path2.join)((0, import_os2.homedir)(), ".hermes-web-ui");
38129
- var TOKEN_FILE = (0, import_path2.join)(APP_HOME, ".token");
38297
+ var import_os3 = require("os");
38298
+ var APP_HOME = (0, import_path3.join)((0, import_os3.homedir)(), ".hermes-web-ui");
38299
+ var TOKEN_FILE = (0, import_path3.join)(APP_HOME, ".token");
38130
38300
  function generateToken() {
38131
38301
  return (0, import_crypto.randomBytes)(32).toString("hex");
38132
38302
  }
@@ -38195,10 +38365,10 @@ function bindShutdown(server2) {
38195
38365
  logger.info("Shutting down (%s)...", signal);
38196
38366
  try {
38197
38367
  if (server2) {
38198
- await new Promise((resolve8) => {
38368
+ await new Promise((resolve10) => {
38199
38369
  server2.close(() => {
38200
38370
  logger.info("HTTP server closed");
38201
- resolve8();
38371
+ resolve10();
38202
38372
  });
38203
38373
  });
38204
38374
  }
@@ -38224,25 +38394,25 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
38224
38394
  var wrapper_default = import_websocket.default;
38225
38395
 
38226
38396
  // packages/server/src/routes/hermes/terminal.ts
38227
- var import_fs4 = require("fs");
38228
- var import_path6 = require("path");
38397
+ var import_fs5 = require("fs");
38398
+ var import_path7 = require("path");
38229
38399
  init_logger();
38230
38400
  var pty = null;
38231
38401
  function ensureNodePtySpawnHelperExecutable() {
38232
38402
  if (process.platform !== "darwin") return;
38233
38403
  try {
38234
- const nodePtyRoot = (0, import_path6.dirname)(require.resolve("node-pty/package.json"));
38404
+ const nodePtyRoot = (0, import_path7.dirname)(require.resolve("node-pty/package.json"));
38235
38405
  const helperCandidates = [
38236
- (0, import_path6.join)(nodePtyRoot, "build", "Release", "spawn-helper"),
38237
- (0, import_path6.join)(nodePtyRoot, "build", "Debug", "spawn-helper"),
38238
- (0, import_path6.join)(nodePtyRoot, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper")
38406
+ (0, import_path7.join)(nodePtyRoot, "build", "Release", "spawn-helper"),
38407
+ (0, import_path7.join)(nodePtyRoot, "build", "Debug", "spawn-helper"),
38408
+ (0, import_path7.join)(nodePtyRoot, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper")
38239
38409
  ];
38240
38410
  for (const helperPath of helperCandidates) {
38241
- if (!(0, import_fs4.existsSync)(helperPath)) continue;
38411
+ if (!(0, import_fs5.existsSync)(helperPath)) continue;
38242
38412
  try {
38243
- (0, import_fs4.accessSync)(helperPath, import_fs4.constants.X_OK);
38413
+ (0, import_fs5.accessSync)(helperPath, import_fs5.constants.X_OK);
38244
38414
  } catch {
38245
- (0, import_fs4.chmodSync)(helperPath, 493);
38415
+ (0, import_fs5.chmodSync)(helperPath, 493);
38246
38416
  logger.debug("Restored execute bit for node-pty helper: %s", helperPath);
38247
38417
  }
38248
38418
  }
@@ -38265,7 +38435,7 @@ function findShell() {
38265
38435
  process.platform === "win32" ? "cmd.exe" : null
38266
38436
  ].filter(Boolean);
38267
38437
  for (const shell of candidates) {
38268
- if ((0, import_fs4.existsSync)(shell)) return shell;
38438
+ if ((0, import_fs5.existsSync)(shell)) return shell;
38269
38439
  }
38270
38440
  return "/bin/bash";
38271
38441
  }
@@ -40033,12 +40203,12 @@ for (const httpMethod of httpMethods) {
40033
40203
 
40034
40204
  // packages/server/src/services/hermes/hermes-cli.ts
40035
40205
  var import_child_process3 = require("child_process");
40036
- var import_fs5 = require("fs");
40206
+ var import_fs6 = require("fs");
40037
40207
  var import_util2 = require("util");
40038
40208
  init_logger();
40039
40209
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
40040
40210
  var execOpts = { windowsHide: true };
40041
- var isDocker2 = (0, import_fs5.existsSync)("/.dockerenv");
40211
+ var isDocker2 = (0, import_fs6.existsSync)("/.dockerenv");
40042
40212
  function resolveHermesBin() {
40043
40213
  const envBin = process.env.HERMES_BIN?.trim();
40044
40214
  if (envBin) return envBin;
@@ -40420,11 +40590,11 @@ async function importProfile(archivePath, name) {
40420
40590
  }
40421
40591
 
40422
40592
  // packages/server/src/controllers/health.ts
40423
- var LOCAL_VERSION = true ? "0.4.2" : (() => {
40593
+ var LOCAL_VERSION = true ? "0.4.3" : (() => {
40424
40594
  try {
40425
- const { readFileSync: readFileSync8 } = null;
40426
- const { resolve: resolve8 } = null;
40427
- return JSON.parse(readFileSync8(resolve8(__dirname, "../../package.json"), "utf-8")).version;
40595
+ const { readFileSync: readFileSync10 } = null;
40596
+ const { resolve: resolve10 } = null;
40597
+ return JSON.parse(readFileSync10(resolve10(__dirname, "../../package.json"), "utf-8")).version;
40428
40598
  } catch {
40429
40599
  return "0.0.0";
40430
40600
  }
@@ -40432,8 +40602,8 @@ var LOCAL_VERSION = true ? "0.4.2" : (() => {
40432
40602
  var cachedLatestVersion = "";
40433
40603
  async function checkLatestVersion() {
40434
40604
  try {
40435
- const { readFileSync: readFileSync8 } = require("fs");
40436
- const pkg = JSON.parse(readFileSync8(resolve5(require("path").join(__dirname, "../../package.json")), "utf-8"));
40605
+ const { readFileSync: readFileSync10 } = require("fs");
40606
+ const pkg = JSON.parse(readFileSync10(resolve6(require("path").join(__dirname, "../../package.json")), "utf-8"));
40437
40607
  const name = pkg.name;
40438
40608
  const res = await fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(1e4) });
40439
40609
  if (res.ok) {
@@ -40471,7 +40641,7 @@ async function healthCheck(ctx) {
40471
40641
  webui_update_available: cachedLatestVersion && cachedLatestVersion !== LOCAL_VERSION
40472
40642
  };
40473
40643
  }
40474
- function resolve5(p) {
40644
+ function resolve6(p) {
40475
40645
  return p;
40476
40646
  }
40477
40647
 
@@ -40661,19 +40831,57 @@ uploadRoutes.post("/upload", handleUpload);
40661
40831
 
40662
40832
  // packages/server/src/controllers/update.ts
40663
40833
  var import_child_process4 = require("child_process");
40834
+ var import_path8 = require("path");
40835
+ function getNodeBinDir() {
40836
+ return (0, import_path8.dirname)(process.execPath);
40837
+ }
40838
+ function getNpmBin() {
40839
+ return (0, import_path8.join)(getNodeBinDir(), process.platform === "win32" ? "npm.cmd" : "npm");
40840
+ }
40841
+ function getCliBin() {
40842
+ return (0, import_path8.join)(getNodeBinDir(), process.platform === "win32" ? "hermes-web-ui.cmd" : "hermes-web-ui");
40843
+ }
40844
+ function getWindowsShell() {
40845
+ return process.env.ComSpec || "cmd.exe";
40846
+ }
40847
+ function quoteForWindowsCommand(value) {
40848
+ return `"${value.replace(/"/g, '""')}"`;
40849
+ }
40850
+ function runUpdateInstall() {
40851
+ if (process.platform === "win32") {
40852
+ return (0, import_child_process4.execFileSync)(getWindowsShell(), ["/d", "/s", "/c", `${quoteForWindowsCommand(getNpmBin())} install -g hermes-web-ui@latest`], {
40853
+ encoding: "utf-8",
40854
+ timeout: 12e4,
40855
+ stdio: ["pipe", "pipe", "pipe"],
40856
+ windowsHide: true
40857
+ });
40858
+ }
40859
+ return (0, import_child_process4.execFileSync)(getNpmBin(), ["install", "-g", "hermes-web-ui@latest"], {
40860
+ encoding: "utf-8",
40861
+ timeout: 12e4,
40862
+ stdio: ["pipe", "pipe", "pipe"]
40863
+ });
40864
+ }
40865
+ function spawnRestart(port) {
40866
+ if (process.platform === "win32") {
40867
+ return (0, import_child_process4.spawn)(getWindowsShell(), ["/d", "/s", "/c", `${quoteForWindowsCommand(getCliBin())} restart --port ${port}`], {
40868
+ detached: true,
40869
+ stdio: "ignore",
40870
+ windowsHide: true
40871
+ });
40872
+ }
40873
+ return (0, import_child_process4.spawn)(getCliBin(), ["restart", "--port", port], {
40874
+ detached: true,
40875
+ stdio: "ignore",
40876
+ windowsHide: true
40877
+ });
40878
+ }
40664
40879
  async function handleUpdate(ctx) {
40665
- const isWin = process.platform === "win32";
40666
- const cmd = isWin ? "cmd /c npm install -g hermes-web-ui@latest" : "npm install -g hermes-web-ui@latest";
40667
40880
  try {
40668
- const { execSync } = await import("child_process");
40669
- const output = execSync(cmd, { encoding: "utf-8", timeout: 12e4, stdio: ["pipe", "pipe", "pipe"] });
40881
+ const output = runUpdateInstall();
40670
40882
  ctx.body = { success: true, message: output.trim() };
40671
40883
  setTimeout(() => {
40672
- (0, import_child_process4.spawn)(isWin ? "cmd" : "sh", isWin ? ["/c", "hermes-web-ui restart"] : ["-c", "hermes-web-ui restart"], {
40673
- detached: true,
40674
- stdio: "ignore",
40675
- windowsHide: true
40676
- }).unref();
40884
+ spawnRestart(process.env.PORT || "8648").unref();
40677
40885
  process.exit(0);
40678
40886
  }, 2e3);
40679
40887
  } catch (err) {
@@ -40686,6 +40894,169 @@ async function handleUpdate(ctx) {
40686
40894
  var updateRoutes = new router_default();
40687
40895
  updateRoutes.post("/api/hermes/update", handleUpdate);
40688
40896
 
40897
+ // packages/server/src/services/credentials.ts
40898
+ var import_promises3 = require("fs/promises");
40899
+ var import_fs7 = require("fs");
40900
+ var import_path9 = require("path");
40901
+ var import_os7 = require("os");
40902
+ var import_node_crypto = require("node:crypto");
40903
+ var APP_HOME2 = (0, import_path9.join)((0, import_os7.homedir)(), ".hermes-web-ui");
40904
+ var CREDENTIALS_FILE = (0, import_path9.join)(APP_HOME2, ".credentials");
40905
+ var SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
40906
+ function hashPassword(password, salt) {
40907
+ return (0, import_node_crypto.scryptSync)(password, salt, 64, SCRYPT_OPTIONS).toString("hex");
40908
+ }
40909
+ async function getCredentials() {
40910
+ try {
40911
+ const data = await (0, import_promises3.readFile)(CREDENTIALS_FILE, "utf-8");
40912
+ return JSON.parse(data);
40913
+ } catch {
40914
+ return null;
40915
+ }
40916
+ }
40917
+ async function setCredentials(username, password) {
40918
+ const salt = (0, import_node_crypto.randomBytes)(16).toString("hex");
40919
+ const password_hash = hashPassword(password, salt);
40920
+ const cred = { username, password_hash, salt, created_at: Date.now() };
40921
+ await (0, import_promises3.mkdir)(APP_HOME2, { recursive: true });
40922
+ await (0, import_promises3.writeFile)(CREDENTIALS_FILE, JSON.stringify(cred, null, 2), { mode: 384 });
40923
+ return cred;
40924
+ }
40925
+ async function deleteCredentials() {
40926
+ try {
40927
+ await (0, import_promises3.unlink)(CREDENTIALS_FILE);
40928
+ } catch {
40929
+ }
40930
+ }
40931
+ async function verifyCredentials(username, password) {
40932
+ const cred = await getCredentials();
40933
+ if (!cred) return false;
40934
+ if (cred.username !== username) return false;
40935
+ const computed = hashPassword(password, cred.salt);
40936
+ return computed === cred.password_hash;
40937
+ }
40938
+ function credentialsFileExists() {
40939
+ return (0, import_fs7.existsSync)(CREDENTIALS_FILE);
40940
+ }
40941
+
40942
+ // packages/server/src/controllers/auth.ts
40943
+ async function authStatus(ctx) {
40944
+ const cred = await getCredentials();
40945
+ ctx.body = {
40946
+ hasPasswordLogin: !!cred,
40947
+ username: cred?.username || null
40948
+ };
40949
+ }
40950
+ async function login(ctx) {
40951
+ const { username, password } = ctx.request.body;
40952
+ if (!username || !password) {
40953
+ ctx.status = 400;
40954
+ ctx.body = { error: "Username and password are required" };
40955
+ return;
40956
+ }
40957
+ const valid = await verifyCredentials(username, password);
40958
+ if (!valid) {
40959
+ ctx.status = 401;
40960
+ ctx.body = { error: "Invalid username or password" };
40961
+ return;
40962
+ }
40963
+ const token = await getToken();
40964
+ if (!token) {
40965
+ ctx.status = 500;
40966
+ ctx.body = { error: "Auth is disabled on this server" };
40967
+ return;
40968
+ }
40969
+ ctx.body = { token };
40970
+ }
40971
+ async function setupPassword(ctx) {
40972
+ const { username, password } = ctx.request.body;
40973
+ if (!username || !password) {
40974
+ ctx.status = 400;
40975
+ ctx.body = { error: "Username and password are required" };
40976
+ return;
40977
+ }
40978
+ if (username.length < 2) {
40979
+ ctx.status = 400;
40980
+ ctx.body = { error: "Username must be at least 2 characters" };
40981
+ return;
40982
+ }
40983
+ if (password.length < 6) {
40984
+ ctx.status = 400;
40985
+ ctx.body = { error: "Password must be at least 6 characters" };
40986
+ return;
40987
+ }
40988
+ await setCredentials(username, password);
40989
+ ctx.body = { success: true };
40990
+ }
40991
+ async function changePassword(ctx) {
40992
+ const { currentPassword, newPassword } = ctx.request.body;
40993
+ if (!currentPassword || !newPassword) {
40994
+ ctx.status = 400;
40995
+ ctx.body = { error: "Current password and new password are required" };
40996
+ return;
40997
+ }
40998
+ if (newPassword.length < 6) {
40999
+ ctx.status = 400;
41000
+ ctx.body = { error: "New password must be at least 6 characters" };
41001
+ return;
41002
+ }
41003
+ const cred = await getCredentials();
41004
+ if (!cred) {
41005
+ ctx.status = 400;
41006
+ ctx.body = { error: "Password login not configured" };
41007
+ return;
41008
+ }
41009
+ const valid = await verifyCredentials(cred.username, currentPassword);
41010
+ if (!valid) {
41011
+ ctx.status = 400;
41012
+ ctx.body = { error: "Current password is incorrect" };
41013
+ return;
41014
+ }
41015
+ await setCredentials(cred.username, newPassword);
41016
+ ctx.body = { success: true };
41017
+ }
41018
+ async function changeUsername(ctx) {
41019
+ const { currentPassword, newUsername } = ctx.request.body;
41020
+ if (!currentPassword || !newUsername) {
41021
+ ctx.status = 400;
41022
+ ctx.body = { error: "Current password and new username are required" };
41023
+ return;
41024
+ }
41025
+ if (newUsername.length < 2) {
41026
+ ctx.status = 400;
41027
+ ctx.body = { error: "Username must be at least 2 characters" };
41028
+ return;
41029
+ }
41030
+ const cred = await getCredentials();
41031
+ if (!cred) {
41032
+ ctx.status = 400;
41033
+ ctx.body = { error: "Password login not configured" };
41034
+ return;
41035
+ }
41036
+ const valid = await verifyCredentials(cred.username, currentPassword);
41037
+ if (!valid) {
41038
+ ctx.status = 400;
41039
+ ctx.body = { error: "Current password is incorrect" };
41040
+ return;
41041
+ }
41042
+ await setCredentials(newUsername, currentPassword);
41043
+ ctx.body = { success: true };
41044
+ }
41045
+ async function removePassword(ctx) {
41046
+ await deleteCredentials();
41047
+ ctx.body = { success: true };
41048
+ }
41049
+
41050
+ // packages/server/src/routes/auth.ts
41051
+ var authPublicRoutes = new router_default();
41052
+ authPublicRoutes.get("/api/auth/status", authStatus);
41053
+ authPublicRoutes.post("/api/auth/login", login);
41054
+ var authProtectedRoutes = new router_default();
41055
+ authProtectedRoutes.post("/api/auth/setup", setupPassword);
41056
+ authProtectedRoutes.post("/api/auth/change-password", changePassword);
41057
+ authProtectedRoutes.post("/api/auth/change-username", changeUsername);
41058
+ authProtectedRoutes.delete("/api/auth/password", removePassword);
41059
+
40689
41060
  // packages/server/src/services/hermes/conversations.ts
40690
41061
  var LINEAGE_TOLERANCE_SECONDS = 3;
40691
41062
  var LIVE_WINDOW_SECONDS = 300;
@@ -40989,9 +41360,9 @@ async function getConversationDetail(sessionId, options = {}) {
40989
41360
  };
40990
41361
  }
40991
41362
 
40992
- // packages/server/src/services/hermes/sessions-db.ts
41363
+ // packages/server/src/db/hermes/sessions-db.ts
40993
41364
  init_hermes_profile();
40994
- var SQLITE_AVAILABLE = (() => {
41365
+ var SQLITE_AVAILABLE2 = (() => {
40995
41366
  const [major, minor] = process.versions.node.split(".").map(Number);
40996
41367
  return major > 22 || major === 22 && minor >= 5;
40997
41368
  })();
@@ -41041,64 +41412,301 @@ function mapRow(row) {
41041
41412
  last_active: normalizeNumber(row.last_active, startedAt)
41042
41413
  };
41043
41414
  }
41044
- var BASE_SELECT = `
41045
- SELECT
41046
- s.id,
41047
- s.source,
41048
- COALESCE(s.user_id, '') AS user_id,
41049
- COALESCE(s.model, '') AS model,
41050
- COALESCE(s.title, '') AS title,
41051
- COALESCE(s.started_at, 0) AS started_at,
41052
- s.ended_at AS ended_at,
41053
- COALESCE(s.end_reason, '') AS end_reason,
41054
- COALESCE(s.message_count, 0) AS message_count,
41055
- COALESCE(s.tool_call_count, 0) AS tool_call_count,
41056
- COALESCE(s.input_tokens, 0) AS input_tokens,
41057
- COALESCE(s.output_tokens, 0) AS output_tokens,
41058
- COALESCE(s.cache_read_tokens, 0) AS cache_read_tokens,
41059
- COALESCE(s.cache_write_tokens, 0) AS cache_write_tokens,
41060
- COALESCE(s.reasoning_tokens, 0) AS reasoning_tokens,
41061
- COALESCE(s.billing_provider, '') AS billing_provider,
41062
- COALESCE(s.estimated_cost_usd, 0) AS estimated_cost_usd,
41063
- s.actual_cost_usd AS actual_cost_usd,
41064
- COALESCE(s.cost_status, '') AS cost_status,
41065
- COALESCE(
41066
- (
41067
- SELECT SUBSTR(REPLACE(REPLACE(m.content, CHAR(10), ' '), CHAR(13), ' '), 1, 63)
41068
- FROM messages m
41069
- WHERE m.session_id = s.id AND m.role = 'user' AND m.content IS NOT NULL
41070
- ORDER BY m.timestamp, m.id
41071
- LIMIT 1
41072
- ),
41073
- ''
41074
- ) AS preview,
41075
- COALESCE((SELECT MAX(m2.timestamp) FROM messages m2 WHERE m2.session_id = s.id), s.started_at) AS last_active
41415
+ var SESSION_SELECT = `
41416
+ s.id,
41417
+ s.source,
41418
+ COALESCE(s.user_id, '') AS user_id,
41419
+ COALESCE(s.model, '') AS model,
41420
+ COALESCE(s.title, '') AS title,
41421
+ COALESCE(s.started_at, 0) AS started_at,
41422
+ s.ended_at AS ended_at,
41423
+ COALESCE(s.end_reason, '') AS end_reason,
41424
+ COALESCE(s.message_count, 0) AS message_count,
41425
+ COALESCE(s.tool_call_count, 0) AS tool_call_count,
41426
+ COALESCE(s.input_tokens, 0) AS input_tokens,
41427
+ COALESCE(s.output_tokens, 0) AS output_tokens,
41428
+ COALESCE(s.cache_read_tokens, 0) AS cache_read_tokens,
41429
+ COALESCE(s.cache_write_tokens, 0) AS cache_write_tokens,
41430
+ COALESCE(s.reasoning_tokens, 0) AS reasoning_tokens,
41431
+ COALESCE(s.billing_provider, '') AS billing_provider,
41432
+ COALESCE(s.estimated_cost_usd, 0) AS estimated_cost_usd,
41433
+ s.actual_cost_usd AS actual_cost_usd,
41434
+ COALESCE(s.cost_status, '') AS cost_status,
41435
+ COALESCE(
41436
+ (
41437
+ SELECT SUBSTR(REPLACE(REPLACE(m.content, CHAR(10), ' '), CHAR(13), ' '), 1, 63)
41438
+ FROM messages m
41439
+ WHERE m.session_id = s.id AND m.role = 'user' AND m.content IS NOT NULL
41440
+ ORDER BY m.timestamp, m.id
41441
+ LIMIT 1
41442
+ ),
41443
+ ''
41444
+ ) AS preview,
41445
+ COALESCE((SELECT MAX(m2.timestamp) FROM messages m2 WHERE m2.session_id = s.id), s.started_at) AS last_active
41446
+ `;
41447
+ var SESSION_FROM = `
41076
41448
  FROM sessions s
41077
41449
  WHERE s.parent_session_id IS NULL
41078
41450
  AND s.source != 'tool'
41079
41451
  `;
41452
+ function buildBaseSessionSql(source) {
41453
+ const sql = source ? `SELECT ${SESSION_SELECT}${SESSION_FROM}
41454
+ AND s.source = ?` : `SELECT ${SESSION_SELECT}${SESSION_FROM}`;
41455
+ return { sql, params: source ? [source] : [] };
41456
+ }
41457
+ function buildListSessionSql(source, limit = 2e3) {
41458
+ const base = buildBaseSessionSql(source);
41459
+ return {
41460
+ sql: `${base.sql}
41461
+ ORDER BY s.started_at DESC
41462
+ LIMIT ?`,
41463
+ params: [...base.params, limit]
41464
+ };
41465
+ }
41466
+ function containsCjk(text) {
41467
+ for (const ch of text) {
41468
+ const cp = ch.codePointAt(0) ?? 0;
41469
+ if (cp >= 19968 && cp <= 40959 || cp >= 13312 && cp <= 19903 || cp >= 131072 && cp <= 173791 || cp >= 12288 && cp <= 12351 || cp >= 12352 && cp <= 12447 || cp >= 12448 && cp <= 12543 || cp >= 44032 && cp <= 55215) {
41470
+ return true;
41471
+ }
41472
+ }
41473
+ return false;
41474
+ }
41475
+ function sanitizeFtsQuery(query) {
41476
+ const quotedParts = [];
41477
+ const preserved = query.replace(/"[^"]*"/g, (match) => {
41478
+ quotedParts.push(match);
41479
+ return `\0Q${quotedParts.length - 1}\0`;
41480
+ });
41481
+ let sanitized = preserved.replace(/[+{}()"^]/g, " ");
41482
+ sanitized = sanitized.replace(/\*+/g, "*");
41483
+ sanitized = sanitized.replace(/(^|\s)\*/g, "$1");
41484
+ sanitized = sanitized.trim().replace(/^(AND|OR|NOT)\b\s*/i, "");
41485
+ sanitized = sanitized.trim().replace(/\s+(AND|OR|NOT)\s*$/i, "");
41486
+ sanitized = sanitized.replace(/\b(\w+(?:[.-]\w+)+)\b/g, '"$1"');
41487
+ for (let i = 0; i < quotedParts.length; i += 1) {
41488
+ sanitized = sanitized.replace(`\0Q${i}\0`, quotedParts[i]);
41489
+ }
41490
+ return sanitized.trim();
41491
+ }
41492
+ function toPrefixQuery(query) {
41493
+ const tokens = query.match(/"[^"]*"|\S+/g);
41494
+ if (!tokens) return "";
41495
+ return tokens.map((token) => {
41496
+ if (token === "AND" || token === "OR" || token === "NOT") return token;
41497
+ if (token.startsWith('"') && token.endsWith('"')) return token;
41498
+ if (token.endsWith("*")) return token;
41499
+ return `${token}*`;
41500
+ }).join(" ");
41501
+ }
41502
+ function mapSearchRow(row) {
41503
+ return {
41504
+ ...mapRow(row),
41505
+ matched_message_id: normalizeNullableNumber(row.matched_message_id),
41506
+ snippet: String(row.snippet || row.preview || ""),
41507
+ rank: Number.isFinite(Number(row.rank)) ? Number(row.rank) : 0
41508
+ };
41509
+ }
41080
41510
  async function listSessionSummaries(source, limit = 2e3) {
41081
- if (!SQLITE_AVAILABLE) {
41511
+ if (!SQLITE_AVAILABLE2) {
41082
41512
  throw new Error(`node:sqlite requires Node >= 22.5, current: ${process.versions.node}`);
41083
41513
  }
41084
- const { DatabaseSync } = await import("node:sqlite");
41085
- const db = new DatabaseSync(sessionDbPath(), { open: true, readOnly: true });
41514
+ const { DatabaseSync: DatabaseSync2 } = await import("node:sqlite");
41515
+ const db = new DatabaseSync2(sessionDbPath(), { open: true, readOnly: true });
41086
41516
  try {
41087
- const sql = source ? `${BASE_SELECT}
41088
- AND s.source = ?
41089
- ORDER BY s.started_at DESC
41090
- LIMIT ?` : `${BASE_SELECT}
41091
- ORDER BY s.started_at DESC
41092
- LIMIT ?`;
41517
+ const { sql, params } = buildListSessionSql(source, limit);
41093
41518
  const statement = db.prepare(sql);
41094
- const rows = source ? statement.all(source, limit) : statement.all(limit);
41519
+ const rows = statement.all(...params);
41095
41520
  return rows.map(mapRow);
41096
41521
  } finally {
41097
41522
  db.close();
41098
41523
  }
41099
41524
  }
41525
+ async function searchSessionSummaries(query, source, limit = 20) {
41526
+ if (!SQLITE_AVAILABLE2) {
41527
+ throw new Error(`node:sqlite requires Node >= 22.5, current: ${process.versions.node}`);
41528
+ }
41529
+ const trimmed = query.trim();
41530
+ if (!trimmed) {
41531
+ const recent = await listSessionSummaries(source, limit);
41532
+ return recent.map((row) => ({
41533
+ ...row,
41534
+ matched_message_id: null,
41535
+ snippet: row.preview,
41536
+ rank: 0
41537
+ }));
41538
+ }
41539
+ const { DatabaseSync: DatabaseSync2 } = await import("node:sqlite");
41540
+ const db = new DatabaseSync2(sessionDbPath(), { open: true, readOnly: true });
41541
+ const normalized = sanitizeFtsQuery(trimmed);
41542
+ const prefixQuery = toPrefixQuery(normalized);
41543
+ try {
41544
+ const titleBase = buildBaseSessionSql(source);
41545
+ const contentBase = buildBaseSessionSql(source);
41546
+ const titleSql = `
41547
+ WITH base AS (
41548
+ ${titleBase.sql}
41549
+ )
41550
+ SELECT
41551
+ base.*,
41552
+ NULL AS matched_message_id,
41553
+ CASE
41554
+ WHEN base.title IS NOT NULL AND base.title != '' THEN base.title
41555
+ ELSE base.preview
41556
+ END AS snippet,
41557
+ 0 AS rank
41558
+ FROM base
41559
+ WHERE LOWER(COALESCE(base.title, '')) LIKE ?
41560
+ ORDER BY base.last_active DESC
41561
+ LIMIT ?
41562
+ `;
41563
+ const titleStatement = db.prepare(titleSql);
41564
+ const titleRows = titleStatement.all(...titleBase.params, `%${trimmed.toLowerCase()}%`, limit);
41565
+ const contentSql = `
41566
+ WITH base AS (
41567
+ ${contentBase.sql}
41568
+ )
41569
+ SELECT
41570
+ base.*,
41571
+ m.id AS matched_message_id,
41572
+ snippet(messages_fts, 0, '>>>', '<<<', '...', 40) AS snippet,
41573
+ bm25(messages_fts) AS rank
41574
+ FROM messages_fts
41575
+ JOIN messages m ON m.id = messages_fts.rowid
41576
+ JOIN base ON base.id = m.session_id
41577
+ WHERE messages_fts MATCH ?
41578
+ ORDER BY rank, base.last_active DESC
41579
+ LIMIT ?
41580
+ `;
41581
+ const contentRows = prefixQuery ? db.prepare(contentSql).all(...contentBase.params, prefixQuery, limit * 4) : [];
41582
+ const merged = /* @__PURE__ */ new Map();
41583
+ for (const row of titleRows) {
41584
+ const mapped = mapSearchRow(row);
41585
+ merged.set(mapped.id, mapped);
41586
+ }
41587
+ for (const row of contentRows) {
41588
+ const mapped = mapSearchRow(row);
41589
+ if (!merged.has(mapped.id)) {
41590
+ merged.set(mapped.id, mapped);
41591
+ }
41592
+ }
41593
+ const items = [...merged.values()];
41594
+ items.sort((a, b) => {
41595
+ if (a.rank !== b.rank) return a.rank - b.rank;
41596
+ return b.last_active - a.last_active;
41597
+ });
41598
+ return items.slice(0, limit);
41599
+ } catch (err) {
41600
+ if (containsCjk(normalized)) {
41601
+ const likeBase = buildBaseSessionSql(source);
41602
+ const likeSql = `
41603
+ WITH base AS (
41604
+ ${likeBase.sql}
41605
+ )
41606
+ SELECT
41607
+ base.*,
41608
+ m.id AS matched_message_id,
41609
+ substr(
41610
+ m.content,
41611
+ max(1, instr(m.content, ?) - 40),
41612
+ 120
41613
+ ) AS snippet,
41614
+ 0 AS rank
41615
+ FROM base
41616
+ JOIN messages m ON m.session_id = base.id
41617
+ WHERE m.content LIKE ?
41618
+ ORDER BY base.last_active DESC, m.timestamp DESC
41619
+ `;
41620
+ const likeStatement = db.prepare(likeSql);
41621
+ const likeRows = likeStatement.all(...likeBase.params, trimmed, `%${trimmed}%`);
41622
+ const merged = /* @__PURE__ */ new Map();
41623
+ for (const row of likeRows) {
41624
+ const mapped = mapSearchRow(row);
41625
+ if (!merged.has(mapped.id)) {
41626
+ merged.set(mapped.id, mapped);
41627
+ }
41628
+ }
41629
+ return [...merged.values()].slice(0, limit);
41630
+ }
41631
+ const message2 = err instanceof Error ? err.message : String(err);
41632
+ throw new Error(`Failed to search sessions: ${message2}`);
41633
+ } finally {
41634
+ db.close();
41635
+ }
41636
+ }
41637
+
41638
+ // packages/server/src/services/hermes/model-context.ts
41639
+ var import_path10 = require("path");
41640
+ var import_os8 = require("os");
41641
+ var import_fs8 = require("fs");
41642
+ var HERMES_BASE3 = (0, import_path10.resolve)((0, import_os8.homedir)(), ".hermes");
41643
+ var MODELS_DEV_CACHE = (0, import_path10.resolve)(HERMES_BASE3, "models_dev_cache.json");
41644
+ var DEFAULT_CONTEXT_LENGTH = 2e5;
41645
+ var _cache = null;
41646
+ var _cacheMtime = 0;
41647
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
41648
+ var _cacheLoadedAt = 0;
41649
+ function loadModelsDevCache() {
41650
+ if (!(0, import_fs8.existsSync)(MODELS_DEV_CACHE)) return null;
41651
+ try {
41652
+ const stat2 = (0, import_fs8.statSync)(MODELS_DEV_CACHE);
41653
+ const now = Date.now();
41654
+ if (_cache && stat2.mtimeMs === _cacheMtime && now - _cacheLoadedAt < CACHE_TTL_MS) {
41655
+ return _cache;
41656
+ }
41657
+ const raw = (0, import_fs8.readFileSync)(MODELS_DEV_CACHE, "utf-8");
41658
+ _cache = JSON.parse(raw);
41659
+ _cacheMtime = stat2.mtimeMs;
41660
+ _cacheLoadedAt = now;
41661
+ return _cache;
41662
+ } catch {
41663
+ return _cache;
41664
+ }
41665
+ }
41666
+ function getProfileDir2(profile) {
41667
+ if (!profile || profile === "default") return HERMES_BASE3;
41668
+ const dir = (0, import_path10.join)(HERMES_BASE3, "profiles", profile);
41669
+ return (0, import_fs8.existsSync)(dir) ? dir : HERMES_BASE3;
41670
+ }
41671
+ function getDefaultModel(profileDir) {
41672
+ const configPath3 = (0, import_path10.join)(profileDir, "config.yaml");
41673
+ if (!(0, import_fs8.existsSync)(configPath3)) return null;
41674
+ try {
41675
+ const content = (0, import_fs8.readFileSync)(configPath3, "utf-8");
41676
+ const match = content.match(/^model:\s*\n\s+default:\s*(.+)$/m);
41677
+ return match ? match[1].trim() : null;
41678
+ } catch {
41679
+ return null;
41680
+ }
41681
+ }
41682
+ function lookupContextFromCache(modelName) {
41683
+ const data = loadModelsDevCache();
41684
+ if (!data) return null;
41685
+ for (const prov of Object.values(data)) {
41686
+ const models = prov.models || {};
41687
+ const entry = models[modelName];
41688
+ if (entry?.limit?.context) return entry.limit.context;
41689
+ }
41690
+ const lower = modelName.toLowerCase();
41691
+ for (const prov of Object.values(data)) {
41692
+ const models = prov.models || {};
41693
+ for (const [name, entry] of Object.entries(models)) {
41694
+ if (name.toLowerCase() === lower && entry?.limit?.context) {
41695
+ return entry.limit.context;
41696
+ }
41697
+ }
41698
+ }
41699
+ return null;
41700
+ }
41701
+ function getModelContextLength(profile) {
41702
+ const profileDir = getProfileDir2(profile);
41703
+ const model = getDefaultModel(profileDir);
41704
+ if (!model) return DEFAULT_CONTEXT_LENGTH;
41705
+ return lookupContextFromCache(model) || DEFAULT_CONTEXT_LENGTH;
41706
+ }
41100
41707
 
41101
41708
  // packages/server/src/controllers/hermes/sessions.ts
41709
+ init_usage_store();
41102
41710
  init_logger();
41103
41711
  function parseHumanOnly(value) {
41104
41712
  if (typeof value !== "string") return true;
@@ -41140,6 +41748,19 @@ async function list(ctx) {
41140
41748
  const sessions2 = await listSessions(source, limit);
41141
41749
  ctx.body = { sessions: sessions2 };
41142
41750
  }
41751
+ async function search(ctx) {
41752
+ const q = typeof ctx.query.q === "string" ? ctx.query.q : "";
41753
+ const source = typeof ctx.query.source === "string" && ctx.query.source.trim() ? ctx.query.source.trim() : void 0;
41754
+ const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : void 0;
41755
+ try {
41756
+ const results = await searchSessionSummaries(q, source, limit && limit > 0 ? limit : 20);
41757
+ ctx.body = { results };
41758
+ } catch (err) {
41759
+ logger.error(err, "Hermes Session DB: search failed");
41760
+ ctx.status = 500;
41761
+ ctx.body = { error: "Failed to search sessions" };
41762
+ }
41763
+ }
41143
41764
  async function get(ctx) {
41144
41765
  const session = await getSession(ctx.params.id);
41145
41766
  if (!session) {
@@ -41156,8 +41777,26 @@ async function remove(ctx) {
41156
41777
  ctx.body = { error: "Failed to delete session" };
41157
41778
  return;
41158
41779
  }
41780
+ deleteUsage(ctx.params.id);
41159
41781
  ctx.body = { ok: true };
41160
41782
  }
41783
+ async function usageBatch(ctx) {
41784
+ const ids = ctx.query.ids;
41785
+ if (!ids) {
41786
+ ctx.body = {};
41787
+ return;
41788
+ }
41789
+ const idList = ids.split(",").filter(Boolean);
41790
+ ctx.body = getUsageBatch(idList);
41791
+ }
41792
+ async function usageSingle(ctx) {
41793
+ const result = getUsage(ctx.params.id);
41794
+ if (!result) {
41795
+ ctx.body = { input_tokens: 0, output_tokens: 0 };
41796
+ return;
41797
+ }
41798
+ ctx.body = result;
41799
+ }
41161
41800
  async function rename(ctx) {
41162
41801
  const { title } = ctx.request.body;
41163
41802
  if (!title || typeof title !== "string") {
@@ -41173,21 +41812,30 @@ async function rename(ctx) {
41173
41812
  }
41174
41813
  ctx.body = { ok: true };
41175
41814
  }
41815
+ async function contextLength(ctx) {
41816
+ const profile = ctx.query.profile || void 0;
41817
+ ctx.body = { context_length: getModelContextLength(profile) };
41818
+ }
41176
41819
 
41177
41820
  // packages/server/src/routes/hermes/sessions.ts
41178
41821
  var sessionRoutes = new router_default();
41179
41822
  sessionRoutes.get("/api/hermes/sessions/conversations", listConversations);
41180
41823
  sessionRoutes.get("/api/hermes/sessions/conversations/:id/messages", getConversationMessages);
41181
41824
  sessionRoutes.get("/api/hermes/sessions", list);
41825
+ sessionRoutes.get("/api/hermes/search/sessions", search);
41826
+ sessionRoutes.get("/api/hermes/sessions/search", search);
41827
+ sessionRoutes.get("/api/hermes/sessions/usage", usageBatch);
41828
+ sessionRoutes.get("/api/hermes/sessions/context-length", contextLength);
41182
41829
  sessionRoutes.get("/api/hermes/sessions/:id", get);
41830
+ sessionRoutes.get("/api/hermes/sessions/:id/usage", usageSingle);
41183
41831
  sessionRoutes.delete("/api/hermes/sessions/:id", remove);
41184
41832
  sessionRoutes.post("/api/hermes/sessions/:id/rename", rename);
41185
41833
 
41186
41834
  // packages/server/src/controllers/hermes/profiles.ts
41187
- var import_fs6 = require("fs");
41188
- var import_promises3 = require("fs/promises");
41189
- var import_path7 = require("path");
41190
- var import_os6 = require("os");
41835
+ var import_fs9 = require("fs");
41836
+ var import_promises4 = require("fs/promises");
41837
+ var import_path11 = require("path");
41838
+ var import_os9 = require("os");
41191
41839
  init_logger();
41192
41840
  async function list2(ctx) {
41193
41841
  try {
@@ -41294,15 +41942,15 @@ async function switchProfile(ctx) {
41294
41942
  try {
41295
41943
  const detail = await getProfile(name);
41296
41944
  logger.debug("Profile detail.path = %s", detail.path);
41297
- if (!(0, import_fs6.existsSync)((0, import_path7.join)(detail.path, "config.yaml"))) {
41945
+ if (!(0, import_fs9.existsSync)((0, import_path11.join)(detail.path, "config.yaml"))) {
41298
41946
  try {
41299
41947
  await setupReset();
41300
41948
  } catch {
41301
41949
  }
41302
41950
  }
41303
- const profileEnv = (0, import_path7.join)(detail.path, ".env");
41304
- if (!(0, import_fs6.existsSync)(profileEnv)) {
41305
- (0, import_fs6.writeFileSync)(profileEnv, "# Hermes Agent Environment Configuration\n", "utf-8");
41951
+ const profileEnv = (0, import_path11.join)(detail.path, ".env");
41952
+ if (!(0, import_fs9.existsSync)(profileEnv)) {
41953
+ (0, import_fs9.writeFileSync)(profileEnv, "# Hermes Agent Environment Configuration\n", "utf-8");
41306
41954
  logger.info("Created .env for: %s", detail.path);
41307
41955
  }
41308
41956
  } catch (err) {
@@ -41316,21 +41964,21 @@ async function switchProfile(ctx) {
41316
41964
  }
41317
41965
  async function exportProfile2(ctx) {
41318
41966
  const { name } = ctx.params;
41319
- const outputPath = (0, import_path7.join)((0, import_os6.tmpdir)(), `hermes-profile-${name}.tar.gz`);
41967
+ const outputPath = (0, import_path11.join)((0, import_os9.tmpdir)(), `hermes-profile-${name}.tar.gz`);
41320
41968
  try {
41321
41969
  await exportProfile(name, outputPath);
41322
- if (!(0, import_fs6.existsSync)(outputPath)) {
41970
+ if (!(0, import_fs9.existsSync)(outputPath)) {
41323
41971
  ctx.status = 500;
41324
41972
  ctx.body = { error: "Export file not found" };
41325
41973
  return;
41326
41974
  }
41327
- const filename = (0, import_path7.basename)(outputPath);
41975
+ const filename = (0, import_path11.basename)(outputPath);
41328
41976
  ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
41329
41977
  ctx.set("Content-Type", "application/gzip");
41330
- ctx.body = (0, import_fs6.createReadStream)(outputPath);
41978
+ ctx.body = (0, import_fs9.createReadStream)(outputPath);
41331
41979
  ctx.res.on("finish", () => {
41332
41980
  try {
41333
- (0, import_fs6.unlinkSync)(outputPath);
41981
+ (0, import_fs9.unlinkSync)(outputPath);
41334
41982
  } catch {
41335
41983
  }
41336
41984
  });
@@ -41352,8 +42000,8 @@ async function importProfile2(ctx) {
41352
42000
  ctx.body = { error: "Missing boundary" };
41353
42001
  return;
41354
42002
  }
41355
- const tmpDir = (0, import_path7.join)((0, import_os6.tmpdir)(), "hermes-import");
41356
- await (0, import_promises3.mkdir)(tmpDir, { recursive: true });
42003
+ const tmpDir = (0, import_path11.join)((0, import_os9.tmpdir)(), "hermes-import");
42004
+ await (0, import_promises4.mkdir)(tmpDir, { recursive: true });
41357
42005
  const chunks = [];
41358
42006
  for await (const chunk of ctx.req) chunks.push(chunk);
41359
42007
  const body = Buffer.concat(chunks).toString("latin1");
@@ -41369,8 +42017,8 @@ async function importProfile2(ctx) {
41369
42017
  const filename = filenameMatch[1];
41370
42018
  const ext = filename.includes(".") ? "." + filename.split(".").pop() : "";
41371
42019
  if (![".gz", ".tar.gz", ".zip", ".tgz"].includes(ext)) continue;
41372
- archivePath = (0, import_path7.join)(tmpDir, filename);
41373
- await (0, import_promises3.writeFile)(archivePath, Buffer.from(data, "binary"));
42020
+ archivePath = (0, import_path11.join)(tmpDir, filename);
42021
+ await (0, import_promises4.writeFile)(archivePath, Buffer.from(data, "binary"));
41374
42022
  break;
41375
42023
  }
41376
42024
  if (!archivePath) {
@@ -41381,13 +42029,13 @@ async function importProfile2(ctx) {
41381
42029
  try {
41382
42030
  const result = await importProfile(archivePath);
41383
42031
  try {
41384
- (0, import_fs6.unlinkSync)(archivePath);
42032
+ (0, import_fs9.unlinkSync)(archivePath);
41385
42033
  } catch {
41386
42034
  }
41387
42035
  ctx.body = { success: true, message: result.trim() };
41388
42036
  } catch (err) {
41389
42037
  try {
41390
- (0, import_fs6.unlinkSync)(archivePath);
42038
+ (0, import_fs9.unlinkSync)(archivePath);
41391
42039
  } catch {
41392
42040
  }
41393
42041
  ctx.status = 500;
@@ -41407,9 +42055,9 @@ profileRoutes.post("/api/hermes/profiles/:name/export", exportProfile2);
41407
42055
  profileRoutes.post("/api/hermes/profiles/import", importProfile2);
41408
42056
 
41409
42057
  // packages/server/src/services/config-helpers.ts
41410
- var import_promises4 = require("fs/promises");
41411
42058
  var import_promises5 = require("fs/promises");
41412
- var import_path8 = require("path");
42059
+ var import_promises6 = require("fs/promises");
42060
+ var import_path12 = require("path");
41413
42061
  init_js_yaml();
41414
42062
  init_hermes_profile();
41415
42063
  init_logger();
@@ -41443,19 +42091,19 @@ async function readConfigYaml() {
41443
42091
  }
41444
42092
  async function writeConfigYaml(config2) {
41445
42093
  const cp = configPath();
41446
- await (0, import_promises4.copyFile)(cp, cp + ".bak");
42094
+ await (0, import_promises5.copyFile)(cp, cp + ".bak");
41447
42095
  const yamlStr = jsYaml.dump(config2, {
41448
42096
  lineWidth: -1,
41449
42097
  noRefs: true,
41450
42098
  quotingType: '"'
41451
42099
  });
41452
- await (0, import_promises4.writeFile)(cp, yamlStr, "utf-8");
42100
+ await (0, import_promises5.writeFile)(cp, yamlStr, "utf-8");
41453
42101
  }
41454
42102
  async function saveEnvValue(key, value) {
41455
42103
  const envPath3 = getActiveEnvPath();
41456
42104
  let raw;
41457
42105
  try {
41458
- raw = await (0, import_promises4.readFile)(envPath3, "utf-8");
42106
+ raw = await (0, import_promises5.readFile)(envPath3, "utf-8");
41459
42107
  } catch {
41460
42108
  raw = "";
41461
42109
  }
@@ -41482,22 +42130,22 @@ async function saveEnvValue(key, value) {
41482
42130
  result.push(`${key}=${value}`);
41483
42131
  }
41484
42132
  let output = result.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "") + "\n";
41485
- await (0, import_promises4.writeFile)(envPath3, output, "utf-8");
42133
+ await (0, import_promises5.writeFile)(envPath3, output, "utf-8");
41486
42134
  try {
41487
- await (0, import_promises4.chmod)(envPath3, 384);
42135
+ await (0, import_promises5.chmod)(envPath3, 384);
41488
42136
  } catch {
41489
42137
  }
41490
42138
  }
41491
42139
  async function safeReadFile(filePath) {
41492
42140
  try {
41493
- return await (0, import_promises4.readFile)(filePath, "utf-8");
42141
+ return await (0, import_promises5.readFile)(filePath, "utf-8");
41494
42142
  } catch {
41495
42143
  return null;
41496
42144
  }
41497
42145
  }
41498
42146
  async function safeStat(filePath) {
41499
42147
  try {
41500
- const s = await (0, import_promises5.stat)(filePath);
42148
+ const s = await (0, import_promises6.stat)(filePath);
41501
42149
  return { mtime: Math.round(s.mtimeMs) };
41502
42150
  } catch {
41503
42151
  return null;
@@ -41529,14 +42177,14 @@ async function listFilesRecursive(dir, prefix) {
41529
42177
  const result = [];
41530
42178
  let entries;
41531
42179
  try {
41532
- entries = await (0, import_promises5.readdir)(dir, { withFileTypes: true });
42180
+ entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
41533
42181
  } catch {
41534
42182
  return result;
41535
42183
  }
41536
42184
  for (const entry of entries) {
41537
42185
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
41538
42186
  if (entry.isDirectory()) {
41539
- result.push(...await listFilesRecursive((0, import_path8.join)(dir, entry.name), relPath));
42187
+ result.push(...await listFilesRecursive((0, import_path12.join)(dir, entry.name), relPath));
41540
42188
  } else {
41541
42189
  result.push({ path: relPath, name: entry.name });
41542
42190
  }
@@ -41596,25 +42244,25 @@ function buildModelGroups(config2) {
41596
42244
  var getHermesDir = () => getActiveProfileDir();
41597
42245
 
41598
42246
  // packages/server/src/controllers/hermes/skills.ts
41599
- var import_promises6 = require("fs/promises");
41600
- var import_path9 = require("path");
42247
+ var import_promises7 = require("fs/promises");
42248
+ var import_path13 = require("path");
41601
42249
  async function list3(ctx) {
41602
- const skillsDir = (0, import_path9.join)(getHermesDir(), "skills");
42250
+ const skillsDir = (0, import_path13.join)(getHermesDir(), "skills");
41603
42251
  try {
41604
42252
  const config2 = await readConfigYaml();
41605
42253
  const disabledList = config2.skills?.disabled || [];
41606
- const entries = await (0, import_promises6.readdir)(skillsDir, { withFileTypes: true });
42254
+ const entries = await (0, import_promises7.readdir)(skillsDir, { withFileTypes: true });
41607
42255
  const categories = [];
41608
42256
  for (const entry of entries) {
41609
42257
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
41610
- const catDir = (0, import_path9.join)(skillsDir, entry.name);
41611
- const catDesc = await safeReadFile((0, import_path9.join)(catDir, "DESCRIPTION.md"));
42258
+ const catDir = (0, import_path13.join)(skillsDir, entry.name);
42259
+ const catDesc = await safeReadFile((0, import_path13.join)(catDir, "DESCRIPTION.md"));
41612
42260
  const catDescription = catDesc ? catDesc.trim().split("\n")[0].replace(/^#+\s*/, "").slice(0, 100) : "";
41613
- const skillEntries = await (0, import_promises6.readdir)(catDir, { withFileTypes: true });
42261
+ const skillEntries = await (0, import_promises7.readdir)(catDir, { withFileTypes: true });
41614
42262
  const skills = [];
41615
42263
  for (const se of skillEntries) {
41616
42264
  if (!se.isDirectory()) continue;
41617
- const skillMd = await safeReadFile((0, import_path9.join)(catDir, se.name, "SKILL.md"));
42265
+ const skillMd = await safeReadFile((0, import_path13.join)(catDir, se.name, "SKILL.md"));
41618
42266
  if (skillMd) {
41619
42267
  skills.push({ name: se.name, description: extractDescription(skillMd), enabled: !disabledList.includes(se.name) });
41620
42268
  }
@@ -41660,7 +42308,7 @@ async function toggle(ctx) {
41660
42308
  }
41661
42309
  async function listFiles(ctx) {
41662
42310
  const { category, skill } = ctx.params;
41663
- const skillDir = (0, import_path9.join)(getHermesDir(), "skills", category, skill);
42311
+ const skillDir = (0, import_path13.join)(getHermesDir(), "skills", category, skill);
41664
42312
  try {
41665
42313
  const allFiles = await listFilesRecursive(skillDir, "");
41666
42314
  const files = allFiles.filter((f) => f.path !== "SKILL.md");
@@ -41673,8 +42321,8 @@ async function listFiles(ctx) {
41673
42321
  async function readFile_(ctx) {
41674
42322
  const filePath = ctx.params.path;
41675
42323
  const hd = getHermesDir();
41676
- const fullPath = (0, import_path9.resolve)((0, import_path9.join)(hd, "skills", filePath));
41677
- if (!fullPath.startsWith((0, import_path9.join)(hd, "skills"))) {
42324
+ const fullPath = (0, import_path13.resolve)((0, import_path13.join)(hd, "skills", filePath));
42325
+ if (!fullPath.startsWith((0, import_path13.join)(hd, "skills"))) {
41678
42326
  ctx.status = 403;
41679
42327
  ctx.body = { error: "Access denied" };
41680
42328
  return;
@@ -41696,13 +42344,13 @@ skillRoutes.get("/api/hermes/skills/:category/:skill/files", listFiles);
41696
42344
  skillRoutes.get("/api/hermes/skills/{*path}", readFile_);
41697
42345
 
41698
42346
  // packages/server/src/controllers/hermes/memory.ts
41699
- var import_promises7 = require("fs/promises");
41700
- var import_path10 = require("path");
42347
+ var import_promises8 = require("fs/promises");
42348
+ var import_path14 = require("path");
41701
42349
  async function get3(ctx) {
41702
42350
  const hd = getHermesDir();
41703
- const memoryPath = (0, import_path10.join)(hd, "memories", "MEMORY.md");
41704
- const userPath = (0, import_path10.join)(hd, "memories", "USER.md");
41705
- const soulPath = (0, import_path10.join)(hd, "SOUL.md");
42351
+ const memoryPath = (0, import_path14.join)(hd, "memories", "MEMORY.md");
42352
+ const userPath = (0, import_path14.join)(hd, "memories", "USER.md");
42353
+ const soulPath = (0, import_path14.join)(hd, "SOUL.md");
41706
42354
  const [memory, user, soul, memoryStat, userStat, soulStat] = await Promise.all([
41707
42355
  safeReadFile(memoryPath),
41708
42356
  safeReadFile(userPath),
@@ -41734,13 +42382,13 @@ async function save(ctx) {
41734
42382
  }
41735
42383
  let filePath;
41736
42384
  if (section === "soul") {
41737
- filePath = (0, import_path10.join)(getHermesDir(), "SOUL.md");
42385
+ filePath = (0, import_path14.join)(getHermesDir(), "SOUL.md");
41738
42386
  } else {
41739
42387
  const fileName = section === "memory" ? "MEMORY.md" : "USER.md";
41740
- filePath = (0, import_path10.join)(getHermesDir(), "memories", fileName);
42388
+ filePath = (0, import_path14.join)(getHermesDir(), "memories", fileName);
41741
42389
  }
41742
42390
  try {
41743
- await (0, import_promises7.writeFile)(filePath, content, "utf-8");
42391
+ await (0, import_promises8.writeFile)(filePath, content, "utf-8");
41744
42392
  ctx.body = { success: true };
41745
42393
  } catch (err) {
41746
42394
  ctx.status = 500;
@@ -42009,8 +42657,8 @@ function buildProviderModelMap() {
42009
42657
  }
42010
42658
 
42011
42659
  // packages/server/src/controllers/hermes/models.ts
42012
- var import_promises8 = require("fs/promises");
42013
- var import_fs8 = require("fs");
42660
+ var import_promises9 = require("fs/promises");
42661
+ var import_fs11 = require("fs");
42014
42662
  init_hermes_profile();
42015
42663
  var PROVIDER_MODEL_CATALOG = buildProviderModelMap();
42016
42664
  async function getAvailable(ctx) {
@@ -42029,7 +42677,7 @@ async function getAvailable(ctx) {
42029
42677
  const seenProviders = /* @__PURE__ */ new Set();
42030
42678
  let envContent = "";
42031
42679
  try {
42032
- envContent = await (0, import_promises8.readFile)(getActiveEnvPath(), "utf-8");
42680
+ envContent = await (0, import_promises9.readFile)(getActiveEnvPath(), "utf-8");
42033
42681
  } catch {
42034
42682
  }
42035
42683
  const envHasValue = (key) => {
@@ -42050,8 +42698,8 @@ async function getAvailable(ctx) {
42050
42698
  const isOAuthAuthorized = (providerKey) => {
42051
42699
  try {
42052
42700
  const authPath = getActiveAuthPath();
42053
- if (!(0, import_fs8.existsSync)(authPath)) return false;
42054
- const auth = JSON.parse((0, import_fs8.readFileSync)(authPath, "utf-8"));
42701
+ if (!(0, import_fs11.existsSync)(authPath)) return false;
42702
+ const auth = JSON.parse((0, import_fs11.readFileSync)(authPath, "utf-8"));
42055
42703
  return !!auth.providers?.[providerKey]?.tokens?.access_token;
42056
42704
  } catch {
42057
42705
  return false;
@@ -42152,8 +42800,8 @@ modelRoutes.get("/api/hermes/config/models", getConfigModels);
42152
42800
  modelRoutes.put("/api/hermes/config/model", setConfigModel);
42153
42801
 
42154
42802
  // packages/server/src/controllers/hermes/providers.ts
42155
- var import_fs9 = require("fs");
42156
- var import_promises9 = require("fs/promises");
42803
+ var import_fs12 = require("fs");
42804
+ var import_promises10 = require("fs/promises");
42157
42805
  init_hermes_profile();
42158
42806
  init_logger();
42159
42807
  async function create2(ctx) {
@@ -42303,15 +42951,15 @@ async function remove3(ctx) {
42303
42951
  } else if (!envMapping?.api_key_env) {
42304
42952
  try {
42305
42953
  const authPath = getActiveAuthPath();
42306
- if ((0, import_fs9.existsSync)(authPath)) {
42307
- const auth = JSON.parse((0, import_fs9.readFileSync)(authPath, "utf-8"));
42954
+ if ((0, import_fs12.existsSync)(authPath)) {
42955
+ const auth = JSON.parse((0, import_fs12.readFileSync)(authPath, "utf-8"));
42308
42956
  if (auth.providers?.[poolKey]) {
42309
42957
  delete auth.providers[poolKey];
42310
42958
  }
42311
42959
  if (auth.credential_pool?.[poolKey]) {
42312
42960
  delete auth.credential_pool[poolKey];
42313
42961
  }
42314
- await (0, import_promises9.writeFile)(authPath, JSON.stringify(auth, null, 2) + "\n", "utf-8");
42962
+ await (0, import_promises10.writeFile)(authPath, JSON.stringify(auth, null, 2) + "\n", "utf-8");
42315
42963
  }
42316
42964
  } catch (err) {
42317
42965
  logger.error(err, "Failed to clear OAuth tokens for %s", poolKey);
@@ -42357,7 +43005,7 @@ providerRoutes.put("/api/hermes/config/providers/:poolKey", update);
42357
43005
  providerRoutes.delete("/api/hermes/config/providers/:poolKey", remove3);
42358
43006
 
42359
43007
  // packages/server/src/controllers/hermes/config.ts
42360
- var import_promises10 = require("fs/promises");
43008
+ var import_promises11 = require("fs/promises");
42361
43009
  init_js_yaml();
42362
43010
  init_hermes_profile();
42363
43011
  var PLATFORM_SECTIONS = /* @__PURE__ */ new Set([
@@ -42430,7 +43078,7 @@ function deepMerge2(target, source) {
42430
43078
  }
42431
43079
  async function readEnvPlatforms() {
42432
43080
  try {
42433
- const raw = await (0, import_promises10.readFile)(envPath(), "utf-8");
43081
+ const raw = await (0, import_promises11.readFile)(envPath(), "utf-8");
42434
43082
  const env = parseEnv(raw);
42435
43083
  const platforms = {};
42436
43084
  for (const [envKey, [platform, cfgPath]] of Object.entries(envPlatformMap)) {
@@ -42447,14 +43095,14 @@ async function readEnvPlatforms() {
42447
43095
  }
42448
43096
  }
42449
43097
  async function readConfig() {
42450
- const raw = await (0, import_promises10.readFile)(configPath2(), "utf-8");
43098
+ const raw = await (0, import_promises11.readFile)(configPath2(), "utf-8");
42451
43099
  return jsYaml.load(raw) || {};
42452
43100
  }
42453
43101
  async function writeConfig(data) {
42454
43102
  const cp = configPath2();
42455
- await (0, import_promises10.copyFile)(cp, cp + ".bak");
43103
+ await (0, import_promises11.copyFile)(cp, cp + ".bak");
42456
43104
  const yamlStr = jsYaml.dump(data, { lineWidth: -1, noRefs: true, quotingType: '"', forceQuotes: false });
42457
- await (0, import_promises10.writeFile)(cp, yamlStr, "utf-8");
43105
+ await (0, import_promises11.writeFile)(cp, yamlStr, "utf-8");
42458
43106
  }
42459
43107
  async function getConfig(ctx) {
42460
43108
  try {
@@ -42578,11 +43226,11 @@ configRoutes.put("/api/hermes/config", updateConfig);
42578
43226
  configRoutes.put("/api/hermes/config/credentials", updateCredentials);
42579
43227
 
42580
43228
  // packages/server/src/controllers/hermes/logs.ts
42581
- var import_fs10 = require("fs");
42582
- var import_promises11 = require("fs/promises");
42583
- var import_path11 = require("path");
42584
- var import_os7 = require("os");
42585
- var WEBUI_LOG_FILE = (0, import_path11.join)((0, import_os7.homedir)(), ".hermes-web-ui", "logs", "server.log");
43229
+ var import_fs13 = require("fs");
43230
+ var import_promises12 = require("fs/promises");
43231
+ var import_path15 = require("path");
43232
+ var import_os10 = require("os");
43233
+ var WEBUI_LOG_FILE = (0, import_path15.join)((0, import_os10.homedir)(), ".hermes-web-ui", "logs", "server.log");
42586
43234
  function parseLine(line) {
42587
43235
  try {
42588
43236
  const obj = JSON.parse(line);
@@ -42605,9 +43253,9 @@ function parseLine(line) {
42605
43253
  }
42606
43254
  async function list4(ctx) {
42607
43255
  const files = await listLogFiles();
42608
- if ((0, import_fs10.existsSync)(WEBUI_LOG_FILE)) {
43256
+ if ((0, import_fs13.existsSync)(WEBUI_LOG_FILE)) {
42609
43257
  try {
42610
- const stat2 = (0, import_fs10.statSync)(WEBUI_LOG_FILE);
43258
+ const stat2 = (0, import_fs13.statSync)(WEBUI_LOG_FILE);
42611
43259
  const size = stat2.size > 1024 * 1024 ? `${(stat2.size / 1024 / 1024).toFixed(1)}MB` : `${(stat2.size / 1024).toFixed(1)}KB`;
42612
43260
  const modified = stat2.mtime.toLocaleString();
42613
43261
  files.push({ name: "webui", size, modified });
@@ -42624,11 +43272,11 @@ async function read(ctx) {
42624
43272
  const since = ctx.query.since || void 0;
42625
43273
  if (logName === "webui") {
42626
43274
  try {
42627
- if (!(0, import_fs10.existsSync)(WEBUI_LOG_FILE)) {
43275
+ if (!(0, import_fs13.existsSync)(WEBUI_LOG_FILE)) {
42628
43276
  ctx.body = { entries: [] };
42629
43277
  return;
42630
43278
  }
42631
- const content = await (0, import_promises11.readFile)(WEBUI_LOG_FILE, "utf-8");
43279
+ const content = await (0, import_promises12.readFile)(WEBUI_LOG_FILE, "utf-8");
42632
43280
  const rawLines = content.split("\n");
42633
43281
  const sliced = rawLines.length > lines ? rawLines.slice(-lines) : rawLines;
42634
43282
  const entries = [];
@@ -42665,9 +43313,9 @@ logRoutes.get("/api/hermes/logs/:name", read);
42665
43313
 
42666
43314
  // packages/server/src/controllers/hermes/codex-auth.ts
42667
43315
  var import_crypto3 = require("crypto");
42668
- var import_path12 = require("path");
42669
- var import_os8 = require("os");
42670
- var import_fs11 = require("fs");
43316
+ var import_path16 = require("path");
43317
+ var import_os11 = require("os");
43318
+ var import_fs14 = require("fs");
42671
43319
  init_hermes_profile();
42672
43320
  init_logger();
42673
43321
  var CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
@@ -42677,7 +43325,7 @@ var CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
42677
43325
  var CODEX_DEFAULT_BASE_URL = "https://chatgpt.com/backend-api/codex";
42678
43326
  var CODEX_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback";
42679
43327
  var CODEX_VERIFICATION_URL = "https://auth.openai.com/codex/device";
42680
- var CODEX_HOME = (0, import_path12.join)((0, import_os8.homedir)(), ".codex");
43328
+ var CODEX_HOME = (0, import_path16.join)((0, import_os11.homedir)(), ".codex");
42681
43329
  var POLL_MAX_DURATION = 15 * 60 * 1e3;
42682
43330
  var POLL_DEFAULT_INTERVAL = 5e3;
42683
43331
  var sessions = /* @__PURE__ */ new Map();
@@ -42691,7 +43339,7 @@ function cleanupExpiredSessions() {
42691
43339
  }
42692
43340
  function loadAuthJson(authPath) {
42693
43341
  try {
42694
- return JSON.parse((0, import_fs11.readFileSync)(authPath, "utf-8"));
43342
+ return JSON.parse((0, import_fs14.readFileSync)(authPath, "utf-8"));
42695
43343
  } catch {
42696
43344
  return { version: 1 };
42697
43345
  }
@@ -42699,15 +43347,15 @@ function loadAuthJson(authPath) {
42699
43347
  function saveAuthJson(authPath, data) {
42700
43348
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
42701
43349
  const dir = authPath.substring(0, authPath.lastIndexOf("/"));
42702
- if (!(0, import_fs11.existsSync)(dir)) (0, import_fs11.mkdirSync)(dir, { recursive: true });
42703
- (0, import_fs11.writeFileSync)(authPath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
43350
+ if (!(0, import_fs14.existsSync)(dir)) (0, import_fs14.mkdirSync)(dir, { recursive: true });
43351
+ (0, import_fs14.writeFileSync)(authPath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
42704
43352
  }
42705
43353
  function saveCodexCliTokens(accessToken, refreshToken) {
42706
43354
  const codexHome = process.env.CODEX_HOME || CODEX_HOME;
42707
- const codexAuthPath = (0, import_path12.join)(codexHome, "auth.json");
43355
+ const codexAuthPath = (0, import_path16.join)(codexHome, "auth.json");
42708
43356
  const dir = codexAuthPath.substring(0, codexAuthPath.lastIndexOf("/"));
42709
- if (!(0, import_fs11.existsSync)(dir)) (0, import_fs11.mkdirSync)(dir, { recursive: true });
42710
- (0, import_fs11.writeFileSync)(codexAuthPath, JSON.stringify({ tokens: { access_token: accessToken, refresh_token: refreshToken }, last_refresh: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n", { mode: 384 });
43357
+ if (!(0, import_fs14.existsSync)(dir)) (0, import_fs14.mkdirSync)(dir, { recursive: true });
43358
+ (0, import_fs14.writeFileSync)(codexAuthPath, JSON.stringify({ tokens: { access_token: accessToken, refresh_token: refreshToken }, last_refresh: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n", { mode: 384 });
42711
43359
  }
42712
43360
  function decodeJwtExp(token) {
42713
43361
  try {
@@ -42724,7 +43372,7 @@ async function codexLoginWorker(session, authPath) {
42724
43372
  const startTime = Date.now();
42725
43373
  const interval = POLL_DEFAULT_INTERVAL;
42726
43374
  while (Date.now() - startTime < POLL_MAX_DURATION) {
42727
- await new Promise((resolve8) => setTimeout(resolve8, interval));
43375
+ await new Promise((resolve10) => setTimeout(resolve10, interval));
42728
43376
  if (session.status !== "pending") return;
42729
43377
  try {
42730
43378
  const pollRes = await fetch(CODEX_DEVICE_TOKEN_URL, {
@@ -44293,10 +44941,10 @@ var CanceledError = class extends AxiosError_default {
44293
44941
  var CanceledError_default = CanceledError;
44294
44942
 
44295
44943
  // node_modules/axios/lib/core/settle.js
44296
- function settle(resolve8, reject, response) {
44944
+ function settle(resolve10, reject, response) {
44297
44945
  const validateStatus2 = response.config.validateStatus;
44298
44946
  if (!response.status || !validateStatus2 || validateStatus2(response.status)) {
44299
- resolve8(response);
44947
+ resolve10(response);
44300
44948
  } else {
44301
44949
  reject(
44302
44950
  new AxiosError_default(
@@ -45083,7 +45731,7 @@ function setProxy(options, configProxy, location) {
45083
45731
  }
45084
45732
  var isHttpAdapterSupported = typeof process !== "undefined" && utils_default.kindOf(process) === "process";
45085
45733
  var wrapAsync = (asyncExecutor) => {
45086
- return new Promise((resolve8, reject) => {
45734
+ return new Promise((resolve10, reject) => {
45087
45735
  let onDone;
45088
45736
  let isDone;
45089
45737
  const done = (value, isRejected) => {
@@ -45093,7 +45741,7 @@ var wrapAsync = (asyncExecutor) => {
45093
45741
  };
45094
45742
  const _resolve = (value) => {
45095
45743
  done(value);
45096
- resolve8(value);
45744
+ resolve10(value);
45097
45745
  };
45098
45746
  const _reject = (reason) => {
45099
45747
  done(reason, true);
@@ -45140,7 +45788,7 @@ var http2Transport = {
45140
45788
  }
45141
45789
  };
45142
45790
  var http_default = isHttpAdapterSupported && function httpAdapter(config2) {
45143
- return wrapAsync(async function dispatchHttpRequest(resolve8, reject, onDone) {
45791
+ return wrapAsync(async function dispatchHttpRequest(resolve10, reject, onDone) {
45144
45792
  let { data, lookup, family, httpVersion = 1, http2Options } = config2;
45145
45793
  const { responseType, responseEncoding } = config2;
45146
45794
  const method = config2.method.toUpperCase();
@@ -45230,7 +45878,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config2) {
45230
45878
  }
45231
45879
  let convertedData;
45232
45880
  if (method !== "GET") {
45233
- return settle(resolve8, reject, {
45881
+ return settle(resolve10, reject, {
45234
45882
  status: 405,
45235
45883
  statusText: "method not allowed",
45236
45884
  headers: {},
@@ -45252,7 +45900,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config2) {
45252
45900
  } else if (responseType === "stream") {
45253
45901
  convertedData = import_stream5.default.Readable.from(convertedData);
45254
45902
  }
45255
- return settle(resolve8, reject, {
45903
+ return settle(resolve10, reject, {
45256
45904
  data: convertedData,
45257
45905
  status: 200,
45258
45906
  statusText: "OK",
@@ -45493,7 +46141,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config2) {
45493
46141
  };
45494
46142
  if (responseType === "stream") {
45495
46143
  response.data = responseStream;
45496
- settle(resolve8, reject, response);
46144
+ settle(resolve10, reject, response);
45497
46145
  } else {
45498
46146
  const responseBuffer = [];
45499
46147
  let totalResponseBytes = 0;
@@ -45543,7 +46191,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config2) {
45543
46191
  } catch (err) {
45544
46192
  return reject(AxiosError_default.from(err, null, config2, response.request, response));
45545
46193
  }
45546
- settle(resolve8, reject, response);
46194
+ settle(resolve10, reject, response);
45547
46195
  });
45548
46196
  }
45549
46197
  abortEmitter.once("abort", (err) => {
@@ -45804,7 +46452,7 @@ var resolveConfig_default = (config2) => {
45804
46452
  // node_modules/axios/lib/adapters/xhr.js
45805
46453
  var isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined";
45806
46454
  var xhr_default = isXHRAdapterSupported && function(config2) {
45807
- return new Promise(function dispatchXhrRequest(resolve8, reject) {
46455
+ return new Promise(function dispatchXhrRequest(resolve10, reject) {
45808
46456
  const _config = resolveConfig_default(config2);
45809
46457
  let requestData = _config.data;
45810
46458
  const requestHeaders = AxiosHeaders_default.from(_config.headers).normalize();
@@ -45839,7 +46487,7 @@ var xhr_default = isXHRAdapterSupported && function(config2) {
45839
46487
  };
45840
46488
  settle(
45841
46489
  function _resolve(value) {
45842
- resolve8(value);
46490
+ resolve10(value);
45843
46491
  done();
45844
46492
  },
45845
46493
  function _reject(err) {
@@ -46239,8 +46887,8 @@ var factory = (env) => {
46239
46887
  config2
46240
46888
  );
46241
46889
  !isStreamResponse && unsubscribe && unsubscribe();
46242
- return await new Promise((resolve8, reject) => {
46243
- settle(resolve8, reject, {
46890
+ return await new Promise((resolve10, reject) => {
46891
+ settle(resolve10, reject, {
46244
46892
  data: responseData,
46245
46893
  headers: AxiosHeaders_default.from(response.headers),
46246
46894
  status: response.status,
@@ -46666,8 +47314,8 @@ var CancelToken = class _CancelToken {
46666
47314
  throw new TypeError("executor must be a function.");
46667
47315
  }
46668
47316
  let resolvePromise;
46669
- this.promise = new Promise(function promiseExecutor(resolve8) {
46670
- resolvePromise = resolve8;
47317
+ this.promise = new Promise(function promiseExecutor(resolve10) {
47318
+ resolvePromise = resolve10;
46671
47319
  });
46672
47320
  const token = this;
46673
47321
  this.promise.then((cancel) => {
@@ -46680,9 +47328,9 @@ var CancelToken = class _CancelToken {
46680
47328
  });
46681
47329
  this.promise.then = (onfulfilled) => {
46682
47330
  let _resolve;
46683
- const promise = new Promise((resolve8) => {
46684
- token.subscribe(resolve8);
46685
- _resolve = resolve8;
47331
+ const promise = new Promise((resolve10) => {
47332
+ token.subscribe(resolve10);
47333
+ _resolve = resolve10;
46686
47334
  }).then(onfulfilled);
46687
47335
  promise.cancel = function reject() {
46688
47336
  token.unsubscribe(_resolve);
@@ -46900,7 +47548,7 @@ var {
46900
47548
  } = axios_default;
46901
47549
 
46902
47550
  // packages/server/src/controllers/hermes/weixin.ts
46903
- var import_promises12 = require("fs/promises");
47551
+ var import_promises13 = require("fs/promises");
46904
47552
  init_hermes_profile();
46905
47553
  var ILINK_BASE = "https://ilinkai.weixin.qq.com";
46906
47554
  var envPath2 = () => getActiveEnvPath();
@@ -46950,7 +47598,7 @@ async function save2(ctx) {
46950
47598
  try {
46951
47599
  let raw;
46952
47600
  try {
46953
- raw = await (0, import_promises12.readFile)(envPath2(), "utf-8");
47601
+ raw = await (0, import_promises13.readFile)(envPath2(), "utf-8");
46954
47602
  } catch {
46955
47603
  raw = "";
46956
47604
  }
@@ -46983,9 +47631,9 @@ async function save2(ctx) {
46983
47631
  }
46984
47632
  let output = result.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "") + "\n";
46985
47633
  const ep = envPath2();
46986
- await (0, import_promises12.writeFile)(ep, output, "utf-8");
47634
+ await (0, import_promises13.writeFile)(ep, output, "utf-8");
46987
47635
  try {
46988
- await (0, import_promises12.chmod)(ep, 384);
47636
+ await (0, import_promises13.chmod)(ep, 384);
46989
47637
  } catch {
46990
47638
  }
46991
47639
  await restartGateway();
@@ -47003,9 +47651,18 @@ weixinRoutes.get("/api/hermes/weixin/qrcode/status", pollStatus);
47003
47651
  weixinRoutes.post("/api/hermes/weixin/save", save2);
47004
47652
 
47005
47653
  // packages/server/src/routes/hermes/proxy-handler.ts
47654
+ init_usage_store();
47006
47655
  function getGatewayManager() {
47007
47656
  return getGatewayManagerInstance();
47008
47657
  }
47658
+ var runSessionMap = /* @__PURE__ */ new Map();
47659
+ function setRunSession(runId, sessionId) {
47660
+ runSessionMap.set(runId, sessionId);
47661
+ setTimeout(() => runSessionMap.delete(runId), 30 * 60 * 1e3);
47662
+ }
47663
+ function getSessionForRun(runId) {
47664
+ return runSessionMap.get(runId);
47665
+ }
47009
47666
  function isTransientGatewayError(err) {
47010
47667
  const msg = String(err?.message || "");
47011
47668
  const causeCode = String(err?.cause?.code || "");
@@ -47023,7 +47680,7 @@ async function waitForGatewayReady(upstream, timeoutMs = 5e3) {
47023
47680
  if (res.ok) return true;
47024
47681
  } catch {
47025
47682
  }
47026
- await new Promise((resolve8) => setTimeout(resolve8, 250));
47683
+ await new Promise((resolve10) => setTimeout(resolve10, 250));
47027
47684
  }
47028
47685
  return false;
47029
47686
  }
@@ -47041,14 +47698,7 @@ function resolveUpstream(ctx) {
47041
47698
  }
47042
47699
  return config.upstream.replace(/\/$/, "");
47043
47700
  }
47044
- async function proxy(ctx) {
47045
- const profile = resolveProfile(ctx);
47046
- const upstream = resolveUpstream(ctx);
47047
- const upstreamPath = ctx.path.replace(/^\/api\/hermes\/v1/, "/v1").replace(/^\/api\/hermes/, "/api");
47048
- const params = new URLSearchParams(ctx.search || "");
47049
- params.delete("token");
47050
- const search = params.toString();
47051
- const url2 = `${upstream}${upstreamPath}${search ? `?${search}` : ""}`;
47701
+ function buildProxyHeaders(ctx, upstream) {
47052
47702
  const headers = {};
47053
47703
  for (const [key, value] of Object.entries(ctx.headers)) {
47054
47704
  if (value == null) continue;
@@ -47064,21 +47714,80 @@ async function proxy(ctx) {
47064
47714
  }
47065
47715
  const mgr = getGatewayManager();
47066
47716
  if (mgr) {
47067
- const apiKey = mgr.getApiKey(profile);
47717
+ const apiKey = mgr.getApiKey(resolveProfile(ctx));
47068
47718
  if (apiKey) {
47069
47719
  headers["authorization"] = `Bearer ${apiKey}`;
47070
47720
  }
47071
47721
  }
47722
+ return headers;
47723
+ }
47724
+ var SSE_EVENTS_PATH = /^\/v1\/runs\/([^/]+)\/events$/;
47725
+ function extractRunCompletedFromChunk(chunk) {
47726
+ const lines = chunk.split("\n");
47727
+ for (const line of lines) {
47728
+ if (!line.startsWith("data: ")) continue;
47729
+ try {
47730
+ const data = JSON.parse(line.slice(6));
47731
+ if (data.event === "run.completed" && data.usage && data.run_id) {
47732
+ const sessionId = getSessionForRun(data.run_id);
47733
+ if (sessionId) {
47734
+ updateUsage(sessionId, data.usage.input_tokens, data.usage.output_tokens);
47735
+ return data.run_id;
47736
+ }
47737
+ }
47738
+ } catch {
47739
+ }
47740
+ }
47741
+ return null;
47742
+ }
47743
+ async function streamSSE(ctx, res) {
47744
+ if (!res.body) {
47745
+ ctx.res.end();
47746
+ return;
47747
+ }
47748
+ const reader = res.body.getReader();
47749
+ const decoder = new TextDecoder();
47750
+ let buffer = "";
47751
+ try {
47752
+ while (true) {
47753
+ const { done, value } = await reader.read();
47754
+ if (done) break;
47755
+ ctx.res.write(value);
47756
+ buffer += decoder.decode(value, { stream: true });
47757
+ let newlineIdx;
47758
+ while ((newlineIdx = buffer.indexOf("\n\n")) !== -1) {
47759
+ const eventBlock = buffer.slice(0, newlineIdx);
47760
+ buffer = buffer.slice(newlineIdx + 2);
47761
+ extractRunCompletedFromChunk(eventBlock);
47762
+ }
47763
+ }
47764
+ if (buffer.trim()) {
47765
+ extractRunCompletedFromChunk(buffer);
47766
+ }
47767
+ } finally {
47768
+ ctx.res.end();
47769
+ }
47770
+ }
47771
+ async function proxy(ctx) {
47772
+ const profile = resolveProfile(ctx);
47773
+ const upstream = resolveUpstream(ctx);
47774
+ const upstreamPath = ctx.path.replace(/^\/api\/hermes\/v1/, "/v1").replace(/^\/api\/hermes/, "/api");
47775
+ const params = new URLSearchParams(ctx.search || "");
47776
+ params.delete("token");
47777
+ const search2 = params.toString();
47778
+ const url2 = `${upstream}${upstreamPath}${search2 ? `?${search2}` : ""}`;
47779
+ const headers = buildProxyHeaders(ctx, upstream);
47072
47780
  try {
47073
47781
  let body;
47074
47782
  if (ctx.req.method !== "GET" && ctx.req.method !== "HEAD") {
47075
- body = ctx.request.rawBody;
47783
+ const parsed = ctx.request.body;
47784
+ if (typeof parsed === "string") {
47785
+ body = parsed;
47786
+ } else if (parsed && typeof parsed === "object") {
47787
+ body = JSON.stringify(parsed);
47788
+ }
47076
47789
  }
47077
- const requestInit = {
47078
- method: ctx.req.method,
47079
- headers,
47080
- body
47081
- };
47790
+ const requestInit = { method: ctx.req.method, headers, body };
47082
47791
  let res;
47083
47792
  try {
47084
47793
  res = await fetch(url2, requestInit);
@@ -47096,6 +47805,30 @@ async function proxy(ctx) {
47096
47805
  }
47097
47806
  });
47098
47807
  ctx.status = res.status;
47808
+ if (ctx.req.method === "POST" && /\/v1\/runs$/.test(upstreamPath) && body) {
47809
+ try {
47810
+ const parsed = JSON.parse(body);
47811
+ if (parsed.session_id) {
47812
+ const resBody = await res.text();
47813
+ ctx.res.write(resBody);
47814
+ ctx.res.end();
47815
+ try {
47816
+ const result = JSON.parse(resBody);
47817
+ if (result.run_id) {
47818
+ setRunSession(result.run_id, parsed.session_id);
47819
+ }
47820
+ } catch {
47821
+ }
47822
+ return;
47823
+ }
47824
+ } catch {
47825
+ }
47826
+ }
47827
+ const sseMatch = upstreamPath.match(SSE_EVENTS_PATH);
47828
+ if (sseMatch) {
47829
+ await streamSSE(ctx, res);
47830
+ return;
47831
+ }
47099
47832
  if (res.body) {
47100
47833
  const reader = res.body.getReader();
47101
47834
  const pump = async () => {
@@ -47136,7 +47869,9 @@ async function proxyMiddleware(ctx, next) {
47136
47869
  function registerRoutes(app, requireAuth2) {
47137
47870
  app.use(healthRoutes.routes());
47138
47871
  app.use(webhookRoutes.routes());
47872
+ app.use(authPublicRoutes.routes());
47139
47873
  app.use(requireAuth2);
47874
+ app.use(authProtectedRoutes.routes());
47140
47875
  app.use(uploadRoutes.routes());
47141
47876
  app.use(updateRoutes.routes());
47142
47877
  app.use(sessionRoutes.routes());
@@ -47158,14 +47893,14 @@ function registerRoutes(app, requireAuth2) {
47158
47893
  var import_cors = __toESM(require_cors());
47159
47894
  var import_koa_static = __toESM(require_koa_static());
47160
47895
  var import_koa_send = __toESM(require_koa_send());
47161
- var import_os9 = __toESM(require("os"));
47162
- var import_path13 = require("path");
47163
- var import_promises13 = require("fs/promises");
47164
- var import_fs12 = require("fs");
47896
+ var import_os12 = __toESM(require("os"));
47897
+ var import_path17 = require("path");
47898
+ var import_promises14 = require("fs/promises");
47899
+ var import_fs15 = require("fs");
47165
47900
  init_logger();
47166
- var APP_VERSION = true ? "0.4.2" : (() => {
47901
+ var APP_VERSION = true ? "0.4.3" : (() => {
47167
47902
  try {
47168
- return JSON.parse(readFileSync7((0, import_path13.resolve)(__dirname, "../../package.json"), "utf-8")).version;
47903
+ return JSON.parse(readFileSync9((0, import_path17.resolve)(__dirname, "../../package.json"), "utf-8")).version;
47169
47904
  } catch {
47170
47905
  return "dev";
47171
47906
  }
@@ -47180,12 +47915,15 @@ process.on("unhandledRejection", (reason) => {
47180
47915
  var server = null;
47181
47916
  async function bootstrap() {
47182
47917
  console.log(`hermes-web-ui v${APP_VERSION} starting...`);
47183
- await (0, import_promises13.mkdir)(config.uploadDir, { recursive: true });
47184
- await (0, import_promises13.mkdir)(config.dataDir, { recursive: true });
47918
+ await (0, import_promises14.mkdir)(config.uploadDir, { recursive: true });
47919
+ await (0, import_promises14.mkdir)(config.dataDir, { recursive: true });
47185
47920
  const authToken = await getToken();
47186
47921
  const app = new koa_default();
47187
47922
  await initGatewayManager();
47188
47923
  console.log("[bootstrap] gateway manager initialized");
47924
+ const { initUsageStore: initUsageStore2 } = await Promise.resolve().then(() => (init_usage_store(), usage_store_exports));
47925
+ initUsageStore2();
47926
+ console.log("[bootstrap] usage store initialized");
47189
47927
  app.use((0, import_cors.default)({ origin: config.corsOrigins }));
47190
47928
  app.use(bodyParserWrapper());
47191
47929
  console.log("[bootstrap] cors + bodyParser registered");
@@ -47196,7 +47934,7 @@ async function bootstrap() {
47196
47934
  console.log(`Auth enabled \u2014 token: ${authToken}`);
47197
47935
  logger.info("Auth enabled \u2014 token: %s", authToken);
47198
47936
  }
47199
- const distDir = (0, import_path13.resolve)(__dirname, "..", "client");
47937
+ const distDir = (0, import_path17.resolve)(__dirname, "..", "client");
47200
47938
  app.use((0, import_koa_static.default)(distDir));
47201
47939
  app.use(async (ctx) => {
47202
47940
  if (!ctx.path.startsWith("/api") && ctx.path !== "/health" && ctx.path !== "/upload" && ctx.path !== "/webhook") {
@@ -47210,7 +47948,7 @@ async function bootstrap() {
47210
47948
  setupTerminalWebSocket(server);
47211
47949
  console.log("[bootstrap] terminal websocket setup");
47212
47950
  server.on("listening", () => {
47213
- const interfaces = import_os9.default.networkInterfaces();
47951
+ const interfaces = import_os12.default.networkInterfaces();
47214
47952
  const localIp = Object.values(interfaces).flat().find((i) => i?.family === "IPv4" && !i?.internal)?.address || "localhost";
47215
47953
  console.log(`Server: http://localhost:${config.port} (LAN: http://${localIp}:${config.port})`);
47216
47954
  console.log(`Upstream: ${config.upstream}`);