@hermespilot/link 0.1.7 → 0.1.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 CHANGED
@@ -40,6 +40,8 @@ hermes update
40
40
  hermes gateway run
41
41
  ```
42
42
 
43
+ If Hermes Agent is configured through `~/.hermes/.env`, Link follows the same `API_SERVER_*` overrides when calling the local API Server.
44
+
43
45
  CLI output follows the current system language when it is Chinese or English. You can override it for a single command with `HERMESLINK_LANG=zh-CN` or `HERMESLINK_LANG=en`.
44
46
 
45
47
  ## Runtime data
@@ -4,7 +4,7 @@ import Router from "@koa/router";
4
4
  import { Readable } from "stream";
5
5
 
6
6
  // src/constants.ts
7
- var LINK_VERSION = "0.1.7";
7
+ var LINK_VERSION = "0.1.8";
8
8
  var LINK_COMMAND = "hermeslink";
9
9
  var LINK_DEFAULT_PORT = 52379;
10
10
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -146,12 +146,16 @@ async function readHermesApiServerConfig(profileName = "default", configPath = r
146
146
  throw error;
147
147
  });
148
148
  if (!existingRaw) {
149
- return {};
149
+ return applyEnvOverrides({}, await readHermesApiServerEnvOverrides(profileName), false);
150
150
  }
151
151
  const config = toRecord(YAML.parse(existingRaw));
152
152
  const platforms = toRecord(config.platforms);
153
153
  const apiServer = toRecord(platforms.api_server);
154
- return readApiServerConfig(apiServer, true);
154
+ return applyEnvOverrides(
155
+ readApiServerConfig(apiServer, true),
156
+ await readHermesApiServerEnvOverrides(profileName),
157
+ true
158
+ );
155
159
  }
156
160
  async function ensureHermesApiServerKey(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
157
161
  return ensureHermesApiServerConfig(profileName, configPath);
@@ -168,10 +172,12 @@ async function ensureHermesApiServerConfig(profileName = "default", configPath =
168
172
  const platforms = ensureRecord(config, "platforms");
169
173
  const apiServer = ensureRecord(platforms, "api_server");
170
174
  const extra = ensureRecord(apiServer, "extra");
171
- const beforeKey = typeof extra.key === "string" && extra.key.length > 0 ? extra.key : null;
172
- const beforeEnabled = apiServer.enabled === true;
173
- const beforeHost = typeof extra.host === "string" && extra.host.trim() ? extra.host : null;
174
- const beforePort = typeof extra.port === "number" && Number.isFinite(extra.port) ? extra.port : null;
175
+ const envOverrides = await readHermesApiServerEnvOverrides(profileName);
176
+ const before = applyEnvOverrides(readApiServerConfig(apiServer), envOverrides, false);
177
+ const beforeKey = before.key?.trim() ? before.key : null;
178
+ const beforeEnabled = before.enabled === true;
179
+ const beforeHost = before.host?.trim() ? before.host : null;
180
+ const beforePort = typeof before.port === "number" && Number.isFinite(before.port) ? before.port : null;
175
181
  let changed = false;
176
182
  let enabledAdded = false;
177
183
  let hostAdded = false;
@@ -198,7 +204,7 @@ async function ensureHermesApiServerConfig(profileName = "default", configPath =
198
204
  if (!changed) {
199
205
  return {
200
206
  configPath,
201
- apiServer: readApiServerConfig(apiServer, true),
207
+ apiServer: applyEnvOverrides(readApiServerConfig(apiServer, true), envOverrides, true),
202
208
  changed: false,
203
209
  keyAdded: false,
204
210
  enabledAdded: false,
@@ -217,7 +223,7 @@ async function ensureHermesApiServerConfig(profileName = "default", configPath =
217
223
  await writeFile(configPath, document.toString(), { mode: 384 });
218
224
  return {
219
225
  configPath,
220
- apiServer: readApiServerConfig(apiServer, true),
226
+ apiServer: applyEnvOverrides(readApiServerConfig(apiServer, true), envOverrides, true),
221
227
  changed: true,
222
228
  keyAdded: !beforeKey,
223
229
  enabledAdded,
@@ -239,6 +245,73 @@ function readApiServerConfig(apiServerOrExtra, withDefaults = false) {
239
245
  key: typeof extra.key === "string" ? extra.key : void 0
240
246
  };
241
247
  }
248
+ async function readHermesApiServerEnvOverrides(profileName) {
249
+ const values = await readHermesEnvFile(profileName);
250
+ for (const key of ["API_SERVER_ENABLED", "API_SERVER_HOST", "API_SERVER_PORT", "API_SERVER_KEY"]) {
251
+ const value = process.env[key];
252
+ if (typeof value === "string" && value.trim()) {
253
+ values[key] = value;
254
+ }
255
+ }
256
+ const port = Number.parseInt(values.API_SERVER_PORT ?? "", 10);
257
+ return {
258
+ enabled: parseEnvBoolean(values.API_SERVER_ENABLED),
259
+ host: values.API_SERVER_HOST?.trim() || void 0,
260
+ port: Number.isFinite(port) ? port : void 0,
261
+ key: values.API_SERVER_KEY?.trim() || void 0
262
+ };
263
+ }
264
+ async function readHermesEnvFile(profileName) {
265
+ const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
266
+ const raw = await readFile2(envPath, "utf8").catch((error) => {
267
+ if (isNodeError2(error, "ENOENT")) {
268
+ return "";
269
+ }
270
+ throw error;
271
+ });
272
+ const values = {};
273
+ for (const line of raw.split(/\r?\n/u)) {
274
+ const trimmed = line.trim();
275
+ if (!trimmed || trimmed.startsWith("#")) {
276
+ continue;
277
+ }
278
+ const match = /^(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)=(?<value>.*)$/u.exec(trimmed);
279
+ if (!match?.groups) {
280
+ continue;
281
+ }
282
+ values[match.groups.key] = unquoteEnvValue(match.groups.value.trim());
283
+ }
284
+ return values;
285
+ }
286
+ function applyEnvOverrides(config, env, withDefaults) {
287
+ const host = env.host ?? config.host;
288
+ const port = env.port ?? config.port;
289
+ return {
290
+ enabled: env.enabled ?? config.enabled,
291
+ host: withDefaults ? host ?? DEFAULT_HERMES_API_SERVER_HOST : host,
292
+ port: withDefaults ? port ?? DEFAULT_HERMES_API_SERVER_PORT : port,
293
+ key: env.key ?? config.key
294
+ };
295
+ }
296
+ function parseEnvBoolean(value) {
297
+ if (!value) {
298
+ return void 0;
299
+ }
300
+ const normalized = value.trim().toLowerCase();
301
+ if (["1", "true", "yes", "on"].includes(normalized)) {
302
+ return true;
303
+ }
304
+ if (["0", "false", "no", "off"].includes(normalized)) {
305
+ return false;
306
+ }
307
+ return void 0;
308
+ }
309
+ function unquoteEnvValue(value) {
310
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
311
+ return value.slice(1, -1);
312
+ }
313
+ return value;
314
+ }
242
315
  function buildNotice(flags) {
243
316
  const fields = [];
244
317
  if (flags.enabledAdded) {
@@ -305,7 +378,7 @@ var gatewayStartInFlight = null;
305
378
  async function ensureHermesApiServerAvailable(options = {}) {
306
379
  const configResult = await ensureHermesApiServerConfig();
307
380
  const fetcher = options.fetchImpl ?? fetch;
308
- if (await isHermesApiHealthy(configResult.apiServer, fetcher)) {
381
+ if (!options.forceRestart && await isHermesApiHealthy(configResult.apiServer, fetcher)) {
309
382
  return { available: true, configResult, started: false };
310
383
  }
311
384
  if (!shouldAutoStart(options.autoStart)) {
@@ -520,6 +593,15 @@ async function callHermesApi(path9, init, options) {
520
593
  const availability = await ensureHermesApiServerAvailable({ fetchImpl: options.fetchImpl });
521
594
  const config = availability.configResult.apiServer;
522
595
  const fetcher = options.fetchImpl ?? fetch;
596
+ const request = () => fetchHermesApi(fetcher, config, path9, init);
597
+ const response = await request();
598
+ if (response.status !== 401) {
599
+ return response;
600
+ }
601
+ await ensureHermesApiServerAvailable({ fetchImpl: options.fetchImpl, forceRestart: true });
602
+ return await request();
603
+ }
604
+ async function fetchHermesApi(fetcher, config, path9, init) {
523
605
  const headers = new Headers(init.headers);
524
606
  headers.set("accept", headers.get("accept") ?? "application/json");
525
607
  if (config.key) {
@@ -534,12 +616,37 @@ async function callHermesApi(path9, init, options) {
534
616
  });
535
617
  }
536
618
  async function readJsonResponse(response) {
537
- const payload = await response.json().catch(() => null);
619
+ const raw = await response.text().catch(() => "");
620
+ const payload = parseJsonObject(raw);
538
621
  if (!response.ok || typeof payload !== "object" || payload === null) {
539
- throw new LinkHttpError(502, "hermes_response_invalid", "Hermes API Server returned an invalid response");
622
+ throw new LinkHttpError(
623
+ 502,
624
+ "hermes_response_invalid",
625
+ `Hermes API Server returned HTTP ${response.status}: ${readUpstreamMessage(payload, raw)}`
626
+ );
540
627
  }
541
628
  return payload;
542
629
  }
630
+ function parseJsonObject(raw) {
631
+ if (!raw.trim()) {
632
+ return null;
633
+ }
634
+ try {
635
+ const parsed = JSON.parse(raw);
636
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
637
+ } catch {
638
+ return null;
639
+ }
640
+ }
641
+ function readUpstreamMessage(payload, raw) {
642
+ const error = typeof payload?.error === "object" && payload.error !== null ? payload.error : null;
643
+ const message = readString(error ?? {}, "message") ?? readString(payload ?? {}, "message");
644
+ if (message) {
645
+ return message;
646
+ }
647
+ const body = raw.trim().replace(/\s+/gu, " ").slice(0, 500);
648
+ return body || "empty response body";
649
+ }
543
650
  function readString(payload, key) {
544
651
  const value = payload[key];
545
652
  return typeof value === "string" && value.trim() ? value.trim() : null;
@@ -2989,4 +3096,4 @@ export {
2989
3096
  getLinkLogFile,
2990
3097
  createApp
2991
3098
  };
2992
- //# sourceMappingURL=chunk-SCIAZZ4C.js.map
3099
+ //# sourceMappingURL=chunk-L2NM2XMX.js.map