hermes-web-ui 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{Add-cXqy3eJz.js → Add-CIZmky1Q.js} +1 -1
- package/dist/client/assets/{Button-BeWnBoDR.js → Button-Cpfqx5bj.js} +1 -1
- package/dist/client/assets/{ChannelsView-Do43S3tP.css → ChannelsView-CSo2o-P4.css} +1 -1
- package/dist/client/assets/ChannelsView-CqgEMY6f.js +1 -0
- package/dist/client/assets/{ChatView-CqA3Wo54.js → ChatView-o_KHRYai.js} +1 -1
- package/dist/client/assets/{Close-ySsuLJTC.js → Close-Dx71Ggz9.js} +1 -1
- package/dist/client/assets/{FormItem-EcbFnIOF.js → FormItem-D0pABOgK.js} +1 -1
- package/dist/client/assets/{Input-DP5LDvGa.js → Input-DmGT4Mu3.js} +1 -1
- package/dist/client/assets/{InputNumber-CU7XKbbt.js → InputNumber-CQVOZP6a.js} +1 -1
- package/dist/client/assets/JobsView-BwxWBXo3.js +2 -0
- package/dist/client/assets/{JobsView-CvuV9mZY.css → JobsView-CVx2Yv-y.css} +1 -1
- package/dist/client/assets/{LoginView-CDJXoOEZ.js → LoginView-C4Id2mSY.js} +1 -1
- package/dist/client/assets/{LogsView-DS_E9xkh.js → LogsView-H_gfkSSN.js} +1 -1
- package/dist/client/assets/{MarkdownRenderer-BntkPd0f.js → MarkdownRenderer-DJK1b6T2.js} +1 -1
- package/dist/client/assets/{MemoryView-CCCXW1tv.js → MemoryView-DO4Y2le5.js} +1 -1
- package/dist/client/assets/{Modal-D_vF9k8c.js → Modal-fhZL63EZ.js} +1 -1
- package/dist/client/assets/ModelsView-9CSt8tCa.js +1 -0
- package/dist/client/assets/{ModelsView-BhNEZwC0.css → ModelsView-D1p_2trx.css} +1 -1
- package/dist/client/assets/{Popconfirm-IKYJxM7n.js → Popconfirm-CLvbZW3I.js} +1 -1
- package/dist/client/assets/{Popover-BLBLYanQ.js → Popover-Dwi5NArH.js} +1 -1
- package/dist/client/assets/{ProfilesView-qt3NXito.js → ProfilesView-R_u1mtsl.js} +1 -1
- package/dist/client/assets/{Scrollbar-DZGif2x4.js → Scrollbar-zYdleApO.js} +1 -1
- package/dist/client/assets/{Select-3KS3TLPL.js → Select-0jFWg4Lf.js} +1 -1
- package/dist/client/assets/{SettingRow-D26AUezg.js → SettingRow-LTP_cMLl.js} +1 -1
- package/dist/client/assets/{SettingsView-30x1265A.js → SettingsView-5o2GfsQf.js} +2 -2
- package/dist/client/assets/{SettingsView-C78xbLXK.css → SettingsView-BIEQOPzq.css} +1 -1
- package/dist/client/assets/{SkillsView-qq8Oz7fo.js → SkillsView-Cez6jHIn.js} +1 -1
- package/dist/client/assets/{Spin-C9izy3tZ.js → Spin-CfKqXT5a.js} +1 -1
- package/dist/client/assets/{Suffix-CIX5CF-j.js → Suffix-jKQO068E.js} +1 -1
- package/dist/client/assets/{Switch-D9x35tOv.js → Switch-tk7Pkw6L.js} +1 -1
- package/dist/client/assets/{TerminalView-zaRNn2aX.js → TerminalView-C-oFmyFh.js} +1 -1
- package/dist/client/assets/{Tooltip-A5--U35v.js → Tooltip-Dc4tc92D.js} +1 -1
- package/dist/client/assets/{UsageView-DQ3t8CHE.js → UsageView-SSxSWAFO.js} +1 -1
- package/dist/client/assets/{Warning-BZfjWCrB.js → Warning-BPlnDrmx.js} +1 -1
- package/dist/client/assets/{_plugin-vue_export-helper-B4hqCVU_.js → _plugin-vue_export-helper-CcX4e_Is.js} +1 -1
- package/dist/client/assets/{app-DG_zFxqi.js → app-CcLe99Xa.js} +1 -1
- package/dist/client/assets/app-ML9hRyDO.js +1 -0
- package/dist/client/assets/{browser-BanYmQkE.js → browser-BfJIjfY3.js} +1 -1
- package/dist/client/assets/{chat-C6pq-bYQ.js → chat-CuVG21Hv.js} +2 -2
- package/dist/client/assets/composables-BTIEu8ZB.js +1 -0
- package/dist/client/assets/{fade-in-scale-up.cssr-B100njI2.js → fade-in-scale-up.cssr-e30I2JjN.js} +1 -1
- package/dist/client/assets/index-XufFb2mL.js +284 -0
- package/dist/client/assets/{jobs-DK_GVjqW.js → jobs-LBrAH2Mz.js} +1 -1
- package/dist/client/assets/{light-B8HxMQKs.js → light-BF--Dz10.js} +1 -1
- package/dist/client/assets/{light-BT2gqEar.js → light-BJ4xZyd0.js} +1 -1
- package/dist/client/assets/{light-CDN7aXQb.js → light-BLbGAFtS.js} +1 -1
- package/dist/client/assets/{light-Dav3NshO.js → light-CCZMDqco.js} +1 -1
- package/dist/client/assets/{light-BfPLur6t.js → light-CDuRPTUd.js} +1 -1
- package/dist/client/assets/{light-CeMZM90j.js → light-DTBcb4qq.js} +1 -1
- package/dist/client/assets/{pinia-DEKB7D30.js → pinia-D2g49IcO.js} +1 -1
- package/dist/client/assets/{profiles-BSAOLUpt.js → profiles-DbCu_blp.js} +1 -1
- package/dist/client/assets/{router-Zpu1Tghg.js → router-0X4x3p8e.js} +2 -2
- package/dist/client/assets/{sessions-1rFjfO8M.js → sessions-SzjWXR9A.js} +1 -1
- package/dist/client/assets/{skills-Bu5JdqX-.js → skills-CsjvGVsY.js} +1 -1
- package/dist/client/assets/use-compitable-DY4l-k3U.js +1 -0
- package/dist/client/assets/{use-message-CFWjMwGX.js → use-message-4kASja7X.js} +1 -1
- package/dist/client/assets/{useTheme-CHkzsMnk.js → useTheme-Dh7NTo_V.js} +1 -1
- package/dist/client/index.html +27 -27
- package/dist/server/index.js +2 -2
- package/dist/server/routes/hermes/codex-auth.d.ts +2 -0
- package/dist/server/routes/hermes/codex-auth.js +302 -0
- package/dist/server/routes/hermes/config.js +15 -3
- package/dist/server/routes/hermes/filesystem.js +92 -18
- package/dist/server/routes/hermes/index.js +2 -0
- package/dist/server/routes/hermes/logs.js +1 -1
- package/dist/server/routes/hermes/profiles.js +1 -1
- package/dist/server/routes/hermes/sessions.js +1 -1
- package/dist/server/routes/hermes/weixin.js +2 -2
- package/dist/server/routes/webhook.js +1 -1
- package/dist/server/services/hermes/hermes-cli.d.ts +125 -0
- package/dist/server/services/hermes/hermes-cli.js +488 -0
- package/dist/server/services/hermes/hermes-profile.d.ts +22 -0
- package/dist/server/services/hermes/hermes-profile.js +60 -0
- package/dist/server/services/hermes/hermes.d.ts +40 -0
- package/dist/server/services/hermes/hermes.js +118 -0
- package/dist/server/shared/providers.js +64 -20
- package/package.json +4 -1
- package/dist/client/assets/ChannelsView-tUAkDoSF.js +0 -1
- package/dist/client/assets/JobsView-g3zcs8_m.js +0 -2
- package/dist/client/assets/ModelsView-C8rPRq6p.js +0 -1
- package/dist/client/assets/app-DVQEsGHs.js +0 -1
- package/dist/client/assets/composables-xsQwqT-T.js +0 -1
- package/dist/client/assets/index-DtDTIaVj.js +0 -284
- package/dist/client/assets/use-compitable-Dl0AqAGv.js +0 -1
|
@@ -38,7 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.logRoutes = void 0;
|
|
40
40
|
const router_1 = __importDefault(require("@koa/router"));
|
|
41
|
-
const hermesCli = __importStar(require("../../services/hermes-cli"));
|
|
41
|
+
const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
|
|
42
42
|
exports.logRoutes = new router_1.default();
|
|
43
43
|
// List available log files
|
|
44
44
|
exports.logRoutes.get('/api/hermes/logs', async (ctx) => {
|
|
@@ -43,7 +43,7 @@ const promises_1 = require("fs/promises");
|
|
|
43
43
|
const path_1 = require("path");
|
|
44
44
|
const os_1 = require("os");
|
|
45
45
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
46
|
-
const hermesCli = __importStar(require("../../services/hermes-cli"));
|
|
46
|
+
const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
|
|
47
47
|
const apiServerDefaults = {
|
|
48
48
|
enabled: true,
|
|
49
49
|
host: '127.0.0.1',
|
|
@@ -38,7 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.sessionRoutes = void 0;
|
|
40
40
|
const router_1 = __importDefault(require("@koa/router"));
|
|
41
|
-
const hermesCli = __importStar(require("../../services/hermes-cli"));
|
|
41
|
+
const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
|
|
42
42
|
exports.sessionRoutes = new router_1.default();
|
|
43
43
|
// List sessions from Hermes
|
|
44
44
|
exports.sessionRoutes.get('/api/hermes/sessions', async (ctx) => {
|
|
@@ -8,8 +8,8 @@ const router_1 = __importDefault(require("@koa/router"));
|
|
|
8
8
|
const axios_1 = __importDefault(require("axios"));
|
|
9
9
|
const promises_1 = require("fs/promises");
|
|
10
10
|
const promises_2 = require("fs/promises");
|
|
11
|
-
const hermes_cli_1 = require("../../services/hermes-cli");
|
|
12
|
-
const hermes_profile_1 = require("../../services/hermes-profile");
|
|
11
|
+
const hermes_cli_1 = require("../../services/hermes/hermes-cli");
|
|
12
|
+
const hermes_profile_1 = require("../../services/hermes/hermes-profile");
|
|
13
13
|
const envPath = () => (0, hermes_profile_1.getActiveEnvPath)();
|
|
14
14
|
const ILINK_BASE = 'https://ilinkai.weixin.qq.com';
|
|
15
15
|
exports.weixinRoutes = new router_1.default();
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.webhookRoutes = void 0;
|
|
7
7
|
const router_1 = __importDefault(require("@koa/router"));
|
|
8
|
-
const hermes_1 = require("../services/hermes");
|
|
8
|
+
const hermes_1 = require("../services/hermes/hermes");
|
|
9
9
|
exports.webhookRoutes = new router_1.default();
|
|
10
10
|
/**
|
|
11
11
|
* POST /webhook — receive callbacks from Hermes Agent
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export interface HermesSession {
|
|
2
|
+
id: string;
|
|
3
|
+
source: string;
|
|
4
|
+
user_id: string | null;
|
|
5
|
+
model: string;
|
|
6
|
+
title: string | null;
|
|
7
|
+
started_at: number;
|
|
8
|
+
ended_at: number | null;
|
|
9
|
+
end_reason: string | null;
|
|
10
|
+
message_count: number;
|
|
11
|
+
tool_call_count: number;
|
|
12
|
+
input_tokens: number;
|
|
13
|
+
output_tokens: number;
|
|
14
|
+
cache_read_tokens: number;
|
|
15
|
+
cache_write_tokens: number;
|
|
16
|
+
reasoning_tokens: number;
|
|
17
|
+
billing_provider: string | null;
|
|
18
|
+
estimated_cost_usd: number;
|
|
19
|
+
actual_cost_usd: number | null;
|
|
20
|
+
cost_status: string;
|
|
21
|
+
messages?: any[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* List sessions from Hermes CLI (without messages)
|
|
25
|
+
*/
|
|
26
|
+
export declare function listSessions(source?: string, limit?: number): Promise<HermesSession[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Get a single session with messages from Hermes CLI
|
|
29
|
+
*/
|
|
30
|
+
export declare function getSession(id: string): Promise<HermesSession | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete a session from Hermes CLI
|
|
33
|
+
*/
|
|
34
|
+
export declare function deleteSession(id: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Rename a session title via Hermes CLI
|
|
37
|
+
*/
|
|
38
|
+
export declare function renameSession(id: string, title: string): Promise<boolean>;
|
|
39
|
+
export interface LogFileInfo {
|
|
40
|
+
name: string;
|
|
41
|
+
size: string;
|
|
42
|
+
modified: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get Hermes version
|
|
46
|
+
*/
|
|
47
|
+
export declare function getVersion(): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Start Hermes gateway (uses launchd/systemd)
|
|
50
|
+
*/
|
|
51
|
+
export declare function startGateway(): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Start Hermes gateway in background (for WSL where launchd/systemd is unavailable)
|
|
54
|
+
* Uses "hermes gateway run" as a detached background process
|
|
55
|
+
*/
|
|
56
|
+
export declare function startGatewayBackground(): Promise<number | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Restart Hermes gateway
|
|
59
|
+
*/
|
|
60
|
+
export declare function restartGateway(): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Stop Hermes gateway
|
|
63
|
+
*/
|
|
64
|
+
export declare function stopGateway(): Promise<string>;
|
|
65
|
+
/**
|
|
66
|
+
* List available log files
|
|
67
|
+
*/
|
|
68
|
+
export declare function listLogFiles(): Promise<LogFileInfo[]>;
|
|
69
|
+
/**
|
|
70
|
+
* Read log lines
|
|
71
|
+
*/
|
|
72
|
+
export declare function readLogs(logName?: string, lines?: number, level?: string, session?: string, since?: string): Promise<string>;
|
|
73
|
+
export interface HermesProfile {
|
|
74
|
+
name: string;
|
|
75
|
+
active: boolean;
|
|
76
|
+
model: string;
|
|
77
|
+
gateway: string;
|
|
78
|
+
alias: string;
|
|
79
|
+
}
|
|
80
|
+
export interface HermesProfileDetail {
|
|
81
|
+
name: string;
|
|
82
|
+
path: string;
|
|
83
|
+
model: string;
|
|
84
|
+
provider: string;
|
|
85
|
+
gateway: string;
|
|
86
|
+
skills: number;
|
|
87
|
+
hasEnv: boolean;
|
|
88
|
+
hasSoulMd: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* List all profiles
|
|
92
|
+
*/
|
|
93
|
+
export declare function listProfiles(): Promise<HermesProfile[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Get profile details
|
|
96
|
+
*/
|
|
97
|
+
export declare function getProfile(name: string): Promise<HermesProfileDetail>;
|
|
98
|
+
/**
|
|
99
|
+
* Create a new profile
|
|
100
|
+
*/
|
|
101
|
+
export declare function createProfile(name: string, clone?: boolean): Promise<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Delete a profile
|
|
104
|
+
*/
|
|
105
|
+
export declare function deleteProfile(name: string): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* Rename a profile
|
|
108
|
+
*/
|
|
109
|
+
export declare function renameProfile(oldName: string, newName: string): Promise<boolean>;
|
|
110
|
+
/**
|
|
111
|
+
* Switch active profile
|
|
112
|
+
*/
|
|
113
|
+
export declare function useProfile(name: string): Promise<string>;
|
|
114
|
+
/**
|
|
115
|
+
* Export profile to archive
|
|
116
|
+
*/
|
|
117
|
+
export declare function exportProfile(name: string, outputPath?: string): Promise<string>;
|
|
118
|
+
/**
|
|
119
|
+
* Run hermes setup --non-interactive --reset to generate default config for current profile
|
|
120
|
+
*/
|
|
121
|
+
export declare function setupReset(): Promise<string>;
|
|
122
|
+
/**
|
|
123
|
+
* Import profile from archive
|
|
124
|
+
*/
|
|
125
|
+
export declare function importProfile(archivePath: string, name?: string): Promise<string>;
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listSessions = listSessions;
|
|
4
|
+
exports.getSession = getSession;
|
|
5
|
+
exports.deleteSession = deleteSession;
|
|
6
|
+
exports.renameSession = renameSession;
|
|
7
|
+
exports.getVersion = getVersion;
|
|
8
|
+
exports.startGateway = startGateway;
|
|
9
|
+
exports.startGatewayBackground = startGatewayBackground;
|
|
10
|
+
exports.restartGateway = restartGateway;
|
|
11
|
+
exports.stopGateway = stopGateway;
|
|
12
|
+
exports.listLogFiles = listLogFiles;
|
|
13
|
+
exports.readLogs = readLogs;
|
|
14
|
+
exports.listProfiles = listProfiles;
|
|
15
|
+
exports.getProfile = getProfile;
|
|
16
|
+
exports.createProfile = createProfile;
|
|
17
|
+
exports.deleteProfile = deleteProfile;
|
|
18
|
+
exports.renameProfile = renameProfile;
|
|
19
|
+
exports.useProfile = useProfile;
|
|
20
|
+
exports.exportProfile = exportProfile;
|
|
21
|
+
exports.setupReset = setupReset;
|
|
22
|
+
exports.importProfile = importProfile;
|
|
23
|
+
const child_process_1 = require("child_process");
|
|
24
|
+
const fs_1 = require("fs");
|
|
25
|
+
const util_1 = require("util");
|
|
26
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
27
|
+
const execOpts = { windowsHide: true };
|
|
28
|
+
const isDocker = (0, fs_1.existsSync)('/.dockerenv');
|
|
29
|
+
function resolveHermesBin() {
|
|
30
|
+
const envBin = process.env.HERMES_BIN?.trim();
|
|
31
|
+
if (envBin)
|
|
32
|
+
return envBin;
|
|
33
|
+
return 'hermes';
|
|
34
|
+
}
|
|
35
|
+
const HERMES_BIN = resolveHermesBin();
|
|
36
|
+
/**
|
|
37
|
+
* List sessions from Hermes CLI (without messages)
|
|
38
|
+
*/
|
|
39
|
+
async function listSessions(source, limit) {
|
|
40
|
+
const args = ['sessions', 'export', '-'];
|
|
41
|
+
if (source)
|
|
42
|
+
args.push('--source', source);
|
|
43
|
+
try {
|
|
44
|
+
const { stdout } = await execFileAsync(HERMES_BIN, args, {
|
|
45
|
+
maxBuffer: 50 * 1024 * 1024, // 50MB
|
|
46
|
+
timeout: 30000,
|
|
47
|
+
...execOpts,
|
|
48
|
+
});
|
|
49
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
50
|
+
const sessions = [];
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = JSON.parse(line);
|
|
54
|
+
let title = raw.title;
|
|
55
|
+
if (!title && raw.messages) {
|
|
56
|
+
const firstUser = raw.messages.find((m) => m.role === 'user');
|
|
57
|
+
if (firstUser?.content) {
|
|
58
|
+
const t = String(firstUser.content).slice(0, 40);
|
|
59
|
+
title = t + (String(firstUser.content).length > 40 ? '...' : '');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
sessions.push({
|
|
63
|
+
id: raw.id,
|
|
64
|
+
source: raw.source,
|
|
65
|
+
user_id: raw.user_id,
|
|
66
|
+
model: raw.model,
|
|
67
|
+
title,
|
|
68
|
+
started_at: raw.started_at,
|
|
69
|
+
ended_at: raw.ended_at,
|
|
70
|
+
end_reason: raw.end_reason,
|
|
71
|
+
message_count: raw.message_count,
|
|
72
|
+
tool_call_count: raw.tool_call_count,
|
|
73
|
+
input_tokens: raw.input_tokens,
|
|
74
|
+
output_tokens: raw.output_tokens,
|
|
75
|
+
cache_read_tokens: raw.cache_read_tokens || 0,
|
|
76
|
+
cache_write_tokens: raw.cache_write_tokens || 0,
|
|
77
|
+
reasoning_tokens: raw.reasoning_tokens || 0,
|
|
78
|
+
billing_provider: raw.billing_provider,
|
|
79
|
+
estimated_cost_usd: raw.estimated_cost_usd,
|
|
80
|
+
actual_cost_usd: raw.actual_cost_usd ?? null,
|
|
81
|
+
cost_status: raw.cost_status || '',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch { /* skip malformed lines */ }
|
|
85
|
+
}
|
|
86
|
+
// Sort by started_at descending
|
|
87
|
+
sessions.sort((a, b) => b.started_at - a.started_at);
|
|
88
|
+
if (limit && limit > 0) {
|
|
89
|
+
return sessions.slice(0, limit);
|
|
90
|
+
}
|
|
91
|
+
return sessions;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error('[Hermes CLI] sessions export failed:', err.message);
|
|
95
|
+
throw new Error(`Failed to list sessions: ${err.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get a single session with messages from Hermes CLI
|
|
100
|
+
*/
|
|
101
|
+
async function getSession(id) {
|
|
102
|
+
const args = ['sessions', 'export', '-', '--session-id', id];
|
|
103
|
+
try {
|
|
104
|
+
const { stdout } = await execFileAsync(HERMES_BIN, args, {
|
|
105
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
106
|
+
timeout: 30000,
|
|
107
|
+
...execOpts,
|
|
108
|
+
});
|
|
109
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
110
|
+
if (lines.length === 0)
|
|
111
|
+
return null;
|
|
112
|
+
if (!lines[0].startsWith('{'))
|
|
113
|
+
return null;
|
|
114
|
+
const raw = JSON.parse(lines[0]);
|
|
115
|
+
return {
|
|
116
|
+
id: raw.id,
|
|
117
|
+
source: raw.source,
|
|
118
|
+
user_id: raw.user_id,
|
|
119
|
+
model: raw.model,
|
|
120
|
+
title: raw.title,
|
|
121
|
+
started_at: raw.started_at,
|
|
122
|
+
ended_at: raw.ended_at,
|
|
123
|
+
end_reason: raw.end_reason,
|
|
124
|
+
message_count: raw.message_count,
|
|
125
|
+
tool_call_count: raw.tool_call_count,
|
|
126
|
+
input_tokens: raw.input_tokens,
|
|
127
|
+
output_tokens: raw.output_tokens,
|
|
128
|
+
cache_read_tokens: raw.cache_read_tokens || 0,
|
|
129
|
+
cache_write_tokens: raw.cache_write_tokens || 0,
|
|
130
|
+
reasoning_tokens: raw.reasoning_tokens || 0,
|
|
131
|
+
billing_provider: raw.billing_provider,
|
|
132
|
+
estimated_cost_usd: raw.estimated_cost_usd,
|
|
133
|
+
actual_cost_usd: raw.actual_cost_usd ?? null,
|
|
134
|
+
cost_status: raw.cost_status || '',
|
|
135
|
+
messages: raw.messages,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
if (err.code === 1 || err.status === 1)
|
|
140
|
+
return null;
|
|
141
|
+
console.error('[Hermes CLI] session export failed:', err.message);
|
|
142
|
+
throw new Error(`Failed to get session: ${err.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Delete a session from Hermes CLI
|
|
147
|
+
*/
|
|
148
|
+
async function deleteSession(id) {
|
|
149
|
+
try {
|
|
150
|
+
await execFileAsync(HERMES_BIN, ['sessions', 'delete', id, '--yes'], {
|
|
151
|
+
timeout: 10000,
|
|
152
|
+
...execOpts,
|
|
153
|
+
});
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error('[Hermes CLI] session delete failed:', err.message);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Rename a session title via Hermes CLI
|
|
163
|
+
*/
|
|
164
|
+
async function renameSession(id, title) {
|
|
165
|
+
try {
|
|
166
|
+
await execFileAsync(HERMES_BIN, ['sessions', 'rename', id, title], {
|
|
167
|
+
timeout: 10000,
|
|
168
|
+
...execOpts,
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.error('[Hermes CLI] session rename failed:', err.message);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get Hermes version
|
|
179
|
+
*/
|
|
180
|
+
async function getVersion() {
|
|
181
|
+
try {
|
|
182
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['--version'], { timeout: 5000, ...execOpts });
|
|
183
|
+
return stdout.trim();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Start Hermes gateway (uses launchd/systemd)
|
|
191
|
+
*/
|
|
192
|
+
async function startGateway() {
|
|
193
|
+
if (isDocker) {
|
|
194
|
+
const pid = await startGatewayBackground();
|
|
195
|
+
return pid ? `Gateway started (PID: ${pid})` : 'Gateway start triggered';
|
|
196
|
+
}
|
|
197
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['gateway', 'start'], {
|
|
198
|
+
timeout: 30000,
|
|
199
|
+
...execOpts,
|
|
200
|
+
});
|
|
201
|
+
return stdout || stderr;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Start Hermes gateway in background (for WSL where launchd/systemd is unavailable)
|
|
205
|
+
* Uses "hermes gateway run" as a detached background process
|
|
206
|
+
*/
|
|
207
|
+
async function startGatewayBackground() {
|
|
208
|
+
const { spawn } = require('child_process');
|
|
209
|
+
const child = spawn(HERMES_BIN, ['gateway', 'run'], {
|
|
210
|
+
detached: true,
|
|
211
|
+
stdio: 'ignore',
|
|
212
|
+
windowsHide: true,
|
|
213
|
+
});
|
|
214
|
+
child.unref();
|
|
215
|
+
return child.pid ?? null;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Restart Hermes gateway
|
|
219
|
+
*/
|
|
220
|
+
async function restartGateway() {
|
|
221
|
+
if (isDocker) {
|
|
222
|
+
try {
|
|
223
|
+
await stopGateway();
|
|
224
|
+
}
|
|
225
|
+
catch { }
|
|
226
|
+
const pid = await startGatewayBackground();
|
|
227
|
+
return pid ? `Gateway restarted (PID: ${pid})` : 'Gateway restart triggered';
|
|
228
|
+
}
|
|
229
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['gateway', 'restart'], {
|
|
230
|
+
timeout: 30000,
|
|
231
|
+
...execOpts,
|
|
232
|
+
});
|
|
233
|
+
return stdout || stderr;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Stop Hermes gateway
|
|
237
|
+
*/
|
|
238
|
+
async function stopGateway() {
|
|
239
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['gateway', 'stop'], {
|
|
240
|
+
timeout: 30000,
|
|
241
|
+
...execOpts,
|
|
242
|
+
});
|
|
243
|
+
return stdout || stderr;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* List available log files
|
|
247
|
+
*/
|
|
248
|
+
async function listLogFiles() {
|
|
249
|
+
try {
|
|
250
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['logs', 'list'], {
|
|
251
|
+
timeout: 10000,
|
|
252
|
+
...execOpts,
|
|
253
|
+
});
|
|
254
|
+
const files = [];
|
|
255
|
+
const lines = stdout.trim().split('\n').filter(l => l.includes('.log'));
|
|
256
|
+
for (const line of lines) {
|
|
257
|
+
const match = line.match(/^\s+(\S+)\s+([\d.]+\w+)\s+(.+)$/);
|
|
258
|
+
if (match) {
|
|
259
|
+
const rawName = match[1];
|
|
260
|
+
const name = rawName.replace(/\.log$/, '');
|
|
261
|
+
if (['agent', 'errors', 'gateway'].includes(name)) {
|
|
262
|
+
files.push({ name, size: match[2], modified: match[3].trim() });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return files;
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
console.error('[Hermes CLI] logs list failed:', err.message);
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Read log lines
|
|
275
|
+
*/
|
|
276
|
+
async function readLogs(logName = 'agent', lines = 100, level, session, since) {
|
|
277
|
+
const args = ['logs', logName, '-n', String(lines)];
|
|
278
|
+
if (level)
|
|
279
|
+
args.push('--level', level);
|
|
280
|
+
if (session)
|
|
281
|
+
args.push('--session', session);
|
|
282
|
+
if (since)
|
|
283
|
+
args.push('--since', since);
|
|
284
|
+
try {
|
|
285
|
+
const { stdout } = await execFileAsync(HERMES_BIN, args, {
|
|
286
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
287
|
+
timeout: 15000,
|
|
288
|
+
...execOpts,
|
|
289
|
+
});
|
|
290
|
+
return stdout;
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
console.error('[Hermes CLI] logs read failed:', err.message);
|
|
294
|
+
throw new Error(`Failed to read logs: ${err.message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* List all profiles
|
|
299
|
+
*/
|
|
300
|
+
async function listProfiles() {
|
|
301
|
+
try {
|
|
302
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['profile', 'list'], {
|
|
303
|
+
timeout: 10000,
|
|
304
|
+
...execOpts,
|
|
305
|
+
});
|
|
306
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
307
|
+
const profiles = [];
|
|
308
|
+
// Skip header lines (starts with " Profile" or " ─")
|
|
309
|
+
for (const line of lines) {
|
|
310
|
+
if (line.startsWith(' Profile') || line.match(/^ ─/))
|
|
311
|
+
continue;
|
|
312
|
+
const match = line.match(/^\s+(◆)?(\S+)\s{2,}(\S+)\s{2,}(\S+)\s{2,}(.*)$/);
|
|
313
|
+
if (match) {
|
|
314
|
+
profiles.push({
|
|
315
|
+
name: match[2],
|
|
316
|
+
active: !!match[1],
|
|
317
|
+
model: match[3],
|
|
318
|
+
gateway: match[4],
|
|
319
|
+
alias: match[5].trim() === '—' ? '' : match[5].trim(),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return profiles;
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
console.error('[Hermes CLI] profile list failed:', err.message);
|
|
327
|
+
throw new Error(`Failed to list profiles: ${err.message}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get profile details
|
|
332
|
+
*/
|
|
333
|
+
async function getProfile(name) {
|
|
334
|
+
try {
|
|
335
|
+
const { stdout } = await execFileAsync(HERMES_BIN, ['profile', 'show', name], {
|
|
336
|
+
timeout: 10000,
|
|
337
|
+
...execOpts,
|
|
338
|
+
});
|
|
339
|
+
const result = {};
|
|
340
|
+
for (const line of stdout.trim().split('\n')) {
|
|
341
|
+
const match = line.match(/^(\w[\w\s]*?):\s+(.+)$/);
|
|
342
|
+
if (match) {
|
|
343
|
+
result[match[1].trim().toLowerCase().replace(/\s+/g, '_')] = match[2].trim();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const modelFull = result.model || '';
|
|
347
|
+
const providerMatch = modelFull.match(/\((.+)\)/);
|
|
348
|
+
const model = providerMatch ? modelFull.replace(/\s*\(.+\)/, '').trim() : modelFull;
|
|
349
|
+
return {
|
|
350
|
+
name: result.profile || name,
|
|
351
|
+
path: result.path || '',
|
|
352
|
+
model,
|
|
353
|
+
provider: providerMatch ? providerMatch[1] : '',
|
|
354
|
+
gateway: result.gateway || '',
|
|
355
|
+
skills: parseInt(result.skills || '0', 10),
|
|
356
|
+
hasEnv: result['.env'] === 'exists',
|
|
357
|
+
hasSoulMd: result.soul_md === 'exists',
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
if (err.code === 1 || err.status === 1) {
|
|
362
|
+
throw new Error(`Profile "${name}" not found`);
|
|
363
|
+
}
|
|
364
|
+
console.error('[Hermes CLI] profile show failed:', err.message);
|
|
365
|
+
throw new Error(`Failed to get profile: ${err.message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Create a new profile
|
|
370
|
+
*/
|
|
371
|
+
async function createProfile(name, clone) {
|
|
372
|
+
const args = ['profile', 'create', name];
|
|
373
|
+
if (clone)
|
|
374
|
+
args.push('--clone');
|
|
375
|
+
try {
|
|
376
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
|
|
377
|
+
timeout: 15000,
|
|
378
|
+
...execOpts,
|
|
379
|
+
});
|
|
380
|
+
return stdout || stderr;
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
console.error('[Hermes CLI] profile create failed:', err.message);
|
|
384
|
+
throw new Error(`Failed to create profile: ${err.message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Delete a profile
|
|
389
|
+
*/
|
|
390
|
+
async function deleteProfile(name) {
|
|
391
|
+
try {
|
|
392
|
+
await execFileAsync(HERMES_BIN, ['profile', 'delete', name, '--yes'], {
|
|
393
|
+
timeout: 10000,
|
|
394
|
+
...execOpts,
|
|
395
|
+
});
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
console.error('[Hermes CLI] profile delete failed:', err.message);
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Rename a profile
|
|
405
|
+
*/
|
|
406
|
+
async function renameProfile(oldName, newName) {
|
|
407
|
+
try {
|
|
408
|
+
await execFileAsync(HERMES_BIN, ['profile', 'rename', oldName, newName], {
|
|
409
|
+
timeout: 10000,
|
|
410
|
+
...execOpts,
|
|
411
|
+
});
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
console.error('[Hermes CLI] profile rename failed:', err.message);
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Switch active profile
|
|
421
|
+
*/
|
|
422
|
+
async function useProfile(name) {
|
|
423
|
+
try {
|
|
424
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['profile', 'use', name], {
|
|
425
|
+
timeout: 10000,
|
|
426
|
+
...execOpts,
|
|
427
|
+
});
|
|
428
|
+
return stdout || stderr;
|
|
429
|
+
}
|
|
430
|
+
catch (err) {
|
|
431
|
+
console.error('[Hermes CLI] profile use failed:', err.message);
|
|
432
|
+
throw new Error(`Failed to switch profile: ${err.message}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Export profile to archive
|
|
437
|
+
*/
|
|
438
|
+
async function exportProfile(name, outputPath) {
|
|
439
|
+
const args = ['profile', 'export', name];
|
|
440
|
+
if (outputPath)
|
|
441
|
+
args.push('--output', outputPath);
|
|
442
|
+
try {
|
|
443
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
|
|
444
|
+
timeout: 60000,
|
|
445
|
+
...execOpts,
|
|
446
|
+
});
|
|
447
|
+
return stdout || stderr;
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
console.error('[Hermes CLI] profile export failed:', err.message);
|
|
451
|
+
throw new Error(`Failed to export profile: ${err.message}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Run hermes setup --non-interactive --reset to generate default config for current profile
|
|
456
|
+
*/
|
|
457
|
+
async function setupReset() {
|
|
458
|
+
try {
|
|
459
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, ['setup', '--non-interactive', '--reset'], {
|
|
460
|
+
timeout: 30000,
|
|
461
|
+
...execOpts,
|
|
462
|
+
});
|
|
463
|
+
return stdout || stderr;
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
console.error('[Hermes CLI] setup reset failed:', err.message);
|
|
467
|
+
throw new Error(`Failed to reset config: ${err.message}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Import profile from archive
|
|
472
|
+
*/
|
|
473
|
+
async function importProfile(archivePath, name) {
|
|
474
|
+
const args = ['profile', 'import', archivePath];
|
|
475
|
+
if (name)
|
|
476
|
+
args.push('--name', name);
|
|
477
|
+
try {
|
|
478
|
+
const { stdout, stderr } = await execFileAsync(HERMES_BIN, args, {
|
|
479
|
+
timeout: 60000,
|
|
480
|
+
...execOpts,
|
|
481
|
+
});
|
|
482
|
+
return stdout || stderr;
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
console.error('[Hermes CLI] profile import failed:', err.message);
|
|
486
|
+
throw new Error(`Failed to import profile: ${err.message}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the active profile's home directory.
|
|
3
|
+
* default → ~/.hermes/
|
|
4
|
+
* other → ~/.hermes/profiles/{name}/
|
|
5
|
+
*/
|
|
6
|
+
export declare function getActiveProfileDir(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Get the active profile's config.yaml path.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getActiveConfigPath(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Get the active profile's auth.json path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getActiveAuthPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Get the active profile's .env path.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getActiveEnvPath(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Get the active profile name.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getActiveProfileName(): string;
|