@creatornader/agent-bridge 0.2.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/CHANGELOG.md +60 -0
- package/LICENSE +216 -0
- package/README.md +338 -0
- package/SKILL.md +24 -0
- package/bin/CLAUDE.md +11 -0
- package/bin/agent-bridge +7 -0
- package/clients/claude-code.json +20 -0
- package/clients/claude-desktop.json +20 -0
- package/clients/codex.json +20 -0
- package/clients/generic-mcp.json +13 -0
- package/clients/openclaw.json +13 -0
- package/dist/bridge-domain-BcnBeHOn.d.ts +97 -0
- package/dist/bridge-service-CfqMcMAn.d.ts +28 -0
- package/dist/bridge-store-BuGjxTn4.d.ts +61 -0
- package/dist/bridge.d.ts +102 -0
- package/dist/bridge.js +83 -0
- package/dist/bridge.js.map +1 -0
- package/dist/chunk-4KAOBC5X.js +773 -0
- package/dist/chunk-4KAOBC5X.js.map +1 -0
- package/dist/chunk-6MAZ6YLZ.js +146 -0
- package/dist/chunk-6MAZ6YLZ.js.map +1 -0
- package/dist/chunk-AXL2I7FH.js +423 -0
- package/dist/chunk-AXL2I7FH.js.map +1 -0
- package/dist/chunk-BV6W4AJK.js +211 -0
- package/dist/chunk-BV6W4AJK.js.map +1 -0
- package/dist/chunk-CKSKUBDQ.js +153 -0
- package/dist/chunk-CKSKUBDQ.js.map +1 -0
- package/dist/chunk-JOUBDRYX.js +272 -0
- package/dist/chunk-JOUBDRYX.js.map +1 -0
- package/dist/chunk-JTC5YURG.js +49 -0
- package/dist/chunk-JTC5YURG.js.map +1 -0
- package/dist/chunk-KVR4JBRF.js +80 -0
- package/dist/chunk-KVR4JBRF.js.map +1 -0
- package/dist/chunk-O26W5QHS.js +409 -0
- package/dist/chunk-O26W5QHS.js.map +1 -0
- package/dist/chunk-WEULPMUY.js +988 -0
- package/dist/chunk-WEULPMUY.js.map +1 -0
- package/dist/chunk-YD6YJATN.js +48 -0
- package/dist/chunk-YD6YJATN.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +670 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-config.d.ts +30 -0
- package/dist/client-config.js +10 -0
- package/dist/client-config.js.map +1 -0
- package/dist/client-runtime.d.ts +15 -0
- package/dist/client-runtime.js +15 -0
- package/dist/client-runtime.js.map +1 -0
- package/dist/gateway-BULozv3K.d.ts +54 -0
- package/dist/gateway-main.d.ts +2 -0
- package/dist/gateway-main.js +105 -0
- package/dist/gateway-main.js.map +1 -0
- package/dist/gateway.d.ts +4 -0
- package/dist/gateway.js +10 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +34 -0
- package/dist/server.js +19 -0
- package/dist/server.js.map +1 -0
- package/dist/sqlite.d.ts +156 -0
- package/dist/sqlite.js +24 -0
- package/dist/sqlite.js.map +1 -0
- package/llms.txt +39 -0
- package/package.json +104 -0
- package/sql/migrations/001_schema_state.sql +34 -0
- package/sql/migrations/002_workspaces_agents_credentials.sql +77 -0
- package/sql/migrations/003_messages_receipts_deliveries.sql +116 -0
- package/sql/migrations/004_message_query_indexes.sql +31 -0
- package/sql/migrations/005_delivery_events_presence.sql +79 -0
- package/sql/migrations/006_legacy_shared_context_import.sql +139 -0
- package/sql/migrations/007_runtime_role.sql +59 -0
- package/sql/setup.sql +68 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runMigrations
|
|
4
|
+
} from "./chunk-6MAZ6YLZ.js";
|
|
5
|
+
import {
|
|
6
|
+
readClientConfigFile,
|
|
7
|
+
resolveClientConfig
|
|
8
|
+
} from "./chunk-KVR4JBRF.js";
|
|
9
|
+
import {
|
|
10
|
+
createClientRuntime
|
|
11
|
+
} from "./chunk-JTC5YURG.js";
|
|
12
|
+
import {
|
|
13
|
+
BridgeHttpError,
|
|
14
|
+
LegacySupabaseError,
|
|
15
|
+
legacyContextMetadata,
|
|
16
|
+
legacyNumericMessageId
|
|
17
|
+
} from "./chunk-AXL2I7FH.js";
|
|
18
|
+
import "./chunk-WEULPMUY.js";
|
|
19
|
+
import "./chunk-YD6YJATN.js";
|
|
20
|
+
import "./chunk-CKSKUBDQ.js";
|
|
21
|
+
import "./chunk-BV6W4AJK.js";
|
|
22
|
+
|
|
23
|
+
// src/cli.ts
|
|
24
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
25
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
26
|
+
import { homedir as homedir2, tmpdir } from "os";
|
|
27
|
+
import { mkdtempSync } from "fs";
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
29
|
+
import pg from "pg";
|
|
30
|
+
|
|
31
|
+
// src/client-installer.ts
|
|
32
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
33
|
+
import { createHash, randomUUID } from "crypto";
|
|
34
|
+
import { homedir } from "os";
|
|
35
|
+
import { dirname, join } from "path";
|
|
36
|
+
import { spawnSync } from "child_process";
|
|
37
|
+
function desktopConfigPath(env) {
|
|
38
|
+
if (process.platform === "win32") {
|
|
39
|
+
const appData = env.APPDATA;
|
|
40
|
+
if (!appData) throw new Error("APPDATA is required for Claude Desktop installation");
|
|
41
|
+
return join(appData, "Claude", "claude_desktop_config.json");
|
|
42
|
+
}
|
|
43
|
+
const home = env.HOME ?? homedir();
|
|
44
|
+
return process.platform === "darwin" ? join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
45
|
+
}
|
|
46
|
+
function safeComponent(value) {
|
|
47
|
+
const readable = value.replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 48) || "client";
|
|
48
|
+
const suffix = createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
49
|
+
return `${readable}-${suffix}`;
|
|
50
|
+
}
|
|
51
|
+
function clientBackendConfigPath(runtime, instance, env) {
|
|
52
|
+
const home = env.HOME ?? homedir();
|
|
53
|
+
return join(
|
|
54
|
+
home,
|
|
55
|
+
".agent-bridge",
|
|
56
|
+
"clients",
|
|
57
|
+
`${runtime}-${safeComponent(instance)}.config`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function replacePrivateFile(path, content) {
|
|
61
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
62
|
+
if (process.platform !== "win32") chmodSync(dirname(path), 448);
|
|
63
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
64
|
+
writeFileSync(temporary, content, { mode: 384 });
|
|
65
|
+
renameSync(temporary, path);
|
|
66
|
+
if (process.platform !== "win32") chmodSync(path, 384);
|
|
67
|
+
}
|
|
68
|
+
function writeBackendConfig(runtime, identity, instance, options) {
|
|
69
|
+
const env = options.env;
|
|
70
|
+
const home = env.HOME ?? homedir();
|
|
71
|
+
const sharedPath = env.AGENT_BRIDGE_CONFIG ?? join(home, ".agent-bridge", "config");
|
|
72
|
+
const shared = readClientConfigFile(sharedPath);
|
|
73
|
+
const configuredProvider = env.AGENT_BRIDGE_PROVIDER?.trim() || shared.AGENT_BRIDGE_PROVIDER?.trim();
|
|
74
|
+
const inferredGateway = !configuredProvider && Boolean(
|
|
75
|
+
env.AGENT_BRIDGE_TOKEN?.trim() || shared.AGENT_BRIDGE_TOKEN?.trim()
|
|
76
|
+
);
|
|
77
|
+
const provider = configuredProvider === "legacy" || configuredProvider === "supabase" ? "legacy-supabase" : configuredProvider ?? (inferredGateway ? "gateway" : void 0);
|
|
78
|
+
const clientToken = options.token?.trim() || env.AGENT_BRIDGE_CLIENT_TOKEN?.trim();
|
|
79
|
+
if (provider === "gateway" && !clientToken) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"gateway client installation requires --token or AGENT_BRIDGE_CLIENT_TOKEN"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const resolved = resolveClientConfig({
|
|
85
|
+
...env,
|
|
86
|
+
AGENT_BRIDGE_AGENT: void 0,
|
|
87
|
+
AGENT_BRIDGE_INSTANCE: void 0,
|
|
88
|
+
AGENT_BRIDGE_TOKEN: clientToken ?? env.AGENT_BRIDGE_TOKEN
|
|
89
|
+
}, identity);
|
|
90
|
+
const values = {
|
|
91
|
+
AGENT_BRIDGE_PROVIDER: resolved.provider,
|
|
92
|
+
AGENT_BRIDGE_WORKSPACE: resolved.principal.workspace,
|
|
93
|
+
AGENT_BRIDGE_URL: resolved.url,
|
|
94
|
+
AGENT_BRIDGE_TOKEN: resolved.provider === "gateway" ? resolved.credential : void 0,
|
|
95
|
+
AGENT_BRIDGE_KEY: resolved.provider === "legacy-supabase" ? resolved.credential : void 0,
|
|
96
|
+
AGENT_BRIDGE_DB: resolved.provider === "local" ? resolved.databasePath : void 0,
|
|
97
|
+
AGENT_BRIDGE_EDGE_DB: resolved.provider === "gateway" ? resolved.edgeDatabasePath : void 0
|
|
98
|
+
};
|
|
99
|
+
for (const value of Object.values(values)) {
|
|
100
|
+
if (value?.includes("\n") || value?.includes("\r")) {
|
|
101
|
+
throw new Error("client backend config values cannot contain newlines");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const path = clientBackendConfigPath(runtime, instance, env);
|
|
105
|
+
replacePrivateFile(
|
|
106
|
+
path,
|
|
107
|
+
`${Object.entries(values).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
108
|
+
`
|
|
109
|
+
);
|
|
110
|
+
return path;
|
|
111
|
+
}
|
|
112
|
+
function writeJsonConfig(path, identity, instance, backendConfigPath, command) {
|
|
113
|
+
let config = {};
|
|
114
|
+
if (existsSync(path)) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
117
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") throw new Error();
|
|
118
|
+
config = parsed;
|
|
119
|
+
} catch {
|
|
120
|
+
throw new Error(`Claude Desktop config is not valid JSON: ${path}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const currentServers = config.mcpServers;
|
|
124
|
+
const mcpServers = currentServers && !Array.isArray(currentServers) && typeof currentServers === "object" ? currentServers : {};
|
|
125
|
+
const next = {
|
|
126
|
+
...config,
|
|
127
|
+
mcpServers: {
|
|
128
|
+
...mcpServers,
|
|
129
|
+
"agent-bridge": {
|
|
130
|
+
command,
|
|
131
|
+
env: {
|
|
132
|
+
AGENT_BRIDGE_AGENT: identity,
|
|
133
|
+
AGENT_BRIDGE_INSTANCE: instance,
|
|
134
|
+
AGENT_BRIDGE_CONFIG: backendConfigPath
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
140
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
141
|
+
writeFileSync(temporary, `${JSON.stringify(next, null, 2)}
|
|
142
|
+
`, { mode: 384 });
|
|
143
|
+
renameSync(temporary, path);
|
|
144
|
+
}
|
|
145
|
+
function installClient(runtime, identity, options = {}, execute = (command, args) => spawnSync(command, args, { encoding: "utf8" })) {
|
|
146
|
+
const normalizedIdentity = identity.trim();
|
|
147
|
+
if (!normalizedIdentity || normalizedIdentity.length > 128) throw new Error("--identity is required");
|
|
148
|
+
const instance = options.instance?.trim() || `${runtime}-${randomUUID()}`;
|
|
149
|
+
if (instance.length > 128) throw new Error("--instance exceeds 128 characters");
|
|
150
|
+
if (options.scope && !["local", "user", "project"].includes(options.scope)) {
|
|
151
|
+
throw new Error("scope must be local, user, or project");
|
|
152
|
+
}
|
|
153
|
+
const command = options.command?.trim() || "agent-bridge-mcp";
|
|
154
|
+
const env = options.env ?? process.env;
|
|
155
|
+
const expectedPath = clientBackendConfigPath(runtime, instance, env);
|
|
156
|
+
const previous = existsSync(expectedPath) ? readFileSync(expectedPath) : void 0;
|
|
157
|
+
try {
|
|
158
|
+
const backendConfigPath = writeBackendConfig(runtime, normalizedIdentity, instance, {
|
|
159
|
+
token: options.token,
|
|
160
|
+
env
|
|
161
|
+
});
|
|
162
|
+
if (runtime === "claude-desktop") {
|
|
163
|
+
const path = desktopConfigPath(env);
|
|
164
|
+
writeJsonConfig(path, normalizedIdentity, instance, backendConfigPath, command);
|
|
165
|
+
return { runtime, identity: normalizedIdentity, instance, method: "json-config", configPath: path, backendConfigPath, restartRequired: true };
|
|
166
|
+
}
|
|
167
|
+
const executable = runtime === "codex" ? "codex" : "claude";
|
|
168
|
+
const args = runtime === "codex" ? [
|
|
169
|
+
"mcp",
|
|
170
|
+
"add",
|
|
171
|
+
"agent-bridge",
|
|
172
|
+
"--env",
|
|
173
|
+
`AGENT_BRIDGE_AGENT=${normalizedIdentity}`,
|
|
174
|
+
"--env",
|
|
175
|
+
`AGENT_BRIDGE_INSTANCE=${instance}`,
|
|
176
|
+
"--env",
|
|
177
|
+
`AGENT_BRIDGE_CONFIG=${backendConfigPath}`,
|
|
178
|
+
"--",
|
|
179
|
+
command
|
|
180
|
+
] : [
|
|
181
|
+
"mcp",
|
|
182
|
+
"add",
|
|
183
|
+
"--scope",
|
|
184
|
+
options.scope ?? "user",
|
|
185
|
+
"agent-bridge",
|
|
186
|
+
"-e",
|
|
187
|
+
`AGENT_BRIDGE_AGENT=${normalizedIdentity}`,
|
|
188
|
+
"-e",
|
|
189
|
+
`AGENT_BRIDGE_INSTANCE=${instance}`,
|
|
190
|
+
"-e",
|
|
191
|
+
`AGENT_BRIDGE_CONFIG=${backendConfigPath}`,
|
|
192
|
+
"--",
|
|
193
|
+
command
|
|
194
|
+
];
|
|
195
|
+
const result = execute(executable, args);
|
|
196
|
+
if (result.error || result.status !== 0) {
|
|
197
|
+
const detail = result.stderr?.trim() || result.error?.message || `exit ${result.status}`;
|
|
198
|
+
throw new Error(`${runtime} MCP installation failed: ${detail}`);
|
|
199
|
+
}
|
|
200
|
+
return { runtime, identity: normalizedIdentity, instance, method: "native-cli", backendConfigPath, restartRequired: true };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
try {
|
|
203
|
+
if (previous) replacePrivateFile(expectedPath, previous);
|
|
204
|
+
else rmSync(expectedPath, { force: true });
|
|
205
|
+
} catch (rollbackError) {
|
|
206
|
+
const detail = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
|
|
207
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; client config rollback failed: ${detail}`);
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/cli.ts
|
|
214
|
+
var SUPPORTED_OPTIONS = /* @__PURE__ */ new Set([
|
|
215
|
+
"agent",
|
|
216
|
+
"as",
|
|
217
|
+
"atrib-receipt-id",
|
|
218
|
+
"category",
|
|
219
|
+
"causation-id",
|
|
220
|
+
"config",
|
|
221
|
+
"content",
|
|
222
|
+
"content-type",
|
|
223
|
+
"correlation-id",
|
|
224
|
+
"cursor",
|
|
225
|
+
"data",
|
|
226
|
+
"db",
|
|
227
|
+
"dead",
|
|
228
|
+
"delivery-id",
|
|
229
|
+
"error",
|
|
230
|
+
"expires-at",
|
|
231
|
+
"force",
|
|
232
|
+
"idempotency-key",
|
|
233
|
+
"ids",
|
|
234
|
+
"informed-by",
|
|
235
|
+
"instance",
|
|
236
|
+
"interval-ms",
|
|
237
|
+
"json",
|
|
238
|
+
"key",
|
|
239
|
+
"kind",
|
|
240
|
+
"lease-ms",
|
|
241
|
+
"lease-token",
|
|
242
|
+
"limit",
|
|
243
|
+
"max-attempts",
|
|
244
|
+
"max-interval-ms",
|
|
245
|
+
"message-id",
|
|
246
|
+
"metadata",
|
|
247
|
+
"payload",
|
|
248
|
+
"payload-ciphertext",
|
|
249
|
+
"payload-mime",
|
|
250
|
+
"payload-ref",
|
|
251
|
+
"polls",
|
|
252
|
+
"priority",
|
|
253
|
+
"project",
|
|
254
|
+
"provider",
|
|
255
|
+
"reply-to-id",
|
|
256
|
+
"since",
|
|
257
|
+
"source",
|
|
258
|
+
"target",
|
|
259
|
+
"target-agent",
|
|
260
|
+
"target-agents",
|
|
261
|
+
"thread-id",
|
|
262
|
+
"token",
|
|
263
|
+
"type",
|
|
264
|
+
"unacked-by",
|
|
265
|
+
"url",
|
|
266
|
+
"workspace",
|
|
267
|
+
"identity",
|
|
268
|
+
"command",
|
|
269
|
+
"scope",
|
|
270
|
+
"runtime",
|
|
271
|
+
"capability",
|
|
272
|
+
"max-pages",
|
|
273
|
+
"max-push"
|
|
274
|
+
]);
|
|
275
|
+
var BOOLEAN_OPTIONS = /* @__PURE__ */ new Set(["dead", "force", "json"]);
|
|
276
|
+
function parse(argv) {
|
|
277
|
+
const command = argv[0] ?? "help";
|
|
278
|
+
const options = {};
|
|
279
|
+
const positionals = [];
|
|
280
|
+
for (let i = 1; i < argv.length; i++) {
|
|
281
|
+
const value = argv[i];
|
|
282
|
+
if (!value.startsWith("--")) {
|
|
283
|
+
positionals.push(value);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const key = value.slice(2);
|
|
287
|
+
const next = argv[i + 1];
|
|
288
|
+
if (!next || next.startsWith("--")) {
|
|
289
|
+
if (!BOOLEAN_OPTIONS.has(key)) throw new Error(`--${key} requires a value`);
|
|
290
|
+
options[key] = true;
|
|
291
|
+
} else {
|
|
292
|
+
i++;
|
|
293
|
+
const previous = options[key];
|
|
294
|
+
options[key] = previous === void 0 ? next : Array.isArray(previous) ? [...previous, next] : [String(previous), next];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return { command, options, positionals };
|
|
298
|
+
}
|
|
299
|
+
function one(options, key) {
|
|
300
|
+
const value = options[key];
|
|
301
|
+
return Array.isArray(value) ? value[value.length - 1] : typeof value === "string" ? value : void 0;
|
|
302
|
+
}
|
|
303
|
+
function list(options, key) {
|
|
304
|
+
const value = options[key];
|
|
305
|
+
return (Array.isArray(value) ? value : value ? [String(value)] : []).flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
306
|
+
}
|
|
307
|
+
function boolean(options, key) {
|
|
308
|
+
const value = options[key];
|
|
309
|
+
const selected = Array.isArray(value) ? value[value.length - 1] : value;
|
|
310
|
+
if (selected === void 0) return false;
|
|
311
|
+
if (selected === true || selected === "true") return true;
|
|
312
|
+
if (selected === false || selected === "false") return false;
|
|
313
|
+
throw new Error(`--${key} must be true or false`);
|
|
314
|
+
}
|
|
315
|
+
function integer(options, key, fallback) {
|
|
316
|
+
const raw = one(options, key);
|
|
317
|
+
const result = raw === void 0 ? fallback : Number(raw);
|
|
318
|
+
if (!Number.isSafeInteger(result) || result < 0) throw new Error(`--${key} must be a non-negative integer`);
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
function json(options, key) {
|
|
322
|
+
const raw = one(options, key);
|
|
323
|
+
if (raw === void 0) return void 0;
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(raw);
|
|
326
|
+
} catch {
|
|
327
|
+
throw new Error(`--${key} must be valid JSON`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function since(options) {
|
|
331
|
+
const raw = one(options, "since");
|
|
332
|
+
if (!raw) return void 0;
|
|
333
|
+
const relative = raw.match(/^(\d+)(h|d)$/);
|
|
334
|
+
if (!relative) return raw;
|
|
335
|
+
const amount = Number(relative[1]);
|
|
336
|
+
const milliseconds = amount * (relative[2] === "h" ? 36e5 : 864e5);
|
|
337
|
+
return new Date(Date.now() - milliseconds).toISOString();
|
|
338
|
+
}
|
|
339
|
+
function output(value) {
|
|
340
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
341
|
+
`);
|
|
342
|
+
}
|
|
343
|
+
function cursor(path) {
|
|
344
|
+
try {
|
|
345
|
+
return readFileSync2(path, "utf8").trim() || void 0;
|
|
346
|
+
} catch {
|
|
347
|
+
return void 0;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function saveCursor(path, value) {
|
|
351
|
+
if (!value) return;
|
|
352
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
353
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
354
|
+
writeFileSync2(temporary, `${value}
|
|
355
|
+
`, { mode: 384 });
|
|
356
|
+
renameSync2(temporary, path);
|
|
357
|
+
}
|
|
358
|
+
function help() {
|
|
359
|
+
process.stdout.write(`agent-bridge: provider-neutral agent messaging
|
|
360
|
+
|
|
361
|
+
Commands:
|
|
362
|
+
init, doctor, status, pending, migrate, sync, demo, join, presence
|
|
363
|
+
send (post), inbox (get), history, acknowledge, claim, extend, ack, nack, watch
|
|
364
|
+
clients install <codex|claude-code|claude-desktop> --identity <name>
|
|
365
|
+
`);
|
|
366
|
+
}
|
|
367
|
+
function rejectUnknownOptions(options) {
|
|
368
|
+
const unknown = Object.keys(options).filter((key) => !SUPPORTED_OPTIONS.has(key));
|
|
369
|
+
if (unknown.length) throw new Error(`unknown option: --${unknown[0]}`);
|
|
370
|
+
}
|
|
371
|
+
async function initialize(options) {
|
|
372
|
+
const home = process.env.HOME ?? homedir2();
|
|
373
|
+
const configPath = one(options, "config") ?? process.env.AGENT_BRIDGE_CONFIG ?? join2(home, ".agent-bridge", "config");
|
|
374
|
+
if (existsSync2(configPath) && !boolean(options, "force")) {
|
|
375
|
+
throw new Error(`Config already exists at ${configPath}; pass --force to replace it`);
|
|
376
|
+
}
|
|
377
|
+
const provider = one(options, "provider") ?? process.env.AGENT_BRIDGE_PROVIDER ?? "local";
|
|
378
|
+
if (one(options, "instance")) {
|
|
379
|
+
throw new Error("--instance is client-scoped; set it in the active client process");
|
|
380
|
+
}
|
|
381
|
+
const values = {
|
|
382
|
+
AGENT_BRIDGE_PROVIDER: provider,
|
|
383
|
+
AGENT_BRIDGE_WORKSPACE: one(options, "workspace") ?? process.env.AGENT_BRIDGE_WORKSPACE ?? (provider === "legacy-supabase" || provider === "legacy" || provider === "supabase" ? "*" : "default"),
|
|
384
|
+
AGENT_BRIDGE_DB: one(options, "db") ?? process.env.AGENT_BRIDGE_DB,
|
|
385
|
+
AGENT_BRIDGE_EDGE_DB: process.env.AGENT_BRIDGE_EDGE_DB,
|
|
386
|
+
AGENT_BRIDGE_URL: one(options, "url") ?? process.env.AGENT_BRIDGE_URL,
|
|
387
|
+
AGENT_BRIDGE_KEY: one(options, "key") ?? process.env.AGENT_BRIDGE_KEY
|
|
388
|
+
};
|
|
389
|
+
const verificationToken = one(options, "token") ?? process.env.AGENT_BRIDGE_TOKEN;
|
|
390
|
+
for (const value of Object.values(values)) {
|
|
391
|
+
if (value?.includes("\n") || value?.includes("\r")) throw new Error("config values cannot contain newlines");
|
|
392
|
+
}
|
|
393
|
+
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
394
|
+
const temporary = `${configPath}.${process.pid}.tmp`;
|
|
395
|
+
writeFileSync2(
|
|
396
|
+
temporary,
|
|
397
|
+
Object.entries(values).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${value}`).join("\n") + "\n",
|
|
398
|
+
{ mode: 384 }
|
|
399
|
+
);
|
|
400
|
+
let config;
|
|
401
|
+
try {
|
|
402
|
+
config = resolveClientConfig(
|
|
403
|
+
{
|
|
404
|
+
HOME: home,
|
|
405
|
+
AGENT_BRIDGE_CONFIG: temporary,
|
|
406
|
+
AGENT_BRIDGE_TOKEN: verificationToken
|
|
407
|
+
},
|
|
408
|
+
"agent-bridge-init"
|
|
409
|
+
);
|
|
410
|
+
const runtime = await createClientRuntime(config);
|
|
411
|
+
try {
|
|
412
|
+
if (config.provider === "gateway") await runtime.store.verifyRemote?.();
|
|
413
|
+
} finally {
|
|
414
|
+
await runtime.close();
|
|
415
|
+
}
|
|
416
|
+
renameSync2(temporary, configPath);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
rmSync2(temporary, { force: true });
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
output({ status: "ok", config: configPath, provider: config.provider, workspace: config.principal.workspace, identity: "process-scoped", database: config.provider === "local" ? config.databasePath : null });
|
|
422
|
+
}
|
|
423
|
+
function configFor(options, command) {
|
|
424
|
+
const explicit = command === "send" ? one(options, "source") ?? one(options, "agent") : command === "acknowledge" || command === "ack" && Boolean(one(options, "ids")) ? one(options, "agent") : one(options, "as");
|
|
425
|
+
const project = one(options, "project");
|
|
426
|
+
const environment = one(options, "instance") ? { ...process.env, AGENT_BRIDGE_INSTANCE: one(options, "instance") } : process.env;
|
|
427
|
+
const config = resolveClientConfig(environment, explicit);
|
|
428
|
+
if (!project || project === config.principal.workspace) return config;
|
|
429
|
+
if (config.provider === "gateway") {
|
|
430
|
+
throw new Error("--project cannot override the workspace bound to a gateway credential");
|
|
431
|
+
}
|
|
432
|
+
return resolveClientConfig(
|
|
433
|
+
{ ...environment, AGENT_BRIDGE_WORKSPACE: project },
|
|
434
|
+
explicit
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
function draft(options, positionals) {
|
|
438
|
+
const content = one(options, "content") ?? positionals.join(" ");
|
|
439
|
+
const metadataInput = json(options, "metadata");
|
|
440
|
+
if (metadataInput !== void 0 && (metadataInput === null || typeof metadataInput !== "object" || Array.isArray(metadataInput))) throw new Error("--metadata must be a JSON object");
|
|
441
|
+
const metadata = metadataInput ?? {};
|
|
442
|
+
const envelope = typeof metadata.message_envelope === "object" && metadata.message_envelope ? { ...metadata.message_envelope } : {};
|
|
443
|
+
if (one(options, "payload-ref")) envelope.payload_ref = one(options, "payload-ref");
|
|
444
|
+
if (one(options, "payload-ciphertext")) envelope.payload_ciphertext = one(options, "payload-ciphertext");
|
|
445
|
+
if (Object.keys(envelope).length) metadata.message_envelope = envelope;
|
|
446
|
+
return {
|
|
447
|
+
type: one(options, "type") ?? one(options, "kind") ?? one(options, "category") ?? "operational",
|
|
448
|
+
content,
|
|
449
|
+
targets: [...list(options, "target"), ...list(options, "target-agent"), ...list(options, "target-agents")],
|
|
450
|
+
priority: one(options, "priority"),
|
|
451
|
+
threadId: one(options, "thread-id"),
|
|
452
|
+
replyToId: one(options, "reply-to-id"),
|
|
453
|
+
correlationId: one(options, "correlation-id"),
|
|
454
|
+
causationId: one(options, "causation-id"),
|
|
455
|
+
id: one(options, "message-id"),
|
|
456
|
+
idempotencyKey: one(options, "idempotency-key"),
|
|
457
|
+
expiresAt: one(options, "expires-at"),
|
|
458
|
+
contentType: one(options, "content-type") ?? one(options, "payload-mime"),
|
|
459
|
+
atribReceiptId: one(options, "atrib-receipt-id"),
|
|
460
|
+
informedBy: list(options, "informed-by"),
|
|
461
|
+
data: json(options, "data") ?? json(options, "payload"),
|
|
462
|
+
metadata
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
async function localDemo() {
|
|
466
|
+
const directory = mkdtempSync(join2(tmpdir(), "agent-bridge-demo-"));
|
|
467
|
+
const databasePath = join2(directory, "demo.sqlite3");
|
|
468
|
+
const base = { provider: "local", databasePath, edgeDatabasePath: join2(directory, "edge.sqlite3"), cursorPath: join2(directory, "cursor"), configPath: join2(directory, "config"), credential: void 0, url: void 0 };
|
|
469
|
+
let sender;
|
|
470
|
+
let worker;
|
|
471
|
+
try {
|
|
472
|
+
sender = await createClientRuntime({ ...base, principal: { workspace: "demo", agent: "demo-sender", instance: "sender-1" } });
|
|
473
|
+
const sent = await sender.service.publish(sender.config.principal, { type: "request", content: "deterministic local task", targets: ["demo-worker"], idempotencyKey: "demo-task-v1" });
|
|
474
|
+
await sender.close();
|
|
475
|
+
sender = void 0;
|
|
476
|
+
worker = await createClientRuntime({ ...base, principal: { workspace: "demo", agent: "demo-worker", instance: "worker-1" } });
|
|
477
|
+
const history = await worker.service.history(worker.config.principal);
|
|
478
|
+
const claim = await worker.service.claim(worker.config.principal, { leaseMs: 5e3 });
|
|
479
|
+
if (!claim) throw new Error("demo claim failed");
|
|
480
|
+
const acked = await worker.service.ack(worker.config.principal, claim.delivery.id, claim.leaseToken);
|
|
481
|
+
output({ status: "ok", principals: ["demo-sender", "demo-worker"], sent: sent.created, read: history.messages.length === 1, claimed: claim.delivery.state === "claimed", acknowledged: acked?.state === "acked" });
|
|
482
|
+
} finally {
|
|
483
|
+
await worker?.close();
|
|
484
|
+
await sender?.close();
|
|
485
|
+
rmSync2(directory, { recursive: true, force: true });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async function runCli(argv = process.argv.slice(2)) {
|
|
489
|
+
const { command: raw, options, positionals } = parse(argv);
|
|
490
|
+
const command = { post: "send", get: "inbox", receipt: "acknowledge" }[raw] ?? raw;
|
|
491
|
+
if (["help", "-h", "--help"].includes(command)) {
|
|
492
|
+
help();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
rejectUnknownOptions(options);
|
|
496
|
+
if (command === "clients") {
|
|
497
|
+
if (positionals[0] !== "install" || !positionals[1]) {
|
|
498
|
+
throw new Error("usage: agent-bridge clients install <runtime> --identity <name>");
|
|
499
|
+
}
|
|
500
|
+
const runtime2 = positionals[1];
|
|
501
|
+
if (!["codex", "claude-code", "claude-desktop"].includes(runtime2)) {
|
|
502
|
+
throw new Error(`unsupported install runtime: ${runtime2}`);
|
|
503
|
+
}
|
|
504
|
+
const scope = one(options, "scope");
|
|
505
|
+
if (scope && !["local", "user", "project"].includes(scope)) {
|
|
506
|
+
throw new Error("--scope must be local, user, or project");
|
|
507
|
+
}
|
|
508
|
+
output(installClient(runtime2, one(options, "identity") ?? "", {
|
|
509
|
+
command: one(options, "command"),
|
|
510
|
+
scope,
|
|
511
|
+
instance: one(options, "instance"),
|
|
512
|
+
token: one(options, "token"),
|
|
513
|
+
env: process.env
|
|
514
|
+
}));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (command === "demo") {
|
|
518
|
+
await localDemo();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (command === "init") {
|
|
522
|
+
await initialize(options);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (command === "migrate") {
|
|
526
|
+
const databaseUrl = process.env.AGENT_BRIDGE_DATABASE_URL;
|
|
527
|
+
if (!databaseUrl) throw new Error("AGENT_BRIDGE_DATABASE_URL is required");
|
|
528
|
+
const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 });
|
|
529
|
+
try {
|
|
530
|
+
const applied = await runMigrations(
|
|
531
|
+
pool,
|
|
532
|
+
fileURLToPath(new URL("../sql/migrations", import.meta.url))
|
|
533
|
+
);
|
|
534
|
+
output({ status: "ok", applied });
|
|
535
|
+
} finally {
|
|
536
|
+
await pool.end();
|
|
537
|
+
}
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const config = configFor(options, command);
|
|
541
|
+
const runtime = await createClientRuntime(config);
|
|
542
|
+
try {
|
|
543
|
+
if (command === "doctor" || command === "status") {
|
|
544
|
+
const diagnostics = await runtime.store.diagnostics?.(config.principal) ?? { schemaVersion: config.provider === "legacy-supabase" ? "legacy-v1" : "local-v2", deliverySupported: false, pending: null, claimed: null, retrying: null, dead: null };
|
|
545
|
+
output({ status: "ok", connected: true, provider: config.provider, workspace: config.principal.workspace, agent: config.principal.agent, instance: config.principal.instance ?? null, schemaVersion: diagnostics.schemaVersion, endpoint: config.url ?? null, database: config.provider === "local" ? config.databasePath : null, cursorPath: config.cursorPath, lastCursor: cursor(config.cursorPath) ?? null, queue: diagnostics });
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (command === "pending") {
|
|
549
|
+
const diagnostics = await runtime.store.diagnostics?.(config.principal);
|
|
550
|
+
const page = await runtime.service.history(config.principal, {
|
|
551
|
+
limit: 1,
|
|
552
|
+
unacknowledgedBy: config.principal.agent
|
|
553
|
+
});
|
|
554
|
+
const pendingDeliveries = diagnostics?.pending ?? 0;
|
|
555
|
+
const retryingDeliveries = diagnostics?.retrying ?? 0;
|
|
556
|
+
const oldest = diagnostics?.oldestAvailableAt ? Date.parse(diagnostics.oldestAvailableAt) : Number.POSITIVE_INFINITY;
|
|
557
|
+
const deliveryAvailable = pendingDeliveries > 0 || retryingDeliveries > 0 && oldest <= Date.now();
|
|
558
|
+
const unread = page.messages.length > 0;
|
|
559
|
+
const available = deliveryAvailable || unread;
|
|
560
|
+
output({
|
|
561
|
+
available,
|
|
562
|
+
unread,
|
|
563
|
+
deliveryAvailable,
|
|
564
|
+
pending: pendingDeliveries,
|
|
565
|
+
retrying: retryingDeliveries,
|
|
566
|
+
authoritative: !diagnostics || !("remoteReachable" in diagnostics) || diagnostics.remoteReachable !== false
|
|
567
|
+
});
|
|
568
|
+
if (!available) process.exitCode = 1;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (command === "send") {
|
|
572
|
+
output(await runtime.service.publish(config.principal, draft(options, positionals)));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (command === "join") {
|
|
576
|
+
output(await runtime.service.heartbeat(config.principal, { leaseMs: integer(options, "lease-ms", 6e4), runtimeType: one(options, "runtime"), capabilities: list(options, "capability") }));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (command === "presence") {
|
|
580
|
+
output({ agents: await runtime.service.presence(config.principal) });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (command === "sync") {
|
|
584
|
+
if (!runtime.store.sync) throw new Error("sync is available only with the gateway provider");
|
|
585
|
+
output(await runtime.store.sync({
|
|
586
|
+
maxPush: integer(options, "max-push", integer(options, "limit", 100)),
|
|
587
|
+
maxPages: integer(options, "max-pages", 20)
|
|
588
|
+
}));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (command === "history" || command === "inbox") {
|
|
592
|
+
const page = await runtime.service.history(config.principal, { cursor: one(options, "cursor"), limit: integer(options, "limit", 20), types: list(options, "type").concat(list(options, "category")), source: one(options, "source"), since: since(options), unacknowledgedBy: one(options, "unacked-by"), threadId: one(options, "thread-id"), latest: raw === "get" });
|
|
593
|
+
if (raw === "get") {
|
|
594
|
+
output(page.messages.map((message) => ({ id: config.provider === "legacy-supabase" ? Number(message.sequence) : message.id, source: message.source, category: message.type, content: message.content, priority: message.priority, project: message.workspace === "*" ? null : message.workspace, metadata: legacyContextMetadata(message), created_at: message.createdAt })));
|
|
595
|
+
} else output(page);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (command === "acknowledge" || command === "ack" && one(options, "ids")) {
|
|
599
|
+
const rawIds = list(options, "ids");
|
|
600
|
+
const acknowledged = config.provider === "legacy-supabase" && runtime.store.recordLegacyReceipt && rawIds.every((id) => /^\d+$/.test(id)) ? await runtime.store.recordLegacyReceipt(rawIds, config.principal.agent) : await runtime.service.acknowledge(
|
|
601
|
+
config.principal,
|
|
602
|
+
rawIds.map(legacyNumericMessageId)
|
|
603
|
+
);
|
|
604
|
+
output({ acknowledged, agent: config.principal.agent });
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (command === "claim") {
|
|
608
|
+
output(await runtime.service.claim(config.principal, { leaseMs: integer(options, "lease-ms", 3e4), maxAttempts: integer(options, "max-attempts", 5) }));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const deliveryId = one(options, "delivery-id") ?? positionals[0];
|
|
612
|
+
const leaseToken = one(options, "lease-token") ?? positionals[1];
|
|
613
|
+
if (!deliveryId || !leaseToken) {
|
|
614
|
+
if (["extend", "ack", "nack"].includes(command)) throw new Error("--delivery-id and --lease-token are required");
|
|
615
|
+
}
|
|
616
|
+
if (command === "extend") {
|
|
617
|
+
output(await runtime.service.extend(config.principal, deliveryId, leaseToken, integer(options, "lease-ms", 3e4)));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (command === "ack") {
|
|
621
|
+
output(await runtime.service.ack(config.principal, deliveryId, leaseToken));
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (command === "nack") {
|
|
625
|
+
output(await runtime.service.nack(config.principal, deliveryId, leaseToken, one(options, "error") ?? "negative acknowledgment", boolean(options, "dead")));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
if (command === "watch") {
|
|
629
|
+
const polls = one(options, "polls") === void 0 ? Number.POSITIVE_INFINITY : integer(options, "polls", 0);
|
|
630
|
+
const baseInterval = Math.min(integer(options, "interval-ms", 1e3), 6e4);
|
|
631
|
+
const maxInterval = Math.max(baseInterval, Math.min(integer(options, "max-interval-ms", 3e4), 3e5));
|
|
632
|
+
let interval = baseInterval;
|
|
633
|
+
let current = cursor(config.cursorPath);
|
|
634
|
+
let seen = 0;
|
|
635
|
+
for (let i = 0; i < polls; i++) {
|
|
636
|
+
try {
|
|
637
|
+
const page = await runtime.service.history(config.principal, { cursor: current, limit: integer(options, "limit", 50) });
|
|
638
|
+
for (const message of page.messages) output(message);
|
|
639
|
+
seen += page.messages.length;
|
|
640
|
+
current = page.cursor;
|
|
641
|
+
saveCursor(config.cursorPath, current);
|
|
642
|
+
interval = page.messages.length ? baseInterval : Math.min(maxInterval, Math.max(baseInterval, interval * 2));
|
|
643
|
+
} catch (error) {
|
|
644
|
+
const status = error instanceof BridgeHttpError || error instanceof LegacySupabaseError ? error.status : void 0;
|
|
645
|
+
const retryable = status !== void 0 && (status === 0 || status === 408 || status === 425 || status === 429 || status >= 500);
|
|
646
|
+
if (!retryable) throw error;
|
|
647
|
+
interval = Math.min(maxInterval, Math.max(baseInterval, interval * 2));
|
|
648
|
+
}
|
|
649
|
+
if (i + 1 < polls) {
|
|
650
|
+
const jittered = Math.round(interval * (0.9 + Math.random() * 0.2));
|
|
651
|
+
await new Promise((resolve) => setTimeout(resolve, jittered));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (!seen && boolean(options, "json")) output({ status: "ok", messages: 0, cursor: current ?? null });
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
throw new Error(`Unknown command: ${raw}`);
|
|
658
|
+
} finally {
|
|
659
|
+
await runtime.close();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) runCli().catch((error) => {
|
|
663
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
664
|
+
`);
|
|
665
|
+
process.exitCode = 1;
|
|
666
|
+
});
|
|
667
|
+
export {
|
|
668
|
+
runCli
|
|
669
|
+
};
|
|
670
|
+
//# sourceMappingURL=cli.js.map
|