@saleso.innovations/bridge 0.1.10 → 0.1.12
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 +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +63 -3
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -0
- package/dist/ensureHermesApi.d.ts.map +1 -1
- package/dist/ensureHermesApi.js +16 -67
- package/dist/gatewayControl.d.ts +12 -0
- package/dist/gatewayControl.d.ts.map +1 -0
- package/dist/gatewayControl.js +119 -0
- package/dist/hermesCommands.d.ts +18 -0
- package/dist/hermesCommands.d.ts.map +1 -0
- package/dist/hermesCommands.js +268 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ This installs the latest package, refreshes the CLI symlink, and restarts `cleos
|
|
|
34
34
|
To pin a specific release (e.g. after a Cleos update):
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash -s -- 0.1.
|
|
37
|
+
curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash -s -- 0.1.12
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Manual usage
|
package/dist/client.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
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;
|
|
@@ -94,6 +96,60 @@ function createReplySender(ws, agentId, conversationId, messageId) {
|
|
|
94
96
|
},
|
|
95
97
|
};
|
|
96
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
|
+
}
|
|
97
153
|
function userSafeHermesError(error) {
|
|
98
154
|
const raw = error instanceof Error ? error.message : String(error);
|
|
99
155
|
if (raw.includes("not reachable") || raw.includes("health check")) {
|
|
@@ -126,6 +182,10 @@ async function openAgentConnection(options) {
|
|
|
126
182
|
ws.on("message", (raw) => {
|
|
127
183
|
void (async () => {
|
|
128
184
|
const envelope = JSON.parse(raw.toString());
|
|
185
|
+
if (envelope.type === "hermes.command") {
|
|
186
|
+
await handleHermesCommandEnvelope(ws, envelope, options.agentId);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
129
189
|
if (envelope.type !== "user.message")
|
|
130
190
|
return;
|
|
131
191
|
const content = typeof envelope.content === "string" ? envelope.content : "";
|
|
@@ -200,7 +260,7 @@ export async function pairCleosAgent(options) {
|
|
|
200
260
|
body: JSON.stringify({
|
|
201
261
|
code: pairing.code,
|
|
202
262
|
agentName: options.agentName ?? "Hermes",
|
|
203
|
-
capabilities: options.capabilities ??
|
|
263
|
+
capabilities: options.capabilities ?? DEFAULT_BRIDGE_CAPABILITIES,
|
|
204
264
|
}),
|
|
205
265
|
});
|
|
206
266
|
if (!response.ok) {
|
|
@@ -228,7 +288,7 @@ export async function connectHermesAgent(options) {
|
|
|
228
288
|
relayWsUrl: credentials.relayWsUrl,
|
|
229
289
|
agentId: credentials.agentId,
|
|
230
290
|
agentToken: credentials.agentToken,
|
|
231
|
-
capabilities: options.capabilities ??
|
|
291
|
+
capabilities: options.capabilities ?? DEFAULT_BRIDGE_CAPABILITIES,
|
|
232
292
|
onUserMessage: options.onUserMessage,
|
|
233
293
|
});
|
|
234
294
|
}
|
|
@@ -242,7 +302,7 @@ export async function reconnectHermesAgent(options = {}) {
|
|
|
242
302
|
relayWsUrl: credentials.relayWsUrl,
|
|
243
303
|
agentId: credentials.agentId,
|
|
244
304
|
agentToken: credentials.agentToken,
|
|
245
|
-
capabilities: options.capabilities ??
|
|
305
|
+
capabilities: options.capabilities ?? DEFAULT_BRIDGE_CAPABILITIES,
|
|
246
306
|
onUserMessage: options.onUserMessage,
|
|
247
307
|
});
|
|
248
308
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -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). */
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -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). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensureHermesApi.d.ts","sourceRoot":"","sources":["../src/ensureHermesApi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ensureHermesApi.d.ts","sourceRoot":"","sources":["../src/ensureHermesApi.ts"],"names":[],"mappings":"AAsEA,wBAAsB,yBAAyB,IAAI,OAAO,CAAC;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CA0BD;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB5D"}
|
package/dist/ensureHermesApi.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { execFile, spawn } from "node:child_process";
|
|
3
2
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
4
|
import { join } from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
|
-
import { DEFAULT_HERMES_API_URL } from "./constants.js";
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
5
|
const HERMES_ENV_PATH = join(homedir(), ".hermes", ".env");
|
|
10
|
-
const HEALTH_URL = DEFAULT_HERMES_API_URL.replace(/\/v1\/chat\/completions\/?$/, "/health");
|
|
11
|
-
const HEALTH_POLL_MS = 1_000;
|
|
12
|
-
const HEALTH_TIMEOUT_MS = 30_000;
|
|
13
6
|
function parseEnvLine(line) {
|
|
14
7
|
const trimmed = line.trim();
|
|
15
8
|
if (!trimmed)
|
|
@@ -89,70 +82,26 @@ export async function ensureHermesApiConfigured() {
|
|
|
89
82
|
}
|
|
90
83
|
return { envPath: HERMES_ENV_PATH, changed, apiKey };
|
|
91
84
|
}
|
|
92
|
-
async function fetchHermesHealth(apiKey) {
|
|
93
|
-
const headers = {};
|
|
94
|
-
if (apiKey)
|
|
95
|
-
headers.authorization = `Bearer ${apiKey}`;
|
|
96
|
-
try {
|
|
97
|
-
const response = await fetch(HEALTH_URL, { headers, signal: AbortSignal.timeout(2_000) });
|
|
98
|
-
return response.ok;
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
async function tryRestartHermesGateway() {
|
|
105
|
-
const units = ["hermes-gateway.service", "hermes.service", "hermes-gateway", "hermes"];
|
|
106
|
-
for (const unit of units) {
|
|
107
|
-
try {
|
|
108
|
-
await execFileAsync("systemctl", ["try-restart", unit], { timeout: 10_000 });
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// Unit may not exist — keep trying other names.
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async function tryStartHermesGateway() {
|
|
116
|
-
await new Promise((resolve) => {
|
|
117
|
-
try {
|
|
118
|
-
const child = spawn("hermes", ["gateway"], {
|
|
119
|
-
detached: true,
|
|
120
|
-
stdio: "ignore",
|
|
121
|
-
});
|
|
122
|
-
child.on("error", () => resolve());
|
|
123
|
-
child.unref();
|
|
124
|
-
resolve();
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
resolve();
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
async function waitForHermesHealth(apiKey) {
|
|
132
|
-
const deadline = Date.now() + HEALTH_TIMEOUT_MS;
|
|
133
|
-
while (Date.now() < deadline) {
|
|
134
|
-
if (await fetchHermesHealth(apiKey))
|
|
135
|
-
return true;
|
|
136
|
-
await new Promise((resolve) => setTimeout(resolve, HEALTH_POLL_MS));
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
85
|
export async function prepareHermesForBridge() {
|
|
141
|
-
const { envPath, changed
|
|
86
|
+
const { envPath, changed } = await ensureHermesApiConfigured();
|
|
87
|
+
const { startHermesGateway } = await import("./gatewayControl.js");
|
|
142
88
|
if (changed) {
|
|
143
89
|
console.log(`Configured Hermes API in ${envPath}`);
|
|
144
90
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.log("Restarting Hermes gateway to apply API settings...");
|
|
149
|
-
await tryRestartHermesGateway();
|
|
150
|
-
if (await waitForHermesHealth(apiKey))
|
|
91
|
+
try {
|
|
92
|
+
const result = await startHermesGateway();
|
|
93
|
+
if (result.alreadyRunning && !changed) {
|
|
151
94
|
return;
|
|
95
|
+
}
|
|
96
|
+
if (changed) {
|
|
97
|
+
console.log("Restarting Hermes gateway to apply API settings...");
|
|
98
|
+
}
|
|
99
|
+
else if (!result.alreadyRunning) {
|
|
100
|
+
console.log("Hermes gateway not detected — starting `hermes gateway`...");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
throw new Error(message);
|
|
152
106
|
}
|
|
153
|
-
console.log("Hermes gateway not detected — starting `hermes gateway`...");
|
|
154
|
-
await tryStartHermesGateway();
|
|
155
|
-
if (await waitForHermesHealth(apiKey))
|
|
156
|
-
return;
|
|
157
|
-
throw new Error("Hermes is not running. Install Hermes, then run `hermes gateway` once on this machine and retry.");
|
|
158
107
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function startHermesGateway(): Promise<{
|
|
2
|
+
ok: true;
|
|
3
|
+
alreadyRunning: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function stopHermesGateway(): Promise<{
|
|
6
|
+
ok: true;
|
|
7
|
+
wasRunning: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function restartHermesGateway(): Promise<{
|
|
10
|
+
ok: true;
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=gatewayControl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gatewayControl.d.ts","sourceRoot":"","sources":["../src/gatewayControl.ts"],"names":[],"mappings":"AA8EA,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC,CAezF;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAiBpF;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAyBlE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { DEFAULT_HERMES_API_URL } from "./constants.js";
|
|
4
|
+
import { ensureHermesApiConfigured } from "./ensureHermesApi.js";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const HEALTH_URL = DEFAULT_HERMES_API_URL.replace(/\/v1\/chat\/completions\/?$/, "/health");
|
|
7
|
+
const HEALTH_POLL_MS = 1_000;
|
|
8
|
+
const HEALTH_TIMEOUT_MS = 30_000;
|
|
9
|
+
const GATEWAY_UNITS = ["hermes-gateway.service", "hermes.service", "hermes-gateway", "hermes"];
|
|
10
|
+
async function fetchHermesHealth(apiKey) {
|
|
11
|
+
const headers = {};
|
|
12
|
+
if (apiKey)
|
|
13
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(HEALTH_URL, { headers, signal: AbortSignal.timeout(2_000) });
|
|
16
|
+
return response.ok;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function waitForHermesHealth(apiKey) {
|
|
23
|
+
const deadline = Date.now() + HEALTH_TIMEOUT_MS;
|
|
24
|
+
while (Date.now() < deadline) {
|
|
25
|
+
if (await fetchHermesHealth(apiKey))
|
|
26
|
+
return true;
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, HEALTH_POLL_MS));
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
async function tryRestartHermesGatewayUnits() {
|
|
32
|
+
for (const unit of GATEWAY_UNITS) {
|
|
33
|
+
try {
|
|
34
|
+
await execFileAsync("systemctl", ["try-restart", unit], { timeout: 10_000 });
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Unit may not exist — keep trying other names.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function tryStopHermesGatewayUnits() {
|
|
42
|
+
for (const unit of GATEWAY_UNITS) {
|
|
43
|
+
try {
|
|
44
|
+
await execFileAsync("systemctl", ["stop", unit], { timeout: 10_000 });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Unit may not exist — keep trying other names.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function tryKillHermesGatewayProcess() {
|
|
52
|
+
try {
|
|
53
|
+
await execFileAsync("pkill", ["-f", "hermes gateway"], { timeout: 5_000 });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// No matching process — that's fine.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function spawnHermesGateway() {
|
|
60
|
+
await new Promise((resolve) => {
|
|
61
|
+
try {
|
|
62
|
+
const child = spawn("hermes", ["gateway"], {
|
|
63
|
+
detached: true,
|
|
64
|
+
stdio: "ignore",
|
|
65
|
+
});
|
|
66
|
+
child.on("error", () => resolve());
|
|
67
|
+
child.unref();
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
resolve();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export async function startHermesGateway() {
|
|
76
|
+
const { apiKey } = await ensureHermesApiConfigured();
|
|
77
|
+
if (await fetchHermesHealth(apiKey)) {
|
|
78
|
+
return { ok: true, alreadyRunning: true };
|
|
79
|
+
}
|
|
80
|
+
await spawnHermesGateway();
|
|
81
|
+
if (await waitForHermesHealth(apiKey)) {
|
|
82
|
+
return { ok: true, alreadyRunning: false };
|
|
83
|
+
}
|
|
84
|
+
throw new Error("Hermes gateway did not become healthy. Install Hermes, then run `hermes gateway` on this machine and retry.");
|
|
85
|
+
}
|
|
86
|
+
export async function stopHermesGateway() {
|
|
87
|
+
const { apiKey } = await ensureHermesApiConfigured();
|
|
88
|
+
const wasRunning = await fetchHermesHealth(apiKey);
|
|
89
|
+
await tryStopHermesGatewayUnits();
|
|
90
|
+
await tryKillHermesGatewayProcess();
|
|
91
|
+
if (wasRunning) {
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, HEALTH_POLL_MS));
|
|
93
|
+
}
|
|
94
|
+
const stillRunning = await fetchHermesHealth(apiKey);
|
|
95
|
+
if (stillRunning) {
|
|
96
|
+
throw new Error("Hermes gateway is still running. Stop it manually on the agent machine.");
|
|
97
|
+
}
|
|
98
|
+
return { ok: true, wasRunning };
|
|
99
|
+
}
|
|
100
|
+
export async function restartHermesGateway() {
|
|
101
|
+
const { changed, apiKey } = await ensureHermesApiConfigured();
|
|
102
|
+
if (changed) {
|
|
103
|
+
await tryRestartHermesGatewayUnits();
|
|
104
|
+
if (await waitForHermesHealth(apiKey)) {
|
|
105
|
+
return { ok: true };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (await fetchHermesHealth(apiKey)) {
|
|
109
|
+
await tryRestartHermesGatewayUnits();
|
|
110
|
+
if (await waitForHermesHealth(apiKey)) {
|
|
111
|
+
return { ok: true };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
await spawnHermesGateway();
|
|
115
|
+
if (await waitForHermesHealth(apiKey)) {
|
|
116
|
+
return { ok: true };
|
|
117
|
+
}
|
|
118
|
+
throw new Error("Hermes gateway did not restart successfully. Run `hermes gateway` on this machine and retry.");
|
|
119
|
+
}
|
|
@@ -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", "gateway.start", "gateway.stop", "gateway.restart"];
|
|
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":"AAGA,eAAO,MAAM,oBAAoB,oXAuBvB,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,CAyJlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { resolveHermesApiConfig } from "./hermesForwarder.js";
|
|
2
|
+
import { restartHermesGateway, startHermesGateway, stopHermesGateway } from "./gatewayControl.js";
|
|
3
|
+
export const HERMES_COMMAND_NAMES = [
|
|
4
|
+
"runtime.health",
|
|
5
|
+
"runtime.detailedHealth",
|
|
6
|
+
"runtime.capabilities",
|
|
7
|
+
"models.list",
|
|
8
|
+
"model.set",
|
|
9
|
+
"responses.create",
|
|
10
|
+
"runs.create",
|
|
11
|
+
"runs.status",
|
|
12
|
+
"runs.stop",
|
|
13
|
+
"jobs.list",
|
|
14
|
+
"jobs.get",
|
|
15
|
+
"jobs.create",
|
|
16
|
+
"jobs.update",
|
|
17
|
+
"jobs.pause",
|
|
18
|
+
"jobs.resume",
|
|
19
|
+
"jobs.runNow",
|
|
20
|
+
"jobs.delete",
|
|
21
|
+
"profiles.list",
|
|
22
|
+
"profiles.create",
|
|
23
|
+
"gateway.start",
|
|
24
|
+
"gateway.stop",
|
|
25
|
+
"gateway.restart",
|
|
26
|
+
];
|
|
27
|
+
export function isHermesCommandName(value) {
|
|
28
|
+
return HERMES_COMMAND_NAMES.includes(value);
|
|
29
|
+
}
|
|
30
|
+
export class HermesCommandError extends Error {
|
|
31
|
+
code;
|
|
32
|
+
constructor(code, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.code = code;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function hermesBaseUrl(apiUrl) {
|
|
38
|
+
return apiUrl.replace(/\/v1\/chat\/completions\/?$/, "");
|
|
39
|
+
}
|
|
40
|
+
function authHeaders(apiKey) {
|
|
41
|
+
const headers = { accept: "application/json" };
|
|
42
|
+
if (apiKey)
|
|
43
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
46
|
+
function requireString(args, key) {
|
|
47
|
+
const value = args[key];
|
|
48
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
49
|
+
throw new HermesCommandError("invalid_command_args", `Missing or invalid "${key}"`);
|
|
50
|
+
}
|
|
51
|
+
return value.trim();
|
|
52
|
+
}
|
|
53
|
+
function optionalString(args, key) {
|
|
54
|
+
const value = args[key];
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
58
|
+
throw new HermesCommandError("invalid_command_args", `Invalid "${key}"`);
|
|
59
|
+
}
|
|
60
|
+
return value.trim();
|
|
61
|
+
}
|
|
62
|
+
function optionalRecord(args, key) {
|
|
63
|
+
const value = args[key];
|
|
64
|
+
if (value === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
67
|
+
throw new HermesCommandError("invalid_command_args", `Invalid "${key}"`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
async function hermesFetchJson(config, path, init = {}) {
|
|
72
|
+
const url = `${hermesBaseUrl(config.apiUrl)}${path}`;
|
|
73
|
+
const headers = {
|
|
74
|
+
...authHeaders(config.apiKey),
|
|
75
|
+
...init.headers,
|
|
76
|
+
};
|
|
77
|
+
let response;
|
|
78
|
+
try {
|
|
79
|
+
response = await fetch(url, { ...init, headers });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
83
|
+
throw new HermesCommandError("hermes_unreachable", `Hermes is not reachable at ${url}. Ensure Hermes is running. ${message}`);
|
|
84
|
+
}
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new HermesCommandError("hermes_request_failed", `Hermes request failed (${response.status}): ${text || response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
if (!text.trim())
|
|
90
|
+
return {};
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(text);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return { raw: text };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function hermesFetchVoid(config, path, init = {}) {
|
|
99
|
+
await hermesFetchJson(config, path, init);
|
|
100
|
+
return { ok: true };
|
|
101
|
+
}
|
|
102
|
+
export async function executeHermesCommand(command, args, options = {}) {
|
|
103
|
+
const config = resolveHermesApiConfig(options);
|
|
104
|
+
switch (command) {
|
|
105
|
+
case "runtime.health":
|
|
106
|
+
return await hermesFetchJson(config, "/health");
|
|
107
|
+
case "runtime.detailedHealth":
|
|
108
|
+
return await hermesFetchJson(config, "/health/detailed");
|
|
109
|
+
case "runtime.capabilities":
|
|
110
|
+
return await hermesFetchJson(config, "/v1/capabilities");
|
|
111
|
+
case "models.list":
|
|
112
|
+
return await hermesFetchJson(config, "/v1/models");
|
|
113
|
+
case "model.set":
|
|
114
|
+
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.");
|
|
115
|
+
case "responses.create": {
|
|
116
|
+
const input = args.input ?? args.prompt;
|
|
117
|
+
if (typeof input !== "string" || input.trim().length === 0) {
|
|
118
|
+
throw new HermesCommandError("invalid_command_args", 'Missing "input" or "prompt"');
|
|
119
|
+
}
|
|
120
|
+
const body = {
|
|
121
|
+
model: optionalString(args, "model") ?? config.model,
|
|
122
|
+
input: input.trim(),
|
|
123
|
+
store: args.store ?? true,
|
|
124
|
+
};
|
|
125
|
+
const instructions = optionalString(args, "instructions");
|
|
126
|
+
if (instructions)
|
|
127
|
+
body.instructions = instructions;
|
|
128
|
+
const conversation = optionalString(args, "conversation");
|
|
129
|
+
if (conversation)
|
|
130
|
+
body.conversation = conversation;
|
|
131
|
+
const previousResponseId = optionalString(args, "previousResponseId");
|
|
132
|
+
if (previousResponseId)
|
|
133
|
+
body.previous_response_id = previousResponseId;
|
|
134
|
+
return await hermesFetchJson(config, "/v1/responses", {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "content-type": "application/json" },
|
|
137
|
+
body: JSON.stringify(body),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
case "runs.create": {
|
|
141
|
+
const input = args.input ?? args.prompt;
|
|
142
|
+
if (typeof input !== "string" || input.trim().length === 0) {
|
|
143
|
+
throw new HermesCommandError("invalid_command_args", 'Missing "input" or "prompt"');
|
|
144
|
+
}
|
|
145
|
+
const body = {
|
|
146
|
+
input: input.trim(),
|
|
147
|
+
};
|
|
148
|
+
const sessionId = optionalString(args, "sessionId");
|
|
149
|
+
if (sessionId)
|
|
150
|
+
body.session_id = sessionId;
|
|
151
|
+
const instructions = optionalString(args, "instructions");
|
|
152
|
+
if (instructions)
|
|
153
|
+
body.instructions = instructions;
|
|
154
|
+
const previousResponseId = optionalString(args, "previousResponseId");
|
|
155
|
+
if (previousResponseId)
|
|
156
|
+
body.previous_response_id = previousResponseId;
|
|
157
|
+
const conversationHistory = optionalRecord(args, "conversationHistory");
|
|
158
|
+
if (conversationHistory)
|
|
159
|
+
body.conversation_history = conversationHistory;
|
|
160
|
+
return await hermesFetchJson(config, "/v1/runs", {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { "content-type": "application/json" },
|
|
163
|
+
body: JSON.stringify(body),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
case "runs.status": {
|
|
167
|
+
const runId = requireString(args, "runId");
|
|
168
|
+
return await hermesFetchJson(config, `/v1/runs/${encodeURIComponent(runId)}`);
|
|
169
|
+
}
|
|
170
|
+
case "runs.stop": {
|
|
171
|
+
const runId = requireString(args, "runId");
|
|
172
|
+
return await hermesFetchJson(config, `/v1/runs/${encodeURIComponent(runId)}/stop`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "content-type": "application/json" },
|
|
175
|
+
body: JSON.stringify({}),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
case "jobs.list":
|
|
179
|
+
return await hermesFetchJson(config, "/api/jobs");
|
|
180
|
+
case "jobs.get": {
|
|
181
|
+
const jobId = requireString(args, "jobId");
|
|
182
|
+
return await hermesFetchJson(config, `/api/jobs/${encodeURIComponent(jobId)}`);
|
|
183
|
+
}
|
|
184
|
+
case "jobs.create": {
|
|
185
|
+
const body = optionalRecord(args, "job") ?? args;
|
|
186
|
+
if (!body || Object.keys(body).length === 0) {
|
|
187
|
+
throw new HermesCommandError("invalid_command_args", 'Missing job payload in "job" or args');
|
|
188
|
+
}
|
|
189
|
+
return await hermesFetchJson(config, "/api/jobs", {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: { "content-type": "application/json" },
|
|
192
|
+
body: JSON.stringify(body),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
case "jobs.update": {
|
|
196
|
+
const jobId = requireString(args, "jobId");
|
|
197
|
+
const patch = optionalRecord(args, "patch") ?? optionalRecord(args, "job");
|
|
198
|
+
if (!patch || Object.keys(patch).length === 0) {
|
|
199
|
+
throw new HermesCommandError("invalid_command_args", 'Missing "patch" or "job" update payload');
|
|
200
|
+
}
|
|
201
|
+
return await hermesFetchJson(config, `/api/jobs/${encodeURIComponent(jobId)}`, {
|
|
202
|
+
method: "PATCH",
|
|
203
|
+
headers: { "content-type": "application/json" },
|
|
204
|
+
body: JSON.stringify(patch),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
case "jobs.pause": {
|
|
208
|
+
const jobId = requireString(args, "jobId");
|
|
209
|
+
return await hermesFetchVoid(config, `/api/jobs/${encodeURIComponent(jobId)}/pause`, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: { "content-type": "application/json" },
|
|
212
|
+
body: JSON.stringify({}),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
case "jobs.resume": {
|
|
216
|
+
const jobId = requireString(args, "jobId");
|
|
217
|
+
return await hermesFetchVoid(config, `/api/jobs/${encodeURIComponent(jobId)}/resume`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "content-type": "application/json" },
|
|
220
|
+
body: JSON.stringify({}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
case "jobs.runNow": {
|
|
224
|
+
const jobId = requireString(args, "jobId");
|
|
225
|
+
return await hermesFetchJson(config, `/api/jobs/${encodeURIComponent(jobId)}/run`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: { "content-type": "application/json" },
|
|
228
|
+
body: JSON.stringify({}),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
case "jobs.delete": {
|
|
232
|
+
const jobId = requireString(args, "jobId");
|
|
233
|
+
return await hermesFetchVoid(config, `/api/jobs/${encodeURIComponent(jobId)}`, {
|
|
234
|
+
method: "DELETE",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
case "profiles.list":
|
|
238
|
+
throw new HermesCommandError("unsupported_by_http", "Listing Hermes profiles is not exposed over the HTTP API.");
|
|
239
|
+
case "profiles.create":
|
|
240
|
+
throw new HermesCommandError("unsupported_by_http", "Creating Hermes profiles is not exposed over the HTTP API. Run `hermes profile create` on the agent machine.");
|
|
241
|
+
case "gateway.start":
|
|
242
|
+
return await startHermesGateway();
|
|
243
|
+
case "gateway.stop":
|
|
244
|
+
return await stopHermesGateway();
|
|
245
|
+
case "gateway.restart":
|
|
246
|
+
return await restartHermesGateway();
|
|
247
|
+
default: {
|
|
248
|
+
const _exhaustive = command;
|
|
249
|
+
throw new HermesCommandError("command_unsupported", `Unsupported command: ${String(_exhaustive)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
export function userSafeCommandError(error) {
|
|
254
|
+
if (error instanceof HermesCommandError) {
|
|
255
|
+
return { code: error.code, message: error.message };
|
|
256
|
+
}
|
|
257
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
258
|
+
if (raw.includes("not reachable") || raw.includes("fetch failed")) {
|
|
259
|
+
return {
|
|
260
|
+
code: "hermes_unreachable",
|
|
261
|
+
message: "Hermes is not running. Start `hermes gateway` on your agent machine and try again.",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
code: "hermes_request_failed",
|
|
266
|
+
message: raw.length > 200 ? `${raw.slice(0, 200)}…` : raw,
|
|
267
|
+
};
|
|
268
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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";
|