@hermespilot/link 0.4.6 → 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 +8 -0
- package/dist/{chunk-DUQDO2LQ.js → chunk-PRYXZRQI.js} +129 -12
- 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
|
|
@@ -4132,7 +4132,7 @@ import os2 from "os";
|
|
|
4132
4132
|
import path5 from "path";
|
|
4133
4133
|
|
|
4134
4134
|
// src/constants.ts
|
|
4135
|
-
var LINK_VERSION = "0.4.
|
|
4135
|
+
var LINK_VERSION = "0.4.7";
|
|
4136
4136
|
var LINK_COMMAND = "hermeslink";
|
|
4137
4137
|
var LINK_DEFAULT_PORT = 52379;
|
|
4138
4138
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -7871,12 +7871,12 @@ var ConversationMetadataCoordinator = class {
|
|
|
7871
7871
|
return { ...next, last_event_seq: event.seq, updated_at: event.created_at };
|
|
7872
7872
|
}
|
|
7873
7873
|
scheduleGeneratedTitleRefresh(conversationId) {
|
|
7874
|
-
for (const
|
|
7874
|
+
for (const delay3 of GENERATED_TITLE_RETRY_DELAYS_MS) {
|
|
7875
7875
|
setTimeout(() => {
|
|
7876
7876
|
void this.generateTitleFromFirstRound(conversationId).catch(
|
|
7877
7877
|
() => void 0
|
|
7878
7878
|
);
|
|
7879
|
-
},
|
|
7879
|
+
}, delay3);
|
|
7880
7880
|
}
|
|
7881
7881
|
}
|
|
7882
7882
|
async renameConversation(conversationId, title, input) {
|
|
@@ -16342,11 +16342,15 @@ function createHttpErrorMiddleware(logger) {
|
|
|
16342
16342
|
import { execFile as execFile4 } from "child_process";
|
|
16343
16343
|
import { readdir as readdir9, readFile as readFile12, rename as rename3, stat as stat12 } from "fs/promises";
|
|
16344
16344
|
import path18 from "path";
|
|
16345
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
16345
16346
|
import { promisify as promisify4 } from "util";
|
|
16346
16347
|
import YAML2 from "yaml";
|
|
16347
16348
|
var DEFAULT_PROFILE = "default";
|
|
16348
16349
|
var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
16349
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;
|
|
16350
16354
|
var execFileAsync4 = promisify4(execFile4);
|
|
16351
16355
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
16352
16356
|
const profiles = /* @__PURE__ */ new Map();
|
|
@@ -16427,8 +16431,8 @@ async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
|
16427
16431
|
`Profile "${name}" does not exist`
|
|
16428
16432
|
);
|
|
16429
16433
|
}
|
|
16430
|
-
await
|
|
16431
|
-
if (await
|
|
16434
|
+
await deleteHermesProfileWithCliAndVerify(name, profile.path);
|
|
16435
|
+
if (!await waitForProfilePathToRemainAbsent(profile.path)) {
|
|
16432
16436
|
throw new LinkHttpError(
|
|
16433
16437
|
502,
|
|
16434
16438
|
"hermes_profile_delete_incomplete",
|
|
@@ -16512,6 +16516,116 @@ async function deleteHermesProfileWithCli(name) {
|
|
|
16512
16516
|
);
|
|
16513
16517
|
}
|
|
16514
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
|
+
}
|
|
16515
16629
|
function formatExecError(error) {
|
|
16516
16630
|
if (!(error instanceof Error)) {
|
|
16517
16631
|
return String(error);
|
|
@@ -16539,6 +16653,9 @@ function readExecErrorOutput2(error) {
|
|
|
16539
16653
|
function isNodeError14(error, code) {
|
|
16540
16654
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
16541
16655
|
}
|
|
16656
|
+
function escapeRegExp2(value) {
|
|
16657
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16658
|
+
}
|
|
16542
16659
|
async function countSkills(root) {
|
|
16543
16660
|
const entries = await readdir9(root, { withFileTypes: true }).catch(
|
|
16544
16661
|
(error) => {
|
|
@@ -17715,7 +17832,7 @@ async function writeEnvValues(profileName, values) {
|
|
|
17715
17832
|
const nextLines = lines.map((line) => {
|
|
17716
17833
|
const trimmed = line.trim();
|
|
17717
17834
|
for (const [key, value] of remaining) {
|
|
17718
|
-
const keyPattern = new RegExp(`^(?:export\\s+)?${
|
|
17835
|
+
const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp3(key)}=`, "u");
|
|
17719
17836
|
if (keyPattern.test(trimmed)) {
|
|
17720
17837
|
remaining.delete(key);
|
|
17721
17838
|
return `${key}=${formatEnvValue2(value)}`;
|
|
@@ -17897,7 +18014,7 @@ function formatEnvValue2(value) {
|
|
|
17897
18014
|
}
|
|
17898
18015
|
return JSON.stringify(value);
|
|
17899
18016
|
}
|
|
17900
|
-
function
|
|
18017
|
+
function escapeRegExp3(value) {
|
|
17901
18018
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
17902
18019
|
}
|
|
17903
18020
|
function isNodeError15(error, code) {
|
|
@@ -19226,7 +19343,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
19226
19343
|
`\u4E0D\u5141\u8BB8\u4ECE App \u5199\u5165 ${key}\u3002`
|
|
19227
19344
|
);
|
|
19228
19345
|
}
|
|
19229
|
-
const keyPattern = new RegExp(`^(?:export\\s+)?${
|
|
19346
|
+
const keyPattern = new RegExp(`^(?:export\\s+)?${escapeRegExp4(key)}=`, "u");
|
|
19230
19347
|
let replaced = false;
|
|
19231
19348
|
for (let index = 0; index < nextLines.length; index += 1) {
|
|
19232
19349
|
if (keyPattern.test(nextLines[index].trim())) {
|
|
@@ -19447,7 +19564,7 @@ function readBoolean3(value) {
|
|
|
19447
19564
|
function formatEnvValue3(value) {
|
|
19448
19565
|
return /^[A-Za-z0-9_./:@-]*$/u.test(value) ? value : JSON.stringify(value);
|
|
19449
19566
|
}
|
|
19450
|
-
function
|
|
19567
|
+
function escapeRegExp4(value) {
|
|
19451
19568
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
19452
19569
|
}
|
|
19453
19570
|
function isNodeError16(error, code) {
|
|
@@ -21172,9 +21289,9 @@ function connectRelayControl(options) {
|
|
|
21172
21289
|
return;
|
|
21173
21290
|
}
|
|
21174
21291
|
reconnectAttempts += 1;
|
|
21175
|
-
const
|
|
21176
|
-
options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${
|
|
21177
|
-
retryTimer = setTimeout(connect,
|
|
21292
|
+
const delay3 = computeBackoffMs(reconnectAttempts, backoffBaseMs, backoffMaxMs);
|
|
21293
|
+
options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${delay3}ms` });
|
|
21294
|
+
retryTimer = setTimeout(connect, delay3);
|
|
21178
21295
|
retryTimer.unref?.();
|
|
21179
21296
|
});
|
|
21180
21297
|
};
|
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
|
}
|