@hermespilot/link 0.5.1 → 0.5.3

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
@@ -14,6 +14,7 @@ import {
14
14
  ensureHermesApiServerAvailable,
15
15
  ensureHermesApiServerConfig,
16
16
  ensureIdentity,
17
+ fetchRelayStreamBatchPolicy,
17
18
  getDaemonStatus,
18
19
  getIdentityStatus,
19
20
  getLinkLogFile,
@@ -36,12 +37,12 @@ import {
36
37
  startDaemonProcess,
37
38
  startLinkService,
38
39
  stopDaemonProcess
39
- } from "../chunk-PULX22HX.js";
40
+ } from "../chunk-5JBXQ3VC.js";
40
41
 
41
42
  // src/cli/index.ts
42
43
  import { Command } from "commander";
43
- import { realpathSync } from "fs";
44
- import path3 from "path";
44
+ import { realpathSync as realpathSync2 } from "fs";
45
+ import path4 from "path";
45
46
  import { createInterface } from "readline/promises";
46
47
  import { pathToFileURL } from "url";
47
48
  import qrcode from "qrcode-terminal";
@@ -139,6 +140,7 @@ async function hasSystemctlUser() {
139
140
  }
140
141
  function launchdDefinition() {
141
142
  const filePath = path.join(os.homedir(), "Library", "LaunchAgents", `${MACOS_LABEL}.plist`);
143
+ const environment = autostartEnvironment();
142
144
  return {
143
145
  method: "launchd",
144
146
  filePath,
@@ -154,6 +156,10 @@ function launchdDefinition() {
154
156
  <string>${xmlEscape(currentCliScriptPath())}</string>
155
157
  <string>daemon-supervisor</string>
156
158
  </array>
159
+ <key>EnvironmentVariables</key>
160
+ <dict>
161
+ ${plistEnvironmentEntries(environment)}
162
+ </dict>
157
163
  <key>RunAtLoad</key>
158
164
  <true/>
159
165
  <key>KeepAlive</key>
@@ -165,6 +171,7 @@ function launchdDefinition() {
165
171
  }
166
172
  function systemdUserDefinition() {
167
173
  const filePath = path.join(os.homedir(), ".config", "systemd", "user", "hermeslink.service");
174
+ const environment = autostartEnvironment();
168
175
  return {
169
176
  method: "systemd-user",
170
177
  filePath,
@@ -174,6 +181,7 @@ After=network-online.target
174
181
 
175
182
  [Service]
176
183
  Type=simple
184
+ ${systemdEnvironmentLines(environment)}
177
185
  ExecStart=${systemdQuote(process.execPath)} ${systemdQuote(currentCliScriptPath())} daemon-supervisor
178
186
  Restart=no
179
187
 
@@ -184,13 +192,14 @@ WantedBy=default.target
184
192
  }
185
193
  function xdgAutostartDefinition() {
186
194
  const filePath = path.join(os.homedir(), ".config", "autostart", "hermeslink.desktop");
195
+ const environment = autostartEnvironment();
187
196
  return {
188
197
  method: "xdg-autostart",
189
198
  filePath,
190
199
  content: `[Desktop Entry]
191
200
  Type=Application
192
201
  Name=Hermes Link
193
- Exec=${desktopQuote(process.execPath)} ${desktopQuote(currentCliScriptPath())} daemon-supervisor
202
+ Exec=${desktopQuote("/usr/bin/env")} ${desktopEnvironmentArgs(environment)} ${desktopQuote(process.execPath)} ${desktopQuote(currentCliScriptPath())} daemon-supervisor
194
203
  Terminal=false
195
204
  X-GNOME-Autostart-enabled=true
196
205
  `
@@ -199,12 +208,16 @@ X-GNOME-Autostart-enabled=true
199
208
  function windowsStartupDefinition() {
200
209
  const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
201
210
  const filePath = path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "HermesLink.cmd");
211
+ const environment = autostartEnvironment();
202
212
  return {
203
213
  method: "windows-startup",
204
214
  filePath,
205
- content: `@echo off\r
206
- start "" /min "${process.execPath}" "${currentCliScriptPath()}" daemon-supervisor\r
207
- `
215
+ content: [
216
+ "@echo off",
217
+ ...cmdEnvironmentLines(environment),
218
+ `start "" /min "${process.execPath}" "${currentCliScriptPath()}" daemon-supervisor`,
219
+ ""
220
+ ].join("\r\n")
208
221
  };
209
222
  }
210
223
  function unsupportedStatus() {
@@ -224,6 +237,57 @@ function systemdQuote(value) {
224
237
  function desktopQuote(value) {
225
238
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
226
239
  }
240
+ function autostartEnvironment() {
241
+ const environment = {};
242
+ const pathValue = buildAutostartPath();
243
+ if (pathValue) {
244
+ environment.PATH = pathValue;
245
+ }
246
+ const hermesBin = process.env.HERMES_BIN?.trim();
247
+ if (hermesBin) {
248
+ environment.HERMES_BIN = hermesBin;
249
+ }
250
+ return environment;
251
+ }
252
+ function buildAutostartPath() {
253
+ const separator = process.platform === "win32" ? ";" : ":";
254
+ const candidates = [
255
+ path.dirname(process.execPath),
256
+ ...(process.env.PATH ?? "").split(separator),
257
+ path.join(os.homedir(), ".local", "bin"),
258
+ ...process.platform === "win32" ? [] : ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"]
259
+ ];
260
+ const seen = /* @__PURE__ */ new Set();
261
+ const entries = [];
262
+ for (const candidate of candidates) {
263
+ const normalized = candidate.trim();
264
+ if (!normalized) {
265
+ continue;
266
+ }
267
+ const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
268
+ if (seen.has(key)) {
269
+ continue;
270
+ }
271
+ seen.add(key);
272
+ entries.push(normalized);
273
+ }
274
+ return entries.join(separator);
275
+ }
276
+ function plistEnvironmentEntries(environment) {
277
+ return Object.entries(environment).map(([key, value]) => ` <key>${xmlEscape(key)}</key>
278
+ <string>${xmlEscape(value)}</string>`).join("\n");
279
+ }
280
+ function systemdEnvironmentLines(environment) {
281
+ return Object.entries(environment).map(([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}`).join("\n");
282
+ }
283
+ function desktopEnvironmentArgs(environment) {
284
+ return Object.entries(environment).map(([key, value]) => desktopQuote(`${key}=${value}`)).join(" ");
285
+ }
286
+ function cmdEnvironmentLines(environment) {
287
+ return Object.entries(environment).map(
288
+ ([key, value]) => `set "${key}=${value.replaceAll('"', "")}"`
289
+ );
290
+ }
227
291
 
228
292
  // src/i18n.ts
229
293
  var messages = {
@@ -303,6 +367,25 @@ var messages = {
303
367
  "pair.autostartUnchanged": "Existing paired devices found. Boot autostart settings were left unchanged.",
304
368
  "pair.autostartFailed": "Pairing succeeded, but boot autostart could not be enabled: {message}",
305
369
  "doctor.description": "Run local diagnostics",
370
+ "doctor.installOnly": "only check npm global command and PATH setup",
371
+ "doctor.installHeader": "Install/PATH diagnostics:",
372
+ "doctor.installNpmPrefix": "npm global prefix: {value}",
373
+ "doctor.installGlobalBin": "npm global bin directory: {value}",
374
+ "doctor.installExpectedCommand": "expected hermeslink command: {value} ({state})",
375
+ "doctor.installCommandOnPath": "hermeslink found on PATH: {value}",
376
+ "doctor.installUnknown": "unknown",
377
+ "doctor.installOk": "ok",
378
+ "doctor.installMissing": "missing",
379
+ "doctor.installReady": "The global hermeslink command is visible to this shell.",
380
+ "doctor.installPrefixUnavailable": "Could not read npm global prefix with `{command} prefix -g`. Check that npm is available in this shell.",
381
+ "doctor.installExpectedMissing": "npm does not appear to have created the global hermeslink shim. Re-run `npm install -g @hermespilot/link` in the same system environment where Hermes runs.",
382
+ "doctor.installPathMissing": "npm installed hermeslink, but this shell cannot see npm's global bin directory on PATH.",
383
+ "doctor.installShadowed": "A different hermeslink command appears earlier on PATH. Remove the old command or move npm's global bin directory earlier on PATH.",
384
+ "doctor.installWslWindowsNpm": "This looks like Windows Node/npm being used from WSL (`/mnt/c/...`). Prefer installing Linux Node.js inside WSL, or run both Hermes Agent and Hermes Link on the Windows host.",
385
+ "doctor.installDirectRun": "Direct run without changing PATH: {command}",
386
+ "doctor.installUnixPathHint": 'Temporary PATH fix: export PATH="{path}:$PATH"',
387
+ "doctor.installWindowsPathHint": "Add this directory to your user Path, then open a new terminal: {path}",
388
+ "doctor.installNpxFallback": "PATH-independent fallback: npx --yes @hermespilot/link doctor --install",
306
389
  "doctor.identityOk": "Runtime identity: OK",
307
390
  "doctor.installId": "Install ID: {value}",
308
391
  "doctor.linkId": "Link ID: {value}",
@@ -401,6 +484,25 @@ var messages = {
401
484
  "pair.autostartUnchanged": "\u68C0\u6D4B\u5230\u5DF2\u6709\u914D\u5BF9\u8BBE\u5907\uFF0C\u5F00\u673A\u81EA\u542F\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8\u3002",
402
485
  "pair.autostartFailed": "\u914D\u5BF9\u5DF2\u6210\u529F\uFF0C\u4F46\u542F\u7528\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A{message}",
403
486
  "doctor.description": "\u8FD0\u884C\u672C\u673A\u8BCA\u65AD",
487
+ "doctor.installOnly": "\u53EA\u68C0\u67E5 npm \u5168\u5C40\u547D\u4EE4\u548C PATH \u8BBE\u7F6E",
488
+ "doctor.installHeader": "\u5B89\u88C5 / PATH \u8BCA\u65AD\uFF1A",
489
+ "doctor.installNpmPrefix": "npm \u5168\u5C40 prefix\uFF1A{value}",
490
+ "doctor.installGlobalBin": "npm \u5168\u5C40 bin \u76EE\u5F55\uFF1A{value}",
491
+ "doctor.installExpectedCommand": "\u9884\u671F\u7684 hermeslink \u547D\u4EE4\uFF1A{value}\uFF08{state}\uFF09",
492
+ "doctor.installCommandOnPath": "\u5F53\u524D PATH \u627E\u5230\u7684 hermeslink\uFF1A{value}",
493
+ "doctor.installUnknown": "\u672A\u77E5",
494
+ "doctor.installOk": "\u5B58\u5728",
495
+ "doctor.installMissing": "\u672A\u627E\u5230",
496
+ "doctor.installReady": "\u5F53\u524D shell \u53EF\u4EE5\u76F4\u63A5\u627E\u5230\u5168\u5C40 hermeslink \u547D\u4EE4\u3002",
497
+ "doctor.installPrefixUnavailable": "\u65E0\u6CD5\u901A\u8FC7 `{command} prefix -g` \u8BFB\u53D6 npm \u5168\u5C40 prefix\u3002\u8BF7\u5148\u786E\u8BA4\u5F53\u524D shell \u80FD\u8FD0\u884C npm\u3002",
498
+ "doctor.installExpectedMissing": "npm \u4F3C\u4E4E\u6CA1\u6709\u521B\u5EFA\u5168\u5C40 hermeslink shim\u3002\u8BF7\u5728 Hermes \u6240\u5728\u7684\u540C\u4E00\u7CFB\u7EDF\u73AF\u5883\u91CC\u91CD\u65B0\u6267\u884C `npm install -g @hermespilot/link`\u3002",
499
+ "doctor.installPathMissing": "npm \u5DF2\u5B89\u88C5 hermeslink\uFF0C\u4F46\u5F53\u524D shell \u7684 PATH \u91CC\u6CA1\u6709 npm \u5168\u5C40 bin \u76EE\u5F55\u3002",
500
+ "doctor.installShadowed": "PATH \u524D\u9762\u5B58\u5728\u53E6\u4E00\u4E2A hermeslink \u547D\u4EE4\u3002\u8BF7\u5220\u9664\u65E7\u547D\u4EE4\uFF0C\u6216\u628A npm \u5168\u5C40 bin \u76EE\u5F55\u653E\u5230 PATH \u66F4\u524D\u9762\u3002",
501
+ "doctor.installWslWindowsNpm": "\u5F53\u524D\u770B\u8D77\u6765\u662F\u5728 WSL \u4E2D\u8C03\u7528\u4E86 Windows Node/npm\uFF08`/mnt/c/...`\uFF09\u3002\u5EFA\u8BAE\u5728 WSL \u5185\u5B89\u88C5 Linux \u7248 Node.js\uFF0C\u6216\u628A Hermes Agent \u4E0E Hermes Link \u90FD\u653E\u5728 Windows \u5BBF\u4E3B\u673A\u8FD0\u884C\u3002",
502
+ "doctor.installDirectRun": "\u4E0D\u6539 PATH \u4E5F\u53EF\u4EE5\u76F4\u63A5\u8FD0\u884C\uFF1A{command}",
503
+ "doctor.installUnixPathHint": '\u4E34\u65F6\u8865 PATH\uFF1Aexport PATH="{path}:$PATH"',
504
+ "doctor.installWindowsPathHint": "\u628A\u8FD9\u4E2A\u76EE\u5F55\u52A0\u5165\u5F53\u524D\u7528\u6237\u7684 Path\uFF0C\u7136\u540E\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\uFF1A{path}",
505
+ "doctor.installNpxFallback": "\u4E0D\u4F9D\u8D56 PATH \u7684\u515C\u5E95\u547D\u4EE4\uFF1Anpx --yes @hermespilot/link doctor --install",
404
506
  "doctor.identityOk": "\u8FD0\u884C\u8EAB\u4EFD\uFF1A\u6B63\u5E38",
405
507
  "doctor.installId": "Install ID\uFF1A{value}",
406
508
  "doctor.linkId": "Link ID\uFF1A{value}",
@@ -701,6 +803,135 @@ async function spawnDetached(command, args) {
701
803
  });
702
804
  }
703
805
 
806
+ // src/runtime/install-info.ts
807
+ import { execFileSync } from "child_process";
808
+ import { existsSync, realpathSync } from "fs";
809
+ import path3 from "path";
810
+ function readInstallPathInfo() {
811
+ return inspectInstallPath({
812
+ npmPrefix: resolveGlobalPrefix()
813
+ });
814
+ }
815
+ function inspectInstallPath(options = {}) {
816
+ const platform = options.platform ?? process.platform;
817
+ const env = options.env ?? process.env;
818
+ const exists = options.exists ?? existsSync;
819
+ const realpath = options.realpath ?? realpathSync;
820
+ const npmPrefix = options.npmPrefix === void 0 ? resolveGlobalPrefix(platform, env) : options.npmPrefix;
821
+ const globalBinDir = npmPrefix ? resolveGlobalBinDir(npmPrefix, platform) : null;
822
+ const expectedCommandPath = globalBinDir ? resolveGlobalCommandPath(globalBinDir, platform) : null;
823
+ const expectedCommandExists = expectedCommandPath ? exists(expectedCommandPath) : false;
824
+ const pathIncludesGlobalBin = globalBinDir ? pathIncludes(globalBinDir, env.PATH ?? "", platform) : false;
825
+ const commandOnPath = findCommandOnPath(LINK_COMMAND, env, platform, exists);
826
+ const commandOnPathMatchesExpected = commandOnPath && expectedCommandPath ? samePath(commandOnPath, expectedCommandPath, platform, realpath) : null;
827
+ return {
828
+ platform,
829
+ npmCommand: resolveNpmCommand(platform),
830
+ npmPrefix,
831
+ globalBinDir,
832
+ expectedCommandPath,
833
+ expectedCommandExists,
834
+ pathIncludesGlobalBin,
835
+ commandOnPath,
836
+ commandOnPathMatchesExpected,
837
+ wslWindowsNpmLikely: platform === "linux" && Boolean(npmPrefix?.startsWith("/mnt/c/"))
838
+ };
839
+ }
840
+ function hasInstallPathIssue(info) {
841
+ return !info.npmPrefix || !info.globalBinDir || !info.expectedCommandExists || !info.pathIncludesGlobalBin || !info.commandOnPath || info.commandOnPathMatchesExpected === false || info.wslWindowsNpmLikely;
842
+ }
843
+ function resolveGlobalBinDir(prefix, platform) {
844
+ return platform === "win32" ? prefix : pathForPlatform(platform).join(prefix, "bin");
845
+ }
846
+ function resolveGlobalCommandPath(binDir, platform) {
847
+ return pathForPlatform(platform).join(
848
+ binDir,
849
+ platform === "win32" ? `${LINK_COMMAND}.cmd` : LINK_COMMAND
850
+ );
851
+ }
852
+ function formatShellCommand(commandPath, args, platform) {
853
+ return [quoteShellToken(commandPath, platform), ...args.map((arg) => quoteShellToken(arg, platform))].join(" ");
854
+ }
855
+ function resolveGlobalPrefix(platform = process.platform, env = process.env) {
856
+ const envPrefix = env.npm_config_prefix?.trim() ?? env.npm_config_global_prefix?.trim() ?? "";
857
+ if (envPrefix) {
858
+ return envPrefix;
859
+ }
860
+ try {
861
+ const output = execFileSync(resolveNpmCommand(platform), ["prefix", "-g"], {
862
+ encoding: "utf8",
863
+ stdio: ["ignore", "pipe", "ignore"],
864
+ timeout: 2e3
865
+ });
866
+ return output.trim() || null;
867
+ } catch {
868
+ return null;
869
+ }
870
+ }
871
+ function resolveNpmCommand(platform) {
872
+ return platform === "win32" ? "npm.cmd" : "npm";
873
+ }
874
+ function pathIncludes(target, pathValue, platform) {
875
+ const normalizedTarget = normalizePath(target, platform);
876
+ return splitPath(pathValue, platform).some(
877
+ (entry) => normalizePath(entry, platform) === normalizedTarget
878
+ );
879
+ }
880
+ function findCommandOnPath(command, env, platform, exists) {
881
+ for (const entry of splitPath(env.PATH ?? "", platform)) {
882
+ for (const fileName of commandFileNames(command, env, platform)) {
883
+ const candidate = pathForPlatform(platform).join(entry, fileName);
884
+ if (exists(candidate)) {
885
+ return candidate;
886
+ }
887
+ }
888
+ }
889
+ return null;
890
+ }
891
+ function commandFileNames(command, env, platform) {
892
+ if (platform !== "win32") {
893
+ return [command];
894
+ }
895
+ const extensions = (env.PATHEXT || ".COM;.EXE;.BAT;.CMD;.PS1").split(";").map((value) => value.trim()).filter(Boolean);
896
+ return [command, ...extensions.map((extension) => `${command}${extension.toLowerCase()}`)];
897
+ }
898
+ function samePath(left, right, platform, realpath) {
899
+ try {
900
+ return normalizeComparablePath(realpath(left), platform) === normalizeComparablePath(realpath(right), platform);
901
+ } catch {
902
+ return normalizeComparablePath(left, platform) === normalizeComparablePath(right, platform);
903
+ }
904
+ }
905
+ function splitPath(pathValue, platform) {
906
+ const delimiter = platform === "win32" ? ";" : ":";
907
+ return pathValue.split(delimiter).map((entry) => stripPathQuotes(entry.trim())).filter(Boolean);
908
+ }
909
+ function stripPathQuotes(value) {
910
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
911
+ return value.slice(1, -1);
912
+ }
913
+ return value;
914
+ }
915
+ function normalizePath(value, platform) {
916
+ return normalizeComparablePath(pathForPlatform(platform).resolve(value), platform);
917
+ }
918
+ function normalizeComparablePath(value, platform) {
919
+ const normalized = pathForPlatform(platform).normalize(value);
920
+ return platform === "win32" ? normalized.toLowerCase() : normalized;
921
+ }
922
+ function pathForPlatform(platform) {
923
+ return platform === "win32" ? path3.win32 : path3.posix;
924
+ }
925
+ function quoteShellToken(value, platform) {
926
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/u.test(value)) {
927
+ return value;
928
+ }
929
+ if (platform === "win32") {
930
+ return `"${value.replaceAll('"', '\\"')}"`;
931
+ }
932
+ return `'${value.replaceAll("'", "'\\''")}'`;
933
+ }
934
+
704
935
  // src/cli/index.ts
705
936
  var program = new Command();
706
937
  var helpLanguage = detectSystemLanguage();
@@ -874,6 +1105,7 @@ program.command("pair").description(helpText("pair.description")).action(async (
874
1105
  const hadActiveDevices = await hasActiveDevices(paths);
875
1106
  const probeBeforePair = await probeLocalLinkService({ port: config.port });
876
1107
  const prepared = await preparePairing(paths);
1108
+ const streamBatchPolicy = await fetchRelayStreamBatchPolicy(config.serverBaseUrl);
877
1109
  await clearPairingClaim(prepared.sessionId, paths);
878
1110
  const probe = await probeLocalLinkService({ port: config.port, linkId: prepared.linkId });
879
1111
  if (probe.reachable && !probe.reusable) {
@@ -892,7 +1124,8 @@ program.command("pair").description(helpText("pair.description")).action(async (
892
1124
  pairingRelayBridge = connectRelayControl({
893
1125
  relayBaseUrl: prepared.relayBaseUrl,
894
1126
  linkId: prepared.linkId,
895
- localPort: config.port
1127
+ localPort: config.port,
1128
+ initialStreamBatchPolicy: streamBatchPolicy
896
1129
  });
897
1130
  pairingRelayBridge.publishNetworkRoutes(prepared.routes);
898
1131
  }
@@ -999,7 +1232,17 @@ program.command("logs").description(helpText("logs.description")).action(async (
999
1232
  console.log(t("logs.servicePath", { path: getLinkLogFile(paths) }));
1000
1233
  console.log(t("logs.daemonPath", { path: daemonLogFile(paths) }));
1001
1234
  });
1002
- program.command("doctor").description(helpText("doctor.description")).action(async () => {
1235
+ program.command("doctor").option("--install", helpText("doctor.installOnly")).description(helpText("doctor.description")).action(async (options) => {
1236
+ const installInfo = readInstallPathInfo();
1237
+ const installLanguage = await loadCliLanguage().catch(() => detectSystemLanguage());
1238
+ const installT = translate.bind(null, installLanguage);
1239
+ if (options.install) {
1240
+ printInstallDiagnostics(installInfo, installT, true);
1241
+ return;
1242
+ }
1243
+ if (hasInstallPathIssue(installInfo)) {
1244
+ printInstallDiagnostics(installInfo, installT, false);
1245
+ }
1003
1246
  const [identity, config] = await Promise.all([ensureIdentity(), loadConfig()]);
1004
1247
  const language = resolveLanguage(config.language);
1005
1248
  const t = translate.bind(null, language);
@@ -1045,6 +1288,53 @@ async function loadCliLanguage() {
1045
1288
  function formatHermesVersion(version) {
1046
1289
  return version.version ?? version.raw;
1047
1290
  }
1291
+ function printInstallDiagnostics(info, t, verbose) {
1292
+ if (!verbose && !hasInstallPathIssue(info)) {
1293
+ return;
1294
+ }
1295
+ console.log(t("doctor.installHeader"));
1296
+ console.log(t("doctor.installNpmPrefix", { value: info.npmPrefix ?? t("doctor.installUnknown") }));
1297
+ console.log(t("doctor.installGlobalBin", { value: info.globalBinDir ?? t("doctor.installUnknown") }));
1298
+ console.log(
1299
+ t("doctor.installExpectedCommand", {
1300
+ value: info.expectedCommandPath ?? t("doctor.installUnknown"),
1301
+ state: info.expectedCommandExists ? t("doctor.installOk") : t("doctor.installMissing")
1302
+ })
1303
+ );
1304
+ console.log(
1305
+ t("doctor.installCommandOnPath", {
1306
+ value: info.commandOnPath ?? t("doctor.installMissing")
1307
+ })
1308
+ );
1309
+ if (!info.npmPrefix) {
1310
+ console.log(t("doctor.installPrefixUnavailable", { command: info.npmCommand }));
1311
+ } else if (!info.expectedCommandExists) {
1312
+ console.log(t("doctor.installExpectedMissing"));
1313
+ } else if (!info.pathIncludesGlobalBin || !info.commandOnPath) {
1314
+ console.log(t("doctor.installPathMissing"));
1315
+ } else if (info.commandOnPathMatchesExpected === false) {
1316
+ console.log(t("doctor.installShadowed"));
1317
+ } else {
1318
+ console.log(t("doctor.installReady"));
1319
+ }
1320
+ if (info.wslWindowsNpmLikely) {
1321
+ console.log(t("doctor.installWslWindowsNpm"));
1322
+ }
1323
+ if (info.expectedCommandPath && info.expectedCommandExists) {
1324
+ console.log(
1325
+ t("doctor.installDirectRun", {
1326
+ command: formatShellCommand(info.expectedCommandPath, ["doctor"], info.platform)
1327
+ })
1328
+ );
1329
+ }
1330
+ if (info.globalBinDir && info.platform !== "win32") {
1331
+ console.log(t("doctor.installUnixPathHint", { path: info.globalBinDir }));
1332
+ }
1333
+ if (info.globalBinDir && info.platform === "win32") {
1334
+ console.log(t("doctor.installWindowsPathHint", { path: info.globalBinDir }));
1335
+ }
1336
+ console.log(t("doctor.installNpxFallback"));
1337
+ }
1048
1338
  function pairingPreflightProgressKey(stage) {
1049
1339
  switch (stage) {
1050
1340
  case "hermes_files":
@@ -1193,9 +1483,9 @@ function isCliEntrypoint(entry = process.argv[1], moduleUrl = import.meta.url) {
1193
1483
  return false;
1194
1484
  }
1195
1485
  try {
1196
- return moduleUrl === pathToFileURL(realpathSync(path3.resolve(entry))).href;
1486
+ return moduleUrl === pathToFileURL(realpathSync2(path4.resolve(entry))).href;
1197
1487
  } catch {
1198
- return moduleUrl === pathToFileURL(path3.resolve(entry)).href;
1488
+ return moduleUrl === pathToFileURL(path4.resolve(entry)).href;
1199
1489
  }
1200
1490
  }
1201
1491
  export {
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-PULX22HX.js";
3
+ } from "../chunk-5JBXQ3VC.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.5.1",
3
+ "version": "0.5.3",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",
@@ -99,9 +99,12 @@ async function main() {
99
99
  console.log(
100
100
  `如果当前 shell 找不到 \`hermeslink\`,说明 npm 全局 bin 目录没有进 PATH:${globalBinDir}`,
101
101
  );
102
+ console.log("你也可以先运行不依赖 PATH 的诊断:npx --yes @hermespilot/link doctor --install");
102
103
  console.log(`你可以直接运行:${commandPath} pair`);
103
104
  if (process.platform !== "win32") {
104
105
  console.log(`或者先补 PATH:export PATH="${globalBinDir}:$PATH"`);
106
+ } else {
107
+ console.log(`或者把这个目录加入当前用户的 Path,然后重新打开终端:${globalBinDir}`);
105
108
  }
106
109
  }
107
110
  } else {
@@ -111,9 +114,12 @@ async function main() {
111
114
  console.log(
112
115
  `If your shell cannot find \`hermeslink\`, npm's global bin directory is not on PATH: ${globalBinDir}`,
113
116
  );
117
+ console.log("You can run a PATH-independent diagnostic first: npx --yes @hermespilot/link doctor --install");
114
118
  console.log(`You can run it directly: ${commandPath} pair`);
115
119
  if (process.platform !== "win32") {
116
120
  console.log(`Or add it to PATH first: export PATH="${globalBinDir}:$PATH"`);
121
+ } else {
122
+ console.log(`Or add this directory to your user Path, then open a new terminal: ${globalBinDir}`);
117
123
  }
118
124
  }
119
125
  }