@hermespilot/link 0.4.6 → 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.
- package/README.md +8 -0
- package/dist/{chunk-DUQDO2LQ.js → chunk-B42L327D.js} +355 -16
- package/dist/cli/index.js +1 -1
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +67 -0
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
|
|
@@ -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 {
|
|
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.
|
|
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(
|
|
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));
|
|
@@ -7871,12 +8093,12 @@ var ConversationMetadataCoordinator = class {
|
|
|
7871
8093
|
return { ...next, last_event_seq: event.seq, updated_at: event.created_at };
|
|
7872
8094
|
}
|
|
7873
8095
|
scheduleGeneratedTitleRefresh(conversationId) {
|
|
7874
|
-
for (const
|
|
8096
|
+
for (const delay3 of GENERATED_TITLE_RETRY_DELAYS_MS) {
|
|
7875
8097
|
setTimeout(() => {
|
|
7876
8098
|
void this.generateTitleFromFirstRound(conversationId).catch(
|
|
7877
8099
|
() => void 0
|
|
7878
8100
|
);
|
|
7879
|
-
},
|
|
8101
|
+
}, delay3);
|
|
7880
8102
|
}
|
|
7881
8103
|
}
|
|
7882
8104
|
async renameConversation(conversationId, title, input) {
|
|
@@ -12677,7 +12899,7 @@ async function resolveHermesPythonCommand() {
|
|
|
12677
12899
|
if (explicit) {
|
|
12678
12900
|
return { command: explicit, args: [] };
|
|
12679
12901
|
}
|
|
12680
|
-
const hermesBin = await
|
|
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
|
|
12915
|
+
async function resolveExecutablePath2(command) {
|
|
12694
12916
|
if (path17.isAbsolute(command)) {
|
|
12695
12917
|
return await isExecutableFile(command) ? command : null;
|
|
12696
12918
|
}
|
|
@@ -16342,11 +16564,15 @@ function createHttpErrorMiddleware(logger) {
|
|
|
16342
16564
|
import { execFile as execFile4 } from "child_process";
|
|
16343
16565
|
import { readdir as readdir9, readFile as readFile12, rename as rename3, stat as stat12 } from "fs/promises";
|
|
16344
16566
|
import path18 from "path";
|
|
16567
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
16345
16568
|
import { promisify as promisify4 } from "util";
|
|
16346
16569
|
import YAML2 from "yaml";
|
|
16347
16570
|
var DEFAULT_PROFILE = "default";
|
|
16348
16571
|
var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
16349
16572
|
var PROFILE_DELETE_TIMEOUT_MS = 3e4;
|
|
16573
|
+
var PROFILE_GATEWAY_STOP_TIMEOUT_MS = 3e3;
|
|
16574
|
+
var PROFILE_DELETE_STABLE_ABSENCE_MS = 1200;
|
|
16575
|
+
var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
|
|
16350
16576
|
var execFileAsync4 = promisify4(execFile4);
|
|
16351
16577
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
16352
16578
|
const profiles = /* @__PURE__ */ new Map();
|
|
@@ -16427,8 +16653,8 @@ async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
|
16427
16653
|
`Profile "${name}" does not exist`
|
|
16428
16654
|
);
|
|
16429
16655
|
}
|
|
16430
|
-
await
|
|
16431
|
-
if (await
|
|
16656
|
+
await deleteHermesProfileWithCliAndVerify(name, profile.path);
|
|
16657
|
+
if (!await waitForProfilePathToRemainAbsent(profile.path)) {
|
|
16432
16658
|
throw new LinkHttpError(
|
|
16433
16659
|
502,
|
|
16434
16660
|
"hermes_profile_delete_incomplete",
|
|
@@ -16512,6 +16738,116 @@ async function deleteHermesProfileWithCli(name) {
|
|
|
16512
16738
|
);
|
|
16513
16739
|
}
|
|
16514
16740
|
}
|
|
16741
|
+
async function deleteHermesProfileWithCliAndVerify(name, profilePath) {
|
|
16742
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
16743
|
+
await stopHermesGatewayProcessesForProfile(name);
|
|
16744
|
+
if (attempt > 0 && !await pathExists(profilePath)) {
|
|
16745
|
+
return;
|
|
16746
|
+
}
|
|
16747
|
+
await deleteHermesProfileWithCli(name);
|
|
16748
|
+
if (await waitForProfilePathToRemainAbsent(profilePath)) {
|
|
16749
|
+
return;
|
|
16750
|
+
}
|
|
16751
|
+
}
|
|
16752
|
+
}
|
|
16753
|
+
async function stopHermesGatewayProcessesForProfile(name) {
|
|
16754
|
+
const pids = await findHermesGatewayProcessIdsForProfile(name);
|
|
16755
|
+
if (pids.length === 0) {
|
|
16756
|
+
return;
|
|
16757
|
+
}
|
|
16758
|
+
for (const pid of pids) {
|
|
16759
|
+
killProcess(pid, "SIGTERM");
|
|
16760
|
+
}
|
|
16761
|
+
const remainingAfterTerm = await waitForProcessesToExit(
|
|
16762
|
+
pids,
|
|
16763
|
+
PROFILE_GATEWAY_STOP_TIMEOUT_MS
|
|
16764
|
+
);
|
|
16765
|
+
if (remainingAfterTerm.length === 0) {
|
|
16766
|
+
return;
|
|
16767
|
+
}
|
|
16768
|
+
for (const pid of remainingAfterTerm) {
|
|
16769
|
+
killProcess(pid, "SIGKILL");
|
|
16770
|
+
}
|
|
16771
|
+
const remainingAfterKill = await waitForProcessesToExit(
|
|
16772
|
+
remainingAfterTerm,
|
|
16773
|
+
PROFILE_GATEWAY_STOP_TIMEOUT_MS
|
|
16774
|
+
);
|
|
16775
|
+
if (remainingAfterKill.length > 0) {
|
|
16776
|
+
throw new LinkHttpError(
|
|
16777
|
+
502,
|
|
16778
|
+
"hermes_profile_gateway_stop_failed",
|
|
16779
|
+
`Could not stop Hermes Gateway process(es) for Profile "${name}": ${remainingAfterKill.join(", ")}`
|
|
16780
|
+
);
|
|
16781
|
+
}
|
|
16782
|
+
}
|
|
16783
|
+
async function findHermesGatewayProcessIdsForProfile(name) {
|
|
16784
|
+
if (process.platform === "win32") {
|
|
16785
|
+
return [];
|
|
16786
|
+
}
|
|
16787
|
+
try {
|
|
16788
|
+
const output = await execFileAsync4("ps", ["-axo", "pid=,command="], {
|
|
16789
|
+
timeout: 3e3,
|
|
16790
|
+
windowsHide: true
|
|
16791
|
+
});
|
|
16792
|
+
return parseHermesGatewayProcessIds(output.stdout.toString(), name);
|
|
16793
|
+
} catch {
|
|
16794
|
+
return [];
|
|
16795
|
+
}
|
|
16796
|
+
}
|
|
16797
|
+
function parseHermesGatewayProcessIds(output, name) {
|
|
16798
|
+
const pids = [];
|
|
16799
|
+
for (const line of output.split(/\r?\n/)) {
|
|
16800
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
16801
|
+
if (!match) {
|
|
16802
|
+
continue;
|
|
16803
|
+
}
|
|
16804
|
+
const pid = Number(match[1]);
|
|
16805
|
+
const command = match[2];
|
|
16806
|
+
if (Number.isSafeInteger(pid) && pid !== process.pid && isHermesGatewayRunCommandForProfile(command, name)) {
|
|
16807
|
+
pids.push(pid);
|
|
16808
|
+
}
|
|
16809
|
+
}
|
|
16810
|
+
return pids;
|
|
16811
|
+
}
|
|
16812
|
+
function isHermesGatewayRunCommandForProfile(command, name) {
|
|
16813
|
+
const escapedName = escapeRegExp2(name);
|
|
16814
|
+
return new RegExp(
|
|
16815
|
+
String.raw`(?:^|\s)-p\s+${escapedName}\s+gateway\s+run(?:\s|$)`
|
|
16816
|
+
).test(command);
|
|
16817
|
+
}
|
|
16818
|
+
function killProcess(pid, signal) {
|
|
16819
|
+
try {
|
|
16820
|
+
process.kill(pid, signal);
|
|
16821
|
+
} catch {
|
|
16822
|
+
}
|
|
16823
|
+
}
|
|
16824
|
+
async function waitForProcessesToExit(pids, timeoutMs) {
|
|
16825
|
+
const deadline = Date.now() + timeoutMs;
|
|
16826
|
+
let remaining = pids.filter(isProcessRunning);
|
|
16827
|
+
while (remaining.length > 0 && Date.now() < deadline) {
|
|
16828
|
+
await delay2(100);
|
|
16829
|
+
remaining = remaining.filter(isProcessRunning);
|
|
16830
|
+
}
|
|
16831
|
+
return remaining;
|
|
16832
|
+
}
|
|
16833
|
+
function isProcessRunning(pid) {
|
|
16834
|
+
try {
|
|
16835
|
+
process.kill(pid, 0);
|
|
16836
|
+
return true;
|
|
16837
|
+
} catch (error) {
|
|
16838
|
+
return isNodeError14(error, "EPERM");
|
|
16839
|
+
}
|
|
16840
|
+
}
|
|
16841
|
+
async function waitForProfilePathToRemainAbsent(profilePath) {
|
|
16842
|
+
const deadline = Date.now() + PROFILE_DELETE_STABLE_ABSENCE_MS;
|
|
16843
|
+
while (Date.now() < deadline) {
|
|
16844
|
+
if (await pathExists(profilePath)) {
|
|
16845
|
+
return false;
|
|
16846
|
+
}
|
|
16847
|
+
await delay2(PROFILE_DELETE_VERIFY_INTERVAL_MS);
|
|
16848
|
+
}
|
|
16849
|
+
return !await pathExists(profilePath);
|
|
16850
|
+
}
|
|
16515
16851
|
function formatExecError(error) {
|
|
16516
16852
|
if (!(error instanceof Error)) {
|
|
16517
16853
|
return String(error);
|
|
@@ -16539,6 +16875,9 @@ function readExecErrorOutput2(error) {
|
|
|
16539
16875
|
function isNodeError14(error, code) {
|
|
16540
16876
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
16541
16877
|
}
|
|
16878
|
+
function escapeRegExp2(value) {
|
|
16879
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16880
|
+
}
|
|
16542
16881
|
async function countSkills(root) {
|
|
16543
16882
|
const entries = await readdir9(root, { withFileTypes: true }).catch(
|
|
16544
16883
|
(error) => {
|
|
@@ -17715,7 +18054,7 @@ async function writeEnvValues(profileName, values) {
|
|
|
17715
18054
|
const nextLines = lines.map((line) => {
|
|
17716
18055
|
const trimmed = line.trim();
|
|
17717
18056
|
for (const [key, value] of remaining) {
|
|
17718
|
-
const keyPattern = new RegExp(`^(?:export\\s+)?${
|
|
18057
|
+
const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp3(key)}=`, "u");
|
|
17719
18058
|
if (keyPattern.test(trimmed)) {
|
|
17720
18059
|
remaining.delete(key);
|
|
17721
18060
|
return `${key}=${formatEnvValue2(value)}`;
|
|
@@ -17897,7 +18236,7 @@ function formatEnvValue2(value) {
|
|
|
17897
18236
|
}
|
|
17898
18237
|
return JSON.stringify(value);
|
|
17899
18238
|
}
|
|
17900
|
-
function
|
|
18239
|
+
function escapeRegExp3(value) {
|
|
17901
18240
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
17902
18241
|
}
|
|
17903
18242
|
function isNodeError15(error, code) {
|
|
@@ -19226,7 +19565,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
19226
19565
|
`\u4E0D\u5141\u8BB8\u4ECE App \u5199\u5165 ${key}\u3002`
|
|
19227
19566
|
);
|
|
19228
19567
|
}
|
|
19229
|
-
const keyPattern = new RegExp(`^(?:export\\s+)?${
|
|
19568
|
+
const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp4(key)}=`, "u");
|
|
19230
19569
|
let replaced = false;
|
|
19231
19570
|
for (let index = 0; index < nextLines.length; index += 1) {
|
|
19232
19571
|
if (keyPattern.test(nextLines[index].trim())) {
|
|
@@ -19447,7 +19786,7 @@ function readBoolean3(value) {
|
|
|
19447
19786
|
function formatEnvValue3(value) {
|
|
19448
19787
|
return /^[A-Za-z0-9_./:@-]*$/u.test(value) ? value : JSON.stringify(value);
|
|
19449
19788
|
}
|
|
19450
|
-
function
|
|
19789
|
+
function escapeRegExp4(value) {
|
|
19451
19790
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
19452
19791
|
}
|
|
19453
19792
|
function isNodeError16(error, code) {
|
|
@@ -21172,9 +21511,9 @@ function connectRelayControl(options) {
|
|
|
21172
21511
|
return;
|
|
21173
21512
|
}
|
|
21174
21513
|
reconnectAttempts += 1;
|
|
21175
|
-
const
|
|
21176
|
-
options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${
|
|
21177
|
-
retryTimer = setTimeout(connect,
|
|
21514
|
+
const delay3 = computeBackoffMs(reconnectAttempts, backoffBaseMs, backoffMaxMs);
|
|
21515
|
+
options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${delay3}ms` });
|
|
21516
|
+
retryTimer = setTimeout(connect, delay3);
|
|
21178
21517
|
retryTimer.unref?.();
|
|
21179
21518
|
});
|
|
21180
21519
|
};
|
package/dist/cli/index.js
CHANGED
package/dist/http/app.js
CHANGED
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -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
|
}
|