@athenafleet/bridge 1.0.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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @athenafleet/bridge
2
+
3
+ CLI bridge connecting your local OpenClaw gateway to the Athena dashboard via WebSocket.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g @athenafleet/bridge
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ athena-bridge --key rtk_xxx
15
+ ```
16
+
17
+ That's it. The bridge connects to the Athena API, authenticates with your runtime key, and begins syncing agent data from your local OpenClaw gateway to the dashboard.
18
+
19
+ ## Options
20
+
21
+ | Flag | Default | Description |
22
+ |------|---------|-------------|
23
+ | `--key`, `-k` | *(required)* | Runtime API key (`rtk_` prefix) |
24
+ | `--api-url` | `https://api.athenafleet.ai` | Athena API URL |
25
+ | `--gateway-id` | hostname | Identifier for this gateway |
26
+ | `--openclaw-url` | `http://localhost:3007` | Local OpenClaw gateway URL |
27
+ | `--verbose`, `-v` | `false` | Enable debug logging |
28
+
29
+ ## What it does
30
+
31
+ 1. **Connects** to the Athena WebSocket endpoint (`/ws/runtime`)
32
+ 2. **Authenticates** with your runtime API key (first-message auth)
33
+ 3. **Syncs** agent data from your local OpenClaw gateway every 60 seconds
34
+ 4. **Heartbeats** every 30 seconds to keep the connection alive
35
+ 5. **Reconnects** automatically with exponential backoff if disconnected
36
+
37
+ ## Generate a key
38
+
39
+ Runtime keys are created in the Athena dashboard under **Settings → Runtime Keys**, or via the API:
40
+
41
+ ```bash
42
+ curl -X POST https://api.athenafleet.ai/api/admin/runtime-keys \
43
+ -H "Authorization: Bearer <token>" \
44
+ -H "Content-Type: application/json" \
45
+ -d '{"name": "My Gateway"}'
46
+ ```
47
+
48
+ The key is shown **once** on creation — save it.
49
+
50
+ ## Requirements
51
+
52
+ - Node.js ≥ 18
53
+ - An Athena account with a runtime API key
54
+ - OpenClaw gateway running locally (optional — bridge works without it, just syncs empty agent list)
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @athenafleet/bridge — WebSocket Client
3
+ *
4
+ * Manages the persistent WS connection to the Athena API:
5
+ * - First-message auth with runtime API key
6
+ * - Health pings on heartbeat_interval_ms
7
+ * - Agent sync every 60s + on server request
8
+ * - Exponential backoff reconnection with jitter
9
+ * - Graceful shutdown
10
+ */
11
+ import type { BridgeOptions } from './types.js';
12
+ export declare class BridgeClient {
13
+ private ws;
14
+ private options;
15
+ private runtimeId;
16
+ private heartbeatIntervalMs;
17
+ private startedAt;
18
+ private heartbeatTimer;
19
+ private syncTimer;
20
+ private reconnectTimer;
21
+ private reconnectAttempt;
22
+ private intentionalClose;
23
+ private lastAgents;
24
+ private gatewayConnected;
25
+ constructor(options: BridgeOptions);
26
+ /**
27
+ * Start the bridge. Connects and begins sync loop.
28
+ */
29
+ start(): void;
30
+ /**
31
+ * Graceful shutdown.
32
+ */
33
+ stop(): void;
34
+ private connect;
35
+ private sendAuth;
36
+ private handleMessage;
37
+ private handleAuthOk;
38
+ private syncAgents;
39
+ private sendHealthPing;
40
+ private scheduleReconnect;
41
+ private send;
42
+ private clearTimers;
43
+ }
package/dist/client.js ADDED
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @athenafleet/bridge — WebSocket Client
3
+ *
4
+ * Manages the persistent WS connection to the Athena API:
5
+ * - First-message auth with runtime API key
6
+ * - Health pings on heartbeat_interval_ms
7
+ * - Agent sync every 60s + on server request
8
+ * - Exponential backoff reconnection with jitter
9
+ * - Graceful shutdown
10
+ */
11
+ import WebSocket from 'ws';
12
+ import { CLOSE_CODES } from './types.js';
13
+ import { fetchAgents } from './gateway.js';
14
+ import * as log from './logger.js';
15
+ // =============================================================================
16
+ // Constants
17
+ // =============================================================================
18
+ const BRIDGE_VERSION = '1.0.0';
19
+ const SYNC_INTERVAL_MS = 60_000; // 1/min (server rate limit)
20
+ const BASE_BACKOFF_MS = 1_000;
21
+ const MAX_BACKOFF_MS = 30_000;
22
+ const JITTER_FACTOR = 0.25; // ±25%
23
+ // =============================================================================
24
+ // Bridge Client
25
+ // =============================================================================
26
+ export class BridgeClient {
27
+ ws = null;
28
+ options;
29
+ runtimeId = null;
30
+ heartbeatIntervalMs = 30_000;
31
+ startedAt = Date.now();
32
+ // Timers
33
+ heartbeatTimer = null;
34
+ syncTimer = null;
35
+ reconnectTimer = null;
36
+ // Reconnection state
37
+ reconnectAttempt = 0;
38
+ intentionalClose = false;
39
+ // Cached agent data
40
+ lastAgents = [];
41
+ gatewayConnected = false;
42
+ constructor(options) {
43
+ this.options = options;
44
+ }
45
+ // ===========================================================================
46
+ // Public API
47
+ // ===========================================================================
48
+ /**
49
+ * Start the bridge. Connects and begins sync loop.
50
+ */
51
+ start() {
52
+ this.startedAt = Date.now();
53
+ this.intentionalClose = false;
54
+ this.connect();
55
+ }
56
+ /**
57
+ * Graceful shutdown.
58
+ */
59
+ stop() {
60
+ log.info('Shutting down...');
61
+ this.intentionalClose = true;
62
+ this.clearTimers();
63
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
64
+ this.ws.close(1000, 'Client shutdown');
65
+ }
66
+ this.ws = null;
67
+ }
68
+ // ===========================================================================
69
+ // Connection
70
+ // ===========================================================================
71
+ connect() {
72
+ const apiUrl = this.options.apiUrl.replace(/\/+$/, '');
73
+ const wsUrl = apiUrl.replace(/^http/, 'ws') + '/ws/runtime';
74
+ log.info(`Connecting to ${wsUrl}...`);
75
+ try {
76
+ this.ws = new WebSocket(wsUrl, {
77
+ headers: {
78
+ 'User-Agent': `@athenafleet/bridge/${BRIDGE_VERSION}`,
79
+ },
80
+ handshakeTimeout: 10_000,
81
+ });
82
+ }
83
+ catch (err) {
84
+ log.error('Failed to create WebSocket', err);
85
+ this.scheduleReconnect();
86
+ return;
87
+ }
88
+ this.ws.on('open', () => {
89
+ log.debug('WebSocket connected, sending auth...');
90
+ this.sendAuth();
91
+ });
92
+ this.ws.on('message', (data) => {
93
+ this.handleMessage(data);
94
+ });
95
+ this.ws.on('close', (code, reason) => {
96
+ const reasonStr = reason?.toString() || 'unknown';
97
+ log.info(`Disconnected (code=${code}, reason=${reasonStr})`);
98
+ this.clearTimers();
99
+ this.ws = null;
100
+ this.runtimeId = null;
101
+ // Don't reconnect on intentional close or fatal auth errors
102
+ if (this.intentionalClose)
103
+ return;
104
+ if (code === CLOSE_CODES.INVALID_AUTH) {
105
+ log.error('Authentication failed — check your --key. Not reconnecting.');
106
+ process.exit(1);
107
+ }
108
+ if (code === CLOSE_CODES.LIMIT_EXCEEDED) {
109
+ log.warn('Connection limit exceeded — will retry with backoff');
110
+ }
111
+ this.scheduleReconnect();
112
+ });
113
+ this.ws.on('error', (err) => {
114
+ log.error('WebSocket error', err);
115
+ // 'close' event will fire after this, triggering reconnect
116
+ });
117
+ // ws library auto-responds to ping with pong
118
+ }
119
+ // ===========================================================================
120
+ // Auth
121
+ // ===========================================================================
122
+ sendAuth() {
123
+ this.send({
124
+ type: 'auth',
125
+ key: this.options.key,
126
+ bridge_version: BRIDGE_VERSION,
127
+ gateway_version: 'unknown', // Future: detect OpenClaw version
128
+ });
129
+ }
130
+ // ===========================================================================
131
+ // Message handling
132
+ // ===========================================================================
133
+ handleMessage(raw) {
134
+ let msg;
135
+ try {
136
+ msg = JSON.parse(raw.toString());
137
+ }
138
+ catch {
139
+ log.debug('Received non-JSON message, ignoring');
140
+ return;
141
+ }
142
+ log.debug(`← ${msg.type}`, msg);
143
+ switch (msg.type) {
144
+ case 'auth_ok':
145
+ this.handleAuthOk(msg.runtime_id, msg.heartbeat_interval_ms);
146
+ break;
147
+ case 'auth_error':
148
+ log.error(`Auth rejected: ${msg.reason}`);
149
+ // Server will close the socket; close handler deals with exit
150
+ break;
151
+ case 'agents.sync_request':
152
+ log.info('Server requested agent sync');
153
+ void this.syncAgents();
154
+ break;
155
+ case 'runtime.disconnect':
156
+ log.warn(`Server disconnecting: ${msg.reason}`);
157
+ break;
158
+ case 'error':
159
+ log.warn(`Server error: ${msg.message}`);
160
+ break;
161
+ default:
162
+ log.debug('Unknown message type', { raw: raw.toString().slice(0, 200) });
163
+ }
164
+ }
165
+ handleAuthOk(runtimeId, heartbeatIntervalMs) {
166
+ this.runtimeId = runtimeId;
167
+ this.heartbeatIntervalMs = heartbeatIntervalMs;
168
+ this.reconnectAttempt = 0; // Reset backoff on successful auth
169
+ log.info(`✓ Authenticated (runtime=${runtimeId}, heartbeat=${heartbeatIntervalMs}ms)`);
170
+ // Start heartbeat
171
+ this.heartbeatTimer = setInterval(() => {
172
+ this.sendHealthPing();
173
+ }, this.heartbeatIntervalMs);
174
+ // Initial sync + periodic sync
175
+ void this.syncAgents();
176
+ this.syncTimer = setInterval(() => {
177
+ void this.syncAgents();
178
+ }, SYNC_INTERVAL_MS);
179
+ }
180
+ // ===========================================================================
181
+ // Agent sync
182
+ // ===========================================================================
183
+ async syncAgents() {
184
+ try {
185
+ const agents = await fetchAgents(this.options.openclawUrl);
186
+ this.lastAgents = agents;
187
+ this.gatewayConnected = agents.length > 0;
188
+ this.send({
189
+ type: 'agents.sync',
190
+ agents,
191
+ gateway_id: this.options.gatewayId,
192
+ timestamp: new Date().toISOString(),
193
+ });
194
+ log.info(`Synced ${agents.length} agent(s)`);
195
+ }
196
+ catch (err) {
197
+ log.error('Agent sync failed', err);
198
+ this.gatewayConnected = false;
199
+ }
200
+ }
201
+ // ===========================================================================
202
+ // Health ping
203
+ // ===========================================================================
204
+ sendHealthPing() {
205
+ this.send({
206
+ type: 'health.ping',
207
+ gateway_connected: this.gatewayConnected,
208
+ agent_count: this.lastAgents.length,
209
+ uptime_ms: Date.now() - this.startedAt,
210
+ timestamp: new Date().toISOString(),
211
+ });
212
+ }
213
+ // ===========================================================================
214
+ // Reconnection (FINF-35: exponential backoff + jitter)
215
+ // ===========================================================================
216
+ scheduleReconnect() {
217
+ this.reconnectAttempt++;
218
+ // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s cap
219
+ const baseDelay = Math.min(BASE_BACKOFF_MS * Math.pow(2, this.reconnectAttempt - 1), MAX_BACKOFF_MS);
220
+ // ±25% jitter
221
+ const jitter = baseDelay * JITTER_FACTOR * (2 * Math.random() - 1);
222
+ const delay = Math.max(500, Math.round(baseDelay + jitter));
223
+ log.info(`Reconnecting in ${(delay / 1000).toFixed(1)}s (attempt ${this.reconnectAttempt})...`);
224
+ this.reconnectTimer = setTimeout(() => {
225
+ this.reconnectTimer = null;
226
+ this.connect();
227
+ }, delay);
228
+ }
229
+ // ===========================================================================
230
+ // Helpers
231
+ // ===========================================================================
232
+ send(msg) {
233
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
234
+ log.debug(`Cannot send ${msg.type} — not connected`);
235
+ return;
236
+ }
237
+ const payload = JSON.stringify(msg);
238
+ log.debug(`→ ${msg.type}`, { size: payload.length });
239
+ try {
240
+ this.ws.send(payload);
241
+ }
242
+ catch (err) {
243
+ log.error(`Failed to send ${msg.type}`, err);
244
+ }
245
+ }
246
+ clearTimers() {
247
+ if (this.heartbeatTimer) {
248
+ clearInterval(this.heartbeatTimer);
249
+ this.heartbeatTimer = null;
250
+ }
251
+ if (this.syncTimer) {
252
+ clearInterval(this.syncTimer);
253
+ this.syncTimer = null;
254
+ }
255
+ if (this.reconnectTimer) {
256
+ clearTimeout(this.reconnectTimer);
257
+ this.reconnectTimer = null;
258
+ }
259
+ }
260
+ }
261
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @athenafleet/bridge — Gateway Data Collector
3
+ *
4
+ * Fetches agent data from the local OpenClaw gateway.
5
+ * Gracefully handles connection failures (returns empty array).
6
+ */
7
+ import type { SyncedAgent } from './types.js';
8
+ /**
9
+ * Fetch agent list from local OpenClaw gateway.
10
+ *
11
+ * Expected response shape from OpenClaw /api/agents:
12
+ * ```json
13
+ * [{ "id": "...", "name": "...", "emoji": "...", "status": "...", ... }]
14
+ * ```
15
+ *
16
+ * Fields are mapped to the SyncedAgent shape expected by the server.
17
+ * Missing fields get safe defaults.
18
+ */
19
+ export declare function fetchAgents(openclawUrl: string): Promise<SyncedAgent[]>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @athenafleet/bridge — Gateway Data Collector
3
+ *
4
+ * Fetches agent data from the local OpenClaw gateway.
5
+ * Gracefully handles connection failures (returns empty array).
6
+ */
7
+ import * as log from './logger.js';
8
+ /**
9
+ * Fetch agent list from local OpenClaw gateway.
10
+ *
11
+ * Expected response shape from OpenClaw /api/agents:
12
+ * ```json
13
+ * [{ "id": "...", "name": "...", "emoji": "...", "status": "...", ... }]
14
+ * ```
15
+ *
16
+ * Fields are mapped to the SyncedAgent shape expected by the server.
17
+ * Missing fields get safe defaults.
18
+ */
19
+ export async function fetchAgents(openclawUrl) {
20
+ const url = `${openclawUrl.replace(/\/+$/, '')}/api/agents`;
21
+ try {
22
+ const controller = new AbortController();
23
+ const timeout = setTimeout(() => controller.abort(), 5_000);
24
+ const res = await fetch(url, {
25
+ signal: controller.signal,
26
+ headers: { Accept: 'application/json' },
27
+ });
28
+ clearTimeout(timeout);
29
+ if (!res.ok) {
30
+ log.warn(`Gateway returned ${res.status} from ${url}`);
31
+ return [];
32
+ }
33
+ const data = await res.json();
34
+ if (!Array.isArray(data)) {
35
+ log.warn('Gateway /api/agents did not return an array');
36
+ return [];
37
+ }
38
+ return data.map(sanitizeAgent).filter((a) => a !== null);
39
+ }
40
+ catch (err) {
41
+ if (err instanceof Error && err.name === 'AbortError') {
42
+ log.debug('Gateway request timed out');
43
+ }
44
+ else {
45
+ log.debug('Gateway unreachable', { error: err instanceof Error ? err.message : String(err) });
46
+ }
47
+ return [];
48
+ }
49
+ }
50
+ /**
51
+ * Map raw gateway agent data to SyncedAgent with safe defaults.
52
+ */
53
+ function sanitizeAgent(raw) {
54
+ if (typeof raw !== 'object' || raw === null)
55
+ return null;
56
+ const r = raw;
57
+ const id = r.id;
58
+ if (!id || typeof id !== 'string')
59
+ return null;
60
+ return {
61
+ id,
62
+ name: typeof r.name === 'string' ? r.name : 'Unknown',
63
+ emoji: typeof r.emoji === 'string' ? r.emoji : '🤖',
64
+ status: typeof r.status === 'string' ? r.status : 'unknown',
65
+ model: typeof r.model === 'string' ? r.model : '',
66
+ uptime: typeof r.uptime === 'number' ? r.uptime : 0,
67
+ lastHeartbeat: typeof r.lastHeartbeat === 'string' ? r.lastHeartbeat : new Date().toISOString(),
68
+ sessionCount: typeof r.sessionCount === 'number' ? r.sessionCount : 0,
69
+ };
70
+ }
71
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAEnC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,WAAmB;IACnD,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC;IAE5D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;QAE5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QAEH,YAAY,CAAC,OAAO,CAAC,CAAC;QAEtB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,oBAAoB,GAAG,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,IAAI,GAAY,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAEvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;YACxD,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACtD,GAAG,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE/C,OAAO;QACL,EAAE;QACF,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACrD,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACnD,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QAC3D,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACjD,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnD,aAAa,EAAE,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC/F,YAAY,EAAE,OAAO,CAAC,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;KACtE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @athenafleet/bridge — CLI Entry Point
4
+ *
5
+ * Usage:
6
+ * athena-bridge --key rtk_xxx [--api-url https://api.athenafleet.ai] [--verbose]
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @athenafleet/bridge — CLI Entry Point
4
+ *
5
+ * Usage:
6
+ * athena-bridge --key rtk_xxx [--api-url https://api.athenafleet.ai] [--verbose]
7
+ */
8
+ import { hostname } from 'os';
9
+ import { BridgeClient } from './client.js';
10
+ import * as log from './logger.js';
11
+ import { setVerbose } from './logger.js';
12
+ // =============================================================================
13
+ // Argument parsing (zero deps — just process.argv)
14
+ // =============================================================================
15
+ function parseArgs(argv) {
16
+ let key;
17
+ let apiUrl = 'https://api.athenafleet.ai';
18
+ let gatewayId = hostname();
19
+ let openclawUrl = 'http://localhost:3007';
20
+ let verbose = false;
21
+ for (let i = 2; i < argv.length; i++) {
22
+ const arg = argv[i];
23
+ if (arg === '--key' || arg === '-k') {
24
+ key = argv[++i];
25
+ }
26
+ else if (arg.startsWith('--key=')) {
27
+ key = arg.slice('--key='.length);
28
+ }
29
+ else if (arg === '--api-url') {
30
+ apiUrl = argv[++i] ?? apiUrl;
31
+ }
32
+ else if (arg.startsWith('--api-url=')) {
33
+ apiUrl = arg.slice('--api-url='.length);
34
+ }
35
+ else if (arg === '--gateway-id') {
36
+ gatewayId = argv[++i] ?? gatewayId;
37
+ }
38
+ else if (arg.startsWith('--gateway-id=')) {
39
+ gatewayId = arg.slice('--gateway-id='.length);
40
+ }
41
+ else if (arg === '--openclaw-url') {
42
+ openclawUrl = argv[++i] ?? openclawUrl;
43
+ }
44
+ else if (arg.startsWith('--openclaw-url=')) {
45
+ openclawUrl = arg.slice('--openclaw-url='.length);
46
+ }
47
+ else if (arg === '--verbose' || arg === '-v') {
48
+ verbose = true;
49
+ }
50
+ else if (arg === '--help' || arg === '-h') {
51
+ printHelp();
52
+ process.exit(0);
53
+ }
54
+ else if (arg === '--version') {
55
+ console.log('1.0.0');
56
+ process.exit(0);
57
+ }
58
+ else {
59
+ console.error(`Unknown argument: ${arg}`);
60
+ printHelp();
61
+ process.exit(1);
62
+ }
63
+ }
64
+ if (!key) {
65
+ console.error('Error: --key is required\n');
66
+ printHelp();
67
+ process.exit(1);
68
+ }
69
+ if (!key.startsWith('rtk_')) {
70
+ console.error('Error: key must start with rtk_ prefix\n');
71
+ process.exit(1);
72
+ }
73
+ return { key, apiUrl, gatewayId, openclawUrl, verbose };
74
+ }
75
+ function printHelp() {
76
+ console.log(`
77
+ @athenafleet/bridge — Connect your OpenClaw gateway to Athena
78
+
79
+ Usage:
80
+ athena-bridge --key rtk_xxx [options]
81
+
82
+ Options:
83
+ --key, -k <key> Runtime API key (required, rtk_ prefix)
84
+ --api-url <url> Athena API URL (default: https://api.athenafleet.ai)
85
+ --gateway-id <id> Gateway identifier (default: hostname)
86
+ --openclaw-url <url> Local OpenClaw URL (default: http://localhost:3007)
87
+ --verbose, -v Enable debug logging
88
+ --version Print version
89
+ --help, -h Print this help
90
+
91
+ Examples:
92
+ athena-bridge --key rtk_abc123
93
+ athena-bridge --key rtk_abc123 --api-url http://localhost:4000 --verbose
94
+ `);
95
+ }
96
+ // =============================================================================
97
+ // Main
98
+ // =============================================================================
99
+ const options = parseArgs(process.argv);
100
+ setVerbose(options.verbose);
101
+ log.info('@athenafleet/bridge starting...');
102
+ log.info(` API: ${options.apiUrl}`);
103
+ log.info(` Gateway: ${options.gatewayId}`);
104
+ log.info(` OpenClaw: ${options.openclawUrl}`);
105
+ log.info(` Verbose: ${options.verbose}`);
106
+ const client = new BridgeClient(options);
107
+ // Graceful shutdown
108
+ function shutdown(signal) {
109
+ log.info(`Received ${signal}`);
110
+ client.stop();
111
+ // Give WS close frame time to send
112
+ setTimeout(() => process.exit(0), 500);
113
+ }
114
+ process.on('SIGINT', () => shutdown('SIGINT'));
115
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
116
+ client.start();
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @athenafleet/bridge — Logger
3
+ *
4
+ * Simple structured logger with verbose mode.
5
+ */
6
+ export declare function setVerbose(enabled: boolean): void;
7
+ export declare function info(msg: string, data?: Record<string, unknown>): void;
8
+ export declare function error(msg: string, err?: unknown): void;
9
+ export declare function warn(msg: string): void;
10
+ export declare function debug(msg: string, data?: Record<string, unknown>): void;
package/dist/logger.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @athenafleet/bridge — Logger
3
+ *
4
+ * Simple structured logger with verbose mode.
5
+ */
6
+ let verboseEnabled = false;
7
+ export function setVerbose(enabled) {
8
+ verboseEnabled = enabled;
9
+ }
10
+ function timestamp() {
11
+ return new Date().toISOString();
12
+ }
13
+ export function info(msg, data) {
14
+ const parts = [`[${timestamp()}] ${msg}`];
15
+ if (data && verboseEnabled) {
16
+ parts.push(JSON.stringify(data));
17
+ }
18
+ console.log(parts.join(' '));
19
+ }
20
+ export function error(msg, err) {
21
+ const parts = [`[${timestamp()}] ERROR: ${msg}`];
22
+ if (err instanceof Error) {
23
+ parts.push(`— ${err.message}`);
24
+ }
25
+ console.error(parts.join(' '));
26
+ }
27
+ export function warn(msg) {
28
+ console.warn(`[${timestamp()}] WARN: ${msg}`);
29
+ }
30
+ export function debug(msg, data) {
31
+ if (!verboseEnabled)
32
+ return;
33
+ const parts = [`[${timestamp()}] DEBUG: ${msg}`];
34
+ if (data) {
35
+ parts.push(JSON.stringify(data));
36
+ }
37
+ console.log(parts.join(' '));
38
+ }
39
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,cAAc,GAAG,OAAO,CAAC;AAC3B,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW,EAAE,IAA8B;IAC9D,MAAM,KAAK,GAAG,CAAC,IAAI,SAAS,EAAE,KAAK,GAAG,EAAE,CAAC,CAAC;IAC1C,IAAI,IAAI,IAAI,cAAc,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW,EAAE,GAAa;IAC9C,MAAM,KAAK,GAAG,CAAC,IAAI,SAAS,EAAE,YAAY,GAAG,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,WAAW,GAAG,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW,EAAE,IAA8B;IAC/D,IAAI,CAAC,cAAc;QAAE,OAAO;IAC5B,MAAM,KAAK,GAAG,CAAC,IAAI,SAAS,EAAE,YAAY,GAAG,EAAE,CAAC,CAAC;IACjD,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @athenafleet/bridge — Message Types
3
+ *
4
+ * Mirrors athena-api src/ws/runtime-types.ts
5
+ */
6
+ export interface SyncedAgent {
7
+ id: string;
8
+ name: string;
9
+ emoji: string;
10
+ status: string;
11
+ model: string;
12
+ uptime: number;
13
+ lastHeartbeat: string;
14
+ sessionCount: number;
15
+ }
16
+ export interface AuthMessage {
17
+ type: 'auth';
18
+ key: string;
19
+ bridge_version: string;
20
+ gateway_version: string;
21
+ }
22
+ export interface AgentsSyncMessage {
23
+ type: 'agents.sync';
24
+ agents: SyncedAgent[];
25
+ gateway_id: string;
26
+ timestamp: string;
27
+ }
28
+ export interface AgentStatusMessage {
29
+ type: 'agents.status';
30
+ agent_id: string;
31
+ status: string;
32
+ timestamp: string;
33
+ }
34
+ export interface HealthPingMessage {
35
+ type: 'health.ping';
36
+ gateway_connected: boolean;
37
+ agent_count: number;
38
+ uptime_ms: number;
39
+ timestamp: string;
40
+ }
41
+ export type BridgeOutbound = AuthMessage | AgentsSyncMessage | AgentStatusMessage | HealthPingMessage;
42
+ export interface AuthOkMessage {
43
+ type: 'auth_ok';
44
+ runtime_id: string;
45
+ heartbeat_interval_ms: number;
46
+ }
47
+ export interface AuthErrorMessage {
48
+ type: 'auth_error';
49
+ reason: string;
50
+ }
51
+ export interface SyncRequestMessage {
52
+ type: 'agents.sync_request';
53
+ }
54
+ export interface RuntimeDisconnectMessage {
55
+ type: 'runtime.disconnect';
56
+ reason: string;
57
+ }
58
+ export interface ErrorMessage {
59
+ type: 'error';
60
+ message: string;
61
+ }
62
+ export type ServerInbound = AuthOkMessage | AuthErrorMessage | SyncRequestMessage | RuntimeDisconnectMessage | ErrorMessage;
63
+ export declare const CLOSE_CODES: {
64
+ readonly INVALID_AUTH: 4001;
65
+ readonly LIMIT_EXCEEDED: 4003;
66
+ readonly AUTH_TIMEOUT: 4008;
67
+ readonly PONG_TIMEOUT: 4009;
68
+ };
69
+ export interface BridgeOptions {
70
+ key: string;
71
+ apiUrl: string;
72
+ gatewayId: string;
73
+ openclawUrl: string;
74
+ verbose: boolean;
75
+ }
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @athenafleet/bridge — Message Types
3
+ *
4
+ * Mirrors athena-api src/ws/runtime-types.ts
5
+ */
6
+ // =============================================================================
7
+ // Close codes (from server)
8
+ // =============================================================================
9
+ export const CLOSE_CODES = {
10
+ INVALID_AUTH: 4001,
11
+ LIMIT_EXCEEDED: 4003,
12
+ AUTH_TIMEOUT: 4008,
13
+ PONG_TIMEOUT: 4009,
14
+ };
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4FH,gFAAgF;AAChF,4BAA4B;AAC5B,gFAAgF;AAEhF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE,IAAI;IAClB,cAAc,EAAE,IAAI;IACpB,YAAY,EAAE,IAAI;IAClB,YAAY,EAAE,IAAI;CACV,CAAC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@athenafleet/bridge",
3
+ "version": "1.0.0",
4
+ "description": "CLI bridge connecting local OpenClaw gateway to Athena dashboard via WebSocket",
5
+ "type": "module",
6
+ "bin": {
7
+ "athena-bridge": "dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepare": "npm run build",
16
+ "dev": "tsc --watch"
17
+ },
18
+ "keywords": [
19
+ "athena",
20
+ "bridge",
21
+ "openclaw",
22
+ "runtime",
23
+ "websocket"
24
+ ],
25
+ "license": "MIT",
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "dependencies": {
30
+ "ws": "^8.18.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.0.0",
34
+ "@types/ws": "^8.5.0",
35
+ "typescript": "^5.7.0"
36
+ }
37
+ }