@bulolo/hermes-link 0.2.9 → 0.3.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.
- package/README.md +526 -44
- package/dist/chunk-AA2LZ6QZ.js +8790 -0
- package/dist/chunk-NP3Y2NVF.js +18 -0
- package/dist/cli/index.js +47 -70
- package/dist/errors-KGEGMUYS.js +8 -0
- package/dist/http/app.d.ts +0 -46
- package/dist/http/app.js +2 -1
- package/package.json +13 -5
- package/dist/chunk-GHAP2EHA.js +0 -3649
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/core/errors.ts
|
|
2
|
+
var LinkHttpError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function isLinkHttpError(error) {
|
|
12
|
+
return error instanceof LinkHttpError;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
LinkHttpError,
|
|
17
|
+
isLinkHttpError
|
|
18
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
DAEMON_LOG_FILE,
|
|
4
4
|
LINK_COMMAND,
|
|
5
5
|
LINK_VERSION,
|
|
6
|
-
bootstrapWithRelay,
|
|
7
6
|
createRotatingTextLogWriter,
|
|
8
7
|
currentCliScriptPath,
|
|
9
8
|
detectRuntimeEnvironment,
|
|
@@ -19,11 +18,11 @@ import {
|
|
|
19
18
|
readRecentGatewayLogEntries,
|
|
20
19
|
readRecentLogEntries,
|
|
21
20
|
resolveRuntimePaths,
|
|
22
|
-
saveAssignedLinkId,
|
|
23
21
|
saveConfig,
|
|
24
22
|
startLinkService,
|
|
25
23
|
writeJsonFile
|
|
26
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-AA2LZ6QZ.js";
|
|
25
|
+
import "../chunk-NP3Y2NVF.js";
|
|
27
26
|
|
|
28
27
|
// src/cli/index.ts
|
|
29
28
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
@@ -234,59 +233,49 @@ function pairingSessionPath(sessionId, paths) {
|
|
|
234
233
|
}
|
|
235
234
|
async function runPairingPreflight(options) {
|
|
236
235
|
const token = await generateAppConnectToken(options.paths);
|
|
237
|
-
const routes =
|
|
236
|
+
const routes = await discoverRouteCandidates({
|
|
238
237
|
port: options.config.port,
|
|
239
|
-
relayBaseUrl: options.config.relayBaseUrl,
|
|
240
|
-
linkId: options.identity.link_id,
|
|
241
|
-
installId: options.identity.install_id,
|
|
242
|
-
publicKeyPem: options.identity.public_key_pem,
|
|
243
238
|
configuredLanHost: options.config.lanHost
|
|
244
|
-
}).catch(() => null)
|
|
239
|
+
}).catch(() => null);
|
|
245
240
|
const localApiUrl = `http://127.0.0.1:${options.config.port}`;
|
|
246
|
-
const preferredUrls = (routes?.preferredUrls ?? []).
|
|
247
|
-
(u) => !u.includes("/api/v1/relay/")
|
|
248
|
-
);
|
|
249
|
-
const bestUrl = preferredUrls[0] ?? localApiUrl;
|
|
241
|
+
const preferredUrls = (routes?.preferredUrls ?? []).length > 0 ? routes.preferredUrls : [localApiUrl];
|
|
250
242
|
const sessionId = `ps_${token.token.slice(0, 16)}`;
|
|
251
243
|
const session = {
|
|
252
244
|
session_id: sessionId,
|
|
253
245
|
code: token.token,
|
|
254
246
|
link_id: options.identity.link_id ?? "",
|
|
255
247
|
display_name: "Hermes Link",
|
|
256
|
-
local_api_url:
|
|
257
|
-
|
|
258
|
-
relay_base_url: options.config.relayBaseUrl,
|
|
259
|
-
preferred_urls: preferredUrls.length > 0 ? preferredUrls : [localApiUrl],
|
|
248
|
+
local_api_url: preferredUrls[0] ?? localApiUrl,
|
|
249
|
+
preferred_urls: preferredUrls,
|
|
260
250
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
261
251
|
expires_at: token.expiresAt
|
|
262
252
|
};
|
|
263
253
|
await mkdir2(options.paths.pairingDir, { recursive: true, mode: 448 }).catch(() => void 0);
|
|
264
254
|
await writeJsonFile(pairingSessionPath(sessionId, options.paths), session);
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
255
|
+
const qrPayload = {
|
|
256
|
+
kind: "hermes_link_pairing",
|
|
257
|
+
version: 1,
|
|
258
|
+
link_id: options.identity.link_id ?? "",
|
|
259
|
+
display_name: "Hermes Link",
|
|
260
|
+
session_id: sessionId,
|
|
261
|
+
code: token.token,
|
|
262
|
+
preferred_urls: preferredUrls
|
|
263
|
+
};
|
|
264
|
+
const pageUrl = buildLocalPairingPageUrl(options.config.port, sessionId, token.token);
|
|
275
265
|
if (options.openBrowser !== false) {
|
|
276
|
-
await openSystemBrowser(
|
|
266
|
+
await openSystemBrowser(pageUrl).catch(() => void 0);
|
|
277
267
|
}
|
|
278
|
-
return {
|
|
268
|
+
return {
|
|
269
|
+
qrPayload: JSON.stringify(qrPayload),
|
|
270
|
+
pageUrl,
|
|
271
|
+
connectToken: token.token,
|
|
272
|
+
sessionId,
|
|
273
|
+
preferredUrls
|
|
274
|
+
};
|
|
279
275
|
}
|
|
280
|
-
function
|
|
281
|
-
const qs = new URLSearchParams({
|
|
282
|
-
|
|
283
|
-
install_id: params.installId,
|
|
284
|
-
connect_token: params.connectToken,
|
|
285
|
-
port: String(params.port)
|
|
286
|
-
});
|
|
287
|
-
if (params.lanUrls.length > 0) qs.set("lan_urls", params.lanUrls.join(","));
|
|
288
|
-
if (params.publicUrls.length > 0) qs.set("public_urls", params.publicUrls.join(","));
|
|
289
|
-
return `hermesapp://pair?${qs.toString()}`;
|
|
276
|
+
function buildLocalPairingPageUrl(port, sessionId, connectToken) {
|
|
277
|
+
const qs = new URLSearchParams({ session_id: sessionId, connect_token: connectToken });
|
|
278
|
+
return `http://127.0.0.1:${port}/pair?${qs.toString()}`;
|
|
290
279
|
}
|
|
291
280
|
|
|
292
281
|
// src/cli/index.ts
|
|
@@ -403,24 +392,9 @@ async function cmdDaemon(paths) {
|
|
|
403
392
|
process.exitCode = 1;
|
|
404
393
|
return;
|
|
405
394
|
}
|
|
406
|
-
const config = await loadConfig(paths);
|
|
407
395
|
const identity = await ensureIdentity(paths);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const bootstrapResult = await bootstrapWithRelay({
|
|
411
|
-
relayBaseUrl: config.relayBaseUrl,
|
|
412
|
-
identity,
|
|
413
|
-
port: config.port
|
|
414
|
-
});
|
|
415
|
-
if (!identity.link_id) {
|
|
416
|
-
await saveAssignedLinkId(bootstrapResult.linkId, paths);
|
|
417
|
-
}
|
|
418
|
-
relayToken = bootstrapResult.token;
|
|
419
|
-
} catch (err) {
|
|
420
|
-
process.stderr.write(`Warning: Relay bootstrap failed: ${err.message}
|
|
421
|
-
`);
|
|
422
|
-
}
|
|
423
|
-
const service = await startLinkService({ config, identity, paths, relayToken });
|
|
396
|
+
const config = await loadConfig(paths);
|
|
397
|
+
const service = await startLinkService({ config, identity, paths });
|
|
424
398
|
process.stdout.write(`Hermes Link running on port ${config.port}
|
|
425
399
|
`);
|
|
426
400
|
const shutdown = async () => {
|
|
@@ -437,22 +411,24 @@ async function cmdDaemonSupervisor(paths) {
|
|
|
437
411
|
async function cmdPair(paths) {
|
|
438
412
|
const config = await loadConfig(paths);
|
|
439
413
|
const identity = await ensureIdentity(paths);
|
|
440
|
-
|
|
441
|
-
process.stderr.write("Error: Hermes Link is not connected to relay. Run 'hermeslink start' first.\n");
|
|
442
|
-
process.exitCode = 1;
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const result = await runPairingPreflight({ identity, config, paths });
|
|
446
|
-
const pageBase = result.bestUrl.replace(/\/+$/u, "");
|
|
447
|
-
const pageUrl = `${pageBase}/pair?connect_token=${encodeURIComponent(result.connectToken)}`;
|
|
414
|
+
const result = await runPairingPreflight({ identity, config, paths, openBrowser: false });
|
|
448
415
|
process.stdout.write("\n");
|
|
449
|
-
qrcode.generate(result.
|
|
416
|
+
qrcode.generate(result.qrPayload, { small: true });
|
|
417
|
+
process.stdout.write(`
|
|
418
|
+
Pairing page: ${result.pageUrl}
|
|
419
|
+
`);
|
|
420
|
+
process.stdout.write(`Session ID: ${result.sessionId}
|
|
421
|
+
`);
|
|
422
|
+
process.stdout.write(`Connect token: ${result.connectToken}
|
|
423
|
+
`);
|
|
424
|
+
process.stdout.write(`Preferred URLs: ${result.preferredUrls.join(", ")}
|
|
425
|
+
`);
|
|
450
426
|
process.stdout.write(`
|
|
451
|
-
|
|
427
|
+
App \u626B\u63CF\u4E8C\u7EF4\u7801\u540E\uFF0C\u8C03\u7528\u4EE5\u4E0B\u63A5\u53E3\u5B8C\u6210\u914D\u5BF9\uFF1A
|
|
452
428
|
`);
|
|
453
|
-
process.stdout.write(`
|
|
429
|
+
process.stdout.write(` POST ${result.preferredUrls[0]}/api/v1/pairing/claim
|
|
454
430
|
`);
|
|
455
|
-
process.stdout.write(`
|
|
431
|
+
process.stdout.write(` Body: { "session_id": "${result.sessionId}", "claim_token": "<code>" }
|
|
456
432
|
`);
|
|
457
433
|
}
|
|
458
434
|
async function cmdConfig(paths) {
|
|
@@ -530,13 +506,13 @@ async function cmdLogs(paths) {
|
|
|
530
506
|
}
|
|
531
507
|
async function cmdAutostart(paths) {
|
|
532
508
|
const subcommand = args[1];
|
|
533
|
-
if (subcommand === "enable") {
|
|
509
|
+
if (subcommand === "enable" || subcommand === "on") {
|
|
534
510
|
const status2 = await enableAutostart();
|
|
535
511
|
process.stdout.write(`Autostart ${status2.enabled ? "enabled" : "could not be enabled"} (${status2.method})
|
|
536
512
|
`);
|
|
537
513
|
return;
|
|
538
514
|
}
|
|
539
|
-
if (subcommand === "disable") {
|
|
515
|
+
if (subcommand === "disable" || subcommand === "off") {
|
|
540
516
|
const status2 = await disableAutostart();
|
|
541
517
|
process.stdout.write(`Autostart ${status2.enabled ? "still enabled" : "disabled"} (${status2.method})
|
|
542
518
|
`);
|
|
@@ -560,6 +536,7 @@ Commands:
|
|
|
560
536
|
config get Show current configuration
|
|
561
537
|
config set Set a configuration value
|
|
562
538
|
autostart Show/enable/disable autostart
|
|
539
|
+
autostart on|off Enable or disable autostart
|
|
563
540
|
logs Show recent log entries (--gateway for gateway logs)
|
|
564
541
|
version Print version
|
|
565
542
|
|
package/dist/http/app.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Koa from 'koa';
|
|
2
2
|
import { Server } from 'http';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { EventEmitter } from 'events';
|
|
5
4
|
|
|
6
5
|
interface RuntimePaths {
|
|
7
6
|
homeDir: string;
|
|
@@ -23,10 +22,6 @@ type Language = "auto" | "en" | "zh-CN";
|
|
|
23
22
|
interface LinkConfig {
|
|
24
23
|
port: number;
|
|
25
24
|
lanHost: string | null;
|
|
26
|
-
serverBaseUrl: string;
|
|
27
|
-
relayBaseUrl: string;
|
|
28
|
-
appConnectTokenIssuer: string;
|
|
29
|
-
appConnectTokenAudience: string;
|
|
30
25
|
language: Language;
|
|
31
26
|
logLevel: LogLevel;
|
|
32
27
|
}
|
|
@@ -55,55 +50,14 @@ declare const linkIdentitySchema: z.ZodObject<{
|
|
|
55
50
|
}>;
|
|
56
51
|
type LinkIdentity = z.infer<typeof linkIdentitySchema>;
|
|
57
52
|
|
|
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
53
|
interface LinkServiceOptions {
|
|
98
54
|
config: LinkConfig;
|
|
99
55
|
identity: LinkIdentity;
|
|
100
56
|
paths: RuntimePaths;
|
|
101
|
-
relayToken: string;
|
|
102
57
|
}
|
|
103
58
|
interface LinkService {
|
|
104
59
|
app: Koa;
|
|
105
60
|
server: Server;
|
|
106
|
-
relayClient: RelayClient;
|
|
107
61
|
stop(): Promise<void>;
|
|
108
62
|
}
|
|
109
63
|
declare function startLinkService(options: LinkServiceOptions): Promise<LinkService>;
|
package/dist/http/app.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bulolo/hermes-link",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Local companion service and CLI for Hermes Agent, enabling mobile and LAN access",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"hermes",
|
|
19
19
|
"hermes-agent",
|
|
20
|
-
"relay",
|
|
21
20
|
"link",
|
|
22
|
-
"cli"
|
|
21
|
+
"cli",
|
|
22
|
+
"local",
|
|
23
|
+
"lan"
|
|
23
24
|
],
|
|
24
25
|
"publishConfig": {
|
|
25
26
|
"access": "public"
|
|
@@ -31,13 +32,20 @@
|
|
|
31
32
|
"build": "tsup src/cli/index.ts src/http/app.ts --format esm --target node20 --dts --clean",
|
|
32
33
|
"check": "tsc --noEmit",
|
|
33
34
|
"dev": "tsx src/cli/index.ts",
|
|
35
|
+
"dev:run": "npm run build && node ./dist/cli/index.js daemon --foreground",
|
|
36
|
+
"dev:watch": "tsup src/cli/index.ts src/http/app.ts --format esm --target node20 --watch",
|
|
34
37
|
"preinstall": "node ./scripts/check-node-version.mjs",
|
|
35
38
|
"postinstall": "node ./scripts/postinstall.mjs",
|
|
36
39
|
"prepack": "npm run build",
|
|
37
40
|
"start": "node ./dist/cli/index.js",
|
|
38
|
-
"test": "vitest",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:e2e": "vitest run tests/e2e",
|
|
39
44
|
"publish:npm": "npm publish --access public --registry https://registry.npmjs.org"
|
|
40
45
|
},
|
|
46
|
+
"overrides": {
|
|
47
|
+
"generator-function": "1.x"
|
|
48
|
+
},
|
|
41
49
|
"dependencies": {
|
|
42
50
|
"@koa/cors": "^5.0.0",
|
|
43
51
|
"@koa/router": "^15.4.0",
|