@hermespilot/link 0.6.6 → 0.6.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.
@@ -1465,6 +1465,9 @@ var messages = {
1465
1465
  "config.notice.autoFilled": "Hermes API Server was auto-filled with {fields}; existing port/host/key were not overwritten.",
1466
1466
  "config.notice.repairDefault": "Hermes API Server kept the default port and rotated the key to repair an old Gateway or a key mismatch that caused 401.",
1467
1467
  "config.notice.repairProfile": "Hermes API Server was reassigned a local port and had its key rotated to repair an old Gateway or a key mismatch that caused 401.",
1468
+ "config.yaml.invalid": "Hermes Profile config is not valid YAML, so Link cannot write settings yet. Fix {path}, then try again.{details}{more}",
1469
+ "config.yaml.details": " YAML error: {errors}",
1470
+ "config.yaml.more": " and {count} more error(s).",
1468
1471
  "daemon.description": "Run Hermes Link in the foreground",
1469
1472
  "daemon.foreground": "Hermes Link foreground daemon is running. Press Ctrl+C to stop.",
1470
1473
  "logs.description": "Show or follow Hermes Link logs",
@@ -1520,6 +1523,7 @@ var messages = {
1520
1523
  "pair.autostartFailed": "Pairing succeeded, but boot autostart could not be enabled: {message}",
1521
1524
  "doctor.description": "Run local diagnostics",
1522
1525
  "doctor.installOnly": "only check npm global command and PATH setup",
1526
+ "doctor.noProfiles": "skip Hermes Profile diagnostics",
1523
1527
  "doctor.installHeader": "Install/PATH diagnostics:",
1524
1528
  "doctor.installNpmPrefix": "npm global prefix: {value}",
1525
1529
  "doctor.installGlobalBin": "npm global bin directory: {value}",
@@ -1549,6 +1553,13 @@ var messages = {
1549
1553
  "doctor.apiReady": "Hermes API Server: ready",
1550
1554
  "doctor.apiStarted": "Hermes API Server: started and ready",
1551
1555
  "doctor.apiUnavailable": "Hermes API Server: unavailable. {message}",
1556
+ "doctor.profilesHeader": "Hermes Profiles:",
1557
+ "doctor.profilesNone": "- no profiles found",
1558
+ "doctor.profileLine": "- {profile}: {endpoint}; {state}",
1559
+ "doctor.profileReady": "ready",
1560
+ "doctor.profileNotRunning": "not running ({message})",
1561
+ "doctor.profilePreparedState": "prepared config; {state}",
1562
+ "doctor.profilePrepareFailed": "prepare failed ({message})",
1552
1563
  "doctor.apiUnavailable.summary": "Hermes API Server: unavailable",
1553
1564
  "doctor.apiUnavailable.profile": "Profile: {profile}; port: {port}",
1554
1565
  "doctor.apiUnavailable.diagnosisDivider": "----- API Server diagnosis -----",
@@ -1668,6 +1679,9 @@ var messages = {
1668
1679
  "config.notice.autoFilled": "\u5DF2\u4E3A Hermes API Server \u81EA\u52A8\u8865\u5145 {fields}\uFF1B\u672A\u8986\u76D6\u5DF2\u6709 port/host/key\u3002",
1669
1680
  "config.notice.repairDefault": "\u5DF2\u4E3A Hermes API Server \u4FDD\u6301\u9ED8\u8BA4\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002",
1670
1681
  "config.notice.repairProfile": "\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",
1682
+ "config.yaml.invalid": "Hermes Profile \u914D\u7F6E\u6587\u4EF6\u4E0D\u662F\u6709\u6548\u7684 YAML\uFF0C\u6682\u65F6\u65E0\u6CD5\u5199\u5165\u914D\u7F6E\u3002\u8BF7\u5148\u4FEE\u590D {path} \u540E\u91CD\u8BD5\u3002{details}{more}",
1683
+ "config.yaml.details": " YAML \u9519\u8BEF\uFF1A{errors}",
1684
+ "config.yaml.more": " \u7B49 {count} \u5904\u9519\u8BEF\u3002",
1671
1685
  "daemon.description": "\u4EE5\u524D\u53F0\u65B9\u5F0F\u8FD0\u884C Hermes Link",
1672
1686
  "daemon.foreground": "Hermes Link \u524D\u53F0\u670D\u52A1\u6B63\u5728\u8FD0\u884C\u3002\u6309 Ctrl+C \u505C\u6B62\u3002",
1673
1687
  "logs.description": "\u663E\u793A\u6216\u8DDF\u8E2A Hermes Link \u65E5\u5FD7",
@@ -1723,6 +1737,7 @@ var messages = {
1723
1737
  "pair.autostartFailed": "\u914D\u5BF9\u5DF2\u6210\u529F\uFF0C\u4F46\u542F\u7528\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A{message}",
1724
1738
  "doctor.description": "\u8FD0\u884C\u672C\u673A\u8BCA\u65AD",
1725
1739
  "doctor.installOnly": "\u53EA\u68C0\u67E5 npm \u5168\u5C40\u547D\u4EE4\u548C PATH \u8BBE\u7F6E",
1740
+ "doctor.noProfiles": "\u8DF3\u8FC7 Hermes Profile \u8BCA\u65AD",
1726
1741
  "doctor.installHeader": "\u5B89\u88C5 / PATH \u8BCA\u65AD\uFF1A",
1727
1742
  "doctor.installNpmPrefix": "npm \u5168\u5C40 prefix\uFF1A{value}",
1728
1743
  "doctor.installGlobalBin": "npm \u5168\u5C40 bin \u76EE\u5F55\uFF1A{value}",
@@ -1752,6 +1767,13 @@ var messages = {
1752
1767
  "doctor.apiReady": "Hermes API Server\uFF1A\u5DF2\u5C31\u7EEA",
1753
1768
  "doctor.apiStarted": "Hermes API Server\uFF1A\u5DF2\u81EA\u52A8\u542F\u52A8\u5E76\u5C31\u7EEA",
1754
1769
  "doctor.apiUnavailable": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528\u3002{message}",
1770
+ "doctor.profilesHeader": "Hermes Profiles\uFF1A",
1771
+ "doctor.profilesNone": "- \u6CA1\u6709\u627E\u5230 Profile",
1772
+ "doctor.profileLine": "- {profile}\uFF1A{endpoint}\uFF1B{state}",
1773
+ "doctor.profileReady": "\u5DF2\u5C31\u7EEA",
1774
+ "doctor.profileNotRunning": "\u5C1A\u672A\u8FD0\u884C\uFF08{message}\uFF09",
1775
+ "doctor.profilePreparedState": "\u5DF2\u81EA\u52A8\u51C6\u5907\u914D\u7F6E\uFF1B{state}",
1776
+ "doctor.profilePrepareFailed": "\u51C6\u5907\u5931\u8D25\uFF08{message}\uFF09",
1755
1777
  "doctor.apiUnavailable.summary": "Hermes API Server\uFF1A\u4E0D\u53EF\u7528",
1756
1778
  "doctor.apiUnavailable.profile": "Profile\uFF1A{profile}\uFF1B\u7AEF\u53E3\uFF1A{port}",
1757
1779
  "doctor.apiUnavailable.diagnosisDivider": "----- API Server \u8BCA\u65AD -----",
@@ -1894,6 +1916,17 @@ var MODEL_CONFIG_RESTART_HINT = "\u6A21\u578B\u914D\u7F6E\u5DF2\u4FDD\u5B58\u300
1894
1916
  var MODEL_DEFAULTS_APPLIED_HINT = "\u9ED8\u8BA4\u6A21\u578B\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u65B0\u7684 Run \u4F1A\u76F4\u63A5\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF0C\u65E0\u9700\u91CD\u8F7D Hermes Gateway\u3002";
1895
1917
  var PROFILE_PERMISSIONS_RESTART_HINT = "\u6743\u9650\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u540E\u7EED\u4EE5\u8BE5 Profile \u53D1\u8D77\u7684\u65B0 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF1B\u5982\u679C\u8BE5 Profile \u7684 Gateway \u5DF2\u7ECF\u5728\u8FD0\u884C\uFF0C\u9700\u8981\u91CD\u8F7D\u5BF9\u5E94 Gateway\u3002";
1896
1918
  var PROFILE_TOOL_CONFIG_RESTART_HINT = "\u5DE5\u5177\u540E\u7AEF\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u540E\u7EED\u4EE5\u8BE5 Profile \u53D1\u8D77\u7684\u65B0 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\uFF1B\u5982\u679C\u8BE5 Profile \u7684 Gateway \u5DF2\u7ECF\u5728\u8FD0\u884C\uFF0C\u9700\u8981\u91CD\u8F7D\u5BF9\u5E94 Gateway\u3002";
1919
+ var HermesConfigYamlError = class extends Error {
1920
+ constructor(configPath, errors, language) {
1921
+ super(buildHermesConfigYamlErrorMessage(configPath, errors, language));
1922
+ this.configPath = configPath;
1923
+ this.errors = errors;
1924
+ this.name = "HermesConfigYamlError";
1925
+ }
1926
+ configPath;
1927
+ errors;
1928
+ code = "hermes_config_yaml_invalid";
1929
+ };
1897
1930
  var REASONING_EFFORTS = [
1898
1931
  "none",
1899
1932
  "minimal",
@@ -2805,12 +2838,12 @@ async function saveHermesProfilePermissions(profileName, input, configPath = res
2805
2838
  restartHint: PROFILE_PERMISSIONS_RESTART_HINT
2806
2839
  };
2807
2840
  }
2808
- async function addHermesCommandAllowlistEntry(profileName, entry, configPath = resolveHermesConfigPath(profileName)) {
2841
+ async function addHermesCommandAllowlistEntry(profileName, entry, configPath = resolveHermesConfigPath(profileName), language) {
2809
2842
  const normalizedEntry = entry.trim();
2810
2843
  if (!normalizedEntry) {
2811
2844
  throw new Error("command_allowlist entry must be non-empty");
2812
2845
  }
2813
- const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
2846
+ const { document, config, existingRaw } = await readHermesConfigDocument(configPath, language);
2814
2847
  const current = readStringList(config.command_allowlist);
2815
2848
  if (current.includes(normalizedEntry)) {
2816
2849
  return {
@@ -3132,7 +3165,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
3132
3165
  notice: buildRepairNotice(language, profileName)
3133
3166
  };
3134
3167
  }
3135
- async function readHermesConfigDocument(configPath) {
3168
+ async function readHermesConfigDocument(configPath, language) {
3136
3169
  const existingRaw = await readFile2(configPath, "utf8").catch(
3137
3170
  (error) => {
3138
3171
  if (isNodeError3(error, "ENOENT")) {
@@ -3142,12 +3175,34 @@ async function readHermesConfigDocument(configPath) {
3142
3175
  }
3143
3176
  );
3144
3177
  const document = existingRaw ? YAML.parseDocument(existingRaw) : new YAML.Document({});
3178
+ assertValidHermesConfigDocument(configPath, document, language);
3145
3179
  return {
3146
3180
  document,
3147
3181
  config: toRecord(document.toJSON()),
3148
3182
  existingRaw
3149
3183
  };
3150
3184
  }
3185
+ function assertValidHermesConfigDocument(configPath, document, language) {
3186
+ const errors = document.errors.map((error) => error.message.trim()).filter((message) => message.length > 0);
3187
+ if (errors.length > 0) {
3188
+ throw new HermesConfigYamlError(configPath, errors, language);
3189
+ }
3190
+ }
3191
+ function buildHermesConfigYamlErrorMessage(configPath, errors, language) {
3192
+ const firstErrors = errors.slice(0, 3);
3193
+ const resolvedLanguage = language ?? "zh-CN";
3194
+ const details = firstErrors.length > 0 ? translate(resolvedLanguage, "config.yaml.details", {
3195
+ errors: firstErrors.join("; ")
3196
+ }) : "";
3197
+ const more = errors.length > firstErrors.length ? translate(resolvedLanguage, "config.yaml.more", {
3198
+ count: errors.length - firstErrors.length
3199
+ }) : "";
3200
+ return translate(resolvedLanguage, "config.yaml.invalid", {
3201
+ path: configPath,
3202
+ details,
3203
+ more
3204
+ });
3205
+ }
3151
3206
  async function writeHermesConfigDocument(input) {
3152
3207
  const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
3153
3208
  if (backupPath) {
@@ -4894,12 +4949,24 @@ async function addConfiguredApiServerPort(ports, profileName, excludedProfileNam
4894
4949
  throw error;
4895
4950
  });
4896
4951
  if (!raw.trim()) {
4952
+ const envPort2 = readApiServerPort(
4953
+ (await readHermesApiServerEnvOverrides(profileName)).port
4954
+ );
4955
+ if (envPort2 !== null) {
4956
+ ports.add(envPort2);
4957
+ }
4897
4958
  return;
4898
4959
  }
4899
4960
  const config = toRecord(YAML.parse(raw));
4900
4961
  const apiServer = toRecord(toRecord(config.platforms).api_server);
4901
- const port = readApiServerConfig(apiServer).port;
4902
- if (typeof port === "number" && Number.isFinite(port)) {
4962
+ const envPort = readApiServerPort(
4963
+ (await readHermesApiServerEnvOverrides(profileName)).port
4964
+ );
4965
+ const configPort = readApiServerPort(readApiServerConfig(apiServer).port);
4966
+ for (const port of [envPort, configPort]) {
4967
+ if (port === null) {
4968
+ continue;
4969
+ }
4903
4970
  ports.add(port);
4904
4971
  }
4905
4972
  }
@@ -5418,7 +5485,7 @@ import os2 from "os";
5418
5485
  import path5 from "path";
5419
5486
 
5420
5487
  // src/constants.ts
5421
- var LINK_VERSION = "0.6.6";
5488
+ var LINK_VERSION = "0.6.7";
5422
5489
  var LINK_COMMAND = "hermeslink";
5423
5490
  var LINK_DEFAULT_PORT = 52379;
5424
5491
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -18816,8 +18883,15 @@ var ConversationService = class {
18816
18883
  );
18817
18884
  const result = await addHermesCommandAllowlistEntry(
18818
18885
  profileName,
18819
- patternKey
18820
- );
18886
+ patternKey,
18887
+ void 0,
18888
+ input.language
18889
+ ).catch((error) => {
18890
+ if (error instanceof HermesConfigYamlError) {
18891
+ throw new LinkHttpError(409, error.code, error.message);
18892
+ }
18893
+ throw error;
18894
+ });
18821
18895
  commandAllowlistUpdated = result.changed;
18822
18896
  configPath = result.configPath;
18823
18897
  requiresGatewayReload = result.requiresGatewayReload;
@@ -19529,6 +19603,36 @@ function readString16(body, key) {
19529
19603
  const value = body[key];
19530
19604
  return typeof value === "string" && value.trim() ? value.trim() : null;
19531
19605
  }
19606
+ function readPreferredLanguage(ctx) {
19607
+ const candidates = [
19608
+ ctx.get("x-hermespilot-language"),
19609
+ ctx.get("x-app-language"),
19610
+ ctx.get("accept-language")
19611
+ ];
19612
+ for (const candidate of candidates) {
19613
+ for (const part of candidate.split(",")) {
19614
+ const normalized = part.split(";")[0]?.trim();
19615
+ if (!normalized) {
19616
+ continue;
19617
+ }
19618
+ const language = readSupportedLanguage(normalized);
19619
+ if (language) {
19620
+ return language;
19621
+ }
19622
+ }
19623
+ }
19624
+ return resolveLanguage();
19625
+ }
19626
+ function readSupportedLanguage(value) {
19627
+ const normalized = value.trim().replace("_", "-").toLowerCase();
19628
+ if (normalized.startsWith("zh")) {
19629
+ return "zh-CN";
19630
+ }
19631
+ if (normalized.startsWith("en")) {
19632
+ return "en";
19633
+ }
19634
+ return null;
19635
+ }
19532
19636
  function readOptionalProfileName(body) {
19533
19637
  return readString16(body, "profile") ?? readString16(body, "profile_name") ?? readString16(body, "profileName") ?? void 0;
19534
19638
  }
@@ -20209,7 +20313,8 @@ function registerConversationRoutes(router, options) {
20209
20313
  ...await conversations.resolveApproval({
20210
20314
  conversationId: ctx.params.conversationId,
20211
20315
  approvalId: ctx.params.approvalId,
20212
- decision: scope
20316
+ decision: scope,
20317
+ language: readPreferredLanguage(ctx)
20213
20318
  })
20214
20319
  };
20215
20320
  }
@@ -20223,7 +20328,8 @@ function registerConversationRoutes(router, options) {
20223
20328
  ...await conversations.resolveApproval({
20224
20329
  conversationId: ctx.params.conversationId,
20225
20330
  approvalId: ctx.params.approvalId,
20226
- decision: "deny"
20331
+ decision: "deny",
20332
+ language: readPreferredLanguage(ctx)
20227
20333
  })
20228
20334
  };
20229
20335
  }
@@ -20448,7 +20554,9 @@ var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
20448
20554
  var execFileAsync4 = promisify4(execFile4);
20449
20555
  async function listHermesProfiles(paths = resolveRuntimePaths()) {
20450
20556
  const profiles = /* @__PURE__ */ new Map();
20451
- profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
20557
+ if (await hasDefaultProfileConfigSource()) {
20558
+ profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
20559
+ }
20452
20560
  const profilesDir = resolveHermesProfilesDir();
20453
20561
  const entries = await readdir9(profilesDir, { withFileTypes: true }).catch(
20454
20562
  (error) => {
@@ -20473,6 +20581,36 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
20473
20581
  return left.name.localeCompare(right.name);
20474
20582
  });
20475
20583
  }
20584
+ async function prepareHermesProfilesForUse(paths = resolveRuntimePaths()) {
20585
+ const profiles = await listHermesProfiles(paths);
20586
+ const prepared = [];
20587
+ for (const profile of profiles) {
20588
+ try {
20589
+ const result = await ensureHermesApiServerKey(
20590
+ profile.name,
20591
+ profile.configPath
20592
+ );
20593
+ prepared.push({
20594
+ profile,
20595
+ apiServer: result.apiServer,
20596
+ changed: result.changed,
20597
+ backupPath: result.backupPath,
20598
+ notice: result.notice,
20599
+ error: null
20600
+ });
20601
+ } catch (error) {
20602
+ prepared.push({
20603
+ profile,
20604
+ apiServer: null,
20605
+ changed: false,
20606
+ backupPath: null,
20607
+ notice: null,
20608
+ error: errorMessage(error)
20609
+ });
20610
+ }
20611
+ }
20612
+ return prepared;
20613
+ }
20476
20614
  async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
20477
20615
  assertProfileName(name);
20478
20616
  const profile = await profileInfo(name, paths);
@@ -20596,6 +20734,19 @@ async function pathExists(targetPath) {
20596
20734
  throw error;
20597
20735
  });
20598
20736
  }
20737
+ async function hasDefaultProfileConfigSource() {
20738
+ const profilePath = resolveHermesProfileDir(DEFAULT_PROFILE);
20739
+ const profileStat = await stat13(profilePath).catch((error) => {
20740
+ if (isNodeError15(error, "ENOENT")) {
20741
+ return null;
20742
+ }
20743
+ throw error;
20744
+ });
20745
+ if (!profileStat?.isDirectory()) {
20746
+ return false;
20747
+ }
20748
+ return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path20.join(profilePath, ".env"));
20749
+ }
20599
20750
  async function deleteHermesProfileWithCli(name) {
20600
20751
  try {
20601
20752
  await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
@@ -20747,6 +20898,9 @@ function readExecErrorOutput2(error) {
20747
20898
  function isNodeError15(error, code) {
20748
20899
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
20749
20900
  }
20901
+ function errorMessage(error) {
20902
+ return error instanceof Error ? error.message : String(error);
20903
+ }
20750
20904
  function escapeRegExp2(value) {
20751
20905
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20752
20906
  }
@@ -21473,7 +21627,11 @@ function registerProfileCatalogRoutes(router, options) {
21473
21627
  });
21474
21628
  }
21475
21629
  async function readProfileCatalogItem(profile) {
21476
- const [capabilities, permissions, modelConfigs] = await Promise.all([
21630
+ const [apiServer, capabilities, permissions, modelConfigs] = await Promise.all([
21631
+ readCatalogField(
21632
+ "apiServer",
21633
+ () => readHermesApiServerConfig(profile.name, profile.configPath)
21634
+ ),
21477
21635
  readCatalogField(
21478
21636
  "capabilities",
21479
21637
  () => readHermesProfileCapabilities(profile.name)
@@ -21486,10 +21644,17 @@ async function readProfileCatalogItem(profile) {
21486
21644
  ]);
21487
21645
  return {
21488
21646
  profile,
21647
+ apiServer: apiServer.value ? {
21648
+ host: apiServer.value.host ?? null,
21649
+ port: apiServer.value.port ?? null,
21650
+ configured: Boolean(apiServer.value.key),
21651
+ changed: false
21652
+ } : null,
21489
21653
  capabilities: capabilities.value,
21490
21654
  permissions: permissions.value,
21491
21655
  modelConfigs: modelConfigs.value,
21492
21656
  errors: [
21657
+ ...apiServer.errors,
21493
21658
  ...capabilities.errors,
21494
21659
  ...permissions.errors,
21495
21660
  ...modelConfigs.errors
@@ -21502,11 +21667,11 @@ async function readCatalogField(field, load) {
21502
21667
  } catch (error) {
21503
21668
  return {
21504
21669
  value: null,
21505
- errors: [{ field, message: errorMessage(error) }]
21670
+ errors: [{ field, message: errorMessage2(error) }]
21506
21671
  };
21507
21672
  }
21508
21673
  }
21509
- function errorMessage(error) {
21674
+ function errorMessage2(error) {
21510
21675
  return error instanceof Error ? error.message : String(error);
21511
21676
  }
21512
21677
 
@@ -21619,7 +21784,7 @@ async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
21619
21784
  const skillChanged = await writeHermesLinkSkill(skillPath);
21620
21785
  const profiles = await listHermesProfiles(paths);
21621
21786
  const results = [];
21622
- for (const profile of profiles) {
21787
+ for (const profile of withDefaultProfilePlaceholder(profiles)) {
21623
21788
  try {
21624
21789
  results.push(await ensureProfileUsesExternalSkillDir(profile, externalDir));
21625
21790
  } catch (error) {
@@ -21673,6 +21838,25 @@ async function ensureHermesLinkSkillInstalledBestEffort(options = {}) {
21673
21838
  });
21674
21839
  }
21675
21840
  }
21841
+ function withDefaultProfilePlaceholder(profiles) {
21842
+ if (profiles.some((profile) => profile.name === "default")) {
21843
+ return profiles;
21844
+ }
21845
+ return [
21846
+ {
21847
+ uid: "default",
21848
+ name: "default",
21849
+ active: false,
21850
+ path: resolveHermesProfileDir("default"),
21851
+ configPath: resolveHermesConfigPath("default"),
21852
+ displayName: null,
21853
+ description: null,
21854
+ avatarType: "default",
21855
+ avatarUrl: null
21856
+ },
21857
+ ...profiles
21858
+ ];
21859
+ }
21676
21860
  function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
21677
21861
  return path21.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
21678
21862
  }
@@ -25153,6 +25337,16 @@ function registerProfileRoutes(router, options) {
25153
25337
  profiles: await listHermesProfiles(paths)
25154
25338
  };
25155
25339
  });
25340
+ router.post("/api/v1/profiles/prepare", async (ctx) => {
25341
+ await authenticateRequest(ctx, paths);
25342
+ ctx.set("cache-control", "no-store");
25343
+ ctx.body = {
25344
+ ok: true,
25345
+ profiles: (await prepareHermesProfilesForUse(paths)).map(
25346
+ formatProfilePreparation
25347
+ )
25348
+ };
25349
+ });
25156
25350
  router.get("/api/v1/profile-creation/status", async (ctx) => {
25157
25351
  await authenticateRequest(ctx, paths);
25158
25352
  ctx.set("cache-control", "no-store");
@@ -25252,6 +25446,20 @@ function registerProfileRoutes(router, options) {
25252
25446
  ctx.status = 204;
25253
25447
  });
25254
25448
  }
25449
+ function formatProfilePreparation(item) {
25450
+ return {
25451
+ profile: item.profile,
25452
+ apiServer: item.apiServer ? {
25453
+ host: item.apiServer.host ?? null,
25454
+ port: item.apiServer.port ?? null,
25455
+ configured: Boolean(item.apiServer.key),
25456
+ changed: item.changed
25457
+ } : null,
25458
+ changed: item.changed,
25459
+ notice: item.notice,
25460
+ error: item.error
25461
+ };
25462
+ }
25255
25463
  function readProfileName(body) {
25256
25464
  if (typeof body.name !== "string") {
25257
25465
  throw new Error("invalid profile name");
@@ -27413,6 +27621,15 @@ async function startLinkService(options = {}) {
27413
27621
  });
27414
27622
  return streamBatchPolicy;
27415
27623
  };
27624
+ let profilePreparation = Promise.resolve();
27625
+ const triggerProfilePreparation = (source) => {
27626
+ profilePreparation = profilePreparation.catch(() => void 0).then(() => prepareHermesProfilesForUse(paths)).then((profiles) => logProfilePreparationResult(logger, source, profiles)).catch((error) => {
27627
+ void logger.warn("profile_preparation_failed", {
27628
+ source,
27629
+ error: error instanceof Error ? error.message : String(error)
27630
+ });
27631
+ });
27632
+ };
27416
27633
  let hermesSessionSync = Promise.resolve();
27417
27634
  const triggerHermesSessionSync = () => {
27418
27635
  hermesSessionSync = Promise.resolve().then(() => conversations.syncHermesSessions()).then(() => void 0).catch((error) => {
@@ -27432,6 +27649,7 @@ async function startLinkService(options = {}) {
27432
27649
  logger,
27433
27650
  source: "pairing_claimed"
27434
27651
  });
27652
+ triggerProfilePreparation("pairing_claimed");
27435
27653
  triggerHermesSessionSync();
27436
27654
  void loadRelayStreamBatchPolicy("pairing_claimed");
27437
27655
  await options.onPairingClaimed?.();
@@ -27492,6 +27710,9 @@ async function startLinkService(options = {}) {
27492
27710
  port: config.port,
27493
27711
  link_id: identity?.link_id ?? null
27494
27712
  });
27713
+ if (identity?.link_id) {
27714
+ triggerProfilePreparation("service_startup");
27715
+ }
27495
27716
  triggerHermesSessionSync();
27496
27717
  const scheduler = startCronDeliveryScheduler({
27497
27718
  paths,
@@ -27576,6 +27797,7 @@ async function startLinkService(options = {}) {
27576
27797
  scheduler.close(),
27577
27798
  hermesSessionSyncScheduler.close(),
27578
27799
  lanIpMonitor?.close(),
27800
+ profilePreparation.catch(() => void 0),
27579
27801
  hermesSessionSync.catch(() => void 0)
27580
27802
  ]);
27581
27803
  await logger.info("service_stopped");
@@ -27601,6 +27823,32 @@ function waitForRelayReadyTimeout(timeoutMs) {
27601
27823
  timer.unref?.();
27602
27824
  });
27603
27825
  }
27826
+ async function logProfilePreparationResult(logger, source, profiles) {
27827
+ const changed = profiles.filter((profile) => profile.changed);
27828
+ const failed = profiles.filter((profile) => profile.error);
27829
+ const summary = {
27830
+ source,
27831
+ total: profiles.length,
27832
+ prepared: profiles.length - failed.length,
27833
+ changed: changed.length,
27834
+ failed: failed.length,
27835
+ changed_profiles: changed.map((profile) => profile.profile.name)
27836
+ };
27837
+ if (changed.length > 0 || failed.length > 0) {
27838
+ await logger.warn("profiles_prepared", summary);
27839
+ } else {
27840
+ await logger.info("profiles_prepared", summary);
27841
+ }
27842
+ if (failed.length > 0) {
27843
+ await logger.warn("profile_preparation_partial_failure", {
27844
+ source,
27845
+ failed_profiles: failed.map((profile) => ({
27846
+ profile: profile.profile.name,
27847
+ error: profile.error
27848
+ }))
27849
+ });
27850
+ }
27851
+ }
27604
27852
  function pidFilePath(paths = resolveRuntimePaths()) {
27605
27853
  return `${paths.runDir}/hermeslink.pid`;
27606
27854
  }
@@ -30585,6 +30833,7 @@ export {
30585
30833
  resolveLanguage,
30586
30834
  translate,
30587
30835
  localizeErrorMessage,
30836
+ DEFAULT_HERMES_API_SERVER_PORT,
30588
30837
  resolveHermesProfileDir,
30589
30838
  resolveHermesConfigPath,
30590
30839
  ensureHermesApiServerConfig,
@@ -30599,6 +30848,7 @@ export {
30599
30848
  HermesApiServerUnavailableError,
30600
30849
  ensureHermesApiServerAvailable,
30601
30850
  readHermesVersion,
30851
+ readHermesApiServerHealth,
30602
30852
  defaultLinkConfig,
30603
30853
  loadConfig,
30604
30854
  saveConfig,
@@ -30609,6 +30859,7 @@ export {
30609
30859
  getIdentityStatus,
30610
30860
  ConversationService,
30611
30861
  hasActiveDevices,
30862
+ prepareHermesProfilesForUse,
30612
30863
  ensureHermesLinkSkillInstalledBestEffort,
30613
30864
  detectRuntimeEnvironment,
30614
30865
  preparePairing,
package/dist/cli/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConversationService,
4
+ DEFAULT_HERMES_API_SERVER_PORT,
4
5
  HermesApiServerUnavailableError,
5
6
  LINK_COMMAND,
6
7
  LINK_VERSION,
@@ -29,8 +30,10 @@ import {
29
30
  localizeErrorMessage,
30
31
  normalizeLanHost,
31
32
  parseLogLevel,
33
+ prepareHermesProfilesForUse,
32
34
  preparePairing,
33
35
  probeLocalLinkService,
36
+ readHermesApiServerHealth,
34
37
  readHermesVersion,
35
38
  readPairingClaim,
36
39
  readRecentGatewayLogEntries,
@@ -48,7 +51,7 @@ import {
48
51
  startLinkService,
49
52
  stopDaemonProcess,
50
53
  translate
51
- } from "../chunk-4OXUJNO6.js";
54
+ } from "../chunk-FWX7ZUP4.js";
52
55
 
53
56
  // src/cli/index.ts
54
57
  import { Command } from "commander";
@@ -1158,7 +1161,7 @@ logsCommand.command("flush").description(helpText("logs.flush.description")).opt
1158
1161
  })
1159
1162
  );
1160
1163
  });
1161
- program.command("doctor").option("--install", helpText("doctor.installOnly")).description(helpText("doctor.description")).action(async (options) => {
1164
+ program.command("doctor").option("--install", helpText("doctor.installOnly")).option("--no-profiles", helpText("doctor.noProfiles")).description(helpText("doctor.description")).action(async (options) => {
1162
1165
  const installInfo = readInstallPathInfo();
1163
1166
  const installLanguage = await loadCliLanguage().catch(() => detectSystemLanguage());
1164
1167
  const installT = translate.bind(null, installLanguage);
@@ -1200,6 +1203,9 @@ program.command("doctor").option("--install", helpText("doctor.installOnly")).de
1200
1203
  } catch (error) {
1201
1204
  console.log(formatHermesApiServerUnavailable(error, language));
1202
1205
  }
1206
+ if (options.profiles !== false) {
1207
+ await printProfileDiagnostics(t);
1208
+ }
1203
1209
  });
1204
1210
  if (isCliEntrypoint()) {
1205
1211
  program.parseAsync(process.argv).catch(async (error) => {
@@ -1212,6 +1218,45 @@ async function loadCliLanguage() {
1212
1218
  const config = await loadConfig();
1213
1219
  return resolveLanguage(config.language);
1214
1220
  }
1221
+ async function printProfileDiagnostics(t) {
1222
+ console.log(t("doctor.profilesHeader"));
1223
+ const profiles = await prepareHermesProfilesForUse();
1224
+ if (profiles.length === 0) {
1225
+ console.log(t("doctor.profilesNone"));
1226
+ return;
1227
+ }
1228
+ for (const profile of profiles) {
1229
+ console.log(await formatProfileDiagnosticLine(profile, t));
1230
+ }
1231
+ }
1232
+ async function formatProfileDiagnosticLine(profile, t) {
1233
+ if (profile.error || !profile.apiServer) {
1234
+ return t("doctor.profileLine", {
1235
+ profile: profile.profile.name,
1236
+ endpoint: t("status.unknown"),
1237
+ state: t("doctor.profilePrepareFailed", {
1238
+ message: profile.error ?? t("status.unknown")
1239
+ })
1240
+ });
1241
+ }
1242
+ const host = profile.apiServer.host ?? "127.0.0.1";
1243
+ const port = profile.apiServer.port ?? DEFAULT_HERMES_API_SERVER_PORT;
1244
+ const health = await readHermesApiServerHealth(profile.apiServer).catch(
1245
+ (error) => ({
1246
+ healthy: false,
1247
+ issue: error instanceof Error ? error.message : String(error)
1248
+ })
1249
+ );
1250
+ const baseState = health.healthy ? t("doctor.profileReady") : t("doctor.profileNotRunning", {
1251
+ message: health.issue ?? t("status.unknown")
1252
+ });
1253
+ const state = profile.changed ? t("doctor.profilePreparedState", { state: baseState }) : baseState;
1254
+ return t("doctor.profileLine", {
1255
+ profile: profile.profile.name,
1256
+ endpoint: `${host}:${port}`,
1257
+ state
1258
+ });
1259
+ }
1215
1260
  function buildRelayStatusPayload(input) {
1216
1261
  if (!input.paired) {
1217
1262
  return emptyRelayStatus(input.relayConfigured, "not_paired");
@@ -381,6 +381,8 @@ declare class FileLogger {
381
381
  private rotateIfNeeded;
382
382
  }
383
383
 
384
+ type SupportedLanguage = 'zh-CN' | 'en';
385
+
384
386
  interface HermesSessionSyncResult {
385
387
  scanned_profiles: number;
386
388
  scanned_sessions: number;
@@ -510,6 +512,7 @@ declare class ConversationService {
510
512
  conversationId: string;
511
513
  approvalId: string;
512
514
  decision: LinkApprovalDecision;
515
+ language?: SupportedLanguage;
513
516
  }): Promise<{
514
517
  conversation_id: string;
515
518
  message_id: string;
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-4OXUJNO6.js";
3
+ } from "../chunk-FWX7ZUP4.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.6.6",
3
+ "version": "0.6.7",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",