@saleso.innovations/bridge 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,12 @@ curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash
31
31
 
32
32
  This installs the latest package, refreshes the CLI symlink, and restarts `cleos-bridge.service` when present.
33
33
 
34
+ To pin a specific release (e.g. after a Cleos update):
35
+
36
+ ```bash
37
+ curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash -s -- 0.1.11
38
+ ```
39
+
34
40
  ## Manual usage
35
41
 
36
42
  The install script puts the bridge in `~/.cleos/bridge`. To run without the symlink:
package/dist/client.d.ts CHANGED
@@ -28,10 +28,16 @@ export type AgentActivityPayload = {
28
28
  emoji?: string;
29
29
  sequence: number;
30
30
  };
31
+ export type AgentFailedPayload = {
32
+ error: string;
33
+ code?: string;
34
+ sequence?: number;
35
+ };
31
36
  export type AgentReply = {
32
37
  delta: (text: string, sequence: number) => void;
33
38
  activity: (activity: AgentActivityPayload) => void;
34
39
  complete: (text: string, sequence: number) => void;
40
+ failed: (failure: AgentFailedPayload) => void;
35
41
  };
36
42
  export type CronDeliveryMeta = {
37
43
  conversationId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAKhG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,cAAc,GAAG,gBAAgB,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtE,CAAC;AA0MF,wBAAsB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CA2BD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAcxF;AAED,wBAAsB,oBAAoB,CAAC,OAAO,GAAE;IAClD,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC5C,GAAG,OAAO,CAAC,aAAa,CAAC,CAc9B"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAYhG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,cAAc,GAAG,gBAAgB,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtE,CAAC;AAoTF,wBAAsB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CA2BD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAcxF;AAED,wBAAsB,oBAAoB,CAAC,OAAO,GAAE;IAClD,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC5C,GAAG,OAAO,CAAC,aAAa,CAAC,CAc9B"}
package/dist/client.js CHANGED
@@ -4,6 +4,8 @@ import { saveCredentials, loadCredentials } from "./credentials.js";
4
4
  import { rememberConversationId } from "./activeConversation.js";
5
5
  import { convexSiteUrlFromEnv, resolvePairingCode } from "./resolve.js";
6
6
  import { normalizePairingCode } from "./normalizePairingCode.js";
7
+ import { executeHermesCommand, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
8
+ import { DEFAULT_BRIDGE_CAPABILITIES } from "./constants.js";
7
9
  function parseUserMessageAttachments(raw) {
8
10
  if (!Array.isArray(raw))
9
11
  return undefined;
@@ -81,8 +83,86 @@ function createReplySender(ws, agentId, conversationId, messageId) {
81
83
  final: true,
82
84
  }));
83
85
  },
86
+ failed(failure) {
87
+ ws.send(JSON.stringify({
88
+ type: "agent.failed",
89
+ agentId,
90
+ conversationId,
91
+ messageId,
92
+ error: failure.error,
93
+ code: failure.code,
94
+ sequence: failure.sequence ?? 0,
95
+ }));
96
+ },
84
97
  };
85
98
  }
99
+ function parseCommandArgs(raw) {
100
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
101
+ return {};
102
+ return raw;
103
+ }
104
+ async function handleHermesCommandEnvelope(ws, envelope, fallbackAgentId) {
105
+ const requestId = typeof envelope.requestId === "string" ? envelope.requestId : "";
106
+ const agentId = typeof envelope.agentId === "string" ? envelope.agentId : fallbackAgentId;
107
+ const commandRaw = typeof envelope.command === "string" ? envelope.command : "";
108
+ if (!requestId || !isHermesCommandName(commandRaw)) {
109
+ ws.send(JSON.stringify({
110
+ type: "hermes.command.failed",
111
+ requestId: requestId || randomUUID(),
112
+ agentId,
113
+ command: isHermesCommandName(commandRaw) ? commandRaw : "runtime.health",
114
+ ok: false,
115
+ error: "Invalid Hermes command envelope",
116
+ code: "invalid_command_args",
117
+ }));
118
+ return;
119
+ }
120
+ const command = commandRaw;
121
+ ws.send(JSON.stringify({
122
+ type: "hermes.command.ack",
123
+ requestId,
124
+ agentId,
125
+ command,
126
+ status: "accepted",
127
+ }));
128
+ try {
129
+ const result = await executeHermesCommand(command, parseCommandArgs(envelope.args));
130
+ ws.send(JSON.stringify({
131
+ type: "hermes.command.result",
132
+ requestId,
133
+ agentId,
134
+ command,
135
+ ok: true,
136
+ result,
137
+ }));
138
+ }
139
+ catch (error) {
140
+ const { code, message } = userSafeCommandError(error);
141
+ console.error(JSON.stringify({ event: "cleos-bridge.command_error", command, code, message }));
142
+ ws.send(JSON.stringify({
143
+ type: "hermes.command.failed",
144
+ requestId,
145
+ agentId,
146
+ command,
147
+ ok: false,
148
+ error: message,
149
+ code,
150
+ }));
151
+ }
152
+ }
153
+ function userSafeHermesError(error) {
154
+ const raw = error instanceof Error ? error.message : String(error);
155
+ if (raw.includes("not reachable") || raw.includes("health check")) {
156
+ return {
157
+ code: "hermes_unreachable",
158
+ message: "Hermes is not running. Start `hermes gateway` on your agent machine and try again.",
159
+ };
160
+ }
161
+ if (raw.includes("Hermes request failed")) {
162
+ return { code: "hermes_request_failed", message: "Hermes could not complete your request. Try again." };
163
+ }
164
+ return { code: "handler_error", message: raw.length > 200 ? `${raw.slice(0, 200)}…` : raw };
165
+ }
86
166
  async function openAgentConnection(options) {
87
167
  const ws = new WebSocket(options.relayWsUrl);
88
168
  let closedResolve = null;
@@ -102,6 +182,10 @@ async function openAgentConnection(options) {
102
182
  ws.on("message", (raw) => {
103
183
  void (async () => {
104
184
  const envelope = JSON.parse(raw.toString());
185
+ if (envelope.type === "hermes.command") {
186
+ await handleHermesCommandEnvelope(ws, envelope, options.agentId);
187
+ return;
188
+ }
105
189
  if (envelope.type !== "user.message")
106
190
  return;
107
191
  const content = typeof envelope.content === "string" ? envelope.content : "";
@@ -114,7 +198,14 @@ async function openAgentConnection(options) {
114
198
  const replyMessageId = randomUUID();
115
199
  const reply = createReplySender(ws, agentId, conversationId, replyMessageId);
116
200
  if (options.onUserMessage) {
117
- await options.onUserMessage(content, { agentId, conversationId, messageId: replyMessageId, attachments }, reply);
201
+ try {
202
+ await options.onUserMessage(content, { agentId, conversationId, messageId: replyMessageId, attachments }, reply);
203
+ }
204
+ catch (error) {
205
+ const { message, code } = userSafeHermesError(error);
206
+ console.error(JSON.stringify({ event: "cleos-bridge.handler_error", code, message }));
207
+ reply.failed({ error: message, code, sequence: 0 });
208
+ }
118
209
  return;
119
210
  }
120
211
  const echo = `Echo: ${content}`;
@@ -169,7 +260,7 @@ export async function pairCleosAgent(options) {
169
260
  body: JSON.stringify({
170
261
  code: pairing.code,
171
262
  agentName: options.agentName ?? "Hermes",
172
- capabilities: options.capabilities ?? ["chat"],
263
+ capabilities: options.capabilities ?? DEFAULT_BRIDGE_CAPABILITIES,
173
264
  }),
174
265
  });
175
266
  if (!response.ok) {
@@ -197,7 +288,7 @@ export async function connectHermesAgent(options) {
197
288
  relayWsUrl: credentials.relayWsUrl,
198
289
  agentId: credentials.agentId,
199
290
  agentToken: credentials.agentToken,
200
- capabilities: options.capabilities ?? ["chat"],
291
+ capabilities: options.capabilities ?? DEFAULT_BRIDGE_CAPABILITIES,
201
292
  onUserMessage: options.onUserMessage,
202
293
  });
203
294
  }
@@ -211,7 +302,7 @@ export async function reconnectHermesAgent(options = {}) {
211
302
  relayWsUrl: credentials.relayWsUrl,
212
303
  agentId: credentials.agentId,
213
304
  agentToken: credentials.agentToken,
214
- capabilities: options.capabilities ?? ["chat"],
305
+ capabilities: options.capabilities ?? DEFAULT_BRIDGE_CAPABILITIES,
215
306
  onUserMessage: options.onUserMessage,
216
307
  });
217
308
  }
@@ -2,6 +2,9 @@
2
2
  export declare const DEFAULT_CLEOS_CONVEX_SITE_URL = "https://amicable-elephant-407.convex.site";
3
3
  /** Default Hermes OpenAI-compatible API (requires `hermes gateway` + API_SERVER_ENABLED). */
4
4
  export declare const DEFAULT_HERMES_API_URL = "http://127.0.0.1:8642/v1/chat/completions";
5
+ /** Capability advertised by current cleos-bridge builds. */
6
+ export declare const HERMES_COMMANDS_CAPABILITY = "hermes.commands.v1";
7
+ export declare const DEFAULT_BRIDGE_CAPABILITIES: string[];
5
8
  /** VPS one-line installer (served from public Convex HTTP). */
6
9
  export declare const DEFAULT_BRIDGE_INSTALL_URL = "https://amicable-elephant-407.convex.site/install-bridge.sh";
7
10
  /** VPS one-line updater (served from public Convex HTTP). */
@@ -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,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,gEACwB,CAAC;AAEhE,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,+DACwB,CAAC;AAE/D,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,eAAO,MAAM,2BAA2B,UAAuC,CAAC;AAEhF,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,gEACwB,CAAC;AAEhE,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,+DACwB,CAAC;AAE/D,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
package/dist/constants.js CHANGED
@@ -2,6 +2,9 @@
2
2
  export const DEFAULT_CLEOS_CONVEX_SITE_URL = "https://amicable-elephant-407.convex.site";
3
3
  /** Default Hermes OpenAI-compatible API (requires `hermes gateway` + API_SERVER_ENABLED). */
4
4
  export const DEFAULT_HERMES_API_URL = "http://127.0.0.1:8642/v1/chat/completions";
5
+ /** Capability advertised by current cleos-bridge builds. */
6
+ export const HERMES_COMMANDS_CAPABILITY = "hermes.commands.v1";
7
+ export const DEFAULT_BRIDGE_CAPABILITIES = ["chat", HERMES_COMMANDS_CAPABILITY];
5
8
  /** VPS one-line installer (served from public Convex HTTP). */
6
9
  export const DEFAULT_BRIDGE_INSTALL_URL = "https://amicable-elephant-407.convex.site/install-bridge.sh";
7
10
  /** VPS one-line updater (served from public Convex HTTP). */
@@ -0,0 +1,18 @@
1
+ export declare const HERMES_COMMAND_NAMES: readonly ["runtime.health", "runtime.detailedHealth", "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"];
2
+ export type HermesCommandName = (typeof HERMES_COMMAND_NAMES)[number];
3
+ export declare function isHermesCommandName(value: string): value is HermesCommandName;
4
+ export type HermesCommandErrorCode = "command_unsupported" | "hermes_unreachable" | "hermes_request_failed" | "invalid_command_args" | "unsupported_by_http";
5
+ export declare class HermesCommandError extends Error {
6
+ readonly code: HermesCommandErrorCode;
7
+ constructor(code: HermesCommandErrorCode, message: string);
8
+ }
9
+ export declare function executeHermesCommand(command: HermesCommandName, args: Record<string, unknown>, options?: {
10
+ apiUrl?: string;
11
+ apiKey?: string;
12
+ model?: string;
13
+ }): Promise<unknown>;
14
+ export declare function userSafeCommandError(error: unknown): {
15
+ code: HermesCommandErrorCode;
16
+ message: string;
17
+ };
18
+ //# sourceMappingURL=hermesCommands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermesCommands.d.ts","sourceRoot":"","sources":["../src/hermesCommands.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,gUAoBvB,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;AAuFD,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,CAmJlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
@@ -0,0 +1,258 @@
1
+ import { resolveHermesApiConfig } from "./hermesForwarder.js";
2
+ export const HERMES_COMMAND_NAMES = [
3
+ "runtime.health",
4
+ "runtime.detailedHealth",
5
+ "runtime.capabilities",
6
+ "models.list",
7
+ "model.set",
8
+ "responses.create",
9
+ "runs.create",
10
+ "runs.status",
11
+ "runs.stop",
12
+ "jobs.list",
13
+ "jobs.get",
14
+ "jobs.create",
15
+ "jobs.update",
16
+ "jobs.pause",
17
+ "jobs.resume",
18
+ "jobs.runNow",
19
+ "jobs.delete",
20
+ "profiles.list",
21
+ "profiles.create",
22
+ ];
23
+ export function isHermesCommandName(value) {
24
+ return HERMES_COMMAND_NAMES.includes(value);
25
+ }
26
+ export class HermesCommandError extends Error {
27
+ code;
28
+ constructor(code, message) {
29
+ super(message);
30
+ this.code = code;
31
+ }
32
+ }
33
+ function hermesBaseUrl(apiUrl) {
34
+ return apiUrl.replace(/\/v1\/chat\/completions\/?$/, "");
35
+ }
36
+ function authHeaders(apiKey) {
37
+ const headers = { accept: "application/json" };
38
+ if (apiKey)
39
+ headers.authorization = `Bearer ${apiKey}`;
40
+ return headers;
41
+ }
42
+ function requireString(args, key) {
43
+ const value = args[key];
44
+ if (typeof value !== "string" || value.trim().length === 0) {
45
+ throw new HermesCommandError("invalid_command_args", `Missing or invalid "${key}"`);
46
+ }
47
+ return value.trim();
48
+ }
49
+ function optionalString(args, key) {
50
+ const value = args[key];
51
+ if (value === undefined)
52
+ return undefined;
53
+ if (typeof value !== "string" || value.trim().length === 0) {
54
+ throw new HermesCommandError("invalid_command_args", `Invalid "${key}"`);
55
+ }
56
+ return value.trim();
57
+ }
58
+ function optionalRecord(args, key) {
59
+ const value = args[key];
60
+ if (value === undefined)
61
+ return undefined;
62
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
63
+ throw new HermesCommandError("invalid_command_args", `Invalid "${key}"`);
64
+ }
65
+ return value;
66
+ }
67
+ async function hermesFetchJson(config, path, init = {}) {
68
+ const url = `${hermesBaseUrl(config.apiUrl)}${path}`;
69
+ const headers = {
70
+ ...authHeaders(config.apiKey),
71
+ ...init.headers,
72
+ };
73
+ let response;
74
+ try {
75
+ response = await fetch(url, { ...init, headers });
76
+ }
77
+ catch (error) {
78
+ const message = error instanceof Error ? error.message : "Unknown error";
79
+ throw new HermesCommandError("hermes_unreachable", `Hermes is not reachable at ${url}. Ensure Hermes is running. ${message}`);
80
+ }
81
+ const text = await response.text();
82
+ if (!response.ok) {
83
+ throw new HermesCommandError("hermes_request_failed", `Hermes request failed (${response.status}): ${text || response.statusText}`);
84
+ }
85
+ if (!text.trim())
86
+ return {};
87
+ try {
88
+ return JSON.parse(text);
89
+ }
90
+ catch {
91
+ return { raw: text };
92
+ }
93
+ }
94
+ async function hermesFetchVoid(config, path, init = {}) {
95
+ await hermesFetchJson(config, path, init);
96
+ return { ok: true };
97
+ }
98
+ export async function executeHermesCommand(command, args, options = {}) {
99
+ const config = resolveHermesApiConfig(options);
100
+ switch (command) {
101
+ case "runtime.health":
102
+ return await hermesFetchJson(config, "/health");
103
+ case "runtime.detailedHealth":
104
+ return await hermesFetchJson(config, "/health/detailed");
105
+ case "runtime.capabilities":
106
+ return await hermesFetchJson(config, "/v1/capabilities");
107
+ case "models.list":
108
+ return await hermesFetchJson(config, "/v1/models");
109
+ case "model.set":
110
+ throw new HermesCommandError("unsupported_by_http", "Changing the active Hermes model is not exposed over the HTTP API. Update Hermes config on the agent machine.");
111
+ case "responses.create": {
112
+ const input = args.input ?? args.prompt;
113
+ if (typeof input !== "string" || input.trim().length === 0) {
114
+ throw new HermesCommandError("invalid_command_args", 'Missing "input" or "prompt"');
115
+ }
116
+ const body = {
117
+ model: optionalString(args, "model") ?? config.model,
118
+ input: input.trim(),
119
+ store: args.store ?? true,
120
+ };
121
+ const instructions = optionalString(args, "instructions");
122
+ if (instructions)
123
+ body.instructions = instructions;
124
+ const conversation = optionalString(args, "conversation");
125
+ if (conversation)
126
+ body.conversation = conversation;
127
+ const previousResponseId = optionalString(args, "previousResponseId");
128
+ if (previousResponseId)
129
+ body.previous_response_id = previousResponseId;
130
+ return await hermesFetchJson(config, "/v1/responses", {
131
+ method: "POST",
132
+ headers: { "content-type": "application/json" },
133
+ body: JSON.stringify(body),
134
+ });
135
+ }
136
+ case "runs.create": {
137
+ const input = args.input ?? args.prompt;
138
+ if (typeof input !== "string" || input.trim().length === 0) {
139
+ throw new HermesCommandError("invalid_command_args", 'Missing "input" or "prompt"');
140
+ }
141
+ const body = {
142
+ input: input.trim(),
143
+ };
144
+ const sessionId = optionalString(args, "sessionId");
145
+ if (sessionId)
146
+ body.session_id = sessionId;
147
+ const instructions = optionalString(args, "instructions");
148
+ if (instructions)
149
+ body.instructions = instructions;
150
+ const previousResponseId = optionalString(args, "previousResponseId");
151
+ if (previousResponseId)
152
+ body.previous_response_id = previousResponseId;
153
+ const conversationHistory = optionalRecord(args, "conversationHistory");
154
+ if (conversationHistory)
155
+ body.conversation_history = conversationHistory;
156
+ return await hermesFetchJson(config, "/v1/runs", {
157
+ method: "POST",
158
+ headers: { "content-type": "application/json" },
159
+ body: JSON.stringify(body),
160
+ });
161
+ }
162
+ case "runs.status": {
163
+ const runId = requireString(args, "runId");
164
+ return await hermesFetchJson(config, `/v1/runs/${encodeURIComponent(runId)}`);
165
+ }
166
+ case "runs.stop": {
167
+ const runId = requireString(args, "runId");
168
+ return await hermesFetchJson(config, `/v1/runs/${encodeURIComponent(runId)}/stop`, {
169
+ method: "POST",
170
+ headers: { "content-type": "application/json" },
171
+ body: JSON.stringify({}),
172
+ });
173
+ }
174
+ case "jobs.list":
175
+ return await hermesFetchJson(config, "/api/jobs");
176
+ case "jobs.get": {
177
+ const jobId = requireString(args, "jobId");
178
+ return await hermesFetchJson(config, `/api/jobs/${encodeURIComponent(jobId)}`);
179
+ }
180
+ case "jobs.create": {
181
+ const body = optionalRecord(args, "job") ?? args;
182
+ if (!body || Object.keys(body).length === 0) {
183
+ throw new HermesCommandError("invalid_command_args", 'Missing job payload in "job" or args');
184
+ }
185
+ return await hermesFetchJson(config, "/api/jobs", {
186
+ method: "POST",
187
+ headers: { "content-type": "application/json" },
188
+ body: JSON.stringify(body),
189
+ });
190
+ }
191
+ case "jobs.update": {
192
+ const jobId = requireString(args, "jobId");
193
+ const patch = optionalRecord(args, "patch") ?? optionalRecord(args, "job");
194
+ if (!patch || Object.keys(patch).length === 0) {
195
+ throw new HermesCommandError("invalid_command_args", 'Missing "patch" or "job" update payload');
196
+ }
197
+ return await hermesFetchJson(config, `/api/jobs/${encodeURIComponent(jobId)}`, {
198
+ method: "PATCH",
199
+ headers: { "content-type": "application/json" },
200
+ body: JSON.stringify(patch),
201
+ });
202
+ }
203
+ case "jobs.pause": {
204
+ const jobId = requireString(args, "jobId");
205
+ return await hermesFetchVoid(config, `/api/jobs/${encodeURIComponent(jobId)}/pause`, {
206
+ method: "POST",
207
+ headers: { "content-type": "application/json" },
208
+ body: JSON.stringify({}),
209
+ });
210
+ }
211
+ case "jobs.resume": {
212
+ const jobId = requireString(args, "jobId");
213
+ return await hermesFetchVoid(config, `/api/jobs/${encodeURIComponent(jobId)}/resume`, {
214
+ method: "POST",
215
+ headers: { "content-type": "application/json" },
216
+ body: JSON.stringify({}),
217
+ });
218
+ }
219
+ case "jobs.runNow": {
220
+ const jobId = requireString(args, "jobId");
221
+ return await hermesFetchJson(config, `/api/jobs/${encodeURIComponent(jobId)}/run`, {
222
+ method: "POST",
223
+ headers: { "content-type": "application/json" },
224
+ body: JSON.stringify({}),
225
+ });
226
+ }
227
+ case "jobs.delete": {
228
+ const jobId = requireString(args, "jobId");
229
+ return await hermesFetchVoid(config, `/api/jobs/${encodeURIComponent(jobId)}`, {
230
+ method: "DELETE",
231
+ });
232
+ }
233
+ case "profiles.list":
234
+ throw new HermesCommandError("unsupported_by_http", "Listing Hermes profiles is not exposed over the HTTP API.");
235
+ case "profiles.create":
236
+ throw new HermesCommandError("unsupported_by_http", "Creating Hermes profiles is not exposed over the HTTP API. Run `hermes profile create` on the agent machine.");
237
+ default: {
238
+ const _exhaustive = command;
239
+ throw new HermesCommandError("command_unsupported", `Unsupported command: ${String(_exhaustive)}`);
240
+ }
241
+ }
242
+ }
243
+ export function userSafeCommandError(error) {
244
+ if (error instanceof HermesCommandError) {
245
+ return { code: error.code, message: error.message };
246
+ }
247
+ const raw = error instanceof Error ? error.message : String(error);
248
+ if (raw.includes("not reachable") || raw.includes("fetch failed")) {
249
+ return {
250
+ code: "hermes_unreachable",
251
+ message: "Hermes is not running. Start `hermes gateway` on your agent machine and try again.",
252
+ };
253
+ }
254
+ return {
255
+ code: "hermes_request_failed",
256
+ message: raw.length > 200 ? `${raw.slice(0, 200)}…` : raw,
257
+ };
258
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { connectHermesAgent, pairCleosAgent, reconnectHermesAgent } from "./client.js";
2
- export type { AgentActivityPayload, AgentReply, ConnectOptions, ConnectResult, CronDeliveryMeta, UserMessageMeta, } from "./client.js";
2
+ export type { AgentActivityPayload, AgentFailedPayload, AgentReply, ConnectOptions, ConnectResult, CronDeliveryMeta, UserMessageMeta, } from "./client.js";
3
3
  export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./credentials.js";
4
4
  export type { SavedAgentCredentials } from "./credentials.js";
5
5
  export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
@@ -7,9 +7,11 @@ 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 } from "./hermesForwarder.js";
10
+ export { executeHermesCommand, HERMES_COMMAND_NAMES, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
11
+ export type { HermesCommandErrorCode, HermesCommandName } from "./hermesCommands.js";
10
12
  export { startCronWatcher } from "./cronWatcher.js";
11
13
  export { resolveActiveConversationId, rememberConversationId } from "./activeConversation.js";
12
14
  export { runBridgeDaemon } from "./daemon.js";
13
15
  export type { RunBridgeOptions } from "./daemon.js";
14
- export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
16
+ export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, DEFAULT_BRIDGE_CAPABILITIES, HERMES_COMMANDS_CAPABILITY, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
15
17
  //# 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,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,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,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,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,MAAM,sBAAsB,CAAC;AACnE,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,MAAM,kBAAkB,CAAC;AACpD,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,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ 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 { executeHermesCommand, HERMES_COMMAND_NAMES, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
6
7
  export { startCronWatcher } from "./cronWatcher.js";
7
8
  export { resolveActiveConversationId, rememberConversationId } from "./activeConversation.js";
8
9
  export { runBridgeDaemon } from "./daemon.js";
9
- export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
10
+ export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_BRIDGE_UPDATE_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, DEFAULT_BRIDGE_CAPABILITIES, HERMES_COMMANDS_CAPABILITY, bridgeInstallCommand, bridgeUpdateCommand, } from "./constants.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saleso.innovations/bridge",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Connect your Hermes agent to the Cleos iOS app via pairing code.",
5
5
  "type": "module",
6
6
  "license": "MIT",