@hermespilot/link 0.2.7 → 0.2.8

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/cli/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  clearPairingClaim,
7
7
  currentCliScriptPath,
8
8
  daemonLogFile,
9
+ detectRuntimeEnvironment,
9
10
  ensureHermesApiServerAvailable,
10
11
  ensureHermesApiServerConfig,
11
12
  ensureIdentity,
@@ -15,21 +16,25 @@ import {
15
16
  hasActiveDevices,
16
17
  loadConfig,
17
18
  loadIdentity,
19
+ normalizeLanHost,
18
20
  preparePairing,
19
21
  probeLocalLinkService,
20
22
  readHermesApiServerConfig,
21
23
  readPairingClaim,
24
+ reportLinkStatusToServer,
22
25
  resolveHermesConfigPath,
23
26
  resolveHermesProfileDir,
24
27
  resolveRuntimePaths,
25
28
  runDaemonSupervisor,
29
+ saveConfig,
26
30
  startDaemonProcess,
27
31
  startLinkService,
28
32
  stopDaemonProcess
29
- } from "../chunk-SSJ7WDD7.js";
33
+ } from "../chunk-W4U6XJ4W.js";
30
34
 
31
35
  // src/cli/index.ts
32
36
  import { Command } from "commander";
37
+ import { createInterface } from "readline/promises";
33
38
  import qrcode from "qrcode-terminal";
34
39
 
35
40
  // src/autostart/autostart.ts
@@ -221,6 +226,9 @@ var messages = {
221
226
  "status.runtime": "Runtime: {value}",
222
227
  "status.mode": "Mode: {value}",
223
228
  "status.port": "Local port: {value}",
229
+ "status.lanHost": "Configured LAN host: {value}",
230
+ "status.notSet": "not set",
231
+ "status.environmentWarning": "Network note: {message}",
224
232
  "status.linkId": "Link ID: {value}",
225
233
  "status.notPaired": "not paired",
226
234
  "start.description": "Start Hermes Link daemon",
@@ -234,6 +242,15 @@ var messages = {
234
242
  "stop.stopped": "Hermes Link stopped.",
235
243
  "stop.notRunning": "Hermes Link is not running.",
236
244
  "restart.description": "Restart the background Hermes Link daemon",
245
+ "config.description": "Manage local Hermes Link configuration",
246
+ "config.set.description": "Set a configuration value",
247
+ "config.unset.description": "Unset a configuration value",
248
+ "config.unknownKey": "Unknown config key: {key}",
249
+ "config.lanHostInvalid": "lan-host must be a private LAN IPv4 address, such as 192.168.1.23.",
250
+ "config.lanHostSet": "Configured LAN host: {value}",
251
+ "config.lanHostUnset": "Configured LAN host cleared.",
252
+ "config.reported": "Updated HermesPilot Server with the latest LAN address.",
253
+ "config.reportSkippedUnpaired": "Hermes Link is not paired yet. The LAN address will be reported after pairing.",
237
254
  "daemon.description": "Run Hermes Link in the foreground",
238
255
  "daemon.foreground": "Hermes Link foreground daemon is running. Press Ctrl+C to stop.",
239
256
  "logs.description": "Show Hermes Link log paths",
@@ -263,6 +280,9 @@ var messages = {
263
280
  "pair.expires": "Pairing expires in 10 minutes. Press Ctrl+C to cancel waiting.",
264
281
  "pair.claimed": "Pairing succeeded. Starting Hermes Link in the background...",
265
282
  "pair.claimedRunning": "Pairing succeeded. Hermes Link is already running in the background.",
283
+ "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.",
284
+ "pair.relayOnlyLanHostHint": "If you manually expose this Link from Windows or your router, run `hermeslink config set lan-host <Windows LAN IP>` to publish the reachable LAN address.",
285
+ "pair.relayOnlySafetyHint": "Hermes Link will not automatically change Windows/WSL bridge, firewall, or portproxy settings because those are system-level network exposure choices.",
266
286
  "pair.autostartUnchanged": "Existing paired devices found. Boot autostart settings were left unchanged.",
267
287
  "pair.autostartFailed": "Pairing succeeded, but boot autostart could not be enabled: {message}",
268
288
  "doctor.description": "Run local diagnostics",
@@ -270,6 +290,8 @@ var messages = {
270
290
  "doctor.installId": "Install ID: {value}",
271
291
  "doctor.linkId": "Link ID: {value}",
272
292
  "doctor.notAssigned": "not assigned",
293
+ "doctor.lanHost": "Configured LAN host: {value}",
294
+ "doctor.networkWarning": "Network note: {message}",
273
295
  "doctor.apiReady": "Hermes API Server: ready",
274
296
  "doctor.apiStarted": "Hermes API Server: started and ready",
275
297
  "doctor.apiUnavailable": "Hermes API Server: unavailable. {message}",
@@ -290,6 +312,9 @@ var messages = {
290
312
  "status.runtime": "\u8FD0\u884C\u76EE\u5F55\uFF1A{value}",
291
313
  "status.mode": "\u6A21\u5F0F\uFF1A{value}",
292
314
  "status.port": "\u672C\u5730\u7AEF\u53E3\uFF1A{value}",
315
+ "status.lanHost": "\u5DF2\u914D\u7F6E\u5C40\u57DF\u7F51\u4E3B\u673A\uFF1A{value}",
316
+ "status.notSet": "\u672A\u8BBE\u7F6E",
317
+ "status.environmentWarning": "\u7F51\u7EDC\u63D0\u793A\uFF1A{message}",
293
318
  "status.linkId": "Link ID\uFF1A{value}",
294
319
  "status.notPaired": "\u5C1A\u672A\u914D\u5BF9",
295
320
  "start.description": "\u542F\u52A8 Hermes Link \u670D\u52A1",
@@ -303,6 +328,15 @@ var messages = {
303
328
  "stop.stopped": "Hermes Link \u5DF2\u505C\u6B62\u3002",
304
329
  "stop.notRunning": "Hermes Link \u6CA1\u6709\u5728\u8FD0\u884C\u3002",
305
330
  "restart.description": "\u91CD\u542F\u540E\u53F0 Hermes Link \u670D\u52A1",
331
+ "config.description": "\u7BA1\u7406\u672C\u673A Hermes Link \u914D\u7F6E",
332
+ "config.set.description": "\u8BBE\u7F6E\u914D\u7F6E\u9879",
333
+ "config.unset.description": "\u6E05\u9664\u914D\u7F6E\u9879",
334
+ "config.unknownKey": "\u672A\u77E5\u914D\u7F6E\u9879\uFF1A{key}",
335
+ "config.lanHostInvalid": "lan-host \u5FC5\u987B\u662F\u5C40\u57DF\u7F51 IPv4 \u5730\u5740\uFF0C\u4F8B\u5982 192.168.1.23\u3002",
336
+ "config.lanHostSet": "\u5DF2\u914D\u7F6E\u5C40\u57DF\u7F51\u4E3B\u673A\uFF1A{value}",
337
+ "config.lanHostUnset": "\u5DF2\u6E05\u9664\u5C40\u57DF\u7F51\u4E3B\u673A\u914D\u7F6E\u3002",
338
+ "config.reported": "\u5DF2\u628A\u6700\u65B0\u5C40\u57DF\u7F51\u5730\u5740\u66F4\u65B0\u5230 HermesPilot Server\u3002",
339
+ "config.reportSkippedUnpaired": "Hermes Link \u8FD8\u6CA1\u6709\u914D\u5BF9\uFF0C\u5C40\u57DF\u7F51\u5730\u5740\u4F1A\u5728\u914D\u5BF9\u540E\u4E0A\u62A5\u3002",
306
340
  "daemon.description": "\u4EE5\u524D\u53F0\u65B9\u5F0F\u8FD0\u884C Hermes Link",
307
341
  "daemon.foreground": "Hermes Link \u524D\u53F0\u670D\u52A1\u6B63\u5728\u8FD0\u884C\u3002\u6309 Ctrl+C \u505C\u6B62\u3002",
308
342
  "logs.description": "\u663E\u793A Hermes Link \u65E5\u5FD7\u8DEF\u5F84",
@@ -332,6 +366,9 @@ var messages = {
332
366
  "pair.expires": "\u914D\u5BF9\u4F1A\u8BDD 10 \u5206\u949F\u540E\u8FC7\u671F\u3002\u6309 Ctrl+C \u9000\u51FA\u7B49\u5F85\u3002",
333
367
  "pair.claimed": "\u914D\u5BF9\u5DF2\u6210\u529F\u3002\u6B63\u5728\u628A Hermes Link \u5207\u6362\u5230\u540E\u53F0\u8FD0\u884C...",
334
368
  "pair.claimedRunning": "\u914D\u5BF9\u5DF2\u6210\u529F\u3002Hermes Link \u5DF2\u5728\u540E\u53F0\u6301\u7EED\u8FD0\u884C\u3002",
369
+ "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",
370
+ "pair.relayOnlyLanHostHint": "\u5982\u679C\u4F60\u5DF2\u7ECF\u5728 Windows \u6216\u8DEF\u7531\u5668\u4FA7\u624B\u52A8\u628A\u8FD9\u4E2A Link \u66B4\u9732\u5230\u5C40\u57DF\u7F51\uFF0C\u53EF\u4EE5\u8FD0\u884C `hermeslink config set lan-host <Windows \u5C40\u57DF\u7F51 IP>` \u66F4\u65B0\u53EF\u8BBF\u95EE\u5730\u5740\u3002",
371
+ "pair.relayOnlySafetyHint": "Hermes Link \u4E0D\u4F1A\u81EA\u52A8\u4FEE\u6539 Windows/WSL \u6865\u63A5\u3001\u9632\u706B\u5899\u6216\u7AEF\u53E3\u4EE3\u7406\u914D\u7F6E\uFF0C\u56E0\u4E3A\u8FD9\u4E9B\u5C5E\u4E8E\u7CFB\u7EDF\u7EA7\u7F51\u7EDC\u66B4\u9732\u8BBE\u7F6E\u3002",
335
372
  "pair.autostartUnchanged": "\u68C0\u6D4B\u5230\u5DF2\u6709\u914D\u5BF9\u8BBE\u5907\uFF0C\u5F00\u673A\u81EA\u542F\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8\u3002",
336
373
  "pair.autostartFailed": "\u914D\u5BF9\u5DF2\u6210\u529F\uFF0C\u4F46\u542F\u7528\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A{message}",
337
374
  "doctor.description": "\u8FD0\u884C\u672C\u673A\u8BCA\u65AD",
@@ -339,6 +376,8 @@ var messages = {
339
376
  "doctor.installId": "Install ID\uFF1A{value}",
340
377
  "doctor.linkId": "Link ID\uFF1A{value}",
341
378
  "doctor.notAssigned": "\u5C1A\u672A\u5206\u914D",
379
+ "doctor.lanHost": "\u5DF2\u914D\u7F6E\u5C40\u57DF\u7F51\u4E3B\u673A\uFF1A{value}",
380
+ "doctor.networkWarning": "\u7F51\u7EDC\u63D0\u793A\uFF1A{message}",
342
381
  "doctor.apiReady": "Hermes API Server\uFF1A\u5DF2\u5C31\u7EEA",
343
382
  "doctor.apiStarted": "Hermes API Server\uFF1A\u5DF2\u81EA\u52A8\u542F\u52A8\u5E76\u5C31\u7EEA",
344
383
  "doctor.apiUnavailable": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528\u3002{message}",
@@ -598,6 +637,8 @@ program.command("status").option("--json", helpText("status.json")).description(
598
637
  paired: Boolean(identity?.link_id),
599
638
  mode: identity?.link_id ? "paired" : "local-only",
600
639
  port: config.port,
640
+ lanHost: config.lanHost,
641
+ environment: detectRuntimeEnvironment(),
601
642
  identity: identity ? getIdentityStatus(identity) : null,
602
643
  relay: {
603
644
  configured: Boolean(config.relayBaseUrl),
@@ -612,8 +653,43 @@ program.command("status").option("--json", helpText("status.json")).description(
612
653
  console.log(t("status.runtime", { value: payload.runtimeHome }));
613
654
  console.log(t("status.mode", { value: payload.mode }));
614
655
  console.log(t("status.port", { value: payload.port }));
656
+ console.log(t("status.lanHost", { value: payload.lanHost ?? t("status.notSet") }));
657
+ if (payload.environment.warning) {
658
+ console.log(t("status.environmentWarning", { message: payload.environment.warning }));
659
+ }
615
660
  console.log(t("status.linkId", { value: payload.identity?.linkId ?? t("status.notPaired") }));
616
661
  });
662
+ var configCommand = program.command("config").description(helpText("config.description"));
663
+ configCommand.command("set").argument("<key>").argument("<value>").description(helpText("config.set.description")).action(async (key, value) => {
664
+ const paths = resolveRuntimePaths();
665
+ const current = await loadConfig(paths);
666
+ const language = resolveLanguage(current.language);
667
+ const t = translate.bind(null, language);
668
+ const normalizedKey = key.trim().toLowerCase();
669
+ if (normalizedKey !== "lan-host") {
670
+ throw new Error(t("config.unknownKey", { key }));
671
+ }
672
+ const lanHost = normalizeLanHost(value);
673
+ if (!lanHost) {
674
+ throw new Error(t("config.lanHostInvalid"));
675
+ }
676
+ const next = await saveConfig({ lanHost }, paths);
677
+ console.log(t("config.lanHostSet", { value: next.lanHost ?? lanHost }));
678
+ await reportConfigNetworkUpdate(paths, t);
679
+ });
680
+ configCommand.command("unset").argument("<key>").description(helpText("config.unset.description")).action(async (key) => {
681
+ const paths = resolveRuntimePaths();
682
+ const current = await loadConfig(paths);
683
+ const language = resolveLanguage(current.language);
684
+ const t = translate.bind(null, language);
685
+ const normalizedKey = key.trim().toLowerCase();
686
+ if (normalizedKey !== "lan-host") {
687
+ throw new Error(t("config.unknownKey", { key }));
688
+ }
689
+ await saveConfig({ lanHost: null }, paths);
690
+ console.log(t("config.lanHostUnset"));
691
+ await reportConfigNetworkUpdate(paths, t);
692
+ });
617
693
  program.command("start").description(helpText("start.description")).action(async () => {
618
694
  const [config, status] = await Promise.all([loadConfig(), getDaemonStatus()]);
619
695
  const language = resolveLanguage(config.language);
@@ -661,11 +737,17 @@ program.command("daemon-supervisor", { hidden: true }).action(async () => {
661
737
  });
662
738
  program.command("pair").description(helpText("pair.description")).action(async () => {
663
739
  const paths = resolveRuntimePaths();
664
- const config = await loadConfig(paths);
665
- const language = resolveLanguage(config.language);
740
+ let config = await loadConfig(paths);
741
+ const languageChoice = await resolvePairingLanguage(paths, config);
742
+ config = languageChoice.config;
743
+ const language = languageChoice.language;
666
744
  const t = translate.bind(null, language);
667
745
  console.log(t("pair.preflight"));
668
746
  const preflight = await assertPairingPreflightReady({ paths });
747
+ const environment = detectRuntimeEnvironment();
748
+ if (environment.warning) {
749
+ console.log(t("doctor.networkWarning", { message: environment.warning }));
750
+ }
669
751
  console.log(t("pair.hermesHome", { path: preflight.hermesHome }));
670
752
  console.log(t("pair.apiReady", { port: preflight.apiServer.port ?? "unknown" }));
671
753
  console.log(t("pair.preparing"));
@@ -705,6 +787,7 @@ program.command("pair").description(helpText("pair.description")).action(async (
705
787
  if (result === "claimed") {
706
788
  await clearPairingClaim(prepared.sessionId, paths);
707
789
  console.log(t(reusedRunningService ? "pair.claimedRunning" : "pair.claimed"));
790
+ printPostPairingNetworkNotice(prepared.routes.environment, config, t);
708
791
  try {
709
792
  if (hadActiveDevices) {
710
793
  console.log(t("pair.autostartUnchanged"));
@@ -789,6 +872,11 @@ program.command("doctor").description(helpText("doctor.description")).action(asy
789
872
  console.log(t("doctor.identityOk"));
790
873
  console.log(t("doctor.installId", { value: identity.install_id }));
791
874
  console.log(t("doctor.linkId", { value: identity.link_id ?? t("doctor.notAssigned") }));
875
+ const environment = detectRuntimeEnvironment();
876
+ if (environment.warning) {
877
+ console.log(t("doctor.networkWarning", { message: environment.warning }));
878
+ }
879
+ console.log(t("doctor.lanHost", { value: config.lanHost ?? t("status.notSet") }));
792
880
  if (hermesConfig.notice) {
793
881
  console.log(hermesConfig.notice);
794
882
  if (hermesConfig.backupPath) {
@@ -811,6 +899,48 @@ async function loadCliLanguage() {
811
899
  const config = await loadConfig();
812
900
  return resolveLanguage(config.language);
813
901
  }
902
+ async function resolvePairingLanguage(paths, config) {
903
+ if (config.language !== "auto") {
904
+ return { config, language: resolveLanguage(config.language) };
905
+ }
906
+ const selected = await promptForLanguage();
907
+ if (!selected) {
908
+ return { config, language: resolveLanguage(config.language) };
909
+ }
910
+ const next = await saveConfig({ language: selected }, paths);
911
+ return { config: next, language: selected };
912
+ }
913
+ async function promptForLanguage() {
914
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
915
+ return null;
916
+ }
917
+ const rl = createInterface({
918
+ input: process.stdin,
919
+ output: process.stdout
920
+ });
921
+ try {
922
+ for (; ; ) {
923
+ const answer = await rl.question(
924
+ [
925
+ "\u8BF7\u9009\u62E9 Hermes Link \u663E\u793A\u8BED\u8A00 / Choose Hermes Link language:",
926
+ " 1. \u4E2D\u6587",
927
+ " 2. English",
928
+ "\u8F93\u5165 1 \u6216 2 \u540E\u56DE\u8F66 / Enter 1 or 2: "
929
+ ].join("\n")
930
+ );
931
+ const normalized = answer.trim().toLowerCase();
932
+ if (normalized === "1" || normalized === "zh" || normalized === "zh-cn" || normalized === "\u4E2D\u6587") {
933
+ return "zh-CN";
934
+ }
935
+ if (normalized === "2" || normalized === "en" || normalized === "english") {
936
+ return "en";
937
+ }
938
+ console.log("\u8BF7\u8F93\u5165 1 \u6216 2\u3002 / Please enter 1 or 2.");
939
+ }
940
+ } finally {
941
+ rl.close();
942
+ }
943
+ }
814
944
  async function waitForShutdown(cleanup) {
815
945
  await new Promise((resolve) => {
816
946
  const stop = () => resolve();
@@ -843,3 +973,20 @@ async function waitForPairingOrShutdown(sessionId, paths) {
843
973
  function sleep(ms) {
844
974
  return new Promise((resolve) => setTimeout(resolve, ms));
845
975
  }
976
+ async function reportConfigNetworkUpdate(paths, t) {
977
+ const identity = await loadIdentity(paths);
978
+ if (!identity?.link_id) {
979
+ console.log(t("config.reportSkippedUnpaired"));
980
+ return;
981
+ }
982
+ await reportLinkStatusToServer({ paths });
983
+ console.log(t("config.reported"));
984
+ }
985
+ function printPostPairingNetworkNotice(environment, config, t) {
986
+ if (environment.lanAutoDiscoveryUsable || config.lanHost) {
987
+ return;
988
+ }
989
+ console.log(t("pair.relayOnlyNotice", { kind: environment.kind }));
990
+ console.log(t("pair.relayOnlyLanHostHint"));
991
+ console.log(t("pair.relayOnlySafetyHint"));
992
+ }
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-SSJ7WDD7.js";
3
+ } from "../chunk-W4U6XJ4W.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hermespilot/link",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",