@hermespilot/link 0.3.8 → 0.4.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/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  LINK_VERSION,
6
6
  LinkHttpError,
7
7
  clearPairingClaim,
8
+ connectRelayControl,
8
9
  createFileLogger,
9
10
  currentCliScriptPath,
10
11
  daemonLogFile,
@@ -22,6 +23,7 @@ import {
22
23
  preparePairing,
23
24
  probeLocalLinkService,
24
25
  readHermesApiServerConfig,
26
+ readHermesVersion,
25
27
  readPairingClaim,
26
28
  reportLinkStatusToServer,
27
29
  resolveHermesConfigPath,
@@ -32,7 +34,7 @@ import {
32
34
  startDaemonProcess,
33
35
  startLinkService,
34
36
  stopDaemonProcess
35
- } from "../chunk-RJB5VUT4.js";
37
+ } from "../chunk-FWPHQZP6.js";
36
38
 
37
39
  // src/cli/index.ts
38
40
  import { Command } from "commander";
@@ -273,16 +275,16 @@ var messages = {
273
275
  "autostart.alreadyEnabled": "Boot autostart is already enabled via {method}: {path}",
274
276
  "pair.description": "Create a Hermes Link pairing session",
275
277
  "pair.preflight": "Checking local Hermes configuration before pairing...",
278
+ "pair.preflight.hermesFiles": "Checking Hermes data directory, config, and environment files...",
279
+ "pair.preflight.hermesCli": "Checking whether the Hermes CLI is available...",
280
+ "pair.preflight.hermesApiServer": "Checking whether the Hermes API Server is ready...",
276
281
  "pair.hermesHome": "Hermes home: {path}",
282
+ "pair.hermesVersion": "Hermes CLI: {value}",
277
283
  "pair.apiReady": "Hermes API Server is ready on 127.0.0.1:{port}",
278
- "pair.preparing": "Preparing pairing session through HermesPilot Server and Relay...",
279
- "pair.server": "Server: {url}",
280
- "pair.relay": "Relay: {url}",
281
- "pair.linkId": "Hermes Link ID: {value}",
282
- "pair.code": "Pairing code: {value}",
283
- "pair.localApi": "Local API: http://127.0.0.1:{port}",
284
- "pair.scan": "Open this pairing page in the HermesPilot App or scan the QR code below:",
285
- "pair.openBrowserFailed": "Could not open the system browser automatically. You can still scan the QR code below or open this URL manually: {url}",
284
+ "pair.preparing": "Creating the pairing session...",
285
+ "pair.scan": "Please scan the QR code below in the HermesPilot App:",
286
+ "pair.openPairingPage": "If the QR code is hard to scan, you can open this page locally: {url}",
287
+ "pair.manualCode": "You can also use the HermesPilot App manual connection mode and enter this pairing code:",
286
288
  "pair.expires": "Pairing expires in 10 minutes. Press Ctrl+C to cancel waiting.",
287
289
  "pair.claimed": "Pairing succeeded. Starting Hermes Link in the background...",
288
290
  "pair.claimedRunning": "Pairing succeeded. Hermes Link is already running in the background.",
@@ -298,6 +300,8 @@ var messages = {
298
300
  "doctor.notAssigned": "not assigned",
299
301
  "doctor.lanHost": "Configured LAN host: {value}",
300
302
  "doctor.networkWarning": "Network note: {message}",
303
+ "doctor.hermesCli": "Hermes CLI: {value}",
304
+ "doctor.hermesCliUnavailable": "Hermes CLI is unavailable. Please make sure the `hermes` command can run in this system.",
301
305
  "doctor.apiReady": "Hermes API Server: ready",
302
306
  "doctor.apiStarted": "Hermes API Server: started and ready",
303
307
  "doctor.apiUnavailable": "Hermes API Server: unavailable. {message}",
@@ -306,6 +310,8 @@ var messages = {
306
310
  "error.relayLinkInvalid": "Relay did not return a valid link_id.",
307
311
  "error.relayEmpty": "Relay returned an empty response.",
308
312
  "error.serverHttp": "HermesPilot Server request failed with HTTP {status}.",
313
+ "error.pairingServerUnreachable": "Could not reach HermesPilot Server while creating the pairing session. Check whether {url} is reachable, then try again. If you use a proxy network, add hermes-server.clawpilot.me and hermes-relay.clawpilot.me to the proxy exclusion list, or temporarily turn off VPN/proxy and retry.",
314
+ "error.pairingRelayUnreachable": "Could not reach Hermes Relay while creating the pairing session. Check whether {url} is reachable, then try again. If you use a proxy network, add hermes-server.clawpilot.me and hermes-relay.clawpilot.me to the proxy exclusion list, or temporarily turn off VPN/proxy and retry.",
309
315
  "error.portInUse": "Local port {port} is already in use by another process. Stop that process or change the Hermes Link port, then run `hermeslink pair` again.",
310
316
  "error.pairingRequires": "Pairing needs HermesPilot Server and Relay, but this command could not start a complete pairing session.",
311
317
  "error.pairingRequires.detail": "The deployed services may be healthy, but the installed Link package must call Server for a short-lived relay bootstrap token before it can request a link_id."
@@ -363,16 +369,16 @@ var messages = {
363
369
  "autostart.alreadyEnabled": "\u5F00\u673A\u81EA\u542F\u5DF2\u542F\u7528\uFF0C\u65B9\u5F0F\uFF1A{method}\uFF0C\u6587\u4EF6\uFF1A{path}",
364
370
  "pair.description": "\u521B\u5EFA Hermes Link \u914D\u5BF9\u4F1A\u8BDD",
365
371
  "pair.preflight": "\u6B63\u5728\u914D\u5BF9\u524D\u68C0\u67E5\u672C\u673A Hermes \u914D\u7F6E...",
372
+ "pair.preflight.hermesFiles": "\u6B63\u5728\u68C0\u67E5 Hermes \u6570\u636E\u76EE\u5F55\u3001\u914D\u7F6E\u6587\u4EF6\u548C\u73AF\u5883\u6587\u4EF6...",
373
+ "pair.preflight.hermesCli": "\u6B63\u5728\u68C0\u67E5 Hermes CLI \u662F\u5426\u53EF\u7528...",
374
+ "pair.preflight.hermesApiServer": "\u6B63\u5728\u68C0\u67E5 Hermes API Server \u662F\u5426\u5C31\u7EEA...",
366
375
  "pair.hermesHome": "Hermes \u6570\u636E\u76EE\u5F55\uFF1A{path}",
376
+ "pair.hermesVersion": "Hermes CLI\uFF1A{value}",
367
377
  "pair.apiReady": "Hermes API Server \u5DF2\u5C31\u7EEA\uFF1A127.0.0.1:{port}",
368
- "pair.preparing": "\u6B63\u5728\u901A\u8FC7 HermesPilot Server \u548C Relay \u521B\u5EFA\u914D\u5BF9\u4F1A\u8BDD...",
369
- "pair.server": "Server\uFF1A{url}",
370
- "pair.relay": "Relay\uFF1A{url}",
371
- "pair.linkId": "Hermes Link ID\uFF1A{value}",
372
- "pair.code": "\u914D\u5BF9\u7801\uFF1A{value}",
373
- "pair.localApi": "\u672C\u5730 API\uFF1Ahttp://127.0.0.1:{port}",
374
- "pair.scan": "\u8BF7\u5728 HermesPilot App \u4E2D\u6253\u5F00\u8FD9\u4E2A\u914D\u5BF9\u9875\uFF0C\u6216\u626B\u63CF\u4E0B\u9762\u7684\u4E8C\u7EF4\u7801\uFF1A",
375
- "pair.openBrowserFailed": "\u65E0\u6CD5\u81EA\u52A8\u6253\u5F00\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002\u4F60\u4ECD\u7136\u53EF\u4EE5\u626B\u63CF\u4E0B\u9762\u7684\u4E8C\u7EF4\u7801\uFF0C\u6216\u624B\u52A8\u6253\u5F00\u8FD9\u4E2A\u94FE\u63A5\uFF1A{url}",
378
+ "pair.preparing": "\u6B63\u5728\u521B\u5EFA\u914D\u5BF9\u4F1A\u8BDD...",
379
+ "pair.scan": "\u8BF7\u5728 HermesPilot App \u4E2D\u626B\u7801\u4E0B\u9762\u7684\u4E8C\u7EF4\u7801\uFF1A",
380
+ "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
+ "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",
376
382
  "pair.expires": "\u914D\u5BF9\u4F1A\u8BDD 10 \u5206\u949F\u540E\u8FC7\u671F\u3002\u6309 Ctrl+C \u9000\u51FA\u7B49\u5F85\u3002",
377
383
  "pair.claimed": "\u914D\u5BF9\u5DF2\u6210\u529F\u3002\u6B63\u5728\u628A Hermes Link \u5207\u6362\u5230\u540E\u53F0\u8FD0\u884C...",
378
384
  "pair.claimedRunning": "\u914D\u5BF9\u5DF2\u6210\u529F\u3002Hermes Link \u5DF2\u5728\u540E\u53F0\u6301\u7EED\u8FD0\u884C\u3002",
@@ -388,6 +394,8 @@ var messages = {
388
394
  "doctor.notAssigned": "\u5C1A\u672A\u5206\u914D",
389
395
  "doctor.lanHost": "\u5DF2\u914D\u7F6E\u5C40\u57DF\u7F51\u4E3B\u673A\uFF1A{value}",
390
396
  "doctor.networkWarning": "\u7F51\u7EDC\u63D0\u793A\uFF1A{message}",
397
+ "doctor.hermesCli": "Hermes CLI\uFF1A{value}",
398
+ "doctor.hermesCliUnavailable": "Hermes CLI\uFF1A\u4E0D\u53EF\u7528\u3002\u8BF7\u786E\u8BA4\u5F53\u524D\u7CFB\u7EDF\u53EF\u4EE5\u76F4\u63A5\u8FD0\u884C `hermes` \u547D\u4EE4\u3002",
391
399
  "doctor.apiReady": "Hermes API Server\uFF1A\u5DF2\u5C31\u7EEA",
392
400
  "doctor.apiStarted": "Hermes API Server\uFF1A\u5DF2\u81EA\u52A8\u542F\u52A8\u5E76\u5C31\u7EEA",
393
401
  "doctor.apiUnavailable": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528\u3002{message}",
@@ -396,6 +404,8 @@ var messages = {
396
404
  "error.relayLinkInvalid": "Relay \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684 link_id\u3002",
397
405
  "error.relayEmpty": "Relay \u8FD4\u56DE\u4E86\u7A7A\u54CD\u5E94\u3002",
398
406
  "error.serverHttp": "HermesPilot Server \u8BF7\u6C42\u5931\u8D25\uFF0CHTTP \u72B6\u6001\u7801\uFF1A{status}\u3002",
407
+ "error.pairingServerUnreachable": "\u521B\u5EFA\u914D\u5BF9\u4F1A\u8BDD\u65F6\u65E0\u6CD5\u8FDE\u63A5 HermesPilot Server\u3002\u8BF7\u5148\u786E\u8BA4 {url} \u53EF\u4EE5\u8BBF\u95EE\uFF0C\u7136\u540E\u91CD\u8BD5\u3002\u91CD\u70B9\u63D0\u9192\uFF1A\u5982\u679C\u4F60\u4F7F\u7528\u4E86\u4EE3\u7406\u7F51\u7EDC\uFF0C\u53EF\u4EE5\u628A hermes-server.clawpilot.me \u548C hermes-relay.clawpilot.me \u52A0\u5165\u4EE3\u7406\u6392\u9664\u540D\u5355\uFF0C\u6216\u4E34\u65F6\u5173\u95ED VPN/\u4EE3\u7406\u540E\u518D\u8BD5\u3002",
408
+ "error.pairingRelayUnreachable": "\u521B\u5EFA\u914D\u5BF9\u4F1A\u8BDD\u65F6\u65E0\u6CD5\u8FDE\u63A5 Hermes Relay\u3002\u8BF7\u5148\u786E\u8BA4 {url} \u53EF\u4EE5\u8BBF\u95EE\uFF0C\u7136\u540E\u91CD\u8BD5\u3002\u91CD\u70B9\u63D0\u9192\uFF1A\u5982\u679C\u4F60\u4F7F\u7528\u4E86\u4EE3\u7406\u7F51\u7EDC\uFF0C\u53EF\u4EE5\u628A hermes-server.clawpilot.me \u548C hermes-relay.clawpilot.me \u52A0\u5165\u4EE3\u7406\u6392\u9664\u540D\u5355\uFF0C\u6216\u4E34\u65F6\u5173\u95ED VPN/\u4EE3\u7406\u540E\u518D\u8BD5\u3002",
399
409
  "error.portInUse": "\u672C\u5730\u7AEF\u53E3 {port} \u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528\u3002\u8BF7\u5148\u505C\u6B62\u5360\u7528\u8BE5\u7AEF\u53E3\u7684\u7A0B\u5E8F\uFF0C\u6216\u8C03\u6574 Hermes Link \u7AEF\u53E3\u540E\u91CD\u65B0\u8FD0\u884C `hermeslink pair`\u3002",
400
410
  "error.pairingRequires": "\u914D\u5BF9\u9700\u8981 HermesPilot Server \u548C Relay\uFF0C\u4F46\u5F53\u524D\u547D\u4EE4\u6CA1\u6709\u80FD\u542F\u52A8\u5B8C\u6574\u914D\u5BF9\u4F1A\u8BDD\u3002",
401
411
  "error.pairingRequires.detail": "\u4E91\u7AEF\u670D\u52A1\u53EF\u4EE5\u662F\u5DF2\u90E8\u7F72\u4E14\u5065\u5EB7\u7684\uFF1B\u672C\u673A Link \u4ECD\u5FC5\u987B\u5148\u5411 Server \u7533\u8BF7\u77ED\u671F relay bootstrap token\uFF0C\u624D\u80FD\u518D\u5411 Relay \u7533\u8BF7 link_id\u3002"
@@ -459,6 +469,18 @@ function translateKnownError(message, language) {
459
469
  if (serverHttp?.groups?.status) {
460
470
  return translate(language, "error.serverHttp", { status: serverHttp.groups.status });
461
471
  }
472
+ const pairingServerUnreachable = /^HermesPilot Server is unreachable while trying to [^.]+\. Please check whether (?<url>\S+) is reachable\./u.exec(message);
473
+ if (pairingServerUnreachable?.groups?.url) {
474
+ return translate(language, "error.pairingServerUnreachable", {
475
+ url: pairingServerUnreachable.groups.url
476
+ });
477
+ }
478
+ const pairingRelayUnreachable = /^Hermes Relay is unreachable while trying to [^.]+\. Please check whether (?<url>\S+) is reachable\./u.exec(message);
479
+ if (pairingRelayUnreachable?.groups?.url) {
480
+ return translate(language, "error.pairingRelayUnreachable", {
481
+ url: pairingRelayUnreachable.groups.url
482
+ });
483
+ }
462
484
  if (message.includes("Pairing requires HermesPilot Server and Relay")) {
463
485
  return [translate(language, "error.pairingRequires"), translate(language, "error.pairingRequires.detail")].join("\n");
464
486
  }
@@ -487,6 +509,7 @@ async function assertPairingPreflightReady(options = {}) {
487
509
  const configPath = resolveHermesConfigPath(profileName);
488
510
  const envPath = path2.join(hermesHome, ".env");
489
511
  const failures = [];
512
+ options.onProgress?.("hermes_files");
490
513
  if (!await isDirectory(hermesHome)) {
491
514
  failures.push({
492
515
  code: "hermes_home_missing",
@@ -517,6 +540,23 @@ async function assertPairingPreflightReady(options = {}) {
517
540
  if (failures.length > 0) {
518
541
  throwPairingPreflightError(failures);
519
542
  }
543
+ options.onProgress?.("hermes_cli");
544
+ let hermesVersion;
545
+ try {
546
+ const readVersion = options.readHermesVersion ?? readHermesVersion;
547
+ hermesVersion = await readVersion();
548
+ } catch {
549
+ throwPairingPreflightError([
550
+ {
551
+ code: "hermes_cli_unavailable",
552
+ zh: "\u6682\u65F6\u627E\u4E0D\u5230\u53EF\u7528\u7684 Hermes CLI \u547D\u4EE4\u3002",
553
+ en: "Hermes CLI is not available right now.",
554
+ actionZh: "\u8BF7\u786E\u8BA4 Hermes Link \u548C Hermes Agent \u5B89\u88C5\u5728\u540C\u4E00\u4E2A\u7CFB\u7EDF\u73AF\u5883\u4E2D\uFF0C\u5E76\u4E14\u7EC8\u7AEF\u80FD\u76F4\u63A5\u8FD0\u884C `hermes`\u3002\u5982\u679C\u4F60\u4F7F\u7528\u4E86\u81EA\u5B9A\u4E49\u5B89\u88C5\u8DEF\u5F84\uFF0C\u4E5F\u53EF\u4EE5\u8BBE\u7F6E HERMES_BIN\u3002",
555
+ actionEn: "Make sure Hermes Link and Hermes Agent are installed in the same system environment, and that the terminal can run `hermes` directly. If Hermes is installed in a custom location, set HERMES_BIN."
556
+ }
557
+ ]);
558
+ }
559
+ options.onProgress?.("hermes_api_server");
520
560
  const apiServerConfig = await readHermesApiServerConfig(profileName, configPath);
521
561
  if (apiServerConfig.enabled !== true) {
522
562
  throwPairingPreflightError([
@@ -548,6 +588,10 @@ async function assertPairingPreflightReady(options = {}) {
548
588
  started: availability.started,
549
589
  host: availability.configResult.apiServer.host ?? null,
550
590
  port: availability.configResult.apiServer.port ?? null
591
+ },
592
+ hermesVersion: {
593
+ raw: hermesVersion.raw,
594
+ version: hermesVersion.version
551
595
  }
552
596
  };
553
597
  } catch (error) {
@@ -780,16 +824,17 @@ program.command("pair").description(helpText("pair.description")).action(async (
780
824
  const language = languageChoice.language;
781
825
  const t = translate.bind(null, language);
782
826
  console.log(t("pair.preflight"));
783
- const preflight = await assertPairingPreflightReady({ paths });
784
827
  const environment = detectRuntimeEnvironment();
785
828
  if (environment.warning) {
786
829
  console.log(t("doctor.networkWarning", { message: environment.warning }));
787
830
  }
788
- console.log(t("pair.hermesHome", { path: preflight.hermesHome }));
789
- console.log(t("pair.apiReady", { port: preflight.apiServer.port ?? "unknown" }));
831
+ await assertPairingPreflightReady({
832
+ paths,
833
+ onProgress: (stage) => {
834
+ console.log(t(pairingPreflightProgressKey(stage)));
835
+ }
836
+ });
790
837
  console.log(t("pair.preparing"));
791
- console.log(t("pair.server", { url: config.serverBaseUrl }));
792
- console.log(t("pair.relay", { url: config.relayBaseUrl }));
793
838
  await ensureIdentity(paths);
794
839
  const hadActiveDevices = await hasActiveDevices(paths);
795
840
  const probeBeforePair = await probeLocalLinkService({ port: config.port });
@@ -803,62 +848,71 @@ program.command("pair").description(helpText("pair.description")).action(async (
803
848
  }
804
849
  const reusedRunningService = probe.reusable;
805
850
  const restartReusedServiceAfterClaim = reusedRunningService && !probeBeforePair.linkId;
806
- const service = reusedRunningService ? null : await startLinkService({ paths });
851
+ const service = reusedRunningService ? null : await startLinkService({
852
+ paths,
853
+ waitForRelayReady: true
854
+ });
855
+ let pairingRelayBridge = null;
856
+ if (restartReusedServiceAfterClaim) {
857
+ pairingRelayBridge = connectRelayControl({
858
+ relayBaseUrl: prepared.relayBaseUrl,
859
+ linkId: prepared.linkId,
860
+ localPort: config.port
861
+ });
862
+ pairingRelayBridge.publishNetworkRoutes(prepared.routes);
863
+ }
807
864
  const qrValue = JSON.stringify(prepared.qrPayload);
808
865
  const pairingPageUrl = `http://127.0.0.1:${config.port}/pair?session_id=${encodeURIComponent(prepared.sessionId)}`;
809
- console.log(t("pair.linkId", { value: prepared.linkId }));
810
- console.log(t("pair.code", { value: prepared.code }));
811
- console.log(t("pair.localApi", { port: config.port }));
812
- if (!reusedRunningService) {
813
- console.log(t("start.listening", { port: config.port }));
814
- }
815
- console.log(t("pair.scan"));
816
- console.log(`Pairing page: ${pairingPageUrl}`);
817
- const browserOpened = await openSystemBrowser(pairingPageUrl);
818
- if (!browserOpened) {
819
- console.log(t("pair.openBrowserFailed", { url: pairingPageUrl }));
820
- }
821
- qrcode.generate(qrValue, { small: true });
822
- console.log(t("pair.expires"));
823
- const result = await waitForPairingOrShutdown(prepared.sessionId, paths);
824
- if (service) {
825
- await service.close();
826
- }
827
- if (result === "claimed") {
828
- await clearPairingClaim(prepared.sessionId, paths);
829
- console.log(t(reusedRunningService ? "pair.claimedRunning" : "pair.claimed"));
830
- printPostPairingNetworkNotice(prepared.routes.environment, config, t);
831
- try {
832
- if (hadActiveDevices) {
833
- console.log(t("pair.autostartUnchanged"));
834
- } else {
835
- const currentAutostart = await getAutostartStatus();
836
- if (currentAutostart.supported && currentAutostart.enabled) {
837
- console.log(
838
- t("autostart.alreadyEnabled", {
839
- method: currentAutostart.method,
840
- path: currentAutostart.filePath ?? ""
841
- })
842
- );
866
+ try {
867
+ console.log(t("pair.scan"));
868
+ qrcode.generate(qrValue, { small: true });
869
+ await openSystemBrowser(pairingPageUrl);
870
+ console.log(t("pair.openPairingPage", { url: pairingPageUrl }));
871
+ console.log(t("pair.manualCode"));
872
+ console.log(prepared.code);
873
+ console.log(t("pair.expires"));
874
+ const result = await waitForPairingOrShutdown(prepared.sessionId, paths);
875
+ if (service) {
876
+ await service.close();
877
+ }
878
+ if (result === "claimed") {
879
+ await clearPairingClaim(prepared.sessionId, paths);
880
+ console.log(t(reusedRunningService ? "pair.claimedRunning" : "pair.claimed"));
881
+ printPostPairingNetworkNotice(prepared.routes.environment, config, t);
882
+ try {
883
+ if (hadActiveDevices) {
884
+ console.log(t("pair.autostartUnchanged"));
843
885
  } else {
844
- const autostart2 = await enableAutostart();
845
- if (autostart2.supported && autostart2.enabled) {
846
- console.log(t("autostart.enabled", { method: autostart2.method, path: autostart2.filePath ?? "" }));
886
+ const currentAutostart = await getAutostartStatus();
887
+ if (currentAutostart.supported && currentAutostart.enabled) {
888
+ console.log(
889
+ t("autostart.alreadyEnabled", {
890
+ method: currentAutostart.method,
891
+ path: currentAutostart.filePath ?? ""
892
+ })
893
+ );
894
+ } else {
895
+ const autostart2 = await enableAutostart();
896
+ if (autostart2.supported && autostart2.enabled) {
897
+ console.log(t("autostart.enabled", { method: autostart2.method, path: autostart2.filePath ?? "" }));
898
+ }
847
899
  }
848
900
  }
901
+ } catch (error) {
902
+ const message = error instanceof Error ? error.message : String(error);
903
+ console.log(t("pair.autostartFailed", { message }));
904
+ }
905
+ if (restartReusedServiceAfterClaim) {
906
+ await stopDaemonProcess(paths);
907
+ const status = await startDaemonProcess(paths);
908
+ console.log(t("start.backgroundStarted", { pid: status.pid ?? "unknown" }));
909
+ } else if (!reusedRunningService) {
910
+ const status = await startDaemonProcess(paths);
911
+ console.log(t("start.backgroundStarted", { pid: status.pid ?? "unknown" }));
849
912
  }
850
- } catch (error) {
851
- const message = error instanceof Error ? error.message : String(error);
852
- console.log(t("pair.autostartFailed", { message }));
853
- }
854
- if (restartReusedServiceAfterClaim) {
855
- await stopDaemonProcess(paths);
856
- const status = await startDaemonProcess(paths);
857
- console.log(t("start.backgroundStarted", { pid: status.pid ?? "unknown" }));
858
- } else if (!reusedRunningService) {
859
- const status = await startDaemonProcess(paths);
860
- console.log(t("start.backgroundStarted", { pid: status.pid ?? "unknown" }));
861
913
  }
914
+ } finally {
915
+ pairingRelayBridge?.close();
862
916
  }
863
917
  });
864
918
  var autostart = program.command("autostart").description(helpText("autostart.description"));
@@ -923,6 +977,12 @@ program.command("doctor").description(helpText("doctor.description")).action(asy
923
977
  console.log(`Hermes config backup: ${hermesConfig.backupPath}`);
924
978
  }
925
979
  }
980
+ try {
981
+ const hermesVersion = await readHermesVersion();
982
+ console.log(t("doctor.hermesCli", { value: formatHermesVersion(hermesVersion) }));
983
+ } catch {
984
+ console.log(t("doctor.hermesCliUnavailable"));
985
+ }
926
986
  try {
927
987
  const availability = await ensureHermesApiServerAvailable({ timeoutMs: 5e3 });
928
988
  console.log(t(availability.started ? "doctor.apiStarted" : "doctor.apiReady"));
@@ -939,6 +999,19 @@ async function loadCliLanguage() {
939
999
  const config = await loadConfig();
940
1000
  return resolveLanguage(config.language);
941
1001
  }
1002
+ function formatHermesVersion(version) {
1003
+ return version.version ?? version.raw;
1004
+ }
1005
+ function pairingPreflightProgressKey(stage) {
1006
+ switch (stage) {
1007
+ case "hermes_files":
1008
+ return "pair.preflight.hermesFiles";
1009
+ case "hermes_cli":
1010
+ return "pair.preflight.hermesCli";
1011
+ case "hermes_api_server":
1012
+ return "pair.preflight.hermesApiServer";
1013
+ }
1014
+ }
942
1015
  async function deliverStagedFilesFromCli(stagingDir, paths, config) {
943
1016
  try {
944
1017
  return await deliverStagedFilesThroughDaemon(stagingDir, config.port);
@@ -183,6 +183,19 @@ interface BulkDeleteConversationResult {
183
183
  message: string;
184
184
  };
185
185
  }
186
+ type ConversationClearPlanStatus = 'prepared' | 'executing' | 'completed' | 'failed';
187
+ interface ConversationClearPlan {
188
+ id: string;
189
+ status: ConversationClearPlanStatus;
190
+ created_at: string;
191
+ updated_at: string;
192
+ total_count: number;
193
+ deleted_count: number;
194
+ failed_count: number;
195
+ conversation_ids: string[];
196
+ conversations: BulkDeleteConversationResult[];
197
+ completed_at?: string;
198
+ }
186
199
  interface LinkRun {
187
200
  id: string;
188
201
  kind?: 'agent' | 'command';
@@ -462,6 +475,10 @@ declare class ConversationService {
462
475
  last_event_seq: number;
463
476
  }>;
464
477
  deleteConversation(conversationId: string): Promise<DeleteConversationResult>;
478
+ prepareClearAllConversationPlan(): Promise<ConversationClearPlan>;
479
+ readClearAllConversationPlan(planId: string): Promise<ConversationClearPlan>;
480
+ executeClearAllConversationPlan(planId: string): Promise<ConversationClearPlan>;
481
+ startClearAllConversationPlan(planId: string): Promise<ConversationClearPlan>;
465
482
  deleteConversations(conversationIds: string[]): Promise<{
466
483
  deleted_count: number;
467
484
  failed_count: number;
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-RJB5VUT4.js";
3
+ } from "../chunk-FWPHQZP6.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.3.8",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",