@hermespilot/link 0.4.0 → 0.4.2

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.
@@ -1500,6 +1500,13 @@ function resolveHermesProfileDir(profileName = "default") {
1500
1500
  }
1501
1501
  return path3.join(os.homedir(), ".hermes", "profiles", profileName);
1502
1502
  }
1503
+ function resolveHermesProfilesDir() {
1504
+ const hermesHome = process.env.HERMES_HOME?.trim();
1505
+ if (hermesHome) {
1506
+ return path3.join(resolveDefaultHermesRoot(path3.resolve(hermesHome)), "profiles");
1507
+ }
1508
+ return path3.join(os.homedir(), ".hermes", "profiles");
1509
+ }
1503
1510
  function resolveHermesConfigPath(profileName = "default") {
1504
1511
  return path3.join(resolveHermesProfileDir(profileName), "config.yaml");
1505
1512
  }
@@ -3970,7 +3977,7 @@ import os2 from "os";
3970
3977
  import path5 from "path";
3971
3978
 
3972
3979
  // src/constants.ts
3973
- var LINK_VERSION = "0.4.0";
3980
+ var LINK_VERSION = "0.4.2";
3974
3981
  var LINK_COMMAND = "hermeslink";
3975
3982
  var LINK_DEFAULT_PORT = 52379;
3976
3983
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -9253,7 +9260,6 @@ function isNodeError9(error, code) {
9253
9260
  // src/conversations/hermes-session-sync.ts
9254
9261
  import { randomUUID as randomUUID7 } from "crypto";
9255
9262
  import { readdir as readdir7, readFile as readFile9, stat as stat9 } from "fs/promises";
9256
- import os4 from "os";
9257
9263
  import path15 from "path";
9258
9264
 
9259
9265
  // src/conversations/delivery-import.ts
@@ -10616,7 +10622,7 @@ function rememberKnownHermesConversation(map, sessionId, conversationId) {
10616
10622
  }
10617
10623
  async function discoverHermesProfileNames() {
10618
10624
  const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
10619
- const profilesDir = path15.join(os4.homedir(), ".hermes", "profiles");
10625
+ const profilesDir = resolveHermesProfilesDir();
10620
10626
  const entries = await readdir7(profilesDir, { withFileTypes: true }).catch(
10621
10627
  (error) => {
10622
10628
  if (isNodeError11(error, "ENOENT")) {
@@ -15760,7 +15766,6 @@ function createHttpErrorMiddleware(logger) {
15760
15766
 
15761
15767
  // src/hermes/profiles.ts
15762
15768
  import { readdir as readdir9, readFile as readFile12, rename as rename3, rm as rm6, stat as stat12 } from "fs/promises";
15763
- import os5 from "os";
15764
15769
  import path18 from "path";
15765
15770
  import YAML2 from "yaml";
15766
15771
  var DEFAULT_PROFILE = "default";
@@ -15768,7 +15773,7 @@ var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
15768
15773
  async function listHermesProfiles(paths = resolveRuntimePaths()) {
15769
15774
  const profiles = /* @__PURE__ */ new Map();
15770
15775
  profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
15771
- const profilesDir = path18.join(os5.homedir(), ".hermes", "profiles");
15776
+ const profilesDir = resolveHermesProfilesDir();
15772
15777
  const entries = await readdir9(profilesDir, { withFileTypes: true }).catch(
15773
15778
  (error) => {
15774
15779
  if (isNodeError14(error, "ENOENT")) {
@@ -20624,7 +20629,7 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
20624
20629
  // src/runtime/system-info.ts
20625
20630
  import { execFileSync } from "child_process";
20626
20631
  import { readFileSync } from "fs";
20627
- import os6 from "os";
20632
+ import os4 from "os";
20628
20633
  function readLinkSystemInfo() {
20629
20634
  const platform = process.platform;
20630
20635
  const hostname = readHostname(platform);
@@ -20663,7 +20668,7 @@ function readHostname(platform) {
20663
20668
  return computerName;
20664
20669
  }
20665
20670
  }
20666
- return normalizeText(os6.hostname());
20671
+ return normalizeText(os4.hostname());
20667
20672
  }
20668
20673
  function readOsLabel(platform) {
20669
20674
  if (platform === "darwin") {
@@ -20671,12 +20676,12 @@ function readOsLabel(platform) {
20671
20676
  return version ? `macOS ${version}` : "macOS";
20672
20677
  }
20673
20678
  if (platform === "linux") {
20674
- return readLinuxOsRelease() ?? `Linux ${os6.release()}`;
20679
+ return readLinuxOsRelease() ?? `Linux ${os4.release()}`;
20675
20680
  }
20676
20681
  if (platform === "win32") {
20677
- return `Windows ${os6.release()}`;
20682
+ return `Windows ${os4.release()}`;
20678
20683
  }
20679
- return `${os6.type()} ${os6.release()}`.trim();
20684
+ return `${os4.type()} ${os4.release()}`.trim();
20680
20685
  }
20681
20686
  function readLinuxOsRelease() {
20682
20687
  for (const file of ["/etc/os-release", "/usr/lib/os-release"]) {
@@ -20723,11 +20728,11 @@ function truncateText(value, maxLength) {
20723
20728
  }
20724
20729
 
20725
20730
  // src/topology/network.ts
20726
- import os8 from "os";
20731
+ import os6 from "os";
20727
20732
 
20728
20733
  // src/topology/environment.ts
20729
20734
  import { existsSync, readFileSync as readFileSync2 } from "fs";
20730
- import os7 from "os";
20735
+ import os5 from "os";
20731
20736
  function detectRuntimeEnvironment(env = process.env) {
20732
20737
  if (isWsl(env)) {
20733
20738
  return {
@@ -20756,7 +20761,7 @@ function isWsl(env) {
20756
20761
  if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) {
20757
20762
  return true;
20758
20763
  }
20759
- const release = os7.release().toLowerCase();
20764
+ const release = os5.release().toLowerCase();
20760
20765
  return release.includes("microsoft") || release.includes("wsl");
20761
20766
  }
20762
20767
  function isContainer(env) {
@@ -20801,7 +20806,7 @@ async function discoverRouteCandidates(options) {
20801
20806
  };
20802
20807
  }
20803
20808
  function discoverLanIps() {
20804
- return discoverLanIpsFromInterfaces(os8.networkInterfaces());
20809
+ return discoverLanIpsFromInterfaces(os6.networkInterfaces());
20805
20810
  }
20806
20811
  function discoverLanIpsFromInterfaces(interfaces) {
20807
20812
  const result = /* @__PURE__ */ new Set();
@@ -22408,6 +22413,10 @@ async function readPairingSession(sessionId, paths = resolveRuntimePaths()) {
22408
22413
  expires_at: record.expires_at
22409
22414
  };
22410
22415
  }
22416
+ function isPairingSessionExpired(session) {
22417
+ const expiresAtMs = Date.parse(session.expires_at);
22418
+ return !Number.isFinite(expiresAtMs) || Date.now() >= expiresAtMs;
22419
+ }
22411
22420
  async function recordPairingClaim(input, paths = resolveRuntimePaths()) {
22412
22421
  const record = {
22413
22422
  session_id: input.sessionId,
@@ -22437,7 +22446,20 @@ async function clearPairingClaim(sessionId, paths = resolveRuntimePaths()) {
22437
22446
  }
22438
22447
  async function claimPairing(input) {
22439
22448
  const paths = input.paths ?? resolveRuntimePaths();
22440
- const [identity, config] = await Promise.all([loadRequiredIdentity2(paths), loadConfig(paths)]);
22449
+ const [identity, config, localSession] = await Promise.all([
22450
+ loadRequiredIdentity2(paths),
22451
+ loadConfig(paths),
22452
+ readPairingSession(input.sessionId, paths)
22453
+ ]);
22454
+ if (!localSession) {
22455
+ throw new LinkHttpError(404, "pairing_session_not_found", "Pairing session was not found");
22456
+ }
22457
+ if (isPairingSessionExpired(localSession)) {
22458
+ throw new LinkHttpError(404, "pairing_session_expired", "Pairing session has expired");
22459
+ }
22460
+ if (localSession.link_id !== identity.link_id) {
22461
+ throw new LinkHttpError(409, "pairing_claim_mismatch", "Pairing claim does not match this Link");
22462
+ }
22441
22463
  let verified;
22442
22464
  try {
22443
22465
  verified = await postServerJson(
@@ -23251,7 +23273,7 @@ function registerPairingRoutes(router, options) {
23251
23273
  throw new LinkHttpError(404, "pairing_session_not_found", "Pairing session was not found");
23252
23274
  }
23253
23275
  const state = await readPairingState(sessionId, paths);
23254
- if (!state.claimed && isExpiredAt(session.expires_at)) {
23276
+ if (!state.claimed && isPairingSessionExpired(session)) {
23255
23277
  throw new LinkHttpError(404, "pairing_session_expired", "Pairing session has expired");
23256
23278
  }
23257
23279
  ctx.set("cache-control", "no-store");
@@ -23276,7 +23298,7 @@ async function readPairingState(sessionId, paths) {
23276
23298
  async function renderPairingPage(input) {
23277
23299
  const session = input.session;
23278
23300
  const expiresAtMs = Date.parse(session.expires_at);
23279
- const isExpired = !input.state.claimed && Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs;
23301
+ const isExpired = !input.state.claimed && isPairingSessionExpired(session);
23280
23302
  const qrPayload = JSON.stringify({
23281
23303
  kind: "hermes_link_pairing",
23282
23304
  version: 1,
@@ -23687,10 +23709,6 @@ function formatDate(value) {
23687
23709
  const date = new Date(value);
23688
23710
  return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN", { hour12: false });
23689
23711
  }
23690
- function isExpiredAt(value) {
23691
- const expiresAtMs = Date.parse(value);
23692
- return Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs;
23693
- }
23694
23712
 
23695
23713
  // src/http/routes/internal.ts
23696
23714
  function registerInternalRoutes(router, options) {
@@ -1 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { R as RuntimePaths } from '../paths-CmAiZsna.js';
3
+
4
+ declare function waitForPairingOrShutdown(sessionId: string, expiresAt: string, paths: RuntimePaths): Promise<'claimed' | 'expired' | 'shutdown'>;
5
+ declare function isCliEntrypoint(entry?: string, moduleUrl?: string): boolean;
6
+
7
+ export { isCliEntrypoint, waitForPairingOrShutdown };
package/dist/cli/index.js CHANGED
@@ -34,11 +34,14 @@ import {
34
34
  startDaemonProcess,
35
35
  startLinkService,
36
36
  stopDaemonProcess
37
- } from "../chunk-FWPHQZP6.js";
37
+ } from "../chunk-YSSZPVBP.js";
38
38
 
39
39
  // src/cli/index.ts
40
40
  import { Command } from "commander";
41
+ import { realpathSync } from "fs";
42
+ import path3 from "path";
41
43
  import { createInterface } from "readline/promises";
44
+ import { pathToFileURL } from "url";
42
45
  import qrcode from "qrcode-terminal";
43
46
 
44
47
  // src/autostart/autostart.ts
@@ -286,6 +289,7 @@ var messages = {
286
289
  "pair.openPairingPage": "If the QR code is hard to scan, you can open this page locally: {url}",
287
290
  "pair.manualCode": "You can also use the HermesPilot App manual connection mode and enter this pairing code:",
288
291
  "pair.expires": "Pairing expires in 10 minutes. Press Ctrl+C to cancel waiting.",
292
+ "pair.expired": "Pairing expired. Please run `hermeslink pair` again.",
289
293
  "pair.claimed": "Pairing succeeded. Starting Hermes Link in the background...",
290
294
  "pair.claimedRunning": "Pairing succeeded. Hermes Link is already running in the background.",
291
295
  "pair.relayOnlyNotice": "Network note: this {kind} environment does not expose a phone-reachable LAN/public direct address by default. The App will connect through Relay.",
@@ -380,6 +384,7 @@ var messages = {
380
384
  "pair.openPairingPage": "\u5982\u679C\u4E8C\u7EF4\u7801\u4E0D\u5BB9\u6613\u626B\u63CF\uFF0C\u4F60\u53EF\u4EE5\u5728\u672C\u673A\u6253\u5F00\u8FD9\u4E2A\u9875\u9762\uFF1A{url}",
381
385
  "pair.manualCode": "\u4F60\u4E5F\u53EF\u4EE5\u5728 HermesPilot App \u4E2D\u4F7F\u7528\u624B\u52A8\u8FDE\u63A5\u6A21\u5F0F\uFF0C\u8F93\u5165\u4EE5\u4E0B\u914D\u5BF9\u7801\u8FDB\u884C\u8FDE\u63A5\uFF1A",
382
386
  "pair.expires": "\u914D\u5BF9\u4F1A\u8BDD 10 \u5206\u949F\u540E\u8FC7\u671F\u3002\u6309 Ctrl+C \u9000\u51FA\u7B49\u5F85\u3002",
387
+ "pair.expired": "\u914D\u5BF9\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C `hermeslink pair`\u3002",
383
388
  "pair.claimed": "\u914D\u5BF9\u5DF2\u6210\u529F\u3002\u6B63\u5728\u628A Hermes Link \u5207\u6362\u5230\u540E\u53F0\u8FD0\u884C...",
384
389
  "pair.claimedRunning": "\u914D\u5BF9\u5DF2\u6210\u529F\u3002Hermes Link \u5DF2\u5728\u540E\u53F0\u6301\u7EED\u8FD0\u884C\u3002",
385
390
  "pair.relayOnlyNotice": "\u7F51\u7EDC\u63D0\u793A\uFF1A\u5F53\u524D\u662F {kind} \u73AF\u5883\uFF0C\u9ED8\u8BA4\u4E0D\u4F1A\u66B4\u9732\u624B\u673A\u53EF\u8BBF\u95EE\u7684\u5C40\u57DF\u7F51\u6216\u516C\u7F51\u76F4\u8FDE\u5730\u5740\u3002App \u4F1A\u901A\u8FC7 Relay \u8FDE\u63A5\u3002",
@@ -871,7 +876,11 @@ program.command("pair").description(helpText("pair.description")).action(async (
871
876
  console.log(t("pair.manualCode"));
872
877
  console.log(prepared.code);
873
878
  console.log(t("pair.expires"));
874
- const result = await waitForPairingOrShutdown(prepared.sessionId, paths);
879
+ const result = await waitForPairingOrShutdown(
880
+ prepared.sessionId,
881
+ prepared.expiresAt,
882
+ paths
883
+ );
875
884
  if (service) {
876
885
  await service.close();
877
886
  }
@@ -910,6 +919,8 @@ program.command("pair").description(helpText("pair.description")).action(async (
910
919
  const status = await startDaemonProcess(paths);
911
920
  console.log(t("start.backgroundStarted", { pid: status.pid ?? "unknown" }));
912
921
  }
922
+ } else if (result === "expired") {
923
+ console.log(t("pair.expired"));
913
924
  }
914
925
  } finally {
915
926
  pairingRelayBridge?.close();
@@ -990,11 +1001,13 @@ program.command("doctor").description(helpText("doctor.description")).action(asy
990
1001
  console.log(t("doctor.apiUnavailable", { message: error instanceof Error ? error.message : String(error) }));
991
1002
  }
992
1003
  });
993
- program.parseAsync(process.argv).catch(async (error) => {
994
- const language = await loadCliLanguage().catch(() => detectSystemLanguage());
995
- console.error(localizeErrorMessage(error, language));
996
- process.exitCode = 1;
997
- });
1004
+ if (isCliEntrypoint()) {
1005
+ program.parseAsync(process.argv).catch(async (error) => {
1006
+ const language = await loadCliLanguage().catch(() => detectSystemLanguage());
1007
+ console.error(localizeErrorMessage(error, language));
1008
+ process.exitCode = 1;
1009
+ });
1010
+ }
998
1011
  async function loadCliLanguage() {
999
1012
  const config = await loadConfig();
1000
1013
  return resolveLanguage(config.language);
@@ -1100,8 +1113,9 @@ async function waitForShutdown(cleanup) {
1100
1113
  });
1101
1114
  await cleanup();
1102
1115
  }
1103
- async function waitForPairingOrShutdown(sessionId, paths) {
1116
+ async function waitForPairingOrShutdown(sessionId, expiresAt, paths) {
1104
1117
  let shutdownRequested = false;
1118
+ const expiresAtMs = Date.parse(expiresAt);
1105
1119
  const stop = () => {
1106
1120
  shutdownRequested = true;
1107
1121
  };
@@ -1109,6 +1123,9 @@ async function waitForPairingOrShutdown(sessionId, paths) {
1109
1123
  process.once("SIGTERM", stop);
1110
1124
  try {
1111
1125
  while (!shutdownRequested) {
1126
+ if (Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs) {
1127
+ return "expired";
1128
+ }
1112
1129
  const record = await readPairingClaim(sessionId, paths);
1113
1130
  if (record) {
1114
1131
  return "claimed";
@@ -1141,3 +1158,17 @@ function printPostPairingNetworkNotice(environment, config, t) {
1141
1158
  console.log(t("pair.relayOnlyLanHostHint"));
1142
1159
  console.log(t("pair.relayOnlySafetyHint"));
1143
1160
  }
1161
+ function isCliEntrypoint(entry = process.argv[1], moduleUrl = import.meta.url) {
1162
+ if (!entry) {
1163
+ return false;
1164
+ }
1165
+ try {
1166
+ return moduleUrl === pathToFileURL(realpathSync(path3.resolve(entry))).href;
1167
+ } catch {
1168
+ return moduleUrl === pathToFileURL(path3.resolve(entry)).href;
1169
+ }
1170
+ }
1171
+ export {
1172
+ isCliEntrypoint,
1173
+ waitForPairingOrShutdown
1174
+ };
@@ -1,4 +1,5 @@
1
1
  import Koa from 'koa';
2
+ import { R as RuntimePaths } from '../paths-CmAiZsna.js';
2
3
 
3
4
  interface ConversationProfileSummary {
4
5
  uid?: string;
@@ -267,21 +268,6 @@ interface CancelRunResult {
267
268
  }
268
269
  type ConversationEventListener = (event: ConversationEvent) => void;
269
270
 
270
- interface RuntimePaths {
271
- homeDir: string;
272
- identityFile: string;
273
- configFile: string;
274
- stateFile: string;
275
- credentialsFile: string;
276
- databaseFile: string;
277
- conversationsDir: string;
278
- blobsDir: string;
279
- indexesDir: string;
280
- logsDir: string;
281
- runDir: string;
282
- pairingDir: string;
283
- }
284
-
285
271
  interface LinkStatistics {
286
272
  conversations: {
287
273
  total: number;
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-FWPHQZP6.js";
3
+ } from "../chunk-YSSZPVBP.js";
4
4
  export {
5
5
  createApp
6
6
  };
@@ -0,0 +1,16 @@
1
+ interface RuntimePaths {
2
+ homeDir: string;
3
+ identityFile: string;
4
+ configFile: string;
5
+ stateFile: string;
6
+ credentialsFile: string;
7
+ databaseFile: string;
8
+ conversationsDir: string;
9
+ blobsDir: string;
10
+ indexesDir: string;
11
+ logsDir: string;
12
+ runDir: string;
13
+ pairingDir: string;
14
+ }
15
+
16
+ export type { RuntimePaths as R };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hermespilot/link",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",