@bulolo/hermes-link 0.1.0

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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DAEMON_LOG_FILE,
4
+ LINK_COMMAND,
5
+ LINK_VERSION,
6
+ bootstrapWithRelay,
7
+ createRotatingTextLogWriter,
8
+ currentCliScriptPath,
9
+ detectRuntimeEnvironment,
10
+ disableAutostart,
11
+ enableAutostart,
12
+ ensureIdentity,
13
+ generateAppConnectToken,
14
+ getAutostartStatus,
15
+ loadConfig,
16
+ loadIdentity,
17
+ normalizeLanHost,
18
+ readRecentGatewayLogEntries,
19
+ readRecentLogEntries,
20
+ resolveRuntimePaths,
21
+ saveAssignedLinkId,
22
+ saveConfig,
23
+ startLinkService
24
+ } from "../chunk-C24HF73Y.js";
25
+
26
+ // src/cli/index.ts
27
+ import { mkdir as mkdir2 } from "fs/promises";
28
+
29
+ // src/daemon/process.ts
30
+ import { execFile, spawn } from "child_process";
31
+ import { mkdir, readFile, writeFile, rm } from "fs/promises";
32
+ import { setTimeout as sleep } from "timers/promises";
33
+ import path from "path";
34
+ import { promisify } from "util";
35
+ var execFileAsync = promisify(execFile);
36
+ function pidFilePath(paths) {
37
+ return path.join(paths.homeDir, "daemon.pid");
38
+ }
39
+ async function readPid(paths) {
40
+ try {
41
+ const raw = await readFile(pidFilePath(paths), "utf8");
42
+ const pid = Number.parseInt(raw.trim(), 10);
43
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ async function writePid(paths, pid) {
49
+ await mkdir(paths.homeDir, { recursive: true, mode: 448 });
50
+ await writeFile(pidFilePath(paths), String(pid), { mode: 384 });
51
+ }
52
+ async function clearPid(paths) {
53
+ await rm(pidFilePath(paths), { force: true }).catch(() => void 0);
54
+ }
55
+ function isProcessAlive(pid) {
56
+ try {
57
+ process.kill(pid, 0);
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ async function wait(ms) {
64
+ return sleep(ms);
65
+ }
66
+ async function getDaemonStatus(paths) {
67
+ const runtimePaths = paths ?? resolveRuntimePaths();
68
+ const pid = await readPid(runtimePaths);
69
+ if (pid === null) {
70
+ return { state: "stopped", pid: null };
71
+ }
72
+ if (isProcessAlive(pid)) {
73
+ return { state: "running", pid };
74
+ }
75
+ await clearPid(runtimePaths);
76
+ return { state: "stopped", pid: null };
77
+ }
78
+ async function probeLocalLinkService(options) {
79
+ const fetcher = options.fetchImpl ?? fetch;
80
+ try {
81
+ const response = await fetcher(`http://127.0.0.1:${options.port}/api/v1/system/status`, {
82
+ signal: AbortSignal.timeout(3e3)
83
+ });
84
+ return { reachable: true, statusCode: response.status };
85
+ } catch {
86
+ return { reachable: false };
87
+ }
88
+ }
89
+ async function startDaemonProcess(options) {
90
+ const paths = options.paths ?? resolveRuntimePaths();
91
+ const status = await getDaemonStatus(paths);
92
+ if (status.state === "running") return;
93
+ const child = spawn(
94
+ process.execPath,
95
+ [currentCliScriptPath(), "daemon-supervisor"],
96
+ {
97
+ detached: true,
98
+ stdio: "ignore"
99
+ }
100
+ );
101
+ child.unref();
102
+ for (let i = 0; i < 6; i++) {
103
+ await wait(500);
104
+ const newStatus = await getDaemonStatus(paths);
105
+ if (newStatus.state === "running") return;
106
+ }
107
+ }
108
+ async function stopDaemonProcess(options) {
109
+ const paths = options.paths ?? resolveRuntimePaths();
110
+ const timeoutMs = options.timeoutMs ?? 5e3;
111
+ const status = await getDaemonStatus(paths);
112
+ if (status.state !== "running" || status.pid === null) return;
113
+ try {
114
+ process.kill(status.pid, "SIGTERM");
115
+ } catch {
116
+ await clearPid(paths);
117
+ return;
118
+ }
119
+ const deadline = Date.now() + timeoutMs;
120
+ while (Date.now() < deadline) {
121
+ await wait(200);
122
+ if (!isProcessAlive(status.pid)) {
123
+ await clearPid(paths);
124
+ return;
125
+ }
126
+ }
127
+ try {
128
+ process.kill(status.pid, "SIGKILL");
129
+ } catch {
130
+ }
131
+ await clearPid(paths);
132
+ }
133
+ async function runDaemonSupervisor(options) {
134
+ const paths = options.paths ?? resolveRuntimePaths();
135
+ await mkdir(paths.homeDir, { recursive: true, mode: 448 });
136
+ await writePid(paths, process.pid);
137
+ const logWriter = createRotatingTextLogWriter({ paths, fileName: DAEMON_LOG_FILE });
138
+ const cleanup = async () => {
139
+ await clearPid(paths).catch(() => void 0);
140
+ await logWriter.flush().catch(() => void 0);
141
+ };
142
+ process.once("SIGTERM", () => {
143
+ cleanup().then(() => process.exit(0)).catch(() => process.exit(1));
144
+ });
145
+ process.once("SIGINT", () => {
146
+ cleanup().then(() => process.exit(0)).catch(() => process.exit(1));
147
+ });
148
+ const runDaemon = () => new Promise((resolve) => {
149
+ const args2 = [currentCliScriptPath(), "daemon", "--foreground"];
150
+ if (options.port) {
151
+ args2.push("--port", String(options.port));
152
+ }
153
+ const child = spawn(process.execPath, args2, {
154
+ stdio: ["ignore", "pipe", "pipe"]
155
+ });
156
+ child.stdout?.on("data", (chunk) => {
157
+ logWriter.write(chunk).catch(() => void 0);
158
+ });
159
+ child.stderr?.on("data", (chunk) => {
160
+ logWriter.write(chunk).catch(() => void 0);
161
+ });
162
+ child.once("exit", (code) => {
163
+ resolve(code ?? 1);
164
+ });
165
+ child.once("error", () => {
166
+ resolve(1);
167
+ });
168
+ });
169
+ let consecutiveFastExits = 0;
170
+ while (true) {
171
+ const startTime = Date.now();
172
+ const code = await runDaemon();
173
+ const elapsed = Date.now() - startTime;
174
+ if (code === 0) {
175
+ break;
176
+ }
177
+ if (elapsed < 2e3) {
178
+ consecutiveFastExits++;
179
+ } else {
180
+ consecutiveFastExits = 0;
181
+ }
182
+ if (consecutiveFastExits >= 5) {
183
+ break;
184
+ }
185
+ const backoffMs = Math.min(1e3 * consecutiveFastExits, 5e3);
186
+ await wait(backoffMs);
187
+ }
188
+ await cleanup();
189
+ }
190
+
191
+ // src/runtime/browser.ts
192
+ import { spawn as spawn2 } from "child_process";
193
+ async function openSystemBrowser(url) {
194
+ const platform = process.platform;
195
+ if (platform === "win32") {
196
+ return spawnDetached("cmd", ["/c", "start", "", url]);
197
+ }
198
+ if (platform === "darwin") {
199
+ return spawnDetached("open", [url]);
200
+ }
201
+ return spawnDetached("xdg-open", [url]);
202
+ }
203
+ async function spawnDetached(command2, args2) {
204
+ return new Promise((resolve) => {
205
+ let settled = false;
206
+ const settle = (ok) => {
207
+ if (settled) return;
208
+ settled = true;
209
+ resolve(ok);
210
+ };
211
+ try {
212
+ const child = spawn2(command2, args2, { detached: true, stdio: "ignore" });
213
+ child.once("error", () => settle(false));
214
+ child.once("spawn", () => {
215
+ child.unref();
216
+ settle(true);
217
+ });
218
+ } catch {
219
+ settle(false);
220
+ }
221
+ });
222
+ }
223
+
224
+ // src/pairing/preflight.ts
225
+ async function runPairingPreflight(options) {
226
+ const token = await generateAppConnectToken(options.paths);
227
+ const baseUrl = (options.serverBaseUrl ?? options.config.serverBaseUrl).replace(/\/+$/u, "");
228
+ const pairingUrl = buildPairingUrl(baseUrl, {
229
+ linkId: options.identity.link_id ?? "",
230
+ installId: options.identity.install_id,
231
+ connectToken: token.token,
232
+ port: options.config.port
233
+ });
234
+ if (options.openBrowser !== false) {
235
+ await openSystemBrowser(pairingUrl).catch(() => void 0);
236
+ }
237
+ return { pairingUrl, connectToken: token.token };
238
+ }
239
+ function buildPairingUrl(serverBaseUrl, params) {
240
+ const qs = new URLSearchParams({
241
+ link_id: params.linkId,
242
+ install_id: params.installId,
243
+ connect_token: params.connectToken,
244
+ port: String(params.port)
245
+ });
246
+ return `${serverBaseUrl}/link/pair?${qs.toString()}`;
247
+ }
248
+
249
+ // src/cli/index.ts
250
+ var args = process.argv.slice(2);
251
+ var command = args[0];
252
+ function hasFlag(...flags) {
253
+ return args.some((a) => flags.includes(a));
254
+ }
255
+ function getFlagValue(...flags) {
256
+ for (let i = 0; i < args.length - 1; i++) {
257
+ if (flags.includes(args[i])) return args[i + 1];
258
+ }
259
+ return null;
260
+ }
261
+ async function main() {
262
+ if (hasFlag("--version", "-v")) {
263
+ process.stdout.write(`${LINK_VERSION}
264
+ `);
265
+ return;
266
+ }
267
+ if (!command || hasFlag("--help", "-h")) {
268
+ printHelp();
269
+ return;
270
+ }
271
+ const paths = resolveRuntimePaths();
272
+ await mkdir2(paths.homeDir, { recursive: true, mode: 448 });
273
+ switch (command) {
274
+ case "start":
275
+ await cmdStart(paths);
276
+ break;
277
+ case "stop":
278
+ await cmdStop(paths);
279
+ break;
280
+ case "status":
281
+ await cmdStatus(paths);
282
+ break;
283
+ case "restart":
284
+ await cmdStop(paths);
285
+ await cmdStart(paths);
286
+ break;
287
+ case "daemon":
288
+ await cmdDaemon(paths);
289
+ break;
290
+ case "daemon-supervisor":
291
+ await cmdDaemonSupervisor(paths);
292
+ break;
293
+ case "pair":
294
+ await cmdPair(paths);
295
+ break;
296
+ case "config":
297
+ await cmdConfig(paths);
298
+ break;
299
+ case "logs":
300
+ await cmdLogs(paths);
301
+ break;
302
+ case "autostart":
303
+ await cmdAutostart(paths);
304
+ break;
305
+ case "version":
306
+ process.stdout.write(`${LINK_VERSION}
307
+ `);
308
+ break;
309
+ default:
310
+ process.stderr.write(`Unknown command: ${command}
311
+ `);
312
+ printHelp();
313
+ process.exitCode = 1;
314
+ }
315
+ }
316
+ async function cmdStart(paths) {
317
+ const status = await getDaemonStatus(paths);
318
+ if (status.state === "running") {
319
+ process.stdout.write(`Hermes Link is already running (PID ${status.pid}).
320
+ `);
321
+ return;
322
+ }
323
+ await startDaemonProcess({ paths });
324
+ process.stdout.write("Hermes Link started.\n");
325
+ }
326
+ async function cmdStop(paths) {
327
+ const status = await getDaemonStatus(paths);
328
+ if (status.state !== "running") {
329
+ process.stdout.write("Hermes Link is not running.\n");
330
+ return;
331
+ }
332
+ await stopDaemonProcess({ paths });
333
+ process.stdout.write("Hermes Link stopped.\n");
334
+ }
335
+ async function cmdStatus(paths) {
336
+ const config = await loadConfig(paths);
337
+ const daemonStatus = await getDaemonStatus(paths);
338
+ const probe = await probeLocalLinkService({ port: config.port });
339
+ const identity = await loadIdentity(paths).catch(() => null);
340
+ const autostartStatus = await getAutostartStatus();
341
+ const env = detectRuntimeEnvironment();
342
+ process.stdout.write(`Hermes Link ${LINK_VERSION}
343
+ `);
344
+ process.stdout.write(`Daemon: ${daemonStatus.state}${daemonStatus.pid ? ` (PID ${daemonStatus.pid})` : ""}
345
+ `);
346
+ process.stdout.write(`HTTP: ${probe.reachable ? `reachable on port ${config.port}` : `not reachable`}
347
+ `);
348
+ process.stdout.write(`Link ID: ${identity?.link_id ?? "unassigned"}
349
+ `);
350
+ process.stdout.write(`Autostart: ${autostartStatus.enabled ? "enabled" : "disabled"} (${autostartStatus.method})
351
+ `);
352
+ process.stdout.write(`Env: ${env.kind}
353
+ `);
354
+ if (env.warning) process.stdout.write(`Warning: ${env.warning}
355
+ `);
356
+ }
357
+ async function cmdDaemon(paths) {
358
+ if (!hasFlag("--foreground")) {
359
+ process.stderr.write("Use 'hermeslink start' or 'hermeslink daemon-supervisor'\n");
360
+ process.exitCode = 1;
361
+ return;
362
+ }
363
+ const config = await loadConfig(paths);
364
+ const identity = await ensureIdentity(paths);
365
+ let relayToken = "";
366
+ try {
367
+ const bootstrapResult = await bootstrapWithRelay({
368
+ relayBaseUrl: config.relayBaseUrl,
369
+ identity,
370
+ port: config.port
371
+ });
372
+ if (!identity.link_id) {
373
+ await saveAssignedLinkId(bootstrapResult.linkId, paths);
374
+ }
375
+ relayToken = bootstrapResult.token;
376
+ } catch (err) {
377
+ process.stderr.write(`Warning: Relay bootstrap failed: ${err.message}
378
+ `);
379
+ }
380
+ const service = await startLinkService({ config, identity, paths, relayToken });
381
+ process.stdout.write(`Hermes Link running on port ${config.port}
382
+ `);
383
+ const shutdown = async () => {
384
+ await service.stop();
385
+ process.exit(0);
386
+ };
387
+ process.once("SIGTERM", shutdown);
388
+ process.once("SIGINT", shutdown);
389
+ }
390
+ async function cmdDaemonSupervisor(paths) {
391
+ const config = await loadConfig(paths);
392
+ await runDaemonSupervisor({ paths, port: config.port });
393
+ }
394
+ async function cmdPair(paths) {
395
+ const config = await loadConfig(paths);
396
+ const identity = await ensureIdentity(paths);
397
+ if (!identity.link_id) {
398
+ process.stderr.write("Error: Hermes Link is not connected to relay. Run 'hermeslink start' first.\n");
399
+ process.exitCode = 1;
400
+ return;
401
+ }
402
+ const result = await runPairingPreflight({ identity, config, paths });
403
+ process.stdout.write(`Pairing URL: ${result.pairingUrl}
404
+ `);
405
+ process.stdout.write(`Connect token: ${result.connectToken}
406
+ `);
407
+ }
408
+ async function cmdConfig(paths) {
409
+ const subcommand = args[1];
410
+ if (subcommand === "get") {
411
+ const config = await loadConfig(paths);
412
+ process.stdout.write(JSON.stringify(config, null, 2) + "\n");
413
+ return;
414
+ }
415
+ if (subcommand === "set") {
416
+ const key = args[2];
417
+ const value = args[3];
418
+ if (!key || value === void 0) {
419
+ process.stderr.write("Usage: hermeslink config set <key> <value>\n");
420
+ process.exitCode = 1;
421
+ return;
422
+ }
423
+ await applyConfigSet(key, value, paths);
424
+ return;
425
+ }
426
+ process.stderr.write("Usage: hermeslink config [get|set]\n");
427
+ process.exitCode = 1;
428
+ }
429
+ async function applyConfigSet(key, value, paths) {
430
+ switch (key) {
431
+ case "port": {
432
+ const port = Number.parseInt(value, 10);
433
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
434
+ process.stderr.write("Invalid port number\n");
435
+ process.exitCode = 1;
436
+ return;
437
+ }
438
+ await saveConfig({ port }, paths);
439
+ process.stdout.write(`Port set to ${port}
440
+ `);
441
+ break;
442
+ }
443
+ case "lan-host": {
444
+ const lanHost = normalizeLanHost(value) ?? null;
445
+ await saveConfig({ lanHost }, paths);
446
+ process.stdout.write(`LAN host set to ${lanHost ?? "(auto)"}
447
+ `);
448
+ break;
449
+ }
450
+ case "language": {
451
+ const lang = value;
452
+ await saveConfig({ language: lang }, paths);
453
+ process.stdout.write(`Language set to ${lang}
454
+ `);
455
+ break;
456
+ }
457
+ case "log-level": {
458
+ const level = value;
459
+ await saveConfig({ logLevel: level }, paths);
460
+ process.stdout.write(`Log level set to ${level}
461
+ `);
462
+ break;
463
+ }
464
+ default:
465
+ process.stderr.write(`Unknown config key: ${key}
466
+ `);
467
+ process.exitCode = 1;
468
+ }
469
+ }
470
+ async function cmdLogs(paths) {
471
+ const isGateway = hasFlag("--gateway");
472
+ const limit = getFlagValue("--limit", "-n");
473
+ const limitNum = limit ? Number.parseInt(limit, 10) : 50;
474
+ const entries = isGateway ? await readRecentGatewayLogEntries({ paths, limit: limitNum }) : await readRecentLogEntries({ paths, limit: limitNum });
475
+ for (const entry of entries) {
476
+ const ts = entry.ts ? new Date(entry.ts).toLocaleString() : "??";
477
+ process.stdout.write(`[${ts}] ${entry.level.toUpperCase()} ${entry.message}
478
+ `);
479
+ }
480
+ }
481
+ async function cmdAutostart(paths) {
482
+ const subcommand = args[1];
483
+ if (subcommand === "enable") {
484
+ const status2 = await enableAutostart();
485
+ process.stdout.write(`Autostart ${status2.enabled ? "enabled" : "could not be enabled"} (${status2.method})
486
+ `);
487
+ return;
488
+ }
489
+ if (subcommand === "disable") {
490
+ const status2 = await disableAutostart();
491
+ process.stdout.write(`Autostart ${status2.enabled ? "still enabled" : "disabled"} (${status2.method})
492
+ `);
493
+ return;
494
+ }
495
+ const status = await getAutostartStatus();
496
+ process.stdout.write(`Autostart: ${status.enabled ? "enabled" : "disabled"} (${status.method})
497
+ `);
498
+ }
499
+ function printHelp() {
500
+ process.stdout.write(`Hermes Link ${LINK_VERSION} \u2014 Local service for Hermes Agent
501
+
502
+ Usage: ${LINK_COMMAND} <command> [options]
503
+
504
+ Commands:
505
+ start Start the Hermes Link daemon
506
+ stop Stop the Hermes Link daemon
507
+ restart Restart the daemon
508
+ status Show daemon and service status
509
+ pair Generate a pairing URL for your device
510
+ config get Show current configuration
511
+ config set Set a configuration value
512
+ autostart Show/enable/disable autostart
513
+ logs Show recent log entries (--gateway for gateway logs)
514
+ version Print version
515
+
516
+ Options:
517
+ --version, -v Print version
518
+ --help, -h Show this help
519
+ `);
520
+ }
521
+ main().catch((err) => {
522
+ process.stderr.write(`${err.message}
523
+ `);
524
+ process.exitCode = 1;
525
+ });
@@ -0,0 +1,111 @@
1
+ import Koa from 'koa';
2
+ import { Server } from 'http';
3
+ import { z } from 'zod';
4
+ import { EventEmitter } from 'events';
5
+
6
+ interface RuntimePaths {
7
+ homeDir: string;
8
+ identityFile: string;
9
+ configFile: string;
10
+ stateFile: string;
11
+ credentialsFile: string;
12
+ databaseFile: string;
13
+ conversationsDir: string;
14
+ blobsDir: string;
15
+ indexesDir: string;
16
+ logsDir: string;
17
+ runDir: string;
18
+ pairingDir: string;
19
+ }
20
+
21
+ type LogLevel = "debug" | "info" | "warn" | "error";
22
+ type Language = "auto" | "en" | "zh-CN";
23
+ interface LinkConfig {
24
+ port: number;
25
+ lanHost: string | null;
26
+ serverBaseUrl: string;
27
+ relayBaseUrl: string;
28
+ appConnectTokenIssuer: string;
29
+ appConnectTokenAudience: string;
30
+ language: Language;
31
+ logLevel: LogLevel;
32
+ }
33
+
34
+ declare const linkIdentitySchema: z.ZodObject<{
35
+ install_id: z.ZodString;
36
+ link_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
37
+ public_key_pem: z.ZodString;
38
+ private_key_pem: z.ZodString;
39
+ created_at: z.ZodString;
40
+ updated_at: z.ZodString;
41
+ }, "strip", z.ZodTypeAny, {
42
+ install_id: string;
43
+ public_key_pem: string;
44
+ private_key_pem: string;
45
+ created_at: string;
46
+ updated_at: string;
47
+ link_id?: string | null | undefined;
48
+ }, {
49
+ install_id: string;
50
+ public_key_pem: string;
51
+ private_key_pem: string;
52
+ created_at: string;
53
+ updated_at: string;
54
+ link_id?: string | null | undefined;
55
+ }>;
56
+ type LinkIdentity = z.infer<typeof linkIdentitySchema>;
57
+
58
+ type RelayClientState = "disconnected" | "connecting" | "connected" | "closing";
59
+ interface RelayClientOptions {
60
+ relayBaseUrl: string;
61
+ identity: LinkIdentity;
62
+ token: string;
63
+ paths?: RuntimePaths;
64
+ fetchImpl?: typeof fetch;
65
+ reconnectDelayMs?: number;
66
+ maxReconnectDelayMs?: number;
67
+ pingIntervalMs?: number;
68
+ }
69
+ interface RelayMessage {
70
+ type: string;
71
+ [key: string]: unknown;
72
+ }
73
+ declare class RelayClient extends EventEmitter {
74
+ private options;
75
+ private ws;
76
+ private state;
77
+ private token;
78
+ private logger;
79
+ private reconnectTimer;
80
+ private pingTimer;
81
+ private reconnectDelay;
82
+ private closed;
83
+ constructor(options: RelayClientOptions);
84
+ get currentState(): RelayClientState;
85
+ start(): void;
86
+ stop(): void;
87
+ send(message: RelayMessage): void;
88
+ private connect;
89
+ private buildWsUrl;
90
+ private scheduleReconnect;
91
+ private maybeRefreshTokenAndReconnect;
92
+ private startPing;
93
+ private stopPing;
94
+ private clearTimers;
95
+ }
96
+
97
+ interface LinkServiceOptions {
98
+ config: LinkConfig;
99
+ identity: LinkIdentity;
100
+ paths: RuntimePaths;
101
+ relayToken: string;
102
+ }
103
+ interface LinkService {
104
+ app: Koa;
105
+ server: Server;
106
+ relayClient: RelayClient;
107
+ stop(): Promise<void>;
108
+ }
109
+ declare function startLinkService(options: LinkServiceOptions): Promise<LinkService>;
110
+
111
+ export { type LinkService, type LinkServiceOptions, startLinkService };
@@ -0,0 +1,6 @@
1
+ import {
2
+ startLinkService
3
+ } from "../chunk-C24HF73Y.js";
4
+ export {
5
+ startLinkService
6
+ };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@bulolo/hermes-link",
3
+ "version": "0.1.0",
4
+ "description": "Hermes Link companion service and CLI for connecting hermes-agent through zhiji",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "hermeslink": "dist/cli/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "scripts/check-node-version.mjs",
13
+ "scripts/postinstall.mjs",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "keywords": [
18
+ "hermes",
19
+ "hermes-agent",
20
+ "relay",
21
+ "link",
22
+ "cli"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "engines": {
28
+ "node": ">=20.0.0"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup src/cli/index.ts src/http/app.ts --format esm --target node20 --dts --clean",
32
+ "check": "tsc --noEmit",
33
+ "dev": "tsx src/cli/index.ts",
34
+ "preinstall": "node ./scripts/check-node-version.mjs",
35
+ "postinstall": "node ./scripts/postinstall.mjs",
36
+ "prepack": "npm run build",
37
+ "start": "node ./dist/cli/index.js",
38
+ "test": "vitest",
39
+ "publish:npm": "npm publish --access public"
40
+ },
41
+ "dependencies": {
42
+ "@koa/cors": "^5.0.0",
43
+ "@koa/router": "^15.4.0",
44
+ "better-sqlite3": "^12.9.0",
45
+ "koa": "^2.15.3",
46
+ "koa-bodyparser": "^4.4.1",
47
+ "pino": "^9.0.0",
48
+ "qrcode": "^1.5.4",
49
+ "qrcode-terminal": "^0.12.0",
50
+ "ws": "^8.18.0",
51
+ "yaml": "^2.6.1",
52
+ "zod": "^3.24.1"
53
+ },
54
+ "devDependencies": {
55
+ "@types/better-sqlite3": "^7.6.13",
56
+ "@types/koa": "^2.15.0",
57
+ "@types/koa__cors": "^5.0.1",
58
+ "@types/koa-bodyparser": "^4.3.12",
59
+ "@types/node": "^20.19.39",
60
+ "@types/qrcode": "^1.5.6",
61
+ "@types/qrcode-terminal": "^0.12.2",
62
+ "@types/ws": "^8.5.13",
63
+ "pino-pretty": "^13.0.0",
64
+ "tsup": "^8.3.5",
65
+ "tsx": "^4.19.2",
66
+ "typescript": "^5.7.2",
67
+ "vitest": "^2.1.8"
68
+ }
69
+ }