@saleso.innovations/bridge 0.1.42 → 0.1.43

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.
Files changed (55) hide show
  1. package/dist/client.d.ts +2 -0
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +5 -1
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +3 -0
  7. package/dist/cronList.d.ts +2 -0
  8. package/dist/cronList.d.ts.map +1 -1
  9. package/dist/cronList.js +11 -8
  10. package/dist/cronWatcher.d.ts +1 -1
  11. package/dist/cronWatcher.d.ts.map +1 -1
  12. package/dist/cronWatcher.js +70 -29
  13. package/dist/ensureHermesApi.d.ts +1 -1
  14. package/dist/ensureHermesApi.d.ts.map +1 -1
  15. package/dist/ensureHermesApi.js +14 -10
  16. package/dist/gatewayControl.d.ts +3 -3
  17. package/dist/gatewayControl.d.ts.map +1 -1
  18. package/dist/gatewayControl.js +58 -24
  19. package/dist/hermesCommands.d.ts +1 -1
  20. package/dist/hermesCommands.d.ts.map +1 -1
  21. package/dist/hermesCommands.js +48 -27
  22. package/dist/hermesFileCommands.d.ts +5 -5
  23. package/dist/hermesFileCommands.d.ts.map +1 -1
  24. package/dist/hermesFileCommands.js +10 -10
  25. package/dist/hermesFiles.d.ts +7 -6
  26. package/dist/hermesFiles.d.ts.map +1 -1
  27. package/dist/hermesFiles.js +11 -14
  28. package/dist/hermesForwarder.d.ts +2 -0
  29. package/dist/hermesForwarder.d.ts.map +1 -1
  30. package/dist/hermesForwarder.js +27 -20
  31. package/dist/hermesSessionDb.d.ts +10 -6
  32. package/dist/hermesSessionDb.d.ts.map +1 -1
  33. package/dist/hermesSessionDb.js +44 -24
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -1
  37. package/dist/mcpList.d.ts +3 -2
  38. package/dist/mcpList.d.ts.map +1 -1
  39. package/dist/mcpList.js +5 -5
  40. package/dist/profiles.d.ts +66 -0
  41. package/dist/profiles.d.ts.map +1 -0
  42. package/dist/profiles.js +409 -0
  43. package/dist/renameHermesSession.d.ts +1 -1
  44. package/dist/renameHermesSession.d.ts.map +1 -1
  45. package/dist/renameHermesSession.js +2 -2
  46. package/dist/skillLearnedDetector.d.ts +1 -1
  47. package/dist/skillLearnedDetector.d.ts.map +1 -1
  48. package/dist/skillLearnedDetector.js +2 -3
  49. package/dist/skillsList.d.ts +5 -2
  50. package/dist/skillsList.d.ts.map +1 -1
  51. package/dist/skillsList.js +14 -14
  52. package/dist/toolsList.d.ts +2 -2
  53. package/dist/toolsList.d.ts.map +1 -1
  54. package/dist/toolsList.js +4 -4
  55. package/package.json +1 -1
@@ -0,0 +1,409 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { DEFAULT_HERMES_API_URL } from "./constants.js";
7
+ import { countSessions, hermesStateDbExists } from "./hermesSessionDb.js";
8
+ import { listSkillsFromFilesystem } from "./skillsList.js";
9
+ const execFileAsync = promisify(execFile);
10
+ const PROFILE_TIMEOUT_MS = 30_000;
11
+ const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
12
+ /** Hermes default profile is the base home (`~/.hermes`) itself. */
13
+ export const DEFAULT_PROFILE = "default";
14
+ const PROFILE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
15
+ export class HermesProfileError extends Error {
16
+ }
17
+ export function isValidProfileName(name) {
18
+ if (name === "." || name === "..")
19
+ return false;
20
+ return PROFILE_NAME_PATTERN.test(name);
21
+ }
22
+ export function normalizeProfileName(profile) {
23
+ const trimmed = (profile ?? "").trim();
24
+ if (!trimmed || trimmed.toLowerCase() === DEFAULT_PROFILE)
25
+ return DEFAULT_PROFILE;
26
+ return trimmed;
27
+ }
28
+ export function isDefaultProfile(profile) {
29
+ return normalizeProfileName(profile) === DEFAULT_PROFILE;
30
+ }
31
+ /** Base Hermes home — the default profile's directory. */
32
+ export function resolveBaseHermesHome() {
33
+ return process.env.HERMES_HOME?.trim() || join(homedir(), ".hermes");
34
+ }
35
+ export function profilesRootDir() {
36
+ return join(resolveBaseHermesHome(), "profiles");
37
+ }
38
+ /** Resolve the Hermes home directory for a given profile (default → base home). */
39
+ export function resolveProfileHome(profile) {
40
+ const name = normalizeProfileName(profile);
41
+ if (name === DEFAULT_PROFILE)
42
+ return resolveBaseHermesHome();
43
+ return join(profilesRootDir(), name);
44
+ }
45
+ /** Build an env that scopes child `hermes` processes to the given profile. */
46
+ export function profileEnv(profile) {
47
+ return { ...process.env, HERMES_HOME: resolveProfileHome(profile) };
48
+ }
49
+ function readEnvValue(home, key) {
50
+ try {
51
+ const contents = readFileSync(join(home, ".env"), "utf8");
52
+ for (const line of contents.split("\n")) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed || trimmed.startsWith("#"))
55
+ continue;
56
+ const eq = trimmed.indexOf("=");
57
+ if (eq <= 0)
58
+ continue;
59
+ if (trimmed.slice(0, eq).trim() === key) {
60
+ return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Profile env file is optional.
66
+ }
67
+ return undefined;
68
+ }
69
+ function readConfigYaml(home) {
70
+ try {
71
+ return readFileSync(join(home, "config.yaml"), "utf8");
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ /** Best-effort parse of `model.default` from a profile's config.yaml. */
78
+ export function readProfileModel(home) {
79
+ const yaml = readConfigYaml(home);
80
+ if (!yaml)
81
+ return undefined;
82
+ let inModelBlock = false;
83
+ for (const line of yaml.split(/\r?\n/)) {
84
+ const inline = line.match(/^model:\s*(\S.*)$/);
85
+ if (inline?.[1] && !inline[1].trim().startsWith("{")) {
86
+ return inline[1].trim().replace(/^["']|["']$/g, "");
87
+ }
88
+ if (/^model:\s*$/.test(line)) {
89
+ inModelBlock = true;
90
+ continue;
91
+ }
92
+ if (inModelBlock) {
93
+ if (/^\S/.test(line)) {
94
+ inModelBlock = false;
95
+ }
96
+ else {
97
+ const match = line.match(/^\s+default:\s*(.+?)\s*$/);
98
+ if (match?.[1])
99
+ return match[1].trim().replace(/^["']|["']$/g, "");
100
+ }
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+ function readGatewayPortFromConfig(home) {
106
+ const yaml = readConfigYaml(home);
107
+ if (!yaml)
108
+ return undefined;
109
+ let inSection = false;
110
+ for (const line of yaml.split(/\r?\n/)) {
111
+ if (/^(api_server|api|gateway|server):\s*$/.test(line)) {
112
+ inSection = true;
113
+ continue;
114
+ }
115
+ if (inSection) {
116
+ if (/^\S/.test(line)) {
117
+ inSection = false;
118
+ }
119
+ else {
120
+ const match = line.match(/^\s+port:\s*(\d+)/);
121
+ if (match?.[1])
122
+ return Number(match[1]);
123
+ }
124
+ }
125
+ }
126
+ return undefined;
127
+ }
128
+ export function resolveProfileGatewayPort(profile) {
129
+ const home = resolveProfileHome(profile);
130
+ const fromEnv = readEnvValue(home, "API_SERVER_PORT");
131
+ if (fromEnv && /^\d+$/.test(fromEnv))
132
+ return Number(fromEnv);
133
+ return readGatewayPortFromConfig(home);
134
+ }
135
+ export function resolveProfileGatewayUrl(profile) {
136
+ if (isDefaultProfile(profile)) {
137
+ return process.env.HERMES_API_URL?.trim() || DEFAULT_HERMES_API_URL;
138
+ }
139
+ const port = resolveProfileGatewayPort(profile);
140
+ if (port)
141
+ return `http://127.0.0.1:${port}/v1/chat/completions`;
142
+ return DEFAULT_HERMES_API_URL;
143
+ }
144
+ export function resolveProfileContext(profile) {
145
+ const name = normalizeProfileName(profile);
146
+ const home = resolveProfileHome(name);
147
+ const apiKey = isDefaultProfile(name)
148
+ ? readEnvValue(home, "API_SERVER_KEY")
149
+ : readEnvValue(home, "API_SERVER_KEY");
150
+ return {
151
+ profile: name,
152
+ home,
153
+ apiUrl: resolveProfileGatewayUrl(name),
154
+ apiKey,
155
+ env: { ...process.env, HERMES_HOME: home },
156
+ };
157
+ }
158
+ async function gatewayHealthy(apiUrl, apiKey) {
159
+ const healthUrl = apiUrl.replace(/\/v1\/chat\/completions\/?$/, "/health");
160
+ const headers = {};
161
+ if (apiKey)
162
+ headers.authorization = `Bearer ${apiKey}`;
163
+ try {
164
+ const response = await fetch(healthUrl, { headers, signal: AbortSignal.timeout(2_000) });
165
+ return response.ok;
166
+ }
167
+ catch {
168
+ return false;
169
+ }
170
+ }
171
+ function skillsCountForHome(home) {
172
+ try {
173
+ return listSkillsFromFilesystem(home).skills.length;
174
+ }
175
+ catch {
176
+ return 0;
177
+ }
178
+ }
179
+ /** Cheap, sync enumeration of profile homes (default + on-disk profiles). */
180
+ export function listProfileHomes() {
181
+ const homes = [{ profile: DEFAULT_PROFILE, home: resolveBaseHermesHome() }];
182
+ for (const name of discoverProfileNamesFromDisk()) {
183
+ homes.push({ profile: name, home: resolveProfileHome(name) });
184
+ }
185
+ return homes;
186
+ }
187
+ function discoverProfileNamesFromDisk() {
188
+ const root = profilesRootDir();
189
+ if (!existsSync(root))
190
+ return [];
191
+ try {
192
+ return readdirSync(root).filter((name) => {
193
+ if (name.startsWith("."))
194
+ return false;
195
+ try {
196
+ return statSync(join(root, name)).isDirectory();
197
+ }
198
+ catch {
199
+ return false;
200
+ }
201
+ });
202
+ }
203
+ catch {
204
+ return [];
205
+ }
206
+ }
207
+ async function listProfileNamesFromCli() {
208
+ try {
209
+ const { stdout } = await execFileAsync("hermes", ["profile", "list", "--json"], {
210
+ timeout: PROFILE_TIMEOUT_MS,
211
+ maxBuffer: MAX_OUTPUT_BYTES,
212
+ env: process.env,
213
+ });
214
+ const trimmed = stdout.trim();
215
+ if (!trimmed)
216
+ return null;
217
+ const parsed = JSON.parse(trimmed);
218
+ const rows = Array.isArray(parsed)
219
+ ? parsed
220
+ : parsed && typeof parsed === "object" && Array.isArray(parsed.profiles)
221
+ ? (parsed.profiles)
222
+ : [];
223
+ const out = [];
224
+ for (const row of rows) {
225
+ if (typeof row === "string") {
226
+ out.push({ name: row.trim(), active: false });
227
+ }
228
+ else if (row && typeof row === "object") {
229
+ const record = row;
230
+ const name = typeof record.name === "string" ? record.name.trim() : "";
231
+ if (name)
232
+ out.push({ name, active: record.active === true || record.isActive === true });
233
+ }
234
+ }
235
+ return out.length > 0 ? out : null;
236
+ }
237
+ catch {
238
+ return null;
239
+ }
240
+ }
241
+ async function listProfileNamesFromCliText() {
242
+ try {
243
+ const { stdout } = await execFileAsync("hermes", ["profile", "list"], {
244
+ timeout: PROFILE_TIMEOUT_MS,
245
+ maxBuffer: MAX_OUTPUT_BYTES,
246
+ env: process.env,
247
+ });
248
+ const out = [];
249
+ for (const rawLine of stdout.split(/\r?\n/)) {
250
+ const line = rawLine.trim();
251
+ if (!line)
252
+ continue;
253
+ const active = line.startsWith("*");
254
+ const name = line.replace(/^\*/, "").trim().split(/\s+/)[0] ?? "";
255
+ if (name && isValidProfileName(name))
256
+ out.push({ name, active });
257
+ }
258
+ return out.length > 0 ? out : null;
259
+ }
260
+ catch {
261
+ return null;
262
+ }
263
+ }
264
+ export async function listHermesProfiles() {
265
+ const cli = (await listProfileNamesFromCli()) ?? (await listProfileNamesFromCliText());
266
+ const names = new Map();
267
+ names.set(DEFAULT_PROFILE, false);
268
+ if (cli) {
269
+ for (const profile of cli)
270
+ names.set(normalizeProfileName(profile.name), profile.active);
271
+ }
272
+ else {
273
+ for (const name of discoverProfileNamesFromDisk())
274
+ names.set(name, false);
275
+ }
276
+ const entries = await Promise.all([...names.entries()].map(async ([name, active]) => {
277
+ const home = resolveProfileHome(name);
278
+ const apiUrl = resolveProfileGatewayUrl(name);
279
+ const apiKey = readEnvValue(home, "API_SERVER_KEY");
280
+ const [running] = await Promise.all([gatewayHealthy(apiUrl, apiKey)]);
281
+ return {
282
+ name,
283
+ isDefault: name === DEFAULT_PROFILE,
284
+ isActive: active,
285
+ model: readProfileModel(home),
286
+ gatewayRunning: running,
287
+ gatewayPort: resolveProfileGatewayPort(name),
288
+ skillsCount: skillsCountForHome(home),
289
+ sessionsCount: hermesStateDbExists(home) ? countSessions(home) : 0,
290
+ home,
291
+ };
292
+ }));
293
+ entries.sort((left, right) => {
294
+ if (left.isDefault !== right.isDefault)
295
+ return left.isDefault ? -1 : 1;
296
+ return left.name.localeCompare(right.name, undefined, { sensitivity: "base" });
297
+ });
298
+ return { profiles: entries };
299
+ }
300
+ function execFailureMessage(error, fallback) {
301
+ if (error && typeof error === "object") {
302
+ const record = error;
303
+ const stderr = record.stderr?.trim();
304
+ if (stderr)
305
+ return stderr;
306
+ const stdout = record.stdout?.trim();
307
+ if (stdout)
308
+ return stdout;
309
+ if (typeof record.message === "string" && record.message.trim())
310
+ return record.message.trim();
311
+ }
312
+ return fallback;
313
+ }
314
+ export async function createHermesProfile(name, options = {}) {
315
+ const trimmed = name.trim();
316
+ if (!isValidProfileName(trimmed)) {
317
+ throw new HermesProfileError(`Invalid profile name "${name}". Use letters, numbers, dot, dash or underscore.`);
318
+ }
319
+ if (normalizeProfileName(trimmed) === DEFAULT_PROFILE) {
320
+ throw new HermesProfileError('"default" is reserved for the base Hermes home.');
321
+ }
322
+ const args = ["profile", "create", trimmed];
323
+ if (options.clone)
324
+ args.push("--clone");
325
+ try {
326
+ await execFileAsync("hermes", args, {
327
+ timeout: PROFILE_TIMEOUT_MS,
328
+ maxBuffer: MAX_OUTPUT_BYTES,
329
+ env: process.env,
330
+ });
331
+ return { ok: true, name: trimmed };
332
+ }
333
+ catch (error) {
334
+ throw new HermesProfileError(execFailureMessage(error, "Could not create Hermes profile."));
335
+ }
336
+ }
337
+ export async function renameHermesProfile(oldName, newName) {
338
+ const from = oldName.trim();
339
+ const to = newName.trim();
340
+ if (normalizeProfileName(from) === DEFAULT_PROFILE) {
341
+ throw new HermesProfileError("The default profile cannot be renamed.");
342
+ }
343
+ if (!isValidProfileName(from)) {
344
+ throw new HermesProfileError(`Invalid profile name "${oldName}".`);
345
+ }
346
+ if (!isValidProfileName(to) || normalizeProfileName(to) === DEFAULT_PROFILE) {
347
+ throw new HermesProfileError(`Invalid new profile name "${newName}".`);
348
+ }
349
+ try {
350
+ await execFileAsync("hermes", ["profile", "rename", from, to], {
351
+ timeout: PROFILE_TIMEOUT_MS,
352
+ maxBuffer: MAX_OUTPUT_BYTES,
353
+ env: process.env,
354
+ });
355
+ return { ok: true, oldName: from, newName: to };
356
+ }
357
+ catch (error) {
358
+ throw new HermesProfileError(execFailureMessage(error, "Could not rename Hermes profile."));
359
+ }
360
+ }
361
+ export async function deleteHermesProfile(name) {
362
+ const trimmed = name.trim();
363
+ if (normalizeProfileName(trimmed) === DEFAULT_PROFILE) {
364
+ throw new HermesProfileError("The default profile cannot be deleted.");
365
+ }
366
+ if (!isValidProfileName(trimmed)) {
367
+ throw new HermesProfileError(`Invalid profile name "${name}".`);
368
+ }
369
+ try {
370
+ await execFileAsync("hermes", ["profile", "delete", trimmed, "--yes"], {
371
+ timeout: PROFILE_TIMEOUT_MS,
372
+ maxBuffer: MAX_OUTPUT_BYTES,
373
+ env: process.env,
374
+ });
375
+ return { ok: true, name: trimmed };
376
+ }
377
+ catch (error) {
378
+ // Some builds use `--force` instead of `--yes`; retry once without the flag.
379
+ try {
380
+ await execFileAsync("hermes", ["profile", "delete", trimmed], {
381
+ timeout: PROFILE_TIMEOUT_MS,
382
+ maxBuffer: MAX_OUTPUT_BYTES,
383
+ env: process.env,
384
+ });
385
+ return { ok: true, name: trimmed };
386
+ }
387
+ catch {
388
+ throw new HermesProfileError(execFailureMessage(error, "Could not delete Hermes profile."));
389
+ }
390
+ }
391
+ }
392
+ export async function setHermesProfileModel(profile, model) {
393
+ const name = normalizeProfileName(profile);
394
+ const trimmedModel = model.trim();
395
+ if (!trimmedModel) {
396
+ throw new HermesProfileError('Missing "model".');
397
+ }
398
+ try {
399
+ await execFileAsync("hermes", ["config", "set", "model.default", trimmedModel], {
400
+ timeout: PROFILE_TIMEOUT_MS,
401
+ maxBuffer: MAX_OUTPUT_BYTES,
402
+ env: profileEnv(name),
403
+ });
404
+ return { ok: true, profile: name, model: trimmedModel };
405
+ }
406
+ catch (error) {
407
+ throw new HermesProfileError(execFailureMessage(error, "Could not set the model for this profile."));
408
+ }
409
+ }
@@ -3,5 +3,5 @@ export type RenameHermesSessionResult = {
3
3
  sessionId: string;
4
4
  title: string;
5
5
  };
6
- export declare function renameHermesSession(sessionId: string, title: string): Promise<RenameHermesSessionResult>;
6
+ export declare function renameHermesSession(sessionId: string, title: string, env?: NodeJS.ProcessEnv): Promise<RenameHermesSessionResult>;
7
7
  //# sourceMappingURL=renameHermesSession.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renameHermesSession.d.ts","sourceRoot":"","sources":["../src/renameHermesSession.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,yBAAyB,GAAG;IACtC,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAgBF,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,yBAAyB,CAAC,CAmBpC"}
1
+ {"version":3,"file":"renameHermesSession.d.ts","sourceRoot":"","sources":["../src/renameHermesSession.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,yBAAyB,GAAG;IACtC,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAgBF,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,yBAAyB,CAAC,CAmBpC"}
@@ -17,7 +17,7 @@ function execFailureMessage(error) {
17
17
  }
18
18
  return "Could not rename Hermes session.";
19
19
  }
20
- export async function renameHermesSession(sessionId, title) {
20
+ export async function renameHermesSession(sessionId, title, env = process.env) {
21
21
  const trimmedId = sessionId.trim();
22
22
  const trimmedTitle = title.trim().slice(0, 120);
23
23
  if (!trimmedId) {
@@ -29,7 +29,7 @@ export async function renameHermesSession(sessionId, title) {
29
29
  try {
30
30
  await execFileAsync("hermes", ["sessions", "rename", trimmedId, trimmedTitle], {
31
31
  timeout: RENAME_TIMEOUT_MS,
32
- env: process.env,
32
+ env,
33
33
  });
34
34
  return { ok: true, sessionId: trimmedId, title: trimmedTitle };
35
35
  }
@@ -7,7 +7,7 @@ export type SkillSnapshotEntry = HermesSkillEntry & {
7
7
  * Captures the set of skills currently on disk, keyed by relative path, with each
8
8
  * file's mtime so a later diff can ignore writes that predate the turn.
9
9
  */
10
- export declare function snapshotSkills(): Map<string, SkillSnapshotEntry>;
10
+ export declare function snapshotSkills(home?: string): Map<string, SkillSnapshotEntry>;
11
11
  /**
12
12
  * Returns skills present in `after` but not in `before` — i.e. newly written during the
13
13
  * turn. When `turnStartedAt` is provided, entries whose file mtime predates the turn
@@ -1 +1 @@
1
- {"version":3,"file":"skillLearnedDetector.d.ts","sourceRoot":"","sources":["../src/skillLearnedDetector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAchE;AAKD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACvC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACtC,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAuBjD"}
1
+ {"version":3,"file":"skillLearnedDetector.d.ts","sourceRoot":"","sources":["../src/skillLearnedDetector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAA4B,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAalG;AAKD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACvC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACtC,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAuBjD"}
@@ -6,10 +6,9 @@ import { listSkillsFromFilesystem } from "./skillsList.js";
6
6
  * Captures the set of skills currently on disk, keyed by relative path, with each
7
7
  * file's mtime so a later diff can ignore writes that predate the turn.
8
8
  */
9
- export function snapshotSkills() {
10
- const home = resolveHermesHome();
9
+ export function snapshotSkills(home = resolveHermesHome()) {
11
10
  const snapshot = new Map();
12
- for (const skill of listSkillsFromFilesystem().skills) {
11
+ for (const skill of listSkillsFromFilesystem(home).skills) {
13
12
  if (!skill.relativePath)
14
13
  continue;
15
14
  let mtimeMs;
@@ -8,10 +8,13 @@ export type HermesSkillEntry = {
8
8
  * Synchronously reads the Hermes skills directory from disk. Used for fast turn-boundary
9
9
  * snapshots without shelling out to the `hermes` CLI.
10
10
  */
11
- export declare function listSkillsFromFilesystem(): {
11
+ export declare function listSkillsFromFilesystem(home?: string): {
12
12
  skills: HermesSkillEntry[];
13
13
  };
14
- export declare function listHermesSkills(): Promise<{
14
+ export declare function listHermesSkills(options?: {
15
+ home?: string;
16
+ env?: NodeJS.ProcessEnv;
17
+ }): Promise<{
15
18
  skills: HermesSkillEntry[];
16
19
  }>;
17
20
  //# sourceMappingURL=skillsList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"skillsList.d.ts","sourceRoot":"","sources":["../src/skillsList.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA6GF;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAEzE;AA4HD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAOhF"}
1
+ {"version":3,"file":"skillsList.d.ts","sourceRoot":"","sources":["../src/skillsList.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA6GF;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAEtF;AA4HD,wBAAsB,gBAAgB,CACpC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,GACvD,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAOzC"}
@@ -9,8 +9,8 @@ const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
9
9
  const SKILL_FILE_NAME = "SKILL.md";
10
10
  const SKILLS_DIR = "skills";
11
11
  const HIDDEN_DIR_NAMES = new Set([".hub", ".git"]);
12
- function skillsRoot() {
13
- return join(resolveHermesHome(), SKILLS_DIR);
12
+ function skillsRoot(home = resolveHermesHome()) {
13
+ return join(home, SKILLS_DIR);
14
14
  }
15
15
  function normalizeSkillsPayload(value) {
16
16
  if (Array.isArray(value)) {
@@ -109,11 +109,11 @@ function toHermesRelativeSkillPath(skillsRootDir, skillFilePath) {
109
109
  * Synchronously reads the Hermes skills directory from disk. Used for fast turn-boundary
110
110
  * snapshots without shelling out to the `hermes` CLI.
111
111
  */
112
- export function listSkillsFromFilesystem() {
113
- return readSkillsFromDirectory();
112
+ export function listSkillsFromFilesystem(home) {
113
+ return readSkillsFromDirectory(home);
114
114
  }
115
- function readSkillsFromDirectory() {
116
- const root = skillsRoot();
115
+ function readSkillsFromDirectory(home) {
116
+ const root = skillsRoot(home);
117
117
  if (!existsSync(root)) {
118
118
  return { skills: [] };
119
119
  }
@@ -187,8 +187,8 @@ function readSkillsFromDirectory() {
187
187
  });
188
188
  return { skills };
189
189
  }
190
- function enrichSkillsWithFilesystemPaths(skills) {
191
- const filesystemSkills = readSkillsFromDirectory();
190
+ function enrichSkillsWithFilesystemPaths(skills, home) {
191
+ const filesystemSkills = readSkillsFromDirectory(home);
192
192
  const pathByKey = new Map();
193
193
  for (const skill of filesystemSkills.skills) {
194
194
  if (skill.relativePath) {
@@ -206,12 +206,12 @@ function enrichSkillsWithFilesystemPaths(skills) {
206
206
  return { ...skill, relativePath };
207
207
  });
208
208
  }
209
- async function runHermesSkillsListJson() {
209
+ async function runHermesSkillsListJson(env = process.env) {
210
210
  try {
211
211
  const { stdout } = await execFileAsync("hermes", ["skills", "list", "--json"], {
212
212
  timeout: LIST_TIMEOUT_MS,
213
213
  maxBuffer: MAX_OUTPUT_BYTES,
214
- env: process.env,
214
+ env,
215
215
  });
216
216
  const trimmed = stdout.trim();
217
217
  if (!trimmed) {
@@ -223,10 +223,10 @@ async function runHermesSkillsListJson() {
223
223
  return null;
224
224
  }
225
225
  }
226
- export async function listHermesSkills() {
227
- const jsonResult = await runHermesSkillsListJson();
226
+ export async function listHermesSkills(options = {}) {
227
+ const jsonResult = await runHermesSkillsListJson(options.env);
228
228
  if (jsonResult != null) {
229
- return { skills: enrichSkillsWithFilesystemPaths(jsonResult.skills) };
229
+ return { skills: enrichSkillsWithFilesystemPaths(jsonResult.skills, options.home) };
230
230
  }
231
- return readSkillsFromDirectory();
231
+ return readSkillsFromDirectory(options.home);
232
232
  }
@@ -5,10 +5,10 @@ export type HermesToolEntry = {
5
5
  category?: string;
6
6
  };
7
7
  export declare function parseToolsListOutput(stdout: string): HermesToolEntry[];
8
- export declare function listHermesTools(platform?: string): Promise<{
8
+ export declare function listHermesTools(platform?: string, env?: NodeJS.ProcessEnv): Promise<{
9
9
  tools: HermesToolEntry[];
10
10
  }>;
11
- export declare function setHermesToolEnabled(name: string, enabled: boolean): Promise<{
11
+ export declare function setHermesToolEnabled(name: string, enabled: boolean, env?: NodeJS.ProcessEnv): Promise<{
12
12
  ok: true;
13
13
  name: string;
14
14
  enabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"toolsList.d.ts","sourceRoot":"","sources":["../src/toolsList.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA+BF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CAqCtE;AAED,wBAAsB,eAAe,CACnC,QAAQ,GAAE,MAAyB,GAClC,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC,CAYvC;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAQvD"}
1
+ {"version":3,"file":"toolsList.d.ts","sourceRoot":"","sources":["../src/toolsList.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA+BF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CAqCtE;AAED,wBAAsB,eAAe,CACnC,QAAQ,GAAE,MAAyB,EACnC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC,CAYvC;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAQvD"}
package/dist/toolsList.js CHANGED
@@ -64,21 +64,21 @@ export function parseToolsListOutput(stdout) {
64
64
  }
65
65
  return tools;
66
66
  }
67
- export async function listHermesTools(platform = DEFAULT_PLATFORM) {
67
+ export async function listHermesTools(platform = DEFAULT_PLATFORM, env = process.env) {
68
68
  const resolvedPlatform = platform.trim().length > 0 ? platform.trim() : DEFAULT_PLATFORM;
69
69
  const { stdout } = await execFileAsync("hermes", ["tools", "list", "--platform", resolvedPlatform], {
70
70
  timeout: LIST_TIMEOUT_MS,
71
71
  maxBuffer: MAX_OUTPUT_BYTES,
72
- env: process.env,
72
+ env,
73
73
  });
74
74
  return { tools: parseToolsListOutput(stdout) };
75
75
  }
76
- export async function setHermesToolEnabled(name, enabled) {
76
+ export async function setHermesToolEnabled(name, enabled, env = process.env) {
77
77
  const subcommand = enabled ? "enable" : "disable";
78
78
  await execFileAsync("hermes", ["tools", subcommand, name], {
79
79
  timeout: SET_TIMEOUT_MS,
80
80
  maxBuffer: MAX_OUTPUT_BYTES,
81
- env: process.env,
81
+ env,
82
82
  });
83
83
  return { ok: true, name, enabled };
84
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saleso.innovations/bridge",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "description": "Connect your Hermes agent to the Cleos iOS app via pairing code.",
5
5
  "type": "module",
6
6
  "license": "MIT",