@hermespilot/link 0.4.5 → 0.4.7

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 CHANGED
@@ -15,6 +15,14 @@ npm install -g @hermespilot/link
15
15
 
16
16
  The package installs the `hermeslink` command. It does not start the service automatically during installation.
17
17
 
18
+ If your shell cannot find `hermeslink` right after install, your npm global bin directory is probably not on `PATH`. On Unix-like systems you can run:
19
+
20
+ ```bash
21
+ export PATH="$(npm prefix -g)/bin:$PATH"
22
+ ```
23
+
24
+ You can also invoke the installed binary directly with `$(npm prefix -g)/bin/hermeslink`.
25
+
18
26
  ## Common commands
19
27
 
20
28
  ```bash
@@ -2080,6 +2080,14 @@ async function ensureHermesApiServerConfig(profileName = "default", configPath =
2080
2080
  }
2081
2081
  return ensureHermesApiServerConfigUnlocked(profileName, configPath);
2082
2082
  }
2083
+ async function repairHermesApiServerConfig(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2084
+ if (profileName !== "default") {
2085
+ return withProfileApiServerPortAssignmentLock(
2086
+ () => repairHermesApiServerConfigUnlocked(profileName, configPath)
2087
+ );
2088
+ }
2089
+ return repairHermesApiServerConfigUnlocked(profileName, configPath);
2090
+ }
2083
2091
  async function ensureHermesApiServerConfigUnlocked(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2084
2092
  const existingRaw = await readFile2(configPath, "utf8").catch(
2085
2093
  (error) => {
@@ -2096,47 +2104,60 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
2096
2104
  const extra = ensureRecord(apiServer, "extra");
2097
2105
  const configOnly = readApiServerConfig(apiServer);
2098
2106
  const envOverrides = await readHermesApiServerEnvOverrides(profileName);
2099
- const before = applyEnvOverrides(configOnly, envOverrides, false);
2100
- const beforeKey = before.key?.trim() ? before.key : null;
2101
- const beforeEnabled = before.enabled === true;
2102
- const beforeHost = before.host?.trim() ? before.host : null;
2103
- const beforePort = typeof before.port === "number" && Number.isFinite(before.port) ? before.port : null;
2107
+ const configKey = configOnly.key?.trim() ? configOnly.key : null;
2108
+ const envKey = envOverrides.key?.trim() ? envOverrides.key : null;
2109
+ const configHost = configOnly.host?.trim() ? configOnly.host : null;
2110
+ const envHost = envOverrides.host?.trim() ? envOverrides.host : null;
2111
+ const configPort = readApiServerPort(configOnly.port);
2112
+ const envPort = readApiServerPort(envOverrides.port);
2113
+ const desiredHost = configHost ?? envHost ?? DEFAULT_HERMES_API_SERVER_HOST;
2114
+ const desiredPort = await resolveDesiredApiServerPort({
2115
+ profileName,
2116
+ configPort,
2117
+ envPort
2118
+ });
2119
+ const desiredKey = configKey ?? envKey ?? randomBytes(32).toString("base64url");
2104
2120
  let changed = false;
2105
2121
  let enabledAdded = false;
2106
2122
  let hostAdded = false;
2107
2123
  let portAdded = false;
2108
- let assignedPort = null;
2109
- if (!beforeEnabled) {
2124
+ if (apiServer.enabled !== true) {
2110
2125
  apiServer.enabled = true;
2111
2126
  enabledAdded = true;
2112
2127
  changed = true;
2113
2128
  }
2114
- if (!beforeHost) {
2115
- extra.host = DEFAULT_HERMES_API_SERVER_HOST;
2116
- hostAdded = true;
2129
+ if (configHost !== desiredHost) {
2130
+ extra.host = desiredHost;
2131
+ hostAdded = !configHost;
2117
2132
  changed = true;
2118
2133
  }
2119
- if (shouldAssignDedicatedProfileApiServerPort(profileName, configOnly.port)) {
2120
- assignedPort = await nextProfileApiServerPort(profileName);
2121
- extra.port = assignedPort;
2122
- portAdded = true;
2123
- changed = true;
2124
- } else if (!beforePort) {
2125
- assignedPort = DEFAULT_HERMES_API_SERVER_PORT;
2126
- extra.port = assignedPort;
2134
+ if (configPort !== desiredPort) {
2135
+ extra.port = desiredPort;
2127
2136
  portAdded = true;
2128
2137
  changed = true;
2129
2138
  }
2130
- if (!beforeKey) {
2131
- extra.key = randomBytes(32).toString("base64url");
2139
+ if (configKey !== desiredKey) {
2140
+ extra.key = desiredKey;
2132
2141
  changed = true;
2133
2142
  }
2134
- if (!changed) {
2143
+ const desiredApiServer = readApiServerConfig(apiServer, true);
2144
+ const backupPath = changed && existingRaw ? `${configPath}.bak.${Date.now()}` : null;
2145
+ if (backupPath && existingRaw !== null) {
2146
+ await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
2147
+ metadataSourcePath: configPath
2148
+ });
2149
+ }
2150
+ if (changed) {
2151
+ document.contents = document.createNode(config);
2152
+ await atomicWriteFilePreservingMetadata(configPath, document.toString());
2153
+ }
2154
+ const envChanged = await writeHermesApiServerEnv(profileName, desiredApiServer);
2155
+ if (!changed && !envChanged) {
2135
2156
  return {
2136
2157
  configPath,
2137
2158
  apiServer: applyEnvOverrides(
2138
2159
  readApiServerConfig(apiServer, true),
2139
- envOverrides,
2160
+ await readHermesApiServerEnvOverrides(profileName),
2140
2161
  true
2141
2162
  ),
2142
2163
  changed: false,
@@ -2148,36 +2169,79 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
2148
2169
  notice: null
2149
2170
  };
2150
2171
  }
2151
- const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
2152
- if (backupPath && existingRaw !== null) {
2153
- await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
2154
- metadataSourcePath: configPath
2155
- });
2156
- }
2157
- document.contents = document.createNode(config);
2158
- await atomicWriteFilePreservingMetadata(configPath, document.toString());
2159
2172
  return {
2160
2173
  configPath,
2161
2174
  apiServer: applyEnvOverrides(
2162
2175
  readApiServerConfig(apiServer, true),
2163
- envOverrides,
2176
+ await readHermesApiServerEnvOverrides(profileName),
2164
2177
  true
2165
2178
  ),
2166
2179
  changed: true,
2167
- keyAdded: !beforeKey,
2180
+ keyAdded: !configKey,
2168
2181
  enabledAdded,
2169
2182
  hostAdded,
2170
2183
  portAdded,
2171
2184
  backupPath,
2172
2185
  notice: buildNotice({
2173
- keyAdded: !beforeKey,
2186
+ keyAdded: !configKey,
2174
2187
  enabledAdded,
2175
2188
  hostAdded,
2176
2189
  portAdded,
2177
- port: assignedPort ?? beforePort ?? void 0
2190
+ port: desiredPort
2178
2191
  })
2179
2192
  };
2180
2193
  }
2194
+ async function repairHermesApiServerConfigUnlocked(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2195
+ const existingRaw = await readFile2(configPath, "utf8").catch(
2196
+ (error) => {
2197
+ if (isNodeError3(error, "ENOENT")) {
2198
+ return null;
2199
+ }
2200
+ throw error;
2201
+ }
2202
+ );
2203
+ const document = existingRaw ? YAML.parseDocument(existingRaw) : new YAML.Document({});
2204
+ const config = toRecord(document.toJSON());
2205
+ const platforms = ensureRecord(config, "platforms");
2206
+ const apiServer = ensureRecord(platforms, "api_server");
2207
+ const extra = ensureRecord(apiServer, "extra");
2208
+ const previous = applyEnvOverrides(
2209
+ readApiServerConfig(apiServer, true),
2210
+ await readHermesApiServerEnvOverrides(profileName),
2211
+ true
2212
+ );
2213
+ const freshPort = await nextProfileApiServerPort(profileName);
2214
+ const freshKey = randomBytes(32).toString("base64url");
2215
+ apiServer.enabled = true;
2216
+ extra.host = DEFAULT_HERMES_API_SERVER_HOST;
2217
+ extra.port = freshPort;
2218
+ extra.key = freshKey;
2219
+ const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
2220
+ if (backupPath && existingRaw !== null) {
2221
+ await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
2222
+ metadataSourcePath: configPath
2223
+ });
2224
+ }
2225
+ document.contents = document.createNode(config);
2226
+ await atomicWriteFilePreservingMetadata(configPath, document.toString());
2227
+ const apiServerConfig = readApiServerConfig(apiServer, true);
2228
+ await writeHermesApiServerEnv(profileName, apiServerConfig, { force: true });
2229
+ return {
2230
+ configPath,
2231
+ apiServer: applyEnvOverrides(
2232
+ apiServerConfig,
2233
+ await readHermesApiServerEnvOverrides(profileName),
2234
+ true
2235
+ ),
2236
+ changed: true,
2237
+ keyAdded: !previous.key,
2238
+ enabledAdded: previous.enabled !== true,
2239
+ hostAdded: previous.host !== DEFAULT_HERMES_API_SERVER_HOST,
2240
+ portAdded: previous.port !== freshPort,
2241
+ backupPath,
2242
+ notice: "\u5DF2\u4E3A Hermes API Server \u91CD\u65B0\u5206\u914D\u672C\u673A\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002"
2243
+ };
2244
+ }
2181
2245
  async function readHermesConfigDocument(configPath) {
2182
2246
  const existingRaw = await readFile2(configPath, "utf8").catch(
2183
2247
  (error) => {
@@ -3511,6 +3575,21 @@ function shouldAssignDedicatedProfileApiServerPort(profileName, configuredPort)
3511
3575
  }
3512
3576
  return configuredPort === void 0 || configuredPort === DEFAULT_HERMES_API_SERVER_PORT;
3513
3577
  }
3578
+ function readApiServerPort(value) {
3579
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
3580
+ }
3581
+ async function resolveDesiredApiServerPort(input) {
3582
+ if (shouldAssignDedicatedProfileApiServerPort(
3583
+ input.profileName,
3584
+ input.configPort ?? void 0
3585
+ )) {
3586
+ if (input.profileName !== "default" && input.envPort !== null && input.envPort !== DEFAULT_HERMES_API_SERVER_PORT) {
3587
+ return input.envPort;
3588
+ }
3589
+ return nextProfileApiServerPort(input.profileName);
3590
+ }
3591
+ return input.configPort ?? input.envPort ?? DEFAULT_HERMES_API_SERVER_PORT;
3592
+ }
3514
3593
  async function nextProfileApiServerPort(profileName) {
3515
3594
  const usedPorts = await readConfiguredApiServerPorts(profileName);
3516
3595
  for (let port = PROFILE_API_SERVER_PORT_START; port <= PROFILE_API_SERVER_PORT_END; port += 1) {
@@ -3670,14 +3749,82 @@ async function writeHermesEnvValue(profileName, key, value) {
3670
3749
  }
3671
3750
  await atomicWriteFilePreservingMetadata(envPath, nextRaw);
3672
3751
  }
3752
+ async function writeHermesApiServerEnv(profileName, config, options = {}) {
3753
+ const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
3754
+ const existingRaw = await readFile2(envPath, "utf8").catch(
3755
+ (error) => {
3756
+ if (isNodeError3(error, "ENOENT")) {
3757
+ return null;
3758
+ }
3759
+ throw error;
3760
+ }
3761
+ );
3762
+ if (existingRaw === null && profileName === "default" && !options.force) {
3763
+ return false;
3764
+ }
3765
+ return writeHermesEnvValues(
3766
+ profileName,
3767
+ [
3768
+ ["API_SERVER_ENABLED", config.enabled === false ? "false" : "true"],
3769
+ ["API_SERVER_HOST", config.host ?? DEFAULT_HERMES_API_SERVER_HOST],
3770
+ ["API_SERVER_PORT", String(config.port ?? DEFAULT_HERMES_API_SERVER_PORT)],
3771
+ ["API_SERVER_KEY", config.key ?? ""]
3772
+ ],
3773
+ existingRaw ?? ""
3774
+ );
3775
+ }
3776
+ async function writeHermesEnvValues(profileName, values, existingRaw) {
3777
+ const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
3778
+ const raw = existingRaw ?? await readFile2(envPath, "utf8").catch((error) => {
3779
+ if (isNodeError3(error, "ENOENT")) {
3780
+ return "";
3781
+ }
3782
+ throw error;
3783
+ });
3784
+ const lines = raw ? raw.split(/\r?\n/u) : [];
3785
+ const remaining = new Map(values);
3786
+ const nextLines = lines.map((line) => {
3787
+ const trimmed = line.trim();
3788
+ for (const [key, value] of remaining) {
3789
+ const keyPattern = new RegExp(
3790
+ `^(?:export\\s+)?${escapeRegExp(key)}=`,
3791
+ "u"
3792
+ );
3793
+ if (keyPattern.test(trimmed)) {
3794
+ remaining.delete(key);
3795
+ return `${key}=${formatEnvValue(value)}`;
3796
+ }
3797
+ }
3798
+ return line;
3799
+ });
3800
+ if (remaining.size > 0 && nextLines.length > 0 && nextLines.at(-1) !== "") {
3801
+ nextLines.push("");
3802
+ }
3803
+ for (const [key, value] of remaining) {
3804
+ nextLines.push(`${key}=${formatEnvValue(value)}`);
3805
+ }
3806
+ const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
3807
+ if (nextRaw === raw) {
3808
+ return false;
3809
+ }
3810
+ if (raw) {
3811
+ await atomicWriteFilePreservingMetadata(
3812
+ `${envPath}.bak.${Date.now()}`,
3813
+ raw,
3814
+ { metadataSourcePath: envPath }
3815
+ );
3816
+ }
3817
+ await atomicWriteFilePreservingMetadata(envPath, nextRaw);
3818
+ return true;
3819
+ }
3673
3820
  function applyEnvOverrides(config, env, withDefaults) {
3674
- const host = config.host ?? env.host;
3675
- const port = config.port ?? env.port;
3821
+ const host = env.host ?? config.host;
3822
+ const port = env.port ?? config.port;
3676
3823
  return {
3677
- enabled: config.enabled ?? env.enabled,
3824
+ enabled: env.enabled ?? config.enabled,
3678
3825
  host: withDefaults ? host ?? DEFAULT_HERMES_API_SERVER_HOST : host,
3679
3826
  port: withDefaults ? port ?? DEFAULT_HERMES_API_SERVER_PORT : port,
3680
- key: config.key ?? env.key
3827
+ key: env.key ?? config.key
3681
3828
  };
3682
3829
  }
3683
3830
  function parseEnvBoolean(value) {
@@ -3985,7 +4132,7 @@ import os2 from "os";
3985
4132
  import path5 from "path";
3986
4133
 
3987
4134
  // src/constants.ts
3988
- var LINK_VERSION = "0.4.5";
4135
+ var LINK_VERSION = "0.4.7";
3989
4136
  var LINK_COMMAND = "hermeslink";
3990
4137
  var LINK_DEFAULT_PORT = 52379;
3991
4138
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -4548,6 +4695,47 @@ async function ensureHermesApiServerAvailable(options = {}) {
4548
4695
  });
4549
4696
  return { available: true, configResult, started: true, start, health };
4550
4697
  }
4698
+ if (health.authInvalid) {
4699
+ void options.logger?.warn("gateway_api_server_auth_repair_requested", {
4700
+ profile: profileName,
4701
+ port: configResult.apiServer.port ?? null,
4702
+ issue: health.issue ?? "auth_invalid"
4703
+ });
4704
+ const repairedConfigResult = await repairHermesApiServerConfig(profileName);
4705
+ const repairedStart = await startHermesGatewayOnce(
4706
+ options.paths ?? resolveRuntimePaths(),
4707
+ profileName,
4708
+ options.logger
4709
+ );
4710
+ health = await waitForHermesApiHealth(
4711
+ repairedConfigResult.apiServer,
4712
+ fetcher,
4713
+ options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS
4714
+ );
4715
+ if (health.healthy) {
4716
+ void options.logger?.info("gateway_api_server_auth_repair_succeeded", {
4717
+ profile: profileName,
4718
+ pid: repairedStart.pid,
4719
+ port: repairedConfigResult.apiServer.port ?? null,
4720
+ log_path: repairedStart.logPath,
4721
+ hermes_version: repairedStart.version?.version ?? null
4722
+ });
4723
+ return {
4724
+ available: true,
4725
+ configResult: repairedConfigResult,
4726
+ started: true,
4727
+ start: repairedStart,
4728
+ health
4729
+ };
4730
+ }
4731
+ void options.logger?.error("gateway_api_server_auth_repair_failed", {
4732
+ profile: profileName,
4733
+ pid: repairedStart.pid,
4734
+ port: repairedConfigResult.apiServer.port ?? null,
4735
+ log_path: repairedStart.logPath,
4736
+ issue: health.issue ?? "unknown"
4737
+ });
4738
+ }
4551
4739
  const logHint = await readRecentGatewayLogHint(start.logPath);
4552
4740
  void options.logger?.error("gateway_auto_start_failed", {
4553
4741
  profile: profileName,
@@ -4638,12 +4826,9 @@ async function readHermesVersion(options = {}) {
4638
4826
  }
4639
4827
  async function execHermesVersion(hermesBin, logger) {
4640
4828
  const failures = [];
4641
- for (const args of [["version"], ["--version"]]) {
4829
+ for (const args of [["--version"], ["version"]]) {
4642
4830
  try {
4643
- return await execFileAsync2(hermesBin, args, {
4644
- timeout: 5e3,
4645
- windowsHide: true
4646
- });
4831
+ return await spawnHermesVersionCommand(hermesBin, args, 5e3);
4647
4832
  } catch (error) {
4648
4833
  const failure = describeVersionCommandFailure(hermesBin, args, error);
4649
4834
  failures.push(failure);
@@ -4659,6 +4844,85 @@ async function execHermesVersion(hermesBin, logger) {
4659
4844
  });
4660
4845
  throw new Error(summary);
4661
4846
  }
4847
+ async function spawnHermesVersionCommand(hermesBin, args, timeoutMs) {
4848
+ return await new Promise((resolve, reject) => {
4849
+ const child = spawn(hermesBin, args, {
4850
+ stdio: ["ignore", "pipe", "pipe"],
4851
+ windowsHide: true
4852
+ });
4853
+ let stdout = "";
4854
+ let stderr = "";
4855
+ let settled = false;
4856
+ const timer = setTimeout(() => {
4857
+ finish(() => {
4858
+ reject(
4859
+ createVersionCommandError(
4860
+ `${hermesBin} ${args.join(" ")} timed out after ${timeoutMs}ms`,
4861
+ { stdout, stderr, killed: true }
4862
+ )
4863
+ );
4864
+ }, true);
4865
+ }, timeoutMs);
4866
+ const finish = (callback, kill = false) => {
4867
+ if (settled) {
4868
+ return;
4869
+ }
4870
+ settled = true;
4871
+ clearTimeout(timer);
4872
+ if (kill && child.pid && !child.killed) {
4873
+ child.kill();
4874
+ }
4875
+ callback();
4876
+ };
4877
+ const resolveIfVersionPrinted = () => {
4878
+ if (parseHermesVersion(stdout)) {
4879
+ finish(() => resolve({ stdout: `${stdout}
4880
+ ${stderr}`.trim() }), true);
4881
+ }
4882
+ };
4883
+ child.stdout?.on("data", (chunk) => {
4884
+ stdout += chunk.toString();
4885
+ resolveIfVersionPrinted();
4886
+ });
4887
+ child.stderr?.on("data", (chunk) => {
4888
+ stderr += chunk.toString();
4889
+ });
4890
+ child.once("error", (error) => {
4891
+ finish(() => {
4892
+ reject(
4893
+ createVersionCommandError(error.message, {
4894
+ stdout,
4895
+ stderr,
4896
+ cause: error
4897
+ })
4898
+ );
4899
+ });
4900
+ });
4901
+ child.once("close", (code, signal) => {
4902
+ finish(() => {
4903
+ const raw = `${stdout}
4904
+ ${stderr}`.trim();
4905
+ const version = parseHermesVersion(raw);
4906
+ if (code === 0 && (raw || version)) {
4907
+ resolve({ stdout: raw });
4908
+ return;
4909
+ }
4910
+ reject(
4911
+ createVersionCommandError(
4912
+ `${hermesBin} ${args.join(" ")} exited with code ${code ?? "null"}`,
4913
+ {
4914
+ stdout,
4915
+ stderr,
4916
+ code,
4917
+ signal,
4918
+ killed: child.killed
4919
+ }
4920
+ )
4921
+ );
4922
+ });
4923
+ });
4924
+ });
4925
+ }
4662
4926
  function assertHermesRunsApiSupported(version, status) {
4663
4927
  if (status !== 404) {
4664
4928
  return;
@@ -4916,11 +5180,9 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
4916
5180
  detailed: record
4917
5181
  };
4918
5182
  }
4919
- if (resolvedConfig.key) {
4920
- const authIssue = await probeHermesApiServerAuth(resolvedConfig, fetcher);
4921
- if (authIssue) {
4922
- return authIssue;
4923
- }
5183
+ const authIssue = await probeHermesApiServerAuth(resolvedConfig, fetcher);
5184
+ if (authIssue) {
5185
+ return authIssue;
4924
5186
  }
4925
5187
  return { healthy: true };
4926
5188
  }
@@ -4937,9 +5199,6 @@ async function readHermesApiServerHealth(config, fetcher = fetch) {
4937
5199
  };
4938
5200
  }
4939
5201
  async function probeHermesApiServerAuth(config, fetcher) {
4940
- if (!config.key) {
4941
- return null;
4942
- }
4943
5202
  const response = await fetchWithTimeout(
4944
5203
  `http://127.0.0.1:${config.port}/v1/models`,
4945
5204
  {
@@ -5150,6 +5409,21 @@ async function probeHermesVersion(hermesBin, options) {
5150
5409
  }
5151
5410
  }
5152
5411
  }
5412
+ function createVersionCommandError(message, details) {
5413
+ const error = new Error(message, { cause: details.cause });
5414
+ error.stdout = details.stdout;
5415
+ error.stderr = details.stderr;
5416
+ if (details.code !== void 0) {
5417
+ error.code = details.code;
5418
+ }
5419
+ if (details.signal !== void 0) {
5420
+ error.signal = details.signal;
5421
+ }
5422
+ if (details.killed !== void 0) {
5423
+ error.killed = details.killed;
5424
+ }
5425
+ return error;
5426
+ }
5153
5427
  function describeVersionCommandFailure(hermesBin, args, error) {
5154
5428
  const message = error instanceof Error ? error.message : String(error);
5155
5429
  const details = readExecErrorDetails(error, message);
@@ -7597,12 +7871,12 @@ var ConversationMetadataCoordinator = class {
7597
7871
  return { ...next, last_event_seq: event.seq, updated_at: event.created_at };
7598
7872
  }
7599
7873
  scheduleGeneratedTitleRefresh(conversationId) {
7600
- for (const delay2 of GENERATED_TITLE_RETRY_DELAYS_MS) {
7874
+ for (const delay3 of GENERATED_TITLE_RETRY_DELAYS_MS) {
7601
7875
  setTimeout(() => {
7602
7876
  void this.generateTitleFromFirstRound(conversationId).catch(
7603
7877
  () => void 0
7604
7878
  );
7605
- }, delay2);
7879
+ }, delay3);
7606
7880
  }
7607
7881
  }
7608
7882
  async renameConversation(conversationId, title, input) {
@@ -11745,6 +12019,46 @@ async function callHermesApi(path26, init, options) {
11745
12019
  startedAt,
11746
12020
  response
11747
12021
  );
12022
+ if (response.status !== 401) {
12023
+ return response;
12024
+ }
12025
+ void options.logger?.warn("hermes_api_request_repairing_after_401", {
12026
+ method,
12027
+ path: path26,
12028
+ profile: options.profileName ?? "default",
12029
+ port: config.port ?? null,
12030
+ duration_ms: Date.now() - startedAt
12031
+ });
12032
+ const profileName = options.profileName ?? "default";
12033
+ const repairedConfig = await repairHermesApiServerConfig(profileName);
12034
+ const repairedAvailability = await ensureHermesApiServerAvailable({
12035
+ fetchImpl: options.fetchImpl,
12036
+ forceRestart: true,
12037
+ logger: options.logger,
12038
+ profileName
12039
+ });
12040
+ config = repairedAvailability.configResult.apiServer ?? repairedConfig.apiServer;
12041
+ try {
12042
+ response = await request();
12043
+ } catch (error) {
12044
+ logHermesApiError(
12045
+ options.logger,
12046
+ method,
12047
+ path26,
12048
+ options.profileName,
12049
+ startedAt,
12050
+ error
12051
+ );
12052
+ throw error;
12053
+ }
12054
+ logHermesApiResponse(
12055
+ options.logger,
12056
+ method,
12057
+ path26,
12058
+ options.profileName,
12059
+ startedAt,
12060
+ response
12061
+ );
11748
12062
  return response;
11749
12063
  }
11750
12064
  async function fetchHermesApi(fetcher, config, path26, init, options) {
@@ -16025,11 +16339,19 @@ function createHttpErrorMiddleware(logger) {
16025
16339
  }
16026
16340
 
16027
16341
  // src/hermes/profiles.ts
16028
- import { readdir as readdir9, readFile as readFile12, rename as rename3, rm as rm6, stat as stat12 } from "fs/promises";
16342
+ import { execFile as execFile4 } from "child_process";
16343
+ import { readdir as readdir9, readFile as readFile12, rename as rename3, stat as stat12 } from "fs/promises";
16029
16344
  import path18 from "path";
16345
+ import { setTimeout as delay2 } from "timers/promises";
16346
+ import { promisify as promisify4 } from "util";
16030
16347
  import YAML2 from "yaml";
16031
16348
  var DEFAULT_PROFILE = "default";
16032
16349
  var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
16350
+ var PROFILE_DELETE_TIMEOUT_MS = 3e4;
16351
+ var PROFILE_GATEWAY_STOP_TIMEOUT_MS = 3e3;
16352
+ var PROFILE_DELETE_STABLE_ABSENCE_MS = 1200;
16353
+ var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
16354
+ var execFileAsync4 = promisify4(execFile4);
16033
16355
  async function listHermesProfiles(paths = resolveRuntimePaths()) {
16034
16356
  const profiles = /* @__PURE__ */ new Map();
16035
16357
  profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
@@ -16101,12 +16423,7 @@ async function updateHermesProfileMetadata(name, metadata, paths = resolveRuntim
16101
16423
  async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
16102
16424
  assertMutableProfile(name);
16103
16425
  const profile = await profileInfo(name, paths);
16104
- const exists = await stat12(profile.path).then((value) => value.isDirectory()).catch((error) => {
16105
- if (isNodeError14(error, "ENOENT")) {
16106
- return false;
16107
- }
16108
- throw error;
16109
- });
16426
+ const exists = await pathExists(profile.path);
16110
16427
  if (!exists) {
16111
16428
  throw new LinkHttpError(
16112
16429
  404,
@@ -16114,7 +16431,14 @@ async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
16114
16431
  `Profile "${name}" does not exist`
16115
16432
  );
16116
16433
  }
16117
- await rm6(resolveHermesProfileDir(name), { recursive: true, force: true });
16434
+ await deleteHermesProfileWithCliAndVerify(name, profile.path);
16435
+ if (!await waitForProfilePathToRemainAbsent(profile.path)) {
16436
+ throw new LinkHttpError(
16437
+ 502,
16438
+ "hermes_profile_delete_incomplete",
16439
+ `Hermes CLI reported success, but Profile "${name}" still exists at ${profile.path}`
16440
+ );
16441
+ }
16118
16442
  await deleteProfileIdentity(paths, {
16119
16443
  profileName: profile.name,
16120
16444
  profileUid: profile.uid
@@ -16170,9 +16494,168 @@ function assertProfileName(name) {
16170
16494
  throw new LinkHttpError(400, "invalid_profile_name", "invalid profile name");
16171
16495
  }
16172
16496
  }
16497
+ async function pathExists(targetPath) {
16498
+ return await stat12(targetPath).then(() => true).catch((error) => {
16499
+ if (isNodeError14(error, "ENOENT")) {
16500
+ return false;
16501
+ }
16502
+ throw error;
16503
+ });
16504
+ }
16505
+ async function deleteHermesProfileWithCli(name) {
16506
+ try {
16507
+ await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
16508
+ timeout: PROFILE_DELETE_TIMEOUT_MS,
16509
+ windowsHide: true
16510
+ });
16511
+ } catch (error) {
16512
+ throw new LinkHttpError(
16513
+ 502,
16514
+ "hermes_profile_delete_failed",
16515
+ `Hermes CLI could not delete Profile "${name}": ${formatExecError(error)}`
16516
+ );
16517
+ }
16518
+ }
16519
+ async function deleteHermesProfileWithCliAndVerify(name, profilePath) {
16520
+ for (let attempt = 0; attempt < 2; attempt += 1) {
16521
+ await stopHermesGatewayProcessesForProfile(name);
16522
+ if (attempt > 0 && !await pathExists(profilePath)) {
16523
+ return;
16524
+ }
16525
+ await deleteHermesProfileWithCli(name);
16526
+ if (await waitForProfilePathToRemainAbsent(profilePath)) {
16527
+ return;
16528
+ }
16529
+ }
16530
+ }
16531
+ async function stopHermesGatewayProcessesForProfile(name) {
16532
+ const pids = await findHermesGatewayProcessIdsForProfile(name);
16533
+ if (pids.length === 0) {
16534
+ return;
16535
+ }
16536
+ for (const pid of pids) {
16537
+ killProcess(pid, "SIGTERM");
16538
+ }
16539
+ const remainingAfterTerm = await waitForProcessesToExit(
16540
+ pids,
16541
+ PROFILE_GATEWAY_STOP_TIMEOUT_MS
16542
+ );
16543
+ if (remainingAfterTerm.length === 0) {
16544
+ return;
16545
+ }
16546
+ for (const pid of remainingAfterTerm) {
16547
+ killProcess(pid, "SIGKILL");
16548
+ }
16549
+ const remainingAfterKill = await waitForProcessesToExit(
16550
+ remainingAfterTerm,
16551
+ PROFILE_GATEWAY_STOP_TIMEOUT_MS
16552
+ );
16553
+ if (remainingAfterKill.length > 0) {
16554
+ throw new LinkHttpError(
16555
+ 502,
16556
+ "hermes_profile_gateway_stop_failed",
16557
+ `Could not stop Hermes Gateway process(es) for Profile "${name}": ${remainingAfterKill.join(", ")}`
16558
+ );
16559
+ }
16560
+ }
16561
+ async function findHermesGatewayProcessIdsForProfile(name) {
16562
+ if (process.platform === "win32") {
16563
+ return [];
16564
+ }
16565
+ try {
16566
+ const output = await execFileAsync4("ps", ["-axo", "pid=,command="], {
16567
+ timeout: 3e3,
16568
+ windowsHide: true
16569
+ });
16570
+ return parseHermesGatewayProcessIds(output.stdout.toString(), name);
16571
+ } catch {
16572
+ return [];
16573
+ }
16574
+ }
16575
+ function parseHermesGatewayProcessIds(output, name) {
16576
+ const pids = [];
16577
+ for (const line of output.split(/\r?\n/)) {
16578
+ const match = /^\s*(\d+)\s+(.+)$/.exec(line);
16579
+ if (!match) {
16580
+ continue;
16581
+ }
16582
+ const pid = Number(match[1]);
16583
+ const command = match[2];
16584
+ if (Number.isSafeInteger(pid) && pid !== process.pid && isHermesGatewayRunCommandForProfile(command, name)) {
16585
+ pids.push(pid);
16586
+ }
16587
+ }
16588
+ return pids;
16589
+ }
16590
+ function isHermesGatewayRunCommandForProfile(command, name) {
16591
+ const escapedName = escapeRegExp2(name);
16592
+ return new RegExp(
16593
+ String.raw`(?:^|\s)-p\s+${escapedName}\s+gateway\s+run(?:\s|$)`
16594
+ ).test(command);
16595
+ }
16596
+ function killProcess(pid, signal) {
16597
+ try {
16598
+ process.kill(pid, signal);
16599
+ } catch {
16600
+ }
16601
+ }
16602
+ async function waitForProcessesToExit(pids, timeoutMs) {
16603
+ const deadline = Date.now() + timeoutMs;
16604
+ let remaining = pids.filter(isProcessRunning);
16605
+ while (remaining.length > 0 && Date.now() < deadline) {
16606
+ await delay2(100);
16607
+ remaining = remaining.filter(isProcessRunning);
16608
+ }
16609
+ return remaining;
16610
+ }
16611
+ function isProcessRunning(pid) {
16612
+ try {
16613
+ process.kill(pid, 0);
16614
+ return true;
16615
+ } catch (error) {
16616
+ return isNodeError14(error, "EPERM");
16617
+ }
16618
+ }
16619
+ async function waitForProfilePathToRemainAbsent(profilePath) {
16620
+ const deadline = Date.now() + PROFILE_DELETE_STABLE_ABSENCE_MS;
16621
+ while (Date.now() < deadline) {
16622
+ if (await pathExists(profilePath)) {
16623
+ return false;
16624
+ }
16625
+ await delay2(PROFILE_DELETE_VERIFY_INTERVAL_MS);
16626
+ }
16627
+ return !await pathExists(profilePath);
16628
+ }
16629
+ function formatExecError(error) {
16630
+ if (!(error instanceof Error)) {
16631
+ return String(error);
16632
+ }
16633
+ const output = readExecErrorOutput2(error);
16634
+ return output ? `${error.message}
16635
+ ${output}` : error.message;
16636
+ }
16637
+ function readExecErrorOutput2(error) {
16638
+ const parts = [];
16639
+ if ("stdout" in error && error.stdout != null) {
16640
+ const stdout = String(error.stdout).trim();
16641
+ if (stdout) {
16642
+ parts.push(stdout);
16643
+ }
16644
+ }
16645
+ if ("stderr" in error && error.stderr != null) {
16646
+ const stderr = String(error.stderr).trim();
16647
+ if (stderr) {
16648
+ parts.push(stderr);
16649
+ }
16650
+ }
16651
+ return parts.join("\n");
16652
+ }
16173
16653
  function isNodeError14(error, code) {
16174
16654
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
16175
16655
  }
16656
+ function escapeRegExp2(value) {
16657
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16658
+ }
16176
16659
  async function countSkills(root) {
16177
16660
  const entries = await readdir9(root, { withFileTypes: true }).catch(
16178
16661
  (error) => {
@@ -16883,7 +17366,7 @@ import {
16883
17366
  cp,
16884
17367
  mkdir as mkdir10,
16885
17368
  readFile as readFile13,
16886
- rm as rm7,
17369
+ rm as rm6,
16887
17370
  stat as stat13
16888
17371
  } from "fs/promises";
16889
17372
  import path19 from "path";
@@ -17184,7 +17667,7 @@ async function generateProfileName(displayName, paths) {
17184
17667
  for (let attempt = 0; attempt < 100; attempt += 1) {
17185
17668
  const suffix = randomSuffix(attempt);
17186
17669
  const candidate = `${base}-${suffix}`.slice(0, 64).replace(/[-_]+$/u, "");
17187
- if (!existing.has(candidate) && !await pathExists(resolveHermesProfileDir(candidate))) {
17670
+ if (!existing.has(candidate) && !await pathExists2(resolveHermesProfileDir(candidate))) {
17188
17671
  return candidate;
17189
17672
  }
17190
17673
  }
@@ -17210,7 +17693,7 @@ function randomSuffix(attempt) {
17210
17693
  }
17211
17694
  async function applyProfileCreationPostSteps(input) {
17212
17695
  const profilePath = resolveHermesProfileDir(input.profileName);
17213
- if (!await pathExists(profilePath)) {
17696
+ if (!await pathExists2(profilePath)) {
17214
17697
  await ensureDirectoryWithInheritedMetadata(profilePath, 448);
17215
17698
  }
17216
17699
  if (input.sourceProfile && input.copyScopes.length > 0) {
@@ -17349,7 +17832,7 @@ async function writeEnvValues(profileName, values) {
17349
17832
  const nextLines = lines.map((line) => {
17350
17833
  const trimmed = line.trim();
17351
17834
  for (const [key, value] of remaining) {
17352
- const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp2(key)}=`, "u");
17835
+ const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp3(key)}=`, "u");
17353
17836
  if (keyPattern.test(trimmed)) {
17354
17837
  remaining.delete(key);
17355
17838
  return `${key}=${formatEnvValue2(value)}`;
@@ -17376,10 +17859,10 @@ async function writeEnvValues(profileName, values) {
17376
17859
  async function copySkills(sourceProfile, targetProfile) {
17377
17860
  const sourceSkills = path19.join(resolveHermesProfileDir(sourceProfile), "skills");
17378
17861
  const targetSkills = path19.join(resolveHermesProfileDir(targetProfile), "skills");
17379
- if (!await pathExists(sourceSkills)) {
17862
+ if (!await pathExists2(sourceSkills)) {
17380
17863
  return;
17381
17864
  }
17382
- await rm7(targetSkills, { recursive: true, force: true });
17865
+ await rm6(targetSkills, { recursive: true, force: true });
17383
17866
  await cp(sourceSkills, targetSkills, {
17384
17867
  recursive: true,
17385
17868
  force: true,
@@ -17427,7 +17910,7 @@ async function failProfileCreation(input) {
17427
17910
  await input.writer.write(`
17428
17911
  Rolling back ${input.rollbackProfileName}...
17429
17912
  `);
17430
- await rm7(resolveHermesProfileDir(input.rollbackProfileName), {
17913
+ await rm6(resolveHermesProfileDir(input.rollbackProfileName), {
17431
17914
  recursive: true,
17432
17915
  force: true
17433
17916
  }).catch(() => void 0);
@@ -17478,14 +17961,14 @@ function profileCreationLogPath(paths) {
17478
17961
  async function clearProfileCreationLogFiles(paths) {
17479
17962
  const primary = profileCreationLogPath(paths);
17480
17963
  await Promise.all([
17481
- rm7(primary, { force: true }).catch(() => void 0),
17964
+ rm6(primary, { force: true }).catch(() => void 0),
17482
17965
  ...Array.from(
17483
17966
  { length: PROFILE_CREATE_LOG_MAX_FILES },
17484
- (_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
17967
+ (_, index) => rm6(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
17485
17968
  )
17486
17969
  ]);
17487
17970
  }
17488
- async function pathExists(targetPath) {
17971
+ async function pathExists2(targetPath) {
17489
17972
  return await stat13(targetPath).then(() => true).catch((error) => {
17490
17973
  if (isNodeError15(error, "ENOENT")) {
17491
17974
  return false;
@@ -17531,7 +18014,7 @@ function formatEnvValue2(value) {
17531
18014
  }
17532
18015
  return JSON.stringify(value);
17533
18016
  }
17534
- function escapeRegExp2(value) {
18017
+ function escapeRegExp3(value) {
17535
18018
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
17536
18019
  }
17537
18020
  function isNodeError15(error, code) {
@@ -18860,7 +19343,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
18860
19343
  `\u4E0D\u5141\u8BB8\u4ECE App \u5199\u5165 ${key}\u3002`
18861
19344
  );
18862
19345
  }
18863
- const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp3(key)}=`, "u");
19346
+ const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp4(key)}=`, "u");
18864
19347
  let replaced = false;
18865
19348
  for (let index = 0; index < nextLines.length; index += 1) {
18866
19349
  if (keyPattern.test(nextLines[index].trim())) {
@@ -19081,7 +19564,7 @@ function readBoolean3(value) {
19081
19564
  function formatEnvValue3(value) {
19082
19565
  return /^[A-Za-z0-9_./:@-]*$/u.test(value) ? value : JSON.stringify(value);
19083
19566
  }
19084
- function escapeRegExp3(value) {
19567
+ function escapeRegExp4(value) {
19085
19568
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
19086
19569
  }
19087
19570
  function isNodeError16(error, code) {
@@ -20337,7 +20820,7 @@ function readModelList(payload) {
20337
20820
  // src/hermes/updates.ts
20338
20821
  import { EventEmitter as EventEmitter3 } from "events";
20339
20822
  import { spawn as spawn3 } from "child_process";
20340
- import { mkdir as mkdir11, readFile as readFile16, rm as rm8 } from "fs/promises";
20823
+ import { mkdir as mkdir11, readFile as readFile16, rm as rm7 } from "fs/promises";
20341
20824
  import path22 from "path";
20342
20825
  var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
20343
20826
  var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
@@ -20627,10 +21110,10 @@ function updateLogPath(paths) {
20627
21110
  async function clearUpdateLogFiles(paths) {
20628
21111
  const primary = updateLogPath(paths);
20629
21112
  await Promise.all([
20630
- rm8(primary, { force: true }).catch(() => void 0),
21113
+ rm7(primary, { force: true }).catch(() => void 0),
20631
21114
  ...Array.from(
20632
21115
  { length: UPDATE_LOG_MAX_FILES },
20633
- (_, index) => rm8(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
21116
+ (_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
20634
21117
  )
20635
21118
  ]);
20636
21119
  }
@@ -20722,17 +21205,17 @@ function readString17(payload, key) {
20722
21205
  // src/link/updates.ts
20723
21206
  import { spawn as spawn5 } from "child_process";
20724
21207
  import { EventEmitter as EventEmitter4 } from "events";
20725
- import { mkdir as mkdir14, readFile as readFile18, rm as rm11 } from "fs/promises";
21208
+ import { mkdir as mkdir14, readFile as readFile18, rm as rm10 } from "fs/promises";
20726
21209
  import path24 from "path";
20727
21210
 
20728
21211
  // src/daemon/process.ts
20729
21212
  import { spawn as spawn4 } from "child_process";
20730
- import { mkdir as mkdir13, readFile as readFile17, rm as rm10 } from "fs/promises";
21213
+ import { mkdir as mkdir13, readFile as readFile17, rm as rm9 } from "fs/promises";
20731
21214
  import path23 from "path";
20732
21215
 
20733
21216
  // src/daemon/service.ts
20734
21217
  import { createServer } from "http";
20735
- import { mkdir as mkdir12, rm as rm9, writeFile as writeFile3 } from "fs/promises";
21218
+ import { mkdir as mkdir12, rm as rm8, writeFile as writeFile3 } from "fs/promises";
20736
21219
 
20737
21220
  // src/relay/control-client.ts
20738
21221
  import WebSocket from "ws";
@@ -20806,9 +21289,9 @@ function connectRelayControl(options) {
20806
21289
  return;
20807
21290
  }
20808
21291
  reconnectAttempts += 1;
20809
- const delay2 = computeBackoffMs(reconnectAttempts, backoffBaseMs, backoffMaxMs);
20810
- options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${delay2}ms` });
20811
- retryTimer = setTimeout(connect, delay2);
21292
+ const delay3 = computeBackoffMs(reconnectAttempts, backoffBaseMs, backoffMaxMs);
21293
+ options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${delay3}ms` });
21294
+ retryTimer = setTimeout(connect, delay3);
20812
21295
  retryTimer.unref?.();
20813
21296
  });
20814
21297
  };
@@ -21845,7 +22328,7 @@ async function startLinkService(options = {}) {
21845
22328
  await logger.info("service_stopped");
21846
22329
  await logger.flush();
21847
22330
  if (options.writePidFile) {
21848
- await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
22331
+ await rm8(pidFilePath(paths), { force: true }).catch(() => void 0);
21849
22332
  }
21850
22333
  }
21851
22334
  };
@@ -22037,7 +22520,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
22037
22520
  try {
22038
22521
  process.kill(status.pid, "SIGTERM");
22039
22522
  } catch {
22040
- await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
22523
+ await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
22041
22524
  return await getDaemonStatus(paths);
22042
22525
  }
22043
22526
  for (let index = 0; index < 20; index += 1) {
@@ -22059,7 +22542,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
22059
22542
  }
22060
22543
  }
22061
22544
  if (!isProcessAlive3(status.pid) || !await pidBackedServiceIsReachable(paths)) {
22062
- await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
22545
+ await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
22063
22546
  }
22064
22547
  return await getDaemonStatus(paths);
22065
22548
  }
@@ -22067,7 +22550,7 @@ async function getDaemonStatus(paths = resolveRuntimePaths()) {
22067
22550
  const pidFile = pidFilePath(paths);
22068
22551
  const pid = await readPid(pidFile);
22069
22552
  if (pid && !isProcessAlive3(pid)) {
22070
- await rm10(pidFile, { force: true }).catch(() => void 0);
22553
+ await rm9(pidFile, { force: true }).catch(() => void 0);
22071
22554
  return {
22072
22555
  running: false,
22073
22556
  pid: null,
@@ -22522,10 +23005,10 @@ function updateLogPath2(paths) {
22522
23005
  async function clearUpdateLogFiles2(paths) {
22523
23006
  const primary = updateLogPath2(paths);
22524
23007
  await Promise.all([
22525
- rm11(primary, { force: true }).catch(() => void 0),
23008
+ rm10(primary, { force: true }).catch(() => void 0),
22526
23009
  ...Array.from(
22527
23010
  { length: UPDATE_LOG_MAX_FILES2 },
22528
- (_, index) => rm11(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
23011
+ (_, index) => rm10(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
22529
23012
  )
22530
23013
  ]);
22531
23014
  }
@@ -22582,7 +23065,7 @@ function readString18(payload, key) {
22582
23065
 
22583
23066
  // src/pairing/pairing.ts
22584
23067
  import path25 from "path";
22585
- import { rm as rm12 } from "fs/promises";
23068
+ import { rm as rm11 } from "fs/promises";
22586
23069
 
22587
23070
  // src/relay/bootstrap.ts
22588
23071
  var RelayNetworkError = class extends Error {
@@ -22844,7 +23327,7 @@ async function readPairingClaim(sessionId, paths = resolveRuntimePaths()) {
22844
23327
  };
22845
23328
  }
22846
23329
  async function clearPairingClaim(sessionId, paths = resolveRuntimePaths()) {
22847
- await rm12(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
23330
+ await rm11(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
22848
23331
  }
22849
23332
  async function claimPairing(input) {
22850
23333
  const paths = input.paths ?? resolveRuntimePaths();
package/dist/cli/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  startDaemonProcess,
37
37
  startLinkService,
38
38
  stopDaemonProcess
39
- } from "../chunk-UANE2YHT.js";
39
+ } from "../chunk-PRYXZRQI.js";
40
40
 
41
41
  // src/cli/index.ts
42
42
  import { Command } from "commander";
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-UANE2YHT.js";
3
+ } from "../chunk-PRYXZRQI.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.4.5",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",
@@ -1,3 +1,5 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import path from "node:path";
1
3
  import process from "node:process";
2
4
 
3
5
  function isTruthy(value) {
@@ -14,6 +16,49 @@ function shouldPrintInstallHint() {
14
16
  return process.env.npm_config_global === "true";
15
17
  }
16
18
 
19
+ function resolveNpmCommand() {
20
+ return process.platform === "win32" ? "npm.cmd" : "npm";
21
+ }
22
+
23
+ function resolveGlobalPrefix() {
24
+ const envPrefix =
25
+ process.env.npm_config_prefix?.trim() ??
26
+ process.env.npm_config_global_prefix?.trim() ??
27
+ "";
28
+ if (envPrefix) {
29
+ return envPrefix;
30
+ }
31
+ try {
32
+ const output = execFileSync(resolveNpmCommand(), ["prefix", "-g"], {
33
+ encoding: "utf8",
34
+ });
35
+ return output.trim();
36
+ } catch {
37
+ return "";
38
+ }
39
+ }
40
+
41
+ function resolveGlobalBinDir(prefix) {
42
+ return process.platform === "win32" ? prefix : path.join(prefix, "bin");
43
+ }
44
+
45
+ function resolveHermesLinkCommandPath(binDir) {
46
+ return process.platform === "win32"
47
+ ? path.join(binDir, "hermeslink.cmd")
48
+ : path.join(binDir, "hermeslink");
49
+ }
50
+
51
+ function pathIncludes(target) {
52
+ if (!target) {
53
+ return false;
54
+ }
55
+ const normalizedTarget = path.resolve(target);
56
+ return (process.env.PATH ?? "")
57
+ .split(path.delimiter)
58
+ .filter(Boolean)
59
+ .some((entry) => path.resolve(entry) === normalizedTarget);
60
+ }
61
+
17
62
  function detectLanguage() {
18
63
  const candidates = [
19
64
  process.env.HERMESLINK_LANG,
@@ -42,13 +87,35 @@ async function main() {
42
87
  }
43
88
 
44
89
  const language = detectLanguage();
90
+ const globalPrefix = resolveGlobalPrefix();
91
+ const globalBinDir = globalPrefix ? resolveGlobalBinDir(globalPrefix) : "";
92
+ const commandPath = globalBinDir ? resolveHermesLinkCommandPath(globalBinDir) : "";
93
+ const binOnPath = globalBinDir ? pathIncludes(globalBinDir) : false;
45
94
  console.log("");
46
95
  if (language === "zh-CN") {
47
96
  console.log("Hermes Link 已安装。");
48
97
  console.log("运行 `hermeslink pair`,把这台电脑连接到 HermesPilot App。");
98
+ if (globalBinDir && !binOnPath) {
99
+ console.log(
100
+ `如果当前 shell 找不到 \`hermeslink\`,说明 npm 全局 bin 目录没有进 PATH:${globalBinDir}`,
101
+ );
102
+ console.log(`你可以直接运行:${commandPath} pair`);
103
+ if (process.platform !== "win32") {
104
+ console.log(`或者先补 PATH:export PATH="${globalBinDir}:$PATH"`);
105
+ }
106
+ }
49
107
  } else {
50
108
  console.log("Hermes Link installed.");
51
109
  console.log("Run `hermeslink pair` to connect this computer with HermesPilot App.");
110
+ if (globalBinDir && !binOnPath) {
111
+ console.log(
112
+ `If your shell cannot find \`hermeslink\`, npm's global bin directory is not on PATH: ${globalBinDir}`,
113
+ );
114
+ console.log(`You can run it directly: ${commandPath} pair`);
115
+ if (process.platform !== "win32") {
116
+ console.log(`Or add it to PATH first: export PATH="${globalBinDir}:$PATH"`);
117
+ }
118
+ }
52
119
  }
53
120
  console.log("");
54
121
  }