@hermespilot/link 0.4.7 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4117,7 +4117,8 @@ function isConversationMissingError(error) {
4117
4117
 
4118
4118
  // src/hermes/gateway.ts
4119
4119
  import { execFile as execFile2, spawn } from "child_process";
4120
- import { access, readFile as readFile5, stat as stat4 } from "fs/promises";
4120
+ import { constants as fsConstants } from "fs";
4121
+ import { access, readFile as readFile5, realpath, stat as stat4 } from "fs/promises";
4121
4122
  import path7 from "path";
4122
4123
  import { setTimeout as delay } from "timers/promises";
4123
4124
  import { promisify as promisify2 } from "util";
@@ -4132,7 +4133,7 @@ import os2 from "os";
4132
4133
  import path5 from "path";
4133
4134
 
4134
4135
  // src/constants.ts
4135
- var LINK_VERSION = "0.4.7";
4136
+ var LINK_VERSION = "0.4.8";
4136
4137
  var LINK_COMMAND = "hermeslink";
4137
4138
  var LINK_DEFAULT_PORT = 52379;
4138
4139
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -4631,6 +4632,8 @@ var PROFILE_NAME_PATTERN = /^[a-zA-Z0-9._-]{1,64}$/u;
4631
4632
  var DASHBOARD_STATUS_URL = "http://127.0.0.1:9119/api/status";
4632
4633
  var DASHBOARD_STATUS_TIMEOUT_MS = 1500;
4633
4634
  var DEFAULT_VERSION_CACHE_TTL_MS = 6e4;
4635
+ var VERSION_METADATA_TIMEOUT_MS = 3e3;
4636
+ var VERSION_COMMAND_TIMEOUT_MS = 5e3;
4634
4637
  var MAX_VERSION_LOG_OUTPUT_LENGTH = 1200;
4635
4638
  var HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES = ["API_SERVER_"];
4636
4639
  var gatewayStartInFlightByProfile = /* @__PURE__ */ new Map();
@@ -4826,9 +4829,26 @@ async function readHermesVersion(options = {}) {
4826
4829
  }
4827
4830
  async function execHermesVersion(hermesBin, logger) {
4828
4831
  const failures = [];
4832
+ try {
4833
+ const metadataVersion = await readHermesPythonMetadataVersion(
4834
+ hermesBin,
4835
+ VERSION_METADATA_TIMEOUT_MS
4836
+ );
4837
+ if (metadataVersion) {
4838
+ return metadataVersion;
4839
+ }
4840
+ } catch (error) {
4841
+ const failure = describeVersionMetadataFailure(hermesBin, error);
4842
+ failures.push(failure);
4843
+ void logger?.debug("hermes_version_metadata_probe_failed", failure.fields);
4844
+ }
4829
4845
  for (const args of [["--version"], ["version"]]) {
4830
4846
  try {
4831
- return await spawnHermesVersionCommand(hermesBin, args, 5e3);
4847
+ return await spawnHermesVersionCommand(
4848
+ hermesBin,
4849
+ args,
4850
+ VERSION_COMMAND_TIMEOUT_MS
4851
+ );
4832
4852
  } catch (error) {
4833
4853
  const failure = describeVersionCommandFailure(hermesBin, args, error);
4834
4854
  failures.push(failure);
@@ -4844,6 +4864,109 @@ async function execHermesVersion(hermesBin, logger) {
4844
4864
  });
4845
4865
  throw new Error(summary);
4846
4866
  }
4867
+ async function readHermesPythonMetadataVersion(hermesBin, timeoutMs) {
4868
+ const hermesPath = await resolveExecutablePath(hermesBin);
4869
+ if (!hermesPath) {
4870
+ return null;
4871
+ }
4872
+ const firstLine = await readFirstLine(hermesPath);
4873
+ const interpreter = parsePythonShebang(firstLine);
4874
+ if (!interpreter) {
4875
+ return null;
4876
+ }
4877
+ return await spawnHermesMetadataCommand(interpreter, timeoutMs);
4878
+ }
4879
+ async function spawnHermesMetadataCommand(interpreter, timeoutMs) {
4880
+ const script = [
4881
+ "import importlib.metadata as metadata",
4882
+ "try:",
4883
+ ' version = metadata.version("hermes-agent")',
4884
+ ' release_date = ""',
4885
+ "except Exception:",
4886
+ " import hermes_cli",
4887
+ ' version = getattr(hermes_cli, "__version__", "")',
4888
+ ' release_date = getattr(hermes_cli, "__release_date__", "")',
4889
+ "if not version:",
4890
+ ' raise SystemExit("Hermes Agent version metadata not found")',
4891
+ 'suffix = f" ({release_date})" if release_date else ""',
4892
+ 'print(f"Hermes Agent v{version}{suffix}")',
4893
+ ""
4894
+ ].join("\n");
4895
+ return await new Promise((resolve, reject) => {
4896
+ const child = spawn(
4897
+ interpreter.command,
4898
+ [...interpreter.args, "-c", script],
4899
+ {
4900
+ stdio: ["ignore", "pipe", "pipe"],
4901
+ windowsHide: true,
4902
+ env: { ...process.env, HERMES_NONINTERACTIVE: "1" }
4903
+ }
4904
+ );
4905
+ let stdout = "";
4906
+ let stderr = "";
4907
+ let settled = false;
4908
+ const timer = setTimeout(() => {
4909
+ finish(() => {
4910
+ reject(
4911
+ createVersionCommandError(
4912
+ `Hermes Python metadata probe timed out after ${timeoutMs}ms`,
4913
+ { stdout, stderr, killed: true }
4914
+ )
4915
+ );
4916
+ }, true);
4917
+ }, timeoutMs);
4918
+ const finish = (callback, kill = false) => {
4919
+ if (settled) {
4920
+ return;
4921
+ }
4922
+ settled = true;
4923
+ clearTimeout(timer);
4924
+ if (kill && child.pid && !child.killed) {
4925
+ child.kill();
4926
+ }
4927
+ callback();
4928
+ };
4929
+ child.stdout?.on("data", (chunk) => {
4930
+ stdout += chunk.toString();
4931
+ });
4932
+ child.stderr?.on("data", (chunk) => {
4933
+ stderr += chunk.toString();
4934
+ });
4935
+ child.once("error", (error) => {
4936
+ finish(() => {
4937
+ reject(
4938
+ createVersionCommandError(error.message, {
4939
+ stdout,
4940
+ stderr,
4941
+ cause: error
4942
+ })
4943
+ );
4944
+ });
4945
+ });
4946
+ child.once("close", (code, signal) => {
4947
+ finish(() => {
4948
+ const raw = `${stdout}
4949
+ ${stderr}`.trim();
4950
+ if (code === 0 && parseHermesVersion(raw)) {
4951
+ resolve({ stdout: raw });
4952
+ return;
4953
+ }
4954
+ reject(
4955
+ createVersionCommandError(
4956
+ `Hermes Python metadata probe exited with code ${code ?? "null"}`,
4957
+ {
4958
+ stdout,
4959
+ stderr,
4960
+ code,
4961
+ signal,
4962
+ killed: child.killed
4963
+ }
4964
+ )
4965
+ );
4966
+ });
4967
+ });
4968
+ });
4969
+ }
4847
4970
  async function spawnHermesVersionCommand(hermesBin, args, timeoutMs) {
4848
4971
  return await new Promise((resolve, reject) => {
4849
4972
  const child = spawn(hermesBin, args, {
@@ -5424,6 +5547,20 @@ function createVersionCommandError(message, details) {
5424
5547
  }
5425
5548
  return error;
5426
5549
  }
5550
+ function describeVersionMetadataFailure(hermesBin, error) {
5551
+ const message = error instanceof Error ? error.message : String(error);
5552
+ const details = readExecErrorDetails(error, message);
5553
+ return {
5554
+ summary: `${hermesBin} Python metadata probe failed: ${message}`,
5555
+ fields: {
5556
+ hermes_bin: hermesBin,
5557
+ command: "python-metadata",
5558
+ cwd: process.cwd(),
5559
+ error: message,
5560
+ ...details
5561
+ }
5562
+ };
5563
+ }
5427
5564
  function describeVersionCommandFailure(hermesBin, args, error) {
5428
5565
  const message = error instanceof Error ? error.message : String(error);
5429
5566
  const details = readExecErrorDetails(error, message);
@@ -5473,6 +5610,91 @@ function readExecErrorDetails(error, message) {
5473
5610
  function truncateVersionLogOutput(value) {
5474
5611
  return value.length > MAX_VERSION_LOG_OUTPUT_LENGTH ? `${value.slice(0, MAX_VERSION_LOG_OUTPUT_LENGTH)}...` : value;
5475
5612
  }
5613
+ async function resolveExecutablePath(command) {
5614
+ const candidates = executablePathCandidates(command);
5615
+ for (const candidate of candidates) {
5616
+ try {
5617
+ await access(candidate, fsConstants.X_OK);
5618
+ return await realpath(candidate).catch(() => candidate);
5619
+ } catch {
5620
+ }
5621
+ }
5622
+ return null;
5623
+ }
5624
+ function executablePathCandidates(command) {
5625
+ const normalized = command.trim();
5626
+ if (!normalized) {
5627
+ return [];
5628
+ }
5629
+ if (/[\\/]/u.test(normalized)) {
5630
+ return withWindowsExecutableExtensions(normalized);
5631
+ }
5632
+ const pathEntries = (process.env.PATH ?? "").split(path7.delimiter).filter(Boolean);
5633
+ return pathEntries.flatMap(
5634
+ (entry) => withWindowsExecutableExtensions(path7.join(entry, normalized))
5635
+ );
5636
+ }
5637
+ function withWindowsExecutableExtensions(filePath) {
5638
+ if (process.platform !== "win32" || path7.extname(filePath)) {
5639
+ return [filePath];
5640
+ }
5641
+ const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
5642
+ return [filePath, ...extensions.map((extension) => `${filePath}${extension}`)];
5643
+ }
5644
+ async function readFirstLine(filePath) {
5645
+ const raw = await readFile5(filePath, "utf8");
5646
+ return raw.split(/\r?\n/u, 1)[0] ?? "";
5647
+ }
5648
+ function parsePythonShebang(line) {
5649
+ if (!line.startsWith("#!")) {
5650
+ return null;
5651
+ }
5652
+ const parts = splitShellWords(line.slice(2).trim());
5653
+ if (parts.length === 0) {
5654
+ return null;
5655
+ }
5656
+ const commandName = path7.basename(parts[0] ?? "").toLowerCase();
5657
+ if (commandName === "env") {
5658
+ const args = expandEnvShebangArgs(parts.slice(1));
5659
+ const pythonIndex = args.findIndex((part) => isPythonCommandName(part));
5660
+ if (pythonIndex < 0) {
5661
+ return null;
5662
+ }
5663
+ return {
5664
+ command: parts[0],
5665
+ args
5666
+ };
5667
+ }
5668
+ if (!isPythonCommandName(commandName)) {
5669
+ return null;
5670
+ }
5671
+ return {
5672
+ command: parts[0],
5673
+ args: parts.slice(1)
5674
+ };
5675
+ }
5676
+ function isPythonCommandName(command) {
5677
+ return /^python(?:\d+(?:\.\d+)?)?$/iu.test(path7.basename(command));
5678
+ }
5679
+ function expandEnvShebangArgs(args) {
5680
+ const expanded = [];
5681
+ for (let index = 0; index < args.length; index += 1) {
5682
+ const arg = args[index];
5683
+ if (arg === "-S" && args[index + 1]) {
5684
+ expanded.push(...splitShellWords(args[index + 1]));
5685
+ index += 1;
5686
+ continue;
5687
+ }
5688
+ if (arg !== "-S") {
5689
+ expanded.push(arg);
5690
+ }
5691
+ }
5692
+ return expanded;
5693
+ }
5694
+ function splitShellWords(value) {
5695
+ const matches = value.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/gu);
5696
+ return [...matches].map((match) => match[1] ?? match[2] ?? match[3] ?? "");
5697
+ }
5476
5698
  function compareSemver(left, right) {
5477
5699
  const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
5478
5700
  const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
@@ -12677,7 +12899,7 @@ async function resolveHermesPythonCommand() {
12677
12899
  if (explicit) {
12678
12900
  return { command: explicit, args: [] };
12679
12901
  }
12680
- const hermesBin = await resolveExecutablePath(resolveHermesBin());
12902
+ const hermesBin = await resolveExecutablePath2(resolveHermesBin());
12681
12903
  if (hermesBin) {
12682
12904
  const shebang = await readShebang(hermesBin);
12683
12905
  const command = shebangToPythonCommand(shebang);
@@ -12690,7 +12912,7 @@ async function resolveHermesPythonCommand() {
12690
12912
  args: []
12691
12913
  };
12692
12914
  }
12693
- async function resolveExecutablePath(command) {
12915
+ async function resolveExecutablePath2(command) {
12694
12916
  if (path17.isAbsolute(command)) {
12695
12917
  return await isExecutableFile(command) ? command : null;
12696
12918
  }
package/dist/cli/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  startDaemonProcess,
37
37
  startLinkService,
38
38
  stopDaemonProcess
39
- } from "../chunk-PRYXZRQI.js";
39
+ } from "../chunk-B42L327D.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-PRYXZRQI.js";
3
+ } from "../chunk-B42L327D.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.7",
3
+ "version": "0.4.8",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",