@saleso.innovations/bridge 0.1.25 → 0.1.26

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -135,7 +135,20 @@ async function handleHermesCommandEnvelope(ws, envelope, fallbackAgentId) {
135
135
  status: "accepted",
136
136
  }));
137
137
  try {
138
- const result = await executeHermesCommand(command, parseCommandArgs(envelope.args));
138
+ const onDelta = command === "shell.exec"
139
+ ? (stream, delta, deltaSequence) => {
140
+ ws.send(JSON.stringify({
141
+ type: "hermes.command.delta",
142
+ requestId,
143
+ agentId,
144
+ command,
145
+ stream,
146
+ delta,
147
+ sequence: deltaSequence,
148
+ }));
149
+ }
150
+ : undefined;
151
+ const result = await executeHermesCommand(command, parseCommandArgs(envelope.args), { onDelta });
139
152
  ws.send(JSON.stringify({
140
153
  type: "hermes.command.result",
141
154
  requestId,
@@ -6,6 +6,10 @@ export declare const DEFAULT_HERMES_API_URL = "http://127.0.0.1:8642/v1/chat/com
6
6
  export declare const HERMES_COMMANDS_CAPABILITY = "hermes.commands.v1";
7
7
  /** Bridge can resolve auto-generated session titles from ~/.hermes/state.db. */
8
8
  export declare const HERMES_SESSION_TITLES_CAPABILITY = "hermes.commands.sessions.titles.v1";
9
+ /** Bridge can list all Hermes sessions from ~/.hermes/state.db (CLI + Cleos + other clients). */
10
+ export declare const HERMES_SESSIONS_LIST_CAPABILITY = "hermes.commands.sessions.list.v1";
11
+ /** Remote shell command execution over relay. */
12
+ export declare const TERMINAL_SHELL_CAPABILITY = "terminal.shell.v1";
9
13
  export declare const DEFAULT_BRIDGE_CAPABILITIES: string[];
10
14
  /** VPS one-line installer (served from public Convex HTTP). */
11
15
  export declare const DEFAULT_BRIDGE_INSTALL_URL = "https://amicable-elephant-407.convex.site/install-bridge.sh";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,8CAA8C,CAAC;AAEzF,6FAA6F;AAC7F,eAAO,MAAM,sBAAsB,8CAA8C,CAAC;AAElF,4DAA4D;AAC5D,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAE/D,gFAAgF;AAChF,eAAO,MAAM,gCAAgC,uCAAuC,CAAC;AAErF,eAAO,MAAM,2BAA2B,UAIvC,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,gEACwB,CAAC;AAEhE,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,+DACwB,CAAC;AAE/D,uGAAuG;AACvG,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,8CAA8C,CAAC;AAEzF,6FAA6F;AAC7F,eAAO,MAAM,sBAAsB,8CAA8C,CAAC;AAElF,4DAA4D;AAC5D,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAE/D,gFAAgF;AAChF,eAAO,MAAM,gCAAgC,uCAAuC,CAAC;AAErF,iGAAiG;AACjG,eAAO,MAAM,+BAA+B,qCAAqC,CAAC;AAElF,iDAAiD;AACjD,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D,eAAO,MAAM,2BAA2B,UAMvC,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,gEACwB,CAAC;AAEhE,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,+DACwB,CAAC;AAE/D,uGAAuG;AACvG,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
package/dist/constants.js CHANGED
@@ -6,10 +6,16 @@ export const DEFAULT_HERMES_API_URL = "http://127.0.0.1:8642/v1/chat/completions
6
6
  export const HERMES_COMMANDS_CAPABILITY = "hermes.commands.v1";
7
7
  /** Bridge can resolve auto-generated session titles from ~/.hermes/state.db. */
8
8
  export const HERMES_SESSION_TITLES_CAPABILITY = "hermes.commands.sessions.titles.v1";
9
+ /** Bridge can list all Hermes sessions from ~/.hermes/state.db (CLI + Cleos + other clients). */
10
+ export const HERMES_SESSIONS_LIST_CAPABILITY = "hermes.commands.sessions.list.v1";
11
+ /** Remote shell command execution over relay. */
12
+ export const TERMINAL_SHELL_CAPABILITY = "terminal.shell.v1";
9
13
  export const DEFAULT_BRIDGE_CAPABILITIES = [
10
14
  "chat",
11
15
  HERMES_COMMANDS_CAPABILITY,
12
16
  HERMES_SESSION_TITLES_CAPABILITY,
17
+ HERMES_SESSIONS_LIST_CAPABILITY,
18
+ TERMINAL_SHELL_CAPABILITY,
13
19
  ];
14
20
  /** VPS one-line installer (served from public Convex HTTP). */
15
21
  export const DEFAULT_BRIDGE_INSTALL_URL = "https://amicable-elephant-407.convex.site/install-bridge.sh";
@@ -1,4 +1,5 @@
1
- export declare const HERMES_COMMAND_NAMES: readonly ["runtime.health", "runtime.detailedHealth", "runtime.version", "runtime.capabilities", "models.list", "model.set", "responses.create", "runs.create", "runs.status", "runs.stop", "jobs.list", "jobs.get", "jobs.create", "jobs.update", "jobs.pause", "jobs.resume", "jobs.runNow", "jobs.delete", "profiles.list", "profiles.create", "gateway.start", "gateway.stop", "gateway.restart", "hermes.update", "sessions.messages.list", "sessions.messages.countSent", "sessions.titles.resolve", "skills.list", "files.list", "files.read", "files.write", "memories.list"];
1
+ import { type ShellDeltaEmitter } from "./shellSession.js";
2
+ export declare const HERMES_COMMAND_NAMES: readonly ["runtime.health", "runtime.detailedHealth", "runtime.version", "runtime.capabilities", "models.list", "model.set", "responses.create", "runs.create", "runs.status", "runs.stop", "jobs.list", "jobs.get", "jobs.create", "jobs.update", "jobs.pause", "jobs.resume", "jobs.runNow", "jobs.delete", "profiles.list", "profiles.create", "gateway.start", "gateway.stop", "gateway.restart", "hermes.update", "sessions.messages.list", "sessions.messages.countSent", "sessions.titles.resolve", "sessions.list", "skills.list", "files.list", "files.read", "files.write", "memories.list", "shell.exec", "shell.session.reset"];
2
3
  export type HermesCommandName = (typeof HERMES_COMMAND_NAMES)[number];
3
4
  export declare function isHermesCommandName(value: string): value is HermesCommandName;
4
5
  export type HermesCommandErrorCode = "command_unsupported" | "hermes_unreachable" | "hermes_request_failed" | "invalid_command_args" | "unsupported_by_http";
@@ -10,6 +11,7 @@ export declare function executeHermesCommand(command: HermesCommandName, args: R
10
11
  apiUrl?: string;
11
12
  apiKey?: string;
12
13
  model?: string;
14
+ onDelta?: ShellDeltaEmitter;
13
15
  }): Promise<unknown>;
14
16
  export declare function userSafeCommandError(error: unknown): {
15
17
  code: HermesCommandErrorCode;
@@ -1 +1 @@
1
- {"version":3,"file":"hermesCommands.d.ts","sourceRoot":"","sources":["../src/hermesCommands.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,oBAAoB,ujBAiCvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,MAAM,MAAM,sBAAsB,GAC9B,qBAAqB,GACrB,oBAAoB,GACpB,uBAAuB,GACvB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;gBAE1B,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAI1D;AA6GD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACjE,OAAO,CAAC,OAAO,CAAC,CAyMlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
1
+ {"version":3,"file":"hermesCommands.d.ts","sourceRoot":"","sources":["../src/hermesCommands.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAuC,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGhG,eAAO,MAAM,oBAAoB,6mBAoCvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,MAAM,MAAM,sBAAsB,GAC9B,qBAAqB,GACrB,oBAAoB,GACpB,uBAAuB,GACvB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;gBAE1B,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAI1D;AA6GD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;CAAO,GAC9F,OAAO,CAAC,OAAO,CAAC,CAsNlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
@@ -1,10 +1,11 @@
1
1
  import { resolveHermesApiConfig } from "./hermesForwarder.js";
2
2
  import { restartHermesGateway, startHermesGateway, stopHermesGateway } from "./gatewayControl.js";
3
3
  import { listHermesCronJobs } from "./cronList.js";
4
- import { listSessionMessages, countUserMessagesSent, resolveSessionTitles } from "./hermesSessionDb.js";
4
+ import { listHermesSessions, listSessionMessages, countUserMessagesSent, resolveSessionTitles, } from "./hermesSessionDb.js";
5
5
  import { listHermesSkills } from "./skillsList.js";
6
6
  import { runHermesUpdate } from "./hermesUpdate.js";
7
7
  import { executeFilesList, executeFilesRead, executeFilesWrite, executeMemoriesList, } from "./hermesFileCommands.js";
8
+ import { executeShellExec, resetShellSession } from "./shellSession.js";
8
9
  import { fetchHermesRuntimeVersion } from "./runtimeVersion.js";
9
10
  export const HERMES_COMMAND_NAMES = [
10
11
  "runtime.health",
@@ -34,11 +35,14 @@ export const HERMES_COMMAND_NAMES = [
34
35
  "sessions.messages.list",
35
36
  "sessions.messages.countSent",
36
37
  "sessions.titles.resolve",
38
+ "sessions.list",
37
39
  "skills.list",
38
40
  "files.list",
39
41
  "files.read",
40
42
  "files.write",
41
43
  "memories.list",
44
+ "shell.exec",
45
+ "shell.session.reset",
42
46
  ];
43
47
  export function isHermesCommandName(value) {
44
48
  return HERMES_COMMAND_NAMES.includes(value);
@@ -139,6 +143,7 @@ async function hermesFetchVoid(config, path, init = {}) {
139
143
  }
140
144
  export async function executeHermesCommand(command, args, options = {}) {
141
145
  const config = resolveHermesApiConfig(options);
146
+ const { onDelta } = options;
142
147
  switch (command) {
143
148
  case "runtime.health":
144
149
  return await hermesFetchJson(config, "/health");
@@ -306,6 +311,10 @@ export async function executeHermesCommand(command, args, options = {}) {
306
311
  const sessionIds = optionalStringArray(args, "sessionIds") ?? [];
307
312
  return { titles: resolveSessionTitles(sessionIds) };
308
313
  }
314
+ case "sessions.list": {
315
+ const limit = optionalNumber(args, "limit");
316
+ return { sessions: listHermesSessions({ limit: limit ?? 200 }) };
317
+ }
309
318
  case "skills.list":
310
319
  return await listHermesSkills();
311
320
  case "files.list":
@@ -330,6 +339,14 @@ export async function executeHermesCommand(command, args, options = {}) {
330
339
  }
331
340
  case "memories.list":
332
341
  return await executeMemoriesList();
342
+ case "shell.exec": {
343
+ if (!onDelta) {
344
+ throw new HermesCommandError("invalid_command_args", "shell.exec requires streaming support");
345
+ }
346
+ return await executeShellExec(args, onDelta);
347
+ }
348
+ case "shell.session.reset":
349
+ return resetShellSession(requireString(args, "sessionId"));
333
350
  default: {
334
351
  const _exhaustive = command;
335
352
  throw new HermesCommandError("command_unsupported", `Unsupported command: ${String(_exhaustive)}`);
@@ -16,6 +16,17 @@ export declare function getLatestTurnMessageIds(sessionId: string): {
16
16
  userMessageId?: number;
17
17
  assistantMessageId?: number;
18
18
  };
19
+ export type HermesSessionListEntry = {
20
+ id: string;
21
+ title: string | null;
22
+ startedAt: number;
23
+ lastMessageAt: number;
24
+ previewText: string;
25
+ messageCount: number;
26
+ };
27
+ export declare function listHermesSessions(options?: {
28
+ limit?: number;
29
+ }): HermesSessionListEntry[];
19
30
  export declare function hermesStateDbExists(): boolean;
20
31
  export declare function countUserMessagesSent(): {
21
32
  count: number;
@@ -1 +1 @@
1
- {"version":3,"file":"hermesSessionDb.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAyIF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapE;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAuBxF;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACxE,oBAAoB,EAAE,CAwCxB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,CAmCzD;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,qBAAqB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAgBzD"}
1
+ {"version":3,"file":"hermesSessionDb.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAyIF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapE;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAuBxF;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACxE,oBAAoB,EAAE,CAwCxB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,CAmCzD;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,sBAAsB,EAAE,CAyD7F;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,qBAAqB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAgBzD"}
@@ -213,6 +213,55 @@ export function getLatestTurnMessageIds(sessionId) {
213
213
  db.close();
214
214
  }
215
215
  }
216
+ function normalizeTimestampMs(value) {
217
+ return value < 1_000_000_000_000 ? value * 1000 : value;
218
+ }
219
+ export function listHermesSessions(options = {}) {
220
+ if (!hermesStateDbExists()) {
221
+ return [];
222
+ }
223
+ const limit = Math.min(Math.max(options.limit ?? 200, 1), 500);
224
+ const db = openReadOnlyDb();
225
+ try {
226
+ const rows = db
227
+ .prepare(`SELECT
228
+ s.id AS id,
229
+ s.title AS title,
230
+ s.started_at AS started_at,
231
+ MAX(m.timestamp) AS last_message_at,
232
+ COUNT(m.id) AS message_count
233
+ FROM sessions s
234
+ INNER JOIN messages m ON m.session_id = s.id AND m.role IN ('user', 'assistant')
235
+ GROUP BY s.id
236
+ ORDER BY last_message_at DESC
237
+ LIMIT ?`)
238
+ .all(limit);
239
+ return rows.map((row) => {
240
+ const previewRow = db
241
+ .prepare(`SELECT content FROM messages
242
+ WHERE session_id = ? AND role IN ('user', 'assistant')
243
+ ORDER BY timestamp DESC, id DESC
244
+ LIMIT 1`)
245
+ .get(row.id);
246
+ const resolvedTitle = sessionTitleForLookupId(db, row.id);
247
+ const previewText = decodeMessageContent(previewRow?.content ?? null).trim().slice(0, 120);
248
+ return {
249
+ id: row.id,
250
+ title: resolvedTitle ?? row.title?.trim() ?? null,
251
+ startedAt: normalizeTimestampMs(row.started_at ?? row.last_message_at),
252
+ lastMessageAt: normalizeTimestampMs(row.last_message_at),
253
+ previewText,
254
+ messageCount: row.message_count,
255
+ };
256
+ });
257
+ }
258
+ catch {
259
+ return [];
260
+ }
261
+ finally {
262
+ db.close();
263
+ }
264
+ }
216
265
  export function hermesStateDbExists() {
217
266
  try {
218
267
  readFileSync(hermesStateDbPath());
package/dist/index.d.ts CHANGED
@@ -7,7 +7,8 @@ export type { PairingInfo } from "./resolve.js";
7
7
  export { pairWithCleos } from "./pairWithCleos.js";
8
8
  export { createHermesMessageHandler, forwardToHermes, resolveHermesApiConfig } from "./hermesForwarder.js";
9
9
  export type { HermesForwarderOptions, HermesForwardResult } from "./hermesForwarder.js";
10
- export { listSessionMessages, resolveSessionTitle, resolveSessionTitles } from "./hermesSessionDb.js";
10
+ export { listHermesSessions, listSessionMessages, resolveSessionTitle, resolveSessionTitles, } from "./hermesSessionDb.js";
11
+ export type { HermesSessionListEntry } from "./hermesSessionDb.js";
11
12
  export type { HermesSessionMessage } from "./hermesSessionDb.js";
12
13
  export { executeHermesCommand, HERMES_COMMAND_NAMES, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
13
14
  export type { HermesCommandErrorCode, HermesCommandName } from "./hermesCommands.js";
@@ -16,5 +17,5 @@ export { backfillCronDeliveries, describeCronDeliveryState } from "./cronBackfil
16
17
  export { resolveActiveConversationId, rememberConversationId } from "./activeConversation.js";
17
18
  export { runBridgeDaemon } from "./daemon.js";
18
19
  export type { RunBridgeOptions } from "./daemon.js";
19
- export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, DEFAULT_BRIDGE_CAPABILITIES, HERMES_COMMANDS_CAPABILITY, HERMES_SESSION_TITLES_CAPABILITY, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
20
+ export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, DEFAULT_BRIDGE_CAPABILITIES, HERMES_COMMANDS_CAPABILITY, HERMES_SESSION_TITLES_CAPABILITY, HERMES_SESSIONS_LIST_CAPABILITY, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3G,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACtG,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,6BAA6B,EAC7B,sBAAsB,EACtB,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,EAChC,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3G,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,6BAA6B,EAC7B,sBAAsB,EACtB,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,EAChC,+BAA+B,EAC/B,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -3,10 +3,10 @@ export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./c
3
3
  export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
4
4
  export { pairWithCleos } from "./pairWithCleos.js";
5
5
  export { createHermesMessageHandler, forwardToHermes, resolveHermesApiConfig } from "./hermesForwarder.js";
6
- export { listSessionMessages, resolveSessionTitle, resolveSessionTitles } from "./hermesSessionDb.js";
6
+ export { listHermesSessions, listSessionMessages, resolveSessionTitle, resolveSessionTitles, } from "./hermesSessionDb.js";
7
7
  export { executeHermesCommand, HERMES_COMMAND_NAMES, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
8
8
  export { startCronWatcher, listPendingCronFiles, clearDeliveredIndex } from "./cronWatcher.js";
9
9
  export { backfillCronDeliveries, describeCronDeliveryState } from "./cronBackfill.js";
10
10
  export { resolveActiveConversationId, rememberConversationId } from "./activeConversation.js";
11
11
  export { runBridgeDaemon } from "./daemon.js";
12
- export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, DEFAULT_BRIDGE_CAPABILITIES, HERMES_COMMANDS_CAPABILITY, HERMES_SESSION_TITLES_CAPABILITY, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
12
+ export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, DEFAULT_BRIDGE_CAPABILITIES, HERMES_COMMANDS_CAPABILITY, HERMES_SESSION_TITLES_CAPABILITY, HERMES_SESSIONS_LIST_CAPABILITY, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
@@ -0,0 +1,21 @@
1
+ export declare const EXIT_SENTINEL_PREFIX = "__CLEOS_EXIT_";
2
+ export declare const EXIT_SENTINEL_SUFFIX = "__";
3
+ export declare const MAX_OUTPUT_BYTES: number;
4
+ export declare const DEFAULT_COMMAND_TIMEOUT_MS = 120000;
5
+ export type ShellDeltaEmitter = (stream: "stdout" | "stderr", delta: string, sequence: number) => void;
6
+ export type ParsedShellFooter = {
7
+ output: string;
8
+ exitCode: number;
9
+ cwd: string;
10
+ };
11
+ export declare function buildWrappedCommand(userCommand: string): string;
12
+ export declare function parseShellFooter(raw: string): ParsedShellFooter | null;
13
+ export declare function executeShellExec(args: Record<string, unknown>, onDelta: ShellDeltaEmitter, timeoutMs?: number): Promise<{
14
+ exitCode: number;
15
+ sessionId: string;
16
+ }>;
17
+ export declare function resetShellSession(sessionId: string): {
18
+ sessionId: string;
19
+ };
20
+ export declare function clearShellSessionsForTests(): void;
21
+ //# sourceMappingURL=shellSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shellSession.d.ts","sourceRoot":"","sources":["../src/shellSession.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,gBAAgB,QAAa,CAAC;AAC3C,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAGlD,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAEvG,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAmBF,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAWtE;AAmJD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,iBAAiB,EAC1B,SAAS,GAAE,MAAmC,GAC7C,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwClD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAU1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAOjD"}
@@ -0,0 +1,210 @@
1
+ import { spawn } from "node:child_process";
2
+ import { homedir } from "node:os";
3
+ export const EXIT_SENTINEL_PREFIX = "__CLEOS_EXIT_";
4
+ export const EXIT_SENTINEL_SUFFIX = "__";
5
+ export const MAX_OUTPUT_BYTES = 512 * 1024;
6
+ export const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
7
+ const SENTINEL_TAIL_RESERVE = 64;
8
+ const sessions = new Map();
9
+ export function buildWrappedCommand(userCommand) {
10
+ const encoded = Buffer.from(userCommand, "utf8").toString("base64");
11
+ return `{ eval "$(printf '%s' '${encoded}' | base64 -d)"; ec=$?; echo "${EXIT_SENTINEL_PREFIX}\${ec}${EXIT_SENTINEL_SUFFIX}"; pwd; } 2>&1\n`;
12
+ }
13
+ export function parseShellFooter(raw) {
14
+ const pattern = new RegExp(`\\n${EXIT_SENTINEL_PREFIX}(\\d+)${EXIT_SENTINEL_SUFFIX}\\n([^\\n]*)\\n?$`);
15
+ const match = raw.match(pattern);
16
+ if (!match || match.index === undefined)
17
+ return null;
18
+ return {
19
+ output: raw.slice(0, match.index),
20
+ exitCode: Number.parseInt(match[1] ?? "0", 10),
21
+ cwd: match[2] ?? "",
22
+ };
23
+ }
24
+ function truncateNotice() {
25
+ return `\n[output truncated at ${MAX_OUTPUT_BYTES} bytes]\n`;
26
+ }
27
+ function emitDelta(session, delta) {
28
+ if (!delta || !session.pendingOnDelta)
29
+ return;
30
+ session.pendingSequence += 1;
31
+ session.pendingOnDelta("stdout", delta, session.pendingSequence);
32
+ }
33
+ function flushStreamableOutput(session) {
34
+ const footer = parseShellFooter(session.pendingBuffer);
35
+ if (footer) {
36
+ const newOutput = footer.output.slice(session.streamedLength);
37
+ emitDelta(session, newOutput);
38
+ session.streamedLength = footer.output.length;
39
+ return;
40
+ }
41
+ const reserveStart = Math.max(0, session.pendingBuffer.length - SENTINEL_TAIL_RESERVE);
42
+ const streamableEnd = reserveStart;
43
+ if (streamableEnd > session.streamedLength) {
44
+ emitDelta(session, session.pendingBuffer.slice(session.streamedLength, streamableEnd));
45
+ session.streamedLength = streamableEnd;
46
+ }
47
+ }
48
+ function finishPending(session, footer) {
49
+ if (session.pendingTimeout) {
50
+ clearTimeout(session.pendingTimeout);
51
+ session.pendingTimeout = null;
52
+ }
53
+ const resolve = session.pendingResolve;
54
+ session.pendingResolve = null;
55
+ session.pendingReject = null;
56
+ session.pendingOnDelta = null;
57
+ session.pendingBuffer = "";
58
+ session.streamedLength = 0;
59
+ session.pendingBytes = 0;
60
+ session.pendingTruncated = false;
61
+ session.busy = false;
62
+ if (footer.cwd)
63
+ session.cwd = footer.cwd;
64
+ resolve?.(footer);
65
+ }
66
+ function failPending(session, error) {
67
+ if (session.pendingTimeout) {
68
+ clearTimeout(session.pendingTimeout);
69
+ session.pendingTimeout = null;
70
+ }
71
+ const reject = session.pendingReject;
72
+ session.pendingResolve = null;
73
+ session.pendingReject = null;
74
+ session.pendingOnDelta = null;
75
+ session.pendingBuffer = "";
76
+ session.streamedLength = 0;
77
+ session.pendingBytes = 0;
78
+ session.pendingTruncated = false;
79
+ session.busy = false;
80
+ reject?.(error);
81
+ }
82
+ function appendPendingData(session, chunk) {
83
+ session.pendingBytes += Buffer.byteLength(chunk, "utf8");
84
+ if (session.pendingBytes > MAX_OUTPUT_BYTES) {
85
+ if (!session.pendingTruncated) {
86
+ session.pendingTruncated = true;
87
+ const allowed = MAX_OUTPUT_BYTES - Buffer.byteLength(truncateNotice(), "utf8");
88
+ const currentBytes = Buffer.byteLength(session.pendingBuffer, "utf8");
89
+ if (currentBytes > allowed) {
90
+ session.pendingBuffer = Buffer.from(session.pendingBuffer, "utf8")
91
+ .subarray(0, allowed)
92
+ .toString("utf8");
93
+ }
94
+ session.pendingBuffer += truncateNotice();
95
+ flushStreamableOutput(session);
96
+ }
97
+ return;
98
+ }
99
+ session.pendingBuffer += chunk;
100
+ flushStreamableOutput(session);
101
+ const footer = parseShellFooter(session.pendingBuffer);
102
+ if (footer) {
103
+ finishPending(session, footer);
104
+ }
105
+ }
106
+ function attachSessionHandlers(sessionId, session) {
107
+ session.process.stdout.on("data", (data) => {
108
+ appendPendingData(session, data.toString("utf8"));
109
+ });
110
+ session.process.stderr.on("data", (data) => {
111
+ if (session.pendingOnDelta && session.busy) {
112
+ session.pendingSequence += 1;
113
+ session.pendingOnDelta("stderr", data.toString("utf8"), session.pendingSequence);
114
+ }
115
+ });
116
+ session.process.on("exit", () => {
117
+ sessions.delete(sessionId);
118
+ if (session.busy) {
119
+ failPending(session, new Error("Shell session exited unexpectedly"));
120
+ }
121
+ });
122
+ }
123
+ function createSession(sessionId) {
124
+ const child = spawn("bash", ["--login"], {
125
+ cwd: homedir(),
126
+ env: process.env,
127
+ stdio: ["pipe", "pipe", "pipe"],
128
+ });
129
+ const session = {
130
+ process: child,
131
+ cwd: homedir(),
132
+ busy: false,
133
+ pendingBuffer: "",
134
+ pendingResolve: null,
135
+ pendingReject: null,
136
+ pendingTimeout: null,
137
+ pendingSequence: 0,
138
+ pendingBytes: 0,
139
+ pendingTruncated: false,
140
+ pendingOnDelta: null,
141
+ streamedLength: 0,
142
+ };
143
+ attachSessionHandlers(sessionId, session);
144
+ sessions.set(sessionId, session);
145
+ return session;
146
+ }
147
+ function getOrCreateSession(sessionId) {
148
+ const existing = sessions.get(sessionId);
149
+ if (existing && existing.process.exitCode === null && !existing.process.killed) {
150
+ return existing;
151
+ }
152
+ if (existing)
153
+ sessions.delete(sessionId);
154
+ return createSession(sessionId);
155
+ }
156
+ export async function executeShellExec(args, onDelta, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS) {
157
+ const command = args.command;
158
+ const sessionIdRaw = args.sessionId;
159
+ if (typeof command !== "string" || command.trim().length === 0) {
160
+ throw new Error('Missing "command"');
161
+ }
162
+ if (typeof sessionIdRaw !== "string" || sessionIdRaw.trim().length === 0) {
163
+ throw new Error('Missing "sessionId"');
164
+ }
165
+ const sessionId = sessionIdRaw.trim();
166
+ const session = getOrCreateSession(sessionId);
167
+ if (session.busy) {
168
+ throw new Error("Shell session is busy");
169
+ }
170
+ session.busy = true;
171
+ session.pendingBuffer = "";
172
+ session.streamedLength = 0;
173
+ session.pendingBytes = 0;
174
+ session.pendingTruncated = false;
175
+ session.pendingSequence = 0;
176
+ session.pendingOnDelta = onDelta;
177
+ const wrapped = buildWrappedCommand(command);
178
+ return await new Promise((resolve, reject) => {
179
+ session.pendingResolve = (footer) => {
180
+ resolve({ exitCode: footer.exitCode, sessionId });
181
+ };
182
+ session.pendingReject = reject;
183
+ session.pendingTimeout = setTimeout(() => {
184
+ failPending(session, new Error(`Command timed out after ${timeoutMs}ms`));
185
+ }, timeoutMs);
186
+ const wrote = session.process.stdin.write(wrapped);
187
+ if (!wrote) {
188
+ session.process.stdin.once("drain", () => undefined);
189
+ }
190
+ });
191
+ }
192
+ export function resetShellSession(sessionId) {
193
+ const existing = sessions.get(sessionId);
194
+ if (existing) {
195
+ if (existing.busy) {
196
+ throw new Error("Shell session is busy");
197
+ }
198
+ existing.process.kill("SIGTERM");
199
+ sessions.delete(sessionId);
200
+ }
201
+ return { sessionId };
202
+ }
203
+ export function clearShellSessionsForTests() {
204
+ for (const [sessionId, session] of sessions.entries()) {
205
+ if (!session.busy) {
206
+ session.process.kill("SIGTERM");
207
+ }
208
+ sessions.delete(sessionId);
209
+ }
210
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shellSession.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shellSession.test.d.ts","sourceRoot":"","sources":["../src/shellSession.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { buildWrappedCommand, parseShellFooter, EXIT_SENTINEL_PREFIX, EXIT_SENTINEL_SUFFIX, } from "./shellSession.js";
4
+ test("parseShellFooter extracts output, exit code, and cwd", () => {
5
+ const raw = "line one\nline two\n" + `${EXIT_SENTINEL_PREFIX}1${EXIT_SENTINEL_SUFFIX}\n/tmp\n`;
6
+ const parsed = parseShellFooter(raw);
7
+ assert.ok(parsed);
8
+ assert.equal(parsed.output, "line one\nline two");
9
+ assert.equal(parsed.exitCode, 1);
10
+ assert.equal(parsed.cwd, "/tmp");
11
+ });
12
+ test("parseShellFooter returns null when sentinel is missing", () => {
13
+ assert.equal(parseShellFooter("hello\nworld\n"), null);
14
+ });
15
+ test("parseShellFooter handles exit code zero", () => {
16
+ const raw = "ok\n" + `${EXIT_SENTINEL_PREFIX}0${EXIT_SENTINEL_SUFFIX}\n/home/user\n`;
17
+ const parsed = parseShellFooter(raw);
18
+ assert.ok(parsed);
19
+ assert.equal(parsed.exitCode, 0);
20
+ assert.equal(parsed.cwd, "/home/user");
21
+ });
22
+ test("buildWrappedCommand base64-encodes user input", () => {
23
+ const wrapped = buildWrappedCommand("echo hello");
24
+ assert.match(wrapped, /base64 -d/);
25
+ assert.match(wrapped, new RegExp(`${EXIT_SENTINEL_PREFIX}\\$\\{ec\\}${EXIT_SENTINEL_SUFFIX}`));
26
+ assert.match(wrapped, /pwd/);
27
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saleso.innovations/bridge",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Connect your Hermes agent to the Cleos iOS app via pairing code.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "lint": "eslint . --max-warnings 0",
37
37
  "check-types": "tsc --noEmit",
38
38
  "prepublishOnly": "npm run build",
39
- "test": "node --import tsx --test src/hermesFiles.test.ts"
39
+ "test": "node --import tsx --test src/hermesFiles.test.ts src/shellSession.test.ts"
40
40
  },
41
41
  "dependencies": {
42
42
  "better-sqlite3": "^11.10.0",