@saleso.innovations/bridge 0.1.0
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/INTEGRATION.md +79 -0
- package/README.md +33 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +86 -0
- package/dist/client.d.ts +36 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +149 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +9 -0
- package/dist/credentials.d.ts +12 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +23 -0
- package/dist/daemon.d.ts +6 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +36 -0
- package/dist/hermesForwarder.d.ts +15 -0
- package/dist/hermesForwarder.d.ts.map +1 -0
- package/dist/hermesForwarder.js +142 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/pairWithCleos.d.ts +10 -0
- package/dist/pairWithCleos.d.ts.map +1 -0
- package/dist/pairWithCleos.js +15 -0
- package/dist/resolve.d.ts +11 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +16 -0
- package/package.json +47 -0
package/INTEGRATION.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Hermes ↔ Cleos integration
|
|
2
|
+
|
|
3
|
+
End users should **never** clone the Cleos repo or run npm scripts. Pairing is:
|
|
4
|
+
|
|
5
|
+
1. User opens the **Cleos iOS app** and gets an 8-character code.
|
|
6
|
+
2. User opens **their own Hermes** (already running on their machine/VPS).
|
|
7
|
+
3. User enters the code in Hermes (settings UI, web panel, or built-in command).
|
|
8
|
+
4. Done — their Hermes agent is linked to their Cleos account only.
|
|
9
|
+
|
|
10
|
+
Cleos provides this package as an SDK for **Hermes maintainers**, not for end users.
|
|
11
|
+
|
|
12
|
+
## Hermes maintainer checklist
|
|
13
|
+
|
|
14
|
+
- [ ] Add a **Connect to Cleos** screen in Hermes (web UI or CLI subcommand).
|
|
15
|
+
- [ ] User enters the 8-character code from the Cleos app.
|
|
16
|
+
- [ ] Call `pairWithCleos(code)` from this package (see below).
|
|
17
|
+
- [ ] Wire `onUserMessage` to your existing Hermes agent loop.
|
|
18
|
+
- [ ] On Hermes daemon start, call `reconnectHermesAgent()` if credentials exist.
|
|
19
|
+
- [ ] Ship `DEFAULT_CLEOS_CONVEX_SITE_URL` (or your self-hosted URL) in Hermes builds.
|
|
20
|
+
|
|
21
|
+
## Minimal integration
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { pairWithCleos, reconnectHermesAgent } from "@saleso.innovations/bridge";
|
|
25
|
+
|
|
26
|
+
// When user submits pairing code in Hermes UI:
|
|
27
|
+
export async function onCleosPairingCodeSubmitted(code: string) {
|
|
28
|
+
const session = await pairWithCleos(code, {
|
|
29
|
+
agentName: "My Hermes",
|
|
30
|
+
onUserMessage: async (content, meta, reply) => {
|
|
31
|
+
// Forward to YOUR Hermes agent — each user talks to their own agent only.
|
|
32
|
+
const stream = await myHermesAgent.chat(content, { conversationId: meta.conversationId });
|
|
33
|
+
let sequence = 0;
|
|
34
|
+
for await (const chunk of stream) {
|
|
35
|
+
reply.delta(chunk, sequence++);
|
|
36
|
+
}
|
|
37
|
+
reply.complete(stream.finalText, sequence);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return session.agentId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// On Hermes daemon startup:
|
|
44
|
+
export async function resumeCleosConnection() {
|
|
45
|
+
await reconnectHermesAgent({ onUserMessage: handleCleosMessage });
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## What Cleos handles
|
|
50
|
+
|
|
51
|
+
- User authentication (Google / Apple / dev skip)
|
|
52
|
+
- Pairing code generation and expiry
|
|
53
|
+
- Relay routing (Railway) between iOS app and Hermes WebSocket
|
|
54
|
+
- Message history in Convex
|
|
55
|
+
|
|
56
|
+
## What Hermes handles
|
|
57
|
+
|
|
58
|
+
- Pairing UI (code entry)
|
|
59
|
+
- Running the user's agent
|
|
60
|
+
- Translating Cleos chat messages into agent requests and streaming replies back
|
|
61
|
+
|
|
62
|
+
## Public pairing API
|
|
63
|
+
|
|
64
|
+
Hermes can resolve a code without extra config:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
GET https://<convex-site>/pairing/<CODE>
|
|
68
|
+
→ { code, expiresAt, relayHttpUrl, relayWsUrl, connectCommand }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Codes expire after 10 minutes. Each code links **one** Hermes instance to **one** Cleos user account.
|
|
72
|
+
|
|
73
|
+
## Credentials
|
|
74
|
+
|
|
75
|
+
After first pairing, credentials are saved to `~/.cleos/agent.json`. Use `reconnectHermesAgent()` on restart — no new code required.
|
|
76
|
+
|
|
77
|
+
## Self-hosting Cleos
|
|
78
|
+
|
|
79
|
+
Set `CLEOS_CONVEX_SITE_URL` in the Hermes build to your Convex `.site` URL instead of the default.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @saleso.innovations/bridge
|
|
2
|
+
|
|
3
|
+
Connect your **Hermes** agent to the **Cleos** iOS app with a pairing code.
|
|
4
|
+
|
|
5
|
+
## VPS quick install
|
|
6
|
+
|
|
7
|
+
Get a pairing code from the Cleos app, then on the machine where Hermes runs:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -fsSL https://amicable-elephant-407.convex.site/install-bridge.sh | bash -s -- ABCD1234
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requirements:
|
|
14
|
+
|
|
15
|
+
- Node.js 20+
|
|
16
|
+
- Hermes gateway running with API server enabled (`API_SERVER_ENABLED=true` in `~/.hermes/.env`, then `hermes gateway`)
|
|
17
|
+
|
|
18
|
+
## Manual usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @saleso.innovations/bridge
|
|
22
|
+
cleos-bridge connect ABCD1234 # first-time pairing
|
|
23
|
+
cleos-bridge start # reconnecting daemon (systemd)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Credentials are saved to `~/.cleos/agent.json`.
|
|
27
|
+
|
|
28
|
+
## Hermes API
|
|
29
|
+
|
|
30
|
+
By default the bridge forwards chat to Hermes at `http://127.0.0.1:8642/v1/chat/completions`.
|
|
31
|
+
Override with `HERMES_API_URL` or `HERMES_API_KEY` if needed.
|
|
32
|
+
|
|
33
|
+
See [INTEGRATION.md](./INTEGRATION.md) for native Hermes gateway integration.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { credentialsPathForDisplay, loadCredentials } from "./credentials.js";
|
|
3
|
+
import { connectHermesAgent, pairCleosAgent, reconnectHermesAgent } from "./client.js";
|
|
4
|
+
import { runBridgeDaemon } from "./daemon.js";
|
|
5
|
+
import { createHermesMessageHandler } from "./hermesForwarder.js";
|
|
6
|
+
import { convexSiteUrlFromEnv } from "./resolve.js";
|
|
7
|
+
async function main() {
|
|
8
|
+
const [, , command, codeArg] = process.argv;
|
|
9
|
+
const onUserMessage = createHermesMessageHandler();
|
|
10
|
+
if (command === "pair") {
|
|
11
|
+
if (!codeArg) {
|
|
12
|
+
printUsage();
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const payload = await pairCleosAgent({
|
|
16
|
+
code: codeArg.trim(),
|
|
17
|
+
convexSiteUrl: convexSiteUrlFromEnv(),
|
|
18
|
+
});
|
|
19
|
+
console.log(`Paired agent ${payload.agentId}.`);
|
|
20
|
+
console.log(`Credentials saved to ${credentialsPathForDisplay()}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (command === "connect") {
|
|
24
|
+
if (!codeArg) {
|
|
25
|
+
printUsage();
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const session = await connectHermesAgent({
|
|
29
|
+
code: codeArg.trim(),
|
|
30
|
+
convexSiteUrl: convexSiteUrlFromEnv(),
|
|
31
|
+
onUserMessage,
|
|
32
|
+
});
|
|
33
|
+
console.log(`Connected agent ${session.agentId}.`);
|
|
34
|
+
console.log(`Credentials saved to ${credentialsPathForDisplay()}`);
|
|
35
|
+
console.log("Press Ctrl+C to exit.");
|
|
36
|
+
await waitForExit(session);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (command === "start") {
|
|
40
|
+
if (loadCredentials()) {
|
|
41
|
+
await runBridgeDaemon();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!codeArg) {
|
|
45
|
+
console.error("No saved credentials. Run: cleos-bridge connect <CODE>");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
await runBridgeDaemon({ code: codeArg.trim(), convexSiteUrl: convexSiteUrlFromEnv() });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (command === "reconnect") {
|
|
52
|
+
const saved = loadCredentials();
|
|
53
|
+
const session = await reconnectHermesAgent({
|
|
54
|
+
credentials: saved ?? undefined,
|
|
55
|
+
onUserMessage,
|
|
56
|
+
});
|
|
57
|
+
console.log(`Reconnected agent ${session.agentId}.`);
|
|
58
|
+
console.log("Press Ctrl+C to exit.");
|
|
59
|
+
await waitForExit(session);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
printUsage();
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
function printUsage() {
|
|
66
|
+
console.error("Usage:");
|
|
67
|
+
console.error(" cleos-bridge pair <CODE> Save pairing credentials without staying connected");
|
|
68
|
+
console.error(" cleos-bridge connect <CODE> Pair and stay connected in the foreground");
|
|
69
|
+
console.error(" cleos-bridge start [CODE] Run as a reconnecting daemon (systemd)");
|
|
70
|
+
console.error(" cleos-bridge reconnect Reconnect once using saved credentials");
|
|
71
|
+
}
|
|
72
|
+
async function waitForExit(session) {
|
|
73
|
+
await new Promise((resolve) => {
|
|
74
|
+
const onSignal = () => {
|
|
75
|
+
session.close();
|
|
76
|
+
resolve();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
};
|
|
79
|
+
process.on("SIGINT", onSignal);
|
|
80
|
+
process.on("SIGTERM", onSignal);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
main().catch((error) => {
|
|
84
|
+
console.error(error instanceof Error ? error.message : error);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type SavedAgentCredentials } from "./credentials.js";
|
|
2
|
+
export type ConnectOptions = {
|
|
3
|
+
code: string;
|
|
4
|
+
convexSiteUrl?: string;
|
|
5
|
+
relayHttpBaseUrl?: string;
|
|
6
|
+
relayWsUrl?: string;
|
|
7
|
+
agentName?: string;
|
|
8
|
+
capabilities?: string[];
|
|
9
|
+
onUserMessage?: (content: string, meta: UserMessageMeta, reply: AgentReply) => Promise<void> | void;
|
|
10
|
+
};
|
|
11
|
+
export type UserMessageMeta = {
|
|
12
|
+
agentId: string;
|
|
13
|
+
conversationId: string;
|
|
14
|
+
messageId: string;
|
|
15
|
+
};
|
|
16
|
+
export type AgentReply = {
|
|
17
|
+
delta: (text: string, sequence: number) => void;
|
|
18
|
+
complete: (text: string, sequence: number) => void;
|
|
19
|
+
};
|
|
20
|
+
export type ConnectResult = {
|
|
21
|
+
agentId: string;
|
|
22
|
+
agentToken: string;
|
|
23
|
+
close: () => void;
|
|
24
|
+
closed: Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
export declare function pairCleosAgent(options: Omit<ConnectOptions, "onUserMessage">): Promise<{
|
|
27
|
+
agentId: string;
|
|
28
|
+
agentToken: string;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function connectHermesAgent(options: ConnectOptions): Promise<ConnectResult>;
|
|
31
|
+
export declare function reconnectHermesAgent(options?: {
|
|
32
|
+
credentials?: SavedAgentCredentials;
|
|
33
|
+
capabilities?: string[];
|
|
34
|
+
onUserMessage?: ConnectOptions["onUserMessage"];
|
|
35
|
+
}): Promise<ConnectResult>;
|
|
36
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGhG,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,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,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,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,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;CACvB,CAAC;AA6HF,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,CA0BD;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
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { saveCredentials, loadCredentials } from "./credentials.js";
|
|
4
|
+
import { convexSiteUrlFromEnv, resolvePairingCode } from "./resolve.js";
|
|
5
|
+
function createReplySender(ws, agentId, conversationId, messageId) {
|
|
6
|
+
return {
|
|
7
|
+
delta(text, sequence) {
|
|
8
|
+
ws.send(JSON.stringify({
|
|
9
|
+
type: "agent.delta",
|
|
10
|
+
agentId,
|
|
11
|
+
conversationId,
|
|
12
|
+
messageId,
|
|
13
|
+
delta: text,
|
|
14
|
+
sequence,
|
|
15
|
+
}));
|
|
16
|
+
},
|
|
17
|
+
complete(text, sequence) {
|
|
18
|
+
ws.send(JSON.stringify({
|
|
19
|
+
type: "agent.message",
|
|
20
|
+
agentId,
|
|
21
|
+
conversationId,
|
|
22
|
+
messageId,
|
|
23
|
+
content: text,
|
|
24
|
+
sequence,
|
|
25
|
+
final: true,
|
|
26
|
+
}));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function openAgentConnection(options) {
|
|
31
|
+
const ws = new WebSocket(options.relayWsUrl);
|
|
32
|
+
let closedResolve = null;
|
|
33
|
+
const closed = new Promise((resolve) => {
|
|
34
|
+
closedResolve = resolve;
|
|
35
|
+
});
|
|
36
|
+
await new Promise((resolve, reject) => {
|
|
37
|
+
ws.once("open", () => resolve());
|
|
38
|
+
ws.once("error", reject);
|
|
39
|
+
});
|
|
40
|
+
ws.send(JSON.stringify({
|
|
41
|
+
type: "agent.hello",
|
|
42
|
+
token: options.agentToken,
|
|
43
|
+
agentId: options.agentId,
|
|
44
|
+
capabilities: options.capabilities,
|
|
45
|
+
}));
|
|
46
|
+
ws.on("message", (raw) => {
|
|
47
|
+
void (async () => {
|
|
48
|
+
const envelope = JSON.parse(raw.toString());
|
|
49
|
+
if (envelope.type !== "user.message")
|
|
50
|
+
return;
|
|
51
|
+
const content = typeof envelope.content === "string" ? envelope.content : "";
|
|
52
|
+
const agentId = typeof envelope.agentId === "string" ? envelope.agentId : options.agentId;
|
|
53
|
+
const conversationId = typeof envelope.conversationId === "string" ? envelope.conversationId : "unknown";
|
|
54
|
+
const messageId = typeof envelope.clientMessageId === "string" ? envelope.clientMessageId : randomUUID();
|
|
55
|
+
const reply = createReplySender(ws, agentId, conversationId, messageId);
|
|
56
|
+
if (options.onUserMessage) {
|
|
57
|
+
await options.onUserMessage(content, { agentId, conversationId, messageId }, reply);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const echo = `Echo: ${content}`;
|
|
61
|
+
reply.delta(echo, 0);
|
|
62
|
+
reply.complete(echo, 1);
|
|
63
|
+
})();
|
|
64
|
+
});
|
|
65
|
+
const heartbeat = setInterval(() => {
|
|
66
|
+
if (ws.readyState === ws.OPEN) {
|
|
67
|
+
ws.send(JSON.stringify({ type: "heartbeat", sentAt: Date.now() }));
|
|
68
|
+
}
|
|
69
|
+
}, 30_000);
|
|
70
|
+
ws.on("close", () => {
|
|
71
|
+
clearInterval(heartbeat);
|
|
72
|
+
closedResolve?.();
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
agentId: options.agentId,
|
|
76
|
+
agentToken: options.agentToken,
|
|
77
|
+
close: () => {
|
|
78
|
+
clearInterval(heartbeat);
|
|
79
|
+
ws.close();
|
|
80
|
+
},
|
|
81
|
+
closed,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function resolveRelayTargets(options) {
|
|
85
|
+
if (options.relayHttpBaseUrl && options.relayWsUrl) {
|
|
86
|
+
return {
|
|
87
|
+
code: options.code.trim().toUpperCase(),
|
|
88
|
+
expiresAt: Date.now() + 60_000,
|
|
89
|
+
relayHttpUrl: options.relayHttpBaseUrl,
|
|
90
|
+
relayWsUrl: options.relayWsUrl,
|
|
91
|
+
connectCommand: `cleos-bridge connect ${options.code.trim().toUpperCase()}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const convexSiteUrl = options.convexSiteUrl ?? convexSiteUrlFromEnv();
|
|
95
|
+
return await resolvePairingCode(convexSiteUrl, options.code);
|
|
96
|
+
}
|
|
97
|
+
export async function pairCleosAgent(options) {
|
|
98
|
+
const pairing = await resolveRelayTargets(options);
|
|
99
|
+
const response = await fetch(`${pairing.relayHttpUrl.replace(/\/$/, "")}/connect`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "content-type": "application/json" },
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
code: pairing.code,
|
|
104
|
+
agentName: options.agentName ?? "Hermes",
|
|
105
|
+
capabilities: options.capabilities ?? ["chat"],
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Connect failed (${response.status}): ${await response.text()}`);
|
|
110
|
+
}
|
|
111
|
+
const payload = (await response.json());
|
|
112
|
+
saveCredentials({
|
|
113
|
+
agentId: payload.agentId,
|
|
114
|
+
agentToken: payload.agentToken,
|
|
115
|
+
relayHttpUrl: pairing.relayHttpUrl,
|
|
116
|
+
relayWsUrl: pairing.relayWsUrl,
|
|
117
|
+
agentName: options.agentName ?? "Hermes",
|
|
118
|
+
savedAt: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
return payload;
|
|
121
|
+
}
|
|
122
|
+
export async function connectHermesAgent(options) {
|
|
123
|
+
await pairCleosAgent(options);
|
|
124
|
+
const credentials = loadCredentials();
|
|
125
|
+
if (!credentials) {
|
|
126
|
+
throw new Error("Pairing credentials were not saved");
|
|
127
|
+
}
|
|
128
|
+
return await openAgentConnection({
|
|
129
|
+
relayWsUrl: credentials.relayWsUrl,
|
|
130
|
+
agentId: credentials.agentId,
|
|
131
|
+
agentToken: credentials.agentToken,
|
|
132
|
+
capabilities: options.capabilities ?? ["chat"],
|
|
133
|
+
onUserMessage: options.onUserMessage,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export async function reconnectHermesAgent(options = {}) {
|
|
137
|
+
const { loadCredentials } = await import("./credentials.js");
|
|
138
|
+
const credentials = options.credentials ?? loadCredentials();
|
|
139
|
+
if (!credentials) {
|
|
140
|
+
throw new Error("No saved Cleos agent credentials. Run `cleos-bridge connect <CODE>` first.");
|
|
141
|
+
}
|
|
142
|
+
return await openAgentConnection({
|
|
143
|
+
relayWsUrl: credentials.relayWsUrl,
|
|
144
|
+
agentId: credentials.agentId,
|
|
145
|
+
agentToken: credentials.agentToken,
|
|
146
|
+
capabilities: options.capabilities ?? ["chat"],
|
|
147
|
+
onUserMessage: options.onUserMessage,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Default Cleos Convex site — pairing codes resolve relay URLs from here. */
|
|
2
|
+
export declare const DEFAULT_CLEOS_CONVEX_SITE_URL = "https://amicable-elephant-407.convex.site";
|
|
3
|
+
/** Default Hermes OpenAI-compatible API (requires `hermes gateway` + API_SERVER_ENABLED). */
|
|
4
|
+
export declare const DEFAULT_HERMES_API_URL = "http://127.0.0.1:8642/v1/chat/completions";
|
|
5
|
+
/** VPS one-line installer (served from public Convex HTTP). */
|
|
6
|
+
export declare const DEFAULT_BRIDGE_INSTALL_URL = "https://amicable-elephant-407.convex.site/install-bridge.sh";
|
|
7
|
+
export declare function bridgeInstallCommand(code: string): string;
|
|
8
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +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,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Default Cleos Convex site — pairing codes resolve relay URLs from here. */
|
|
2
|
+
export const DEFAULT_CLEOS_CONVEX_SITE_URL = "https://amicable-elephant-407.convex.site";
|
|
3
|
+
/** Default Hermes OpenAI-compatible API (requires `hermes gateway` + API_SERVER_ENABLED). */
|
|
4
|
+
export const DEFAULT_HERMES_API_URL = "http://127.0.0.1:8642/v1/chat/completions";
|
|
5
|
+
/** VPS one-line installer (served from public Convex HTTP). */
|
|
6
|
+
export const DEFAULT_BRIDGE_INSTALL_URL = "https://amicable-elephant-407.convex.site/install-bridge.sh";
|
|
7
|
+
export function bridgeInstallCommand(code) {
|
|
8
|
+
return `curl -fsSL ${DEFAULT_BRIDGE_INSTALL_URL} | bash -s -- ${code}`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type SavedAgentCredentials = {
|
|
2
|
+
agentId: string;
|
|
3
|
+
agentToken: string;
|
|
4
|
+
relayHttpUrl: string;
|
|
5
|
+
relayWsUrl: string;
|
|
6
|
+
agentName?: string;
|
|
7
|
+
savedAt: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function loadCredentials(): SavedAgentCredentials | null;
|
|
10
|
+
export declare function saveCredentials(credentials: SavedAgentCredentials): void;
|
|
11
|
+
export declare function credentialsPathForDisplay(): string;
|
|
12
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF,wBAAgB,eAAe,IAAI,qBAAqB,GAAG,IAAI,CAS9D;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,qBAAqB,GAAG,IAAI,CAGxE;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
const credentialsPath = join(homedir(), ".cleos", "agent.json");
|
|
5
|
+
export function loadCredentials() {
|
|
6
|
+
try {
|
|
7
|
+
const raw = readFileSync(credentialsPath, "utf8");
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
if (!parsed.agentId || !parsed.agentToken || !parsed.relayWsUrl)
|
|
10
|
+
return null;
|
|
11
|
+
return parsed;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function saveCredentials(credentials) {
|
|
18
|
+
mkdirSync(dirname(credentialsPath), { recursive: true });
|
|
19
|
+
writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2));
|
|
20
|
+
}
|
|
21
|
+
export function credentialsPathForDisplay() {
|
|
22
|
+
return credentialsPath;
|
|
23
|
+
}
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,KAAK,CAAC,CA8BpF"}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { connectHermesAgent, reconnectHermesAgent } from "./client.js";
|
|
2
|
+
import { createHermesMessageHandler } from "./hermesForwarder.js";
|
|
3
|
+
const RECONNECT_DELAY_MS = 5_000;
|
|
4
|
+
export async function runBridgeDaemon(options = {}) {
|
|
5
|
+
const onUserMessage = createHermesMessageHandler();
|
|
6
|
+
let pendingCode = options.code;
|
|
7
|
+
while (true) {
|
|
8
|
+
try {
|
|
9
|
+
let session;
|
|
10
|
+
if (pendingCode) {
|
|
11
|
+
session = await connectHermesAgent({
|
|
12
|
+
code: pendingCode,
|
|
13
|
+
convexSiteUrl: options.convexSiteUrl,
|
|
14
|
+
onUserMessage,
|
|
15
|
+
});
|
|
16
|
+
pendingCode = undefined;
|
|
17
|
+
console.log(JSON.stringify({ event: "cleos-bridge.connected", agentId: session.agentId, mode: "pair" }));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
session = await reconnectHermesAgent({ onUserMessage });
|
|
21
|
+
console.log(JSON.stringify({ event: "cleos-bridge.connected", agentId: session.agentId, mode: "reconnect" }));
|
|
22
|
+
}
|
|
23
|
+
await session.closed;
|
|
24
|
+
session.close();
|
|
25
|
+
console.log(JSON.stringify({ event: "cleos-bridge.disconnected", retryInMs: RECONNECT_DELAY_MS }));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
console.error(JSON.stringify({ event: "cleos-bridge.error", message, retryInMs: RECONNECT_DELAY_MS }));
|
|
30
|
+
}
|
|
31
|
+
await sleep(RECONNECT_DELAY_MS);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function sleep(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentReply, UserMessageMeta } from "./client.js";
|
|
2
|
+
export type HermesForwarderOptions = {
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
conversationId?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function resolveHermesApiConfig(options?: HermesForwarderOptions): {
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
apiKey: string | undefined;
|
|
11
|
+
model: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function forwardToHermes(content: string, meta: UserMessageMeta, reply: AgentReply, options?: HermesForwarderOptions): Promise<void>;
|
|
14
|
+
export declare function createHermesMessageHandler(options?: HermesForwarderOptions): (content: string, meta: UserMessageMeta, reply: AgentReply) => Promise<void>;
|
|
15
|
+
//# sourceMappingURL=hermesForwarder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermesForwarder.d.ts","sourceRoot":"","sources":["../src/hermesForwarder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG/D,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAkBF,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,sBAA2B,GAAG;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,CAUA;AAqCD,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAwEf;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,sBAA2B,IAC/D,SAAS,MAAM,EAAE,MAAM,eAAe,EAAE,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAMxF"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { DEFAULT_HERMES_API_URL } from "./constants.js";
|
|
5
|
+
function readHermesApiKeyFromEnvFile() {
|
|
6
|
+
const envPath = join(homedir(), ".hermes", ".env");
|
|
7
|
+
try {
|
|
8
|
+
const contents = readFileSync(envPath, "utf8");
|
|
9
|
+
for (const line of contents.split("\n")) {
|
|
10
|
+
const trimmed = line.trim();
|
|
11
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
12
|
+
continue;
|
|
13
|
+
const match = trimmed.match(/^API_SERVER_KEY=(.+)$/);
|
|
14
|
+
if (match?.[1])
|
|
15
|
+
return match[1].trim().replace(/^["']|["']$/g, "");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Hermes env file is optional.
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
export function resolveHermesApiConfig(options = {}) {
|
|
24
|
+
return {
|
|
25
|
+
apiUrl: options.apiUrl?.trim() || process.env.HERMES_API_URL?.trim() || DEFAULT_HERMES_API_URL,
|
|
26
|
+
apiKey: options.apiKey?.trim() ||
|
|
27
|
+
process.env.HERMES_API_KEY?.trim() ||
|
|
28
|
+
process.env.API_SERVER_KEY?.trim() ||
|
|
29
|
+
readHermesApiKeyFromEnvFile(),
|
|
30
|
+
model: options.model?.trim() || process.env.HERMES_MODEL?.trim() || "hermes-agent",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function ensureHermesReachable(apiUrl, apiKey) {
|
|
34
|
+
const healthUrl = apiUrl.replace(/\/v1\/chat\/completions\/?$/, "/health");
|
|
35
|
+
const headers = {};
|
|
36
|
+
if (apiKey)
|
|
37
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(healthUrl, { headers });
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Hermes health check failed (${response.status})`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
46
|
+
throw new Error(`Hermes is not reachable at ${healthUrl}. Start Hermes with \`hermes gateway\` and enable API_SERVER_ENABLED in ~/.hermes/.env. ${message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function parseSseDataLines(buffer) {
|
|
50
|
+
const parts = buffer.split("\n\n");
|
|
51
|
+
const rest = parts.pop() ?? "";
|
|
52
|
+
return { events: parts, rest };
|
|
53
|
+
}
|
|
54
|
+
function extractDeltaFromChunk(payload) {
|
|
55
|
+
const choices = payload.choices;
|
|
56
|
+
if (!Array.isArray(choices) || choices.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const choice = choices[0];
|
|
59
|
+
if (!choice || typeof choice !== "object")
|
|
60
|
+
return null;
|
|
61
|
+
const delta = choice.delta;
|
|
62
|
+
if (!delta || typeof delta !== "object")
|
|
63
|
+
return null;
|
|
64
|
+
const content = delta.content;
|
|
65
|
+
return typeof content === "string" ? content : null;
|
|
66
|
+
}
|
|
67
|
+
export async function forwardToHermes(content, meta, reply, options = {}) {
|
|
68
|
+
const { apiUrl, apiKey, model } = resolveHermesApiConfig(options);
|
|
69
|
+
await ensureHermesReachable(apiUrl, apiKey);
|
|
70
|
+
const headers = {
|
|
71
|
+
"content-type": "application/json",
|
|
72
|
+
};
|
|
73
|
+
if (apiKey)
|
|
74
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
75
|
+
const conversationKey = options.conversationId ?? meta.conversationId;
|
|
76
|
+
const body = {
|
|
77
|
+
model,
|
|
78
|
+
stream: true,
|
|
79
|
+
messages: [{ role: "user", content }],
|
|
80
|
+
user: conversationKey,
|
|
81
|
+
};
|
|
82
|
+
const response = await fetch(apiUrl, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers,
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const text = await response.text();
|
|
89
|
+
throw new Error(`Hermes request failed (${response.status}): ${text}`);
|
|
90
|
+
}
|
|
91
|
+
if (!response.body) {
|
|
92
|
+
throw new Error("Hermes returned an empty streaming body");
|
|
93
|
+
}
|
|
94
|
+
const reader = response.body.getReader();
|
|
95
|
+
const decoder = new TextDecoder();
|
|
96
|
+
let buffer = "";
|
|
97
|
+
let sequence = 0;
|
|
98
|
+
let fullText = "";
|
|
99
|
+
while (true) {
|
|
100
|
+
const { done, value } = await reader.read();
|
|
101
|
+
if (done)
|
|
102
|
+
break;
|
|
103
|
+
buffer += decoder.decode(value, { stream: true });
|
|
104
|
+
const { events, rest } = parseSseDataLines(buffer);
|
|
105
|
+
buffer = rest;
|
|
106
|
+
for (const event of events) {
|
|
107
|
+
for (const line of event.split("\n")) {
|
|
108
|
+
if (!line.startsWith("data: "))
|
|
109
|
+
continue;
|
|
110
|
+
const data = line.slice(6).trim();
|
|
111
|
+
if (!data || data === "[DONE]")
|
|
112
|
+
continue;
|
|
113
|
+
try {
|
|
114
|
+
const payload = JSON.parse(data);
|
|
115
|
+
const delta = extractDeltaFromChunk(payload);
|
|
116
|
+
if (delta) {
|
|
117
|
+
fullText += delta;
|
|
118
|
+
reply.delta(delta, sequence);
|
|
119
|
+
sequence += 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Ignore malformed SSE chunks.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!fullText.trim()) {
|
|
129
|
+
fullText = "(Hermes returned an empty response)";
|
|
130
|
+
reply.delta(fullText, sequence);
|
|
131
|
+
sequence += 1;
|
|
132
|
+
}
|
|
133
|
+
reply.complete(fullText, sequence);
|
|
134
|
+
}
|
|
135
|
+
export function createHermesMessageHandler(options = {}) {
|
|
136
|
+
return async (content, meta, reply) => {
|
|
137
|
+
await forwardToHermes(content, meta, reply, {
|
|
138
|
+
...options,
|
|
139
|
+
conversationId: meta.conversationId,
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { connectHermesAgent, pairCleosAgent, reconnectHermesAgent } from "./client.js";
|
|
2
|
+
export type { AgentReply, ConnectOptions, ConnectResult, UserMessageMeta } from "./client.js";
|
|
3
|
+
export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./credentials.js";
|
|
4
|
+
export type { SavedAgentCredentials } from "./credentials.js";
|
|
5
|
+
export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
|
|
6
|
+
export type { PairingInfo } from "./resolve.js";
|
|
7
|
+
export { pairWithCleos } from "./pairWithCleos.js";
|
|
8
|
+
export { createHermesMessageHandler, forwardToHermes, resolveHermesApiConfig } from "./hermesForwarder.js";
|
|
9
|
+
export type { HermesForwarderOptions } from "./hermesForwarder.js";
|
|
10
|
+
export { runBridgeDaemon } from "./daemon.js";
|
|
11
|
+
export type { RunBridgeOptions } from "./daemon.js";
|
|
12
|
+
export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, bridgeInstallCommand, } from "./constants.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9F,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,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { connectHermesAgent, pairCleosAgent, reconnectHermesAgent } from "./client.js";
|
|
2
|
+
export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./credentials.js";
|
|
3
|
+
export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
|
|
4
|
+
export { pairWithCleos } from "./pairWithCleos.js";
|
|
5
|
+
export { createHermesMessageHandler, forwardToHermes, resolveHermesApiConfig } from "./hermesForwarder.js";
|
|
6
|
+
export { runBridgeDaemon } from "./daemon.js";
|
|
7
|
+
export { DEFAULT_BRIDGE_INSTALL_URL, DEFAULT_CLEOS_CONVEX_SITE_URL, DEFAULT_HERMES_API_URL, bridgeInstallCommand, } from "./constants.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ConnectOptions, ConnectResult } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pair the local Hermes agent with a user's Cleos account using a code from the iOS app.
|
|
4
|
+
*/
|
|
5
|
+
export declare function pairWithCleos(code: string, options?: {
|
|
6
|
+
convexSiteUrl?: string;
|
|
7
|
+
agentName?: string;
|
|
8
|
+
onUserMessage?: ConnectOptions["onUserMessage"];
|
|
9
|
+
}): Promise<ConnectResult>;
|
|
10
|
+
//# sourceMappingURL=pairWithCleos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairWithCleos.d.ts","sourceRoot":"","sources":["../src/pairWithCleos.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjE;;GAEG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IACP,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC5C,GACL,OAAO,CAAC,aAAa,CAAC,CAUxB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { connectHermesAgent } from "./client.js";
|
|
2
|
+
import { DEFAULT_CLEOS_CONVEX_SITE_URL } from "./constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Pair the local Hermes agent with a user's Cleos account using a code from the iOS app.
|
|
5
|
+
*/
|
|
6
|
+
export async function pairWithCleos(code, options = {}) {
|
|
7
|
+
return connectHermesAgent({
|
|
8
|
+
code,
|
|
9
|
+
convexSiteUrl: options.convexSiteUrl ??
|
|
10
|
+
process.env.CLEOS_CONVEX_SITE_URL?.trim() ??
|
|
11
|
+
DEFAULT_CLEOS_CONVEX_SITE_URL,
|
|
12
|
+
agentName: options.agentName,
|
|
13
|
+
onUserMessage: options.onUserMessage,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type PairingInfo = {
|
|
2
|
+
code: string;
|
|
3
|
+
expiresAt: number;
|
|
4
|
+
relayHttpUrl: string;
|
|
5
|
+
relayWsUrl: string;
|
|
6
|
+
connectCommand: string;
|
|
7
|
+
installCommand?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function convexSiteUrlFromEnv(): string;
|
|
10
|
+
export declare function resolvePairingCode(convexSiteUrl: string, code: string): Promise<PairingInfo>;
|
|
11
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAED,wBAAsB,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAQlG"}
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DEFAULT_CLEOS_CONVEX_SITE_URL } from "./constants.js";
|
|
2
|
+
export function convexSiteUrlFromEnv() {
|
|
3
|
+
const configured = process.env.CLEOS_CONVEX_SITE_URL?.trim() ||
|
|
4
|
+
process.env.CONVEX_SITE_URL?.trim() ||
|
|
5
|
+
DEFAULT_CLEOS_CONVEX_SITE_URL;
|
|
6
|
+
return configured.replace(/\/$/, "");
|
|
7
|
+
}
|
|
8
|
+
export async function resolvePairingCode(convexSiteUrl, code) {
|
|
9
|
+
const normalized = code.trim().toUpperCase();
|
|
10
|
+
const response = await fetch(`${convexSiteUrl.replace(/\/$/, "")}/pairing/${normalized}`);
|
|
11
|
+
if (!response.ok) {
|
|
12
|
+
const body = await response.text();
|
|
13
|
+
throw new Error(`Pairing lookup failed (${response.status}): ${body}`);
|
|
14
|
+
}
|
|
15
|
+
return (await response.json());
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saleso.innovations/bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Connect your Hermes agent to the Cleos iOS app via pairing code.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/RemiRonning/cleos.git",
|
|
10
|
+
"directory": "packages/cleos-hermes-connect"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["cleos", "hermes", "agent", "relay"],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": ["dist", "README.md", "INTEGRATION.md"],
|
|
20
|
+
"bin": {
|
|
21
|
+
"cleos-bridge": "./dist/cli.js",
|
|
22
|
+
"hermes-relay": "./dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"dev": "tsx src/cli.ts",
|
|
27
|
+
"lint": "eslint . --max-warnings 0",
|
|
28
|
+
"check-types": "tsc --noEmit",
|
|
29
|
+
"prepublishOnly": "npm run build",
|
|
30
|
+
"test": "echo 'No @saleso.innovations/bridge tests configured'"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"ws": "^8.18.3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@repo/eslint-config": "*",
|
|
37
|
+
"@repo/typescript-config": "*",
|
|
38
|
+
"@types/node": "^25.4.0",
|
|
39
|
+
"@types/ws": "^8.18.1",
|
|
40
|
+
"eslint": "^9.39.1",
|
|
41
|
+
"tsx": "^4.21.0",
|
|
42
|
+
"typescript": "5.9.2"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
}
|
|
47
|
+
}
|