wake-bridge 0.9.0-preview.8

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.
Files changed (115) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/dist/src/adapters/botlingknows.d.ts +42 -0
  4. package/dist/src/adapters/botlingknows.js +141 -0
  5. package/dist/src/adapters/botlingknows.js.map +1 -0
  6. package/dist/src/adapters/group-chat.d.ts +43 -0
  7. package/dist/src/adapters/group-chat.js +120 -0
  8. package/dist/src/adapters/group-chat.js.map +1 -0
  9. package/dist/src/cli.d.ts +2 -0
  10. package/dist/src/cli.js +637 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/connector-catalog.d.ts +17 -0
  13. package/dist/src/connector-catalog.js +43 -0
  14. package/dist/src/connector-catalog.js.map +1 -0
  15. package/dist/src/connectors/botlingknows-mcp.d.ts +15 -0
  16. package/dist/src/connectors/botlingknows-mcp.js +130 -0
  17. package/dist/src/connectors/botlingknows-mcp.js.map +1 -0
  18. package/dist/src/connectors/group-chat-http.d.ts +17 -0
  19. package/dist/src/connectors/group-chat-http.js +226 -0
  20. package/dist/src/connectors/group-chat-http.js.map +1 -0
  21. package/dist/src/connectors/mcp-http-client.d.ts +11 -0
  22. package/dist/src/connectors/mcp-http-client.js +108 -0
  23. package/dist/src/connectors/mcp-http-client.js.map +1 -0
  24. package/dist/src/core.d.ts +170 -0
  25. package/dist/src/core.js +1720 -0
  26. package/dist/src/core.js.map +1 -0
  27. package/dist/src/daemon.d.ts +31 -0
  28. package/dist/src/daemon.js +605 -0
  29. package/dist/src/daemon.js.map +1 -0
  30. package/dist/src/db.d.ts +31 -0
  31. package/dist/src/db.js +513 -0
  32. package/dist/src/db.js.map +1 -0
  33. package/dist/src/event-sdk.d.ts +43 -0
  34. package/dist/src/event-sdk.js +152 -0
  35. package/dist/src/event-sdk.js.map +1 -0
  36. package/dist/src/host-adapter-registry.d.ts +21 -0
  37. package/dist/src/host-adapter-registry.js +59 -0
  38. package/dist/src/host-adapter-registry.js.map +1 -0
  39. package/dist/src/index.d.ts +5 -0
  40. package/dist/src/index.js +6 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/instance-config.d.ts +46 -0
  43. package/dist/src/instance-config.js +176 -0
  44. package/dist/src/instance-config.js.map +1 -0
  45. package/dist/src/mcp.d.ts +51 -0
  46. package/dist/src/mcp.js +728 -0
  47. package/dist/src/mcp.js.map +1 -0
  48. package/dist/src/operator-control.d.ts +82 -0
  49. package/dist/src/operator-control.js +220 -0
  50. package/dist/src/operator-control.js.map +1 -0
  51. package/dist/src/out-of-process-host.d.ts +37 -0
  52. package/dist/src/out-of-process-host.js +196 -0
  53. package/dist/src/out-of-process-host.js.map +1 -0
  54. package/dist/src/policy-control.d.ts +3 -0
  55. package/dist/src/policy-control.js +120 -0
  56. package/dist/src/policy-control.js.map +1 -0
  57. package/dist/src/release-lifecycle.d.ts +98 -0
  58. package/dist/src/release-lifecycle.js +378 -0
  59. package/dist/src/release-lifecycle.js.map +1 -0
  60. package/dist/src/sdk-error.d.ts +6 -0
  61. package/dist/src/sdk-error.js +12 -0
  62. package/dist/src/sdk-error.js.map +1 -0
  63. package/dist/src/source-adapter.d.ts +32 -0
  64. package/dist/src/source-adapter.js +164 -0
  65. package/dist/src/source-adapter.js.map +1 -0
  66. package/dist/src/source-connector.d.ts +50 -0
  67. package/dist/src/source-connector.js +358 -0
  68. package/dist/src/source-connector.js.map +1 -0
  69. package/dist/src/source-control-client.d.ts +38 -0
  70. package/dist/src/source-control-client.js +61 -0
  71. package/dist/src/source-control-client.js.map +1 -0
  72. package/dist/src/source-ingress.d.ts +16 -0
  73. package/dist/src/source-ingress.js +47 -0
  74. package/dist/src/source-ingress.js.map +1 -0
  75. package/dist/src/source-sdk.d.ts +6 -0
  76. package/dist/src/source-sdk.js +53 -0
  77. package/dist/src/source-sdk.js.map +1 -0
  78. package/dist/src/transport-sdk.d.ts +102 -0
  79. package/dist/src/transport-sdk.js +222 -0
  80. package/dist/src/transport-sdk.js.map +1 -0
  81. package/dist/src/transport.d.ts +23 -0
  82. package/dist/src/transport.js +71 -0
  83. package/dist/src/transport.js.map +1 -0
  84. package/dist/src/types.d.ts +522 -0
  85. package/dist/src/types.js +2 -0
  86. package/dist/src/types.js.map +1 -0
  87. package/dist/src/util.d.ts +26 -0
  88. package/dist/src/util.js +136 -0
  89. package/dist/src/util.js.map +1 -0
  90. package/dist/src/version.d.ts +1 -0
  91. package/dist/src/version.js +2 -0
  92. package/dist/src/version.js.map +1 -0
  93. package/docs/adapters/botlingknows.md +40 -0
  94. package/docs/adapters/gmail-planned.md +16 -0
  95. package/docs/adapters/group-chat.md +126 -0
  96. package/docs/operations/connector-catalog.md +40 -0
  97. package/docs/operations/operator-control.md +83 -0
  98. package/docs/operations/out-of-process-host-adapter.md +150 -0
  99. package/docs/operations/policies.md +106 -0
  100. package/docs/operations/public-sdk.md +87 -0
  101. package/docs/operations/release-lifecycle.md +154 -0
  102. package/docs/operations/source-connectors.md +163 -0
  103. package/docs/operations/source-ingress.md +108 -0
  104. package/docs/operations/templates/botlingknows-connector.env.example +5 -0
  105. package/docs/operations/templates/sources.json.example +15 -0
  106. package/docs/quickstart.md +143 -0
  107. package/docs/releases/0.9.0-preview.8.md +44 -0
  108. package/docs/verification.md +19 -0
  109. package/examples/policies/botlingknows-conservative.json +44 -0
  110. package/examples/reference-host-adapter/README.md +41 -0
  111. package/examples/reference-host-adapter/adapter.mjs +173 -0
  112. package/examples/reference-host-adapter/host-adapters.json.example +26 -0
  113. package/package.json +94 -0
  114. package/schemas/event-v1.schema.json +26 -0
  115. package/schemas/policy-v1.schema.json +66 -0
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { WakeBridge } from "./core.js";
4
+ import { startDaemon } from "./daemon.js";
5
+ import { bridgeConfigFromFile, initializeInstance, instanceConfigSecurity, instanceDataSecurity, rotateOwnerCredential } from "./instance-config.js";
6
+ import { WakeBridgeMcpServer, runMcpStdio } from "./mcp.js";
7
+ import { bootstrapSourceConnector, loadSourceConnectorFile, SourceSupervisor } from "./source-connector.js";
8
+ import { startBotlingKnowsConnector } from "./connectors/botlingknows-mcp.js";
9
+ import { startGroupChatConnector } from "./connectors/group-chat-http.js";
10
+ import { CONNECTOR_RUNTIME_STATES, connectorCatalog } from "./connector-catalog.js";
11
+ import { DaemonSourceControlClient } from "./source-control-client.js";
12
+ import { loadSourceIngestCredentialFile } from "./source-ingress.js";
13
+ import { loadPolicyFile } from "./policy-control.js";
14
+ import { operatorStatus, retryDeadLetter } from "./operator-control.js";
15
+ import { loadOutOfProcessHostFile } from "./out-of-process-host.js";
16
+ import { backupInstance, installMacLaunchAgent, releasePreflight, restoreInstance, uninstallMacLaunchAgent, upgradeInstance, } from "./release-lifecycle.js";
17
+ const KNOWN_COMMANDS = new Set([
18
+ "help", "--help", "init", "owner-token-rotate", "release-preflight", "backup", "upgrade", "restore",
19
+ "service", "doctor", "connector-catalog", "source-validate", "botlingknows-connector", "group-chat-connector",
20
+ "daemon", "mcp", "source-bootstrap", "source-once", "policy", "emit", "claim-schedule", "claim-snooze",
21
+ "claim-dismiss", "claim-consume", "endpoint-register", "endpoint-renew", "takeover", "presence-renew", "tick",
22
+ "dispatch", "inspect", "status", "batch-retry", "events", "claims", "batches", "receipts", "bindings", "endpoints",
23
+ ]);
24
+ function parseArgs(values) {
25
+ const command = values[0] || "help";
26
+ const rest = [];
27
+ const options = {};
28
+ for (let index = 1; index < values.length; index += 1) {
29
+ const value = values[index];
30
+ if (value === "--") {
31
+ rest.push(...values.slice(index + 1));
32
+ break;
33
+ }
34
+ if (!value.startsWith("--")) {
35
+ rest.push(value);
36
+ continue;
37
+ }
38
+ const key = value.slice(2).replaceAll("-", "_");
39
+ const next = values[index + 1];
40
+ if (next && !next.startsWith("--")) {
41
+ options[key] = next;
42
+ index += 1;
43
+ }
44
+ else {
45
+ options[key] = true;
46
+ }
47
+ }
48
+ return { command, rest, options };
49
+ }
50
+ function option(options, key, fallback) {
51
+ const value = options[key];
52
+ return typeof value === "string" ? value : fallback;
53
+ }
54
+ function jsonOption(options, key, fallback) {
55
+ const raw = option(options, key);
56
+ if (!raw)
57
+ return fallback;
58
+ try {
59
+ return JSON.parse(raw);
60
+ }
61
+ catch (error) {
62
+ throw new Error(`--${key.replaceAll("_", "-")} must be JSON: ${String(error)}`);
63
+ }
64
+ }
65
+ function optionalNumber(value, field) {
66
+ if (value === undefined || value === "")
67
+ return undefined;
68
+ const parsed = Number(value);
69
+ if (!Number.isFinite(parsed))
70
+ throw new Error(`${field} must be a finite number`);
71
+ return parsed;
72
+ }
73
+ function instanceConfigPath(options) {
74
+ return option(options, "config", process.env.WAKEBRIDGE_CONFIG);
75
+ }
76
+ function mergedConfig(options) {
77
+ return {
78
+ instance_id: option(options, "instance_id", process.env.WAKEBRIDGE_INSTANCE_ID || "local"),
79
+ owner_id: option(options, "owner_id", process.env.WAKEBRIDGE_OWNER_ID || "local-owner"),
80
+ db_path: option(options, "db", process.env.WAKEBRIDGE_DB || "./wake-bridge.sqlite"),
81
+ timezone: option(options, "timezone", process.env.WAKEBRIDGE_TIMEZONE || "UTC"),
82
+ admin_token: option(options, "admin_token", process.env.WAKEBRIDGE_ADMIN_TOKEN),
83
+ presence_ttl_ms: optionalNumber(process.env.WAKEBRIDGE_PRESENCE_TTL_MS, "WAKEBRIDGE_PRESENCE_TTL_MS"),
84
+ activity_ttl_ms: optionalNumber(process.env.WAKEBRIDGE_ACTIVITY_TTL_MS, "WAKEBRIDGE_ACTIVITY_TTL_MS"),
85
+ endpoint_lease_ms: optionalNumber(process.env.WAKEBRIDGE_ENDPOINT_LEASE_MS, "WAKEBRIDGE_ENDPOINT_LEASE_MS"),
86
+ };
87
+ }
88
+ function configFrom(options) {
89
+ const file = instanceConfigPath(options);
90
+ if (!file)
91
+ return mergedConfig(options);
92
+ const stored = bridgeConfigFromFile(file);
93
+ const explicit = [
94
+ ["instance_id", option(options, "instance_id", process.env.WAKEBRIDGE_INSTANCE_ID), stored.instance_id],
95
+ ["owner_id", option(options, "owner_id", process.env.WAKEBRIDGE_OWNER_ID), stored.owner_id],
96
+ ["db_path", option(options, "db", process.env.WAKEBRIDGE_DB), stored.db_path],
97
+ ["timezone", option(options, "timezone", process.env.WAKEBRIDGE_TIMEZONE), stored.timezone],
98
+ ["admin_token", option(options, "admin_token", process.env.WAKEBRIDGE_ADMIN_TOKEN), stored.admin_token],
99
+ ];
100
+ for (const [field, requested, fixed] of explicit) {
101
+ if (requested !== undefined && requested !== fixed) {
102
+ throw new Error(`${field} cannot override the fixed Agent Space config`);
103
+ }
104
+ }
105
+ return {
106
+ ...stored,
107
+ presence_ttl_ms: optionalNumber(process.env.WAKEBRIDGE_PRESENCE_TTL_MS, "WAKEBRIDGE_PRESENCE_TTL_MS"),
108
+ activity_ttl_ms: optionalNumber(process.env.WAKEBRIDGE_ACTIVITY_TTL_MS, "WAKEBRIDGE_ACTIVITY_TTL_MS"),
109
+ endpoint_lease_ms: optionalNumber(process.env.WAKEBRIDGE_ENDPOINT_LEASE_MS, "WAKEBRIDGE_ENDPOINT_LEASE_MS"),
110
+ };
111
+ }
112
+ function openBridge(config, options, recoverDispatchLeases) {
113
+ return new WakeBridge(config, { recoverDispatchLeases });
114
+ }
115
+ function output(value) {
116
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
117
+ }
118
+ function help() {
119
+ output({
120
+ commands: [
121
+ "emit --source SOURCE --type TYPE --dedupe-key KEY --resource URI [--metadata JSON]",
122
+ "claim-schedule --resource URI --eligible-after ISO [--attention-channel CHANNEL]",
123
+ "claim-snooze CLAIM_ID --until ISO",
124
+ "claim-dismiss CLAIM_ID [--reason TEXT]",
125
+ "claim-consume CLAIM_ID [--result JSON]",
126
+ "endpoint-register --host-kind KIND --session-ref REF [--routes JSON]",
127
+ "endpoint-renew ENDPOINT_ID --lease-token TOKEN",
128
+ "takeover --attention-channel CHANNEL --endpoint-id ID [--expected-generation N]",
129
+ "presence-renew --attention-channel CHANNEL --endpoint-id ID --generation N --lease-token TOKEN",
130
+ "tick",
131
+ "dispatch",
132
+ "policy list",
133
+ "policy install --file PATH",
134
+ "policy test --source SOURCE --event JSON [--file PATH]",
135
+ "policy preview --source SOURCE --event JSON [--file PATH]",
136
+ "inspect | events | claims | batches | receipts | bindings | endpoints",
137
+ "status --config PATH (secret-free queue/host/source health; does not recover leases)",
138
+ "batch-retry BATCH_ID --expected-attempt N --reason TEXT (reopens dead-letter only; does not dispatch)",
139
+ "init --data-dir PATH --instance-id ID --owner-id OWNER [--timezone ZONE]",
140
+ "owner-token-rotate --config PATH (atomically replaces the stored owner credential)",
141
+ "release-preflight [--config PATH]",
142
+ "backup --config PATH --output DIRECTORY",
143
+ "upgrade --config PATH --backup-output DIRECTORY --confirm-offline",
144
+ "restore --config PATH --backup DIRECTORY --rollback-output DIRECTORY --confirm-instance-id ID --confirm-offline",
145
+ "service install --config PATH [--port N] [--launch-agents-dir DIRECTORY] [--logs-dir DIRECTORY]",
146
+ "service uninstall --config PATH [--launch-agents-dir DIRECTORY]",
147
+ "daemon --config PATH --port 4311 [--host 127.0.0.1] [--source-credentials PATH] [--host-adapters PATH]",
148
+ "daemon --unsafe-no-auth --host 127.0.0.1 (explicit development mode only)",
149
+ "doctor",
150
+ "connector-catalog (lists available/planned connectors; all default disabled)",
151
+ "source-validate --connectors PATH",
152
+ "source-once SOURCE --connectors PATH",
153
+ "source-bootstrap SOURCE --mode from-now --connectors PATH --expected-subject-ref REF --expected-binding-fingerprint sha256:...",
154
+ "botlingknows-connector --port 4391 (requires env-only upstream URL, subject ref, and connector token)",
155
+ "group-chat-connector --port 4392 (requires env-only Group Chat URL, service token, and connector token)",
156
+ "mcp (stdio JSON-RPC; use WAKEBRIDGE_DB/WAKEBRIDGE_INSTANCE_ID/WAKEBRIDGE_OWNER_ID)",
157
+ ],
158
+ });
159
+ }
160
+ async function main() {
161
+ const parsed = parseArgs(process.argv.slice(2));
162
+ if (!KNOWN_COMMANDS.has(parsed.command))
163
+ throw new Error(`unknown command: ${parsed.command}`);
164
+ if (parsed.command === "help" || parsed.command === "--help") {
165
+ help();
166
+ return;
167
+ }
168
+ if (parsed.command === "init") {
169
+ if (Object.prototype.hasOwnProperty.call(parsed.options, "admin_token")) {
170
+ throw new Error("init generates the owner credential; --admin-token is not accepted");
171
+ }
172
+ const dataDir = option(parsed.options, "data_dir", process.env.WAKEBRIDGE_DATA_DIR);
173
+ const instanceId = option(parsed.options, "instance_id", process.env.WAKEBRIDGE_INSTANCE_ID);
174
+ const ownerId = option(parsed.options, "owner_id", process.env.WAKEBRIDGE_OWNER_ID);
175
+ if (!dataDir || !instanceId || !ownerId)
176
+ throw new Error("init requires --data-dir, --instance-id, and --owner-id");
177
+ const initialized = initializeInstance({
178
+ data_dir: dataDir,
179
+ instance_id: instanceId,
180
+ owner_id: ownerId,
181
+ timezone: option(parsed.options, "timezone", process.env.WAKEBRIDGE_TIMEZONE || "UTC"),
182
+ db_path: option(parsed.options, "db"),
183
+ config_path: instanceConfigPath(parsed.options),
184
+ });
185
+ output({
186
+ ok: true,
187
+ instance_id: initialized.config.instance_id,
188
+ owner_id: initialized.config.owner_id,
189
+ db_path: initialized.config.db_path,
190
+ config_path: initialized.config_path,
191
+ credential: "stored_in_private_config",
192
+ });
193
+ return;
194
+ }
195
+ if (parsed.command === "owner-token-rotate") {
196
+ const path = instanceConfigPath(parsed.options);
197
+ if (!path)
198
+ throw new Error("owner-token-rotate requires --config");
199
+ if (process.env.WAKEBRIDGE_ADMIN_TOKEN) {
200
+ throw new Error("unset WAKEBRIDGE_ADMIN_TOKEN before rotating the config-backed owner credential");
201
+ }
202
+ output({ ok: true, ...rotateOwnerCredential(path), credential: "stored_in_private_config" });
203
+ return;
204
+ }
205
+ if (parsed.command === "release-preflight") {
206
+ const result = releasePreflight(instanceConfigPath(parsed.options));
207
+ output(result);
208
+ if (!result.ok)
209
+ process.exitCode = 2;
210
+ return;
211
+ }
212
+ if (parsed.command === "backup") {
213
+ const configPath = instanceConfigPath(parsed.options);
214
+ const destination = option(parsed.options, "output");
215
+ if (!configPath || !destination)
216
+ throw new Error("backup requires --config and --output");
217
+ output({ ok: true, backup: backupInstance(configPath, destination) });
218
+ return;
219
+ }
220
+ if (parsed.command === "upgrade") {
221
+ const configPath = instanceConfigPath(parsed.options);
222
+ const destination = option(parsed.options, "backup_output");
223
+ if (!configPath || !destination)
224
+ throw new Error("upgrade requires --config and --backup-output");
225
+ output({ ok: true, ...upgradeInstance(configPath, destination, parsed.options.confirm_offline === true) });
226
+ return;
227
+ }
228
+ if (parsed.command === "restore") {
229
+ const configPath = instanceConfigPath(parsed.options);
230
+ const backupDirectory = option(parsed.options, "backup");
231
+ const rollbackOutput = option(parsed.options, "rollback_output");
232
+ const confirmInstanceId = option(parsed.options, "confirm_instance_id");
233
+ if (!configPath || !backupDirectory || !rollbackOutput || !confirmInstanceId) {
234
+ throw new Error("restore requires --config, --backup, --rollback-output, and --confirm-instance-id");
235
+ }
236
+ output({ ok: true, ...restoreInstance({
237
+ config_path: configPath,
238
+ backup_directory: backupDirectory,
239
+ rollback_output: rollbackOutput,
240
+ confirm_instance_id: confirmInstanceId,
241
+ confirm_offline: parsed.options.confirm_offline === true,
242
+ }) });
243
+ return;
244
+ }
245
+ if (parsed.command === "service") {
246
+ const action = parsed.rest[0];
247
+ const configPath = instanceConfigPath(parsed.options);
248
+ if (!configPath)
249
+ throw new Error("service requires --config");
250
+ if (action === "install") {
251
+ output({ ok: true, service: installMacLaunchAgent({
252
+ config_path: configPath,
253
+ launch_agents_directory: option(parsed.options, "launch_agents_dir"),
254
+ logs_directory: option(parsed.options, "logs_dir"),
255
+ port: optionalNumber(option(parsed.options, "port"), "--port"),
256
+ }) });
257
+ return;
258
+ }
259
+ if (action === "uninstall") {
260
+ output({ ok: true, service: uninstallMacLaunchAgent({
261
+ config_path: configPath,
262
+ launch_agents_directory: option(parsed.options, "launch_agents_dir"),
263
+ }) });
264
+ return;
265
+ }
266
+ throw new Error("service requires install or uninstall");
267
+ }
268
+ if (parsed.command === "doctor") {
269
+ let config;
270
+ let configLoadError = null;
271
+ try {
272
+ config = configFrom(parsed.options);
273
+ }
274
+ catch (error) {
275
+ configLoadError = error instanceof Error ? error.message : String(error);
276
+ config = mergedConfig({ ...parsed.options, config: false });
277
+ }
278
+ let installedSqlite = null;
279
+ try {
280
+ installedSqlite = execFileSync("sqlite3", ["--version"], { encoding: "utf8", timeout: 5_000 }).trim();
281
+ }
282
+ catch {
283
+ // Runtime readiness is reported below.
284
+ }
285
+ const fileSecurity = instanceConfigSecurity(instanceConfigPath(parsed.options));
286
+ const dataSecurity = instanceDataSecurity(config.db_path);
287
+ const adminReady = typeof config.admin_token === "string" && config.admin_token.length >= 32;
288
+ const sourceCredentialPath = option(parsed.options, "source_credentials", process.env.WAKEBRIDGE_SOURCE_CREDENTIALS_FILE);
289
+ const release = releasePreflight(instanceConfigPath(parsed.options));
290
+ let sourceCredentialCount = 0;
291
+ let sourceCredentialError = null;
292
+ if (sourceCredentialPath) {
293
+ try {
294
+ sourceCredentialCount = loadSourceIngestCredentialFile(sourceCredentialPath).length;
295
+ }
296
+ catch (error) {
297
+ sourceCredentialError = error instanceof Error ? error.message : String(error);
298
+ }
299
+ }
300
+ const hostAdapterPath = option(parsed.options, "host_adapters", process.env.WAKEBRIDGE_HOST_ADAPTERS_FILE);
301
+ let externalHostAdapters = [];
302
+ let externalHostError = null;
303
+ if (hostAdapterPath) {
304
+ try {
305
+ externalHostAdapters = loadOutOfProcessHostFile(hostAdapterPath).adapters.map((adapter) => ({
306
+ adapter_kind: adapter.manifest.adapter_kind,
307
+ adapter_version: adapter.manifest.adapter_version,
308
+ host_kinds: [...adapter.manifest.host_kinds],
309
+ support_tier: adapter.manifest.support_tier,
310
+ }));
311
+ }
312
+ catch (error) {
313
+ externalHostError = error instanceof Error ? error.message : String(error);
314
+ }
315
+ }
316
+ output({
317
+ ok: release.ok && Boolean(installedSqlite) && adminReady && fileSecurity.ready && dataSecurity.ready
318
+ && configLoadError === null && sourceCredentialError === null && externalHostError === null,
319
+ release,
320
+ runtime: { sqlite3: { available: Boolean(installedSqlite), installed_version: installedSqlite } },
321
+ security: {
322
+ owner_api_authenticated: adminReady,
323
+ config_load_error: configLoadError,
324
+ config_file: fileSecurity,
325
+ database: dataSecurity,
326
+ source_ingress: {
327
+ configured: Boolean(sourceCredentialPath),
328
+ credential_count: sourceCredentialCount,
329
+ error: sourceCredentialError,
330
+ },
331
+ },
332
+ hosts: {
333
+ contract: {
334
+ protocol: "out_of_process_loopback_v1",
335
+ product_hosts_bundled: false,
336
+ responsibility: "session start/resume and envelope injection belong to the configured Host Adapter",
337
+ receipt_rule: "transport acceptance is not agent start, seen, or consumed",
338
+ },
339
+ configured_adapters: {
340
+ configured: Boolean(hostAdapterPath),
341
+ adapter_count: externalHostAdapters.length,
342
+ adapters: externalHostAdapters,
343
+ error: externalHostError,
344
+ },
345
+ },
346
+ });
347
+ return;
348
+ }
349
+ if (parsed.command === "connector-catalog") {
350
+ output({ schema_version: 1, runtime_states: CONNECTOR_RUNTIME_STATES, connectors: connectorCatalog() });
351
+ return;
352
+ }
353
+ const config = configFrom(parsed.options);
354
+ if (parsed.command === "source-validate") {
355
+ const path = option(parsed.options, "connectors", process.env.WAKEBRIDGE_SOURCE_CONNECTORS_FILE);
356
+ if (!path)
357
+ throw new Error("--connectors is required");
358
+ const sources = loadSourceConnectorFile(path);
359
+ output({ ok: true, sources });
360
+ return;
361
+ }
362
+ if (parsed.command === "botlingknows-connector") {
363
+ if (Object.prototype.hasOwnProperty.call(parsed.options, "upstream_url")
364
+ || Object.prototype.hasOwnProperty.call(parsed.options, "connector_token")) {
365
+ throw new Error("connector secrets must be supplied through the environment");
366
+ }
367
+ const upstreamUrl = process.env.BOTLINGKNOWS_MCP_URL;
368
+ const connectorToken = process.env.WAKEBRIDGE_CONNECTOR_TOKEN;
369
+ const subjectRef = process.env.BOTLINGKNOWS_SUBJECT_REF;
370
+ if (!upstreamUrl || !connectorToken || !subjectRef) {
371
+ throw new Error("BOTLINGKNOWS_MCP_URL, BOTLINGKNOWS_SUBJECT_REF, and WAKEBRIDGE_CONNECTOR_TOKEN are required");
372
+ }
373
+ const connector = await startBotlingKnowsConnector({
374
+ upstream_url: upstreamUrl,
375
+ connector_token: connectorToken,
376
+ subject_ref: subjectRef,
377
+ host: option(parsed.options, "host", "127.0.0.1"),
378
+ port: Number(option(parsed.options, "port", "4391")),
379
+ });
380
+ output({ ok: true, source: "botlingknows", address: connector.address });
381
+ const close = async () => connector.close();
382
+ process.once("SIGINT", () => { void close().finally(() => process.exit(0)); });
383
+ process.once("SIGTERM", () => { void close().finally(() => process.exit(0)); });
384
+ await new Promise(() => undefined);
385
+ return;
386
+ }
387
+ if (parsed.command === "group-chat-connector") {
388
+ if (Object.prototype.hasOwnProperty.call(parsed.options, "upstream_url") ||
389
+ Object.prototype.hasOwnProperty.call(parsed.options, "upstream_token") ||
390
+ Object.prototype.hasOwnProperty.call(parsed.options, "connector_token")) {
391
+ throw new Error("connector secrets and upstream identity must be supplied through the environment");
392
+ }
393
+ const upstreamUrl = process.env.GROUP_CHAT_BASE_URL;
394
+ const upstreamToken = process.env.GROUP_CHAT_SERVICE_TOKEN;
395
+ const connectorToken = process.env.WAKEBRIDGE_CONNECTOR_TOKEN;
396
+ if (!upstreamUrl || !upstreamToken || !connectorToken) {
397
+ throw new Error("GROUP_CHAT_BASE_URL, GROUP_CHAT_SERVICE_TOKEN, and WAKEBRIDGE_CONNECTOR_TOKEN are required");
398
+ }
399
+ const connector = await startGroupChatConnector({
400
+ upstream_url: upstreamUrl,
401
+ upstream_token: upstreamToken,
402
+ connector_token: connectorToken,
403
+ host: option(parsed.options, "host", "127.0.0.1"),
404
+ port: Number(option(parsed.options, "port", "4392")),
405
+ });
406
+ output({ ok: true, source: "group_chat", address: connector.address });
407
+ const close = async () => connector.close();
408
+ process.once("SIGINT", () => { void close().finally(() => process.exit(0)); });
409
+ process.once("SIGTERM", () => { void close().finally(() => process.exit(0)); });
410
+ await new Promise(() => undefined);
411
+ return;
412
+ }
413
+ if (parsed.command === "daemon") {
414
+ if (Object.prototype.hasOwnProperty.call(parsed.options, "admin_token")) {
415
+ throw new Error("daemon owner credentials must come from --config or WAKEBRIDGE_ADMIN_TOKEN, never command arguments");
416
+ }
417
+ const unsafeNoAuth = parsed.options.unsafe_no_auth === true;
418
+ if (!config.admin_token && !unsafeNoAuth) {
419
+ throw new Error("daemon requires an owner credential from --config or WAKEBRIDGE_ADMIN_TOKEN; --unsafe-no-auth is loopback development only");
420
+ }
421
+ const connectorPath = option(parsed.options, "connectors", process.env.WAKEBRIDGE_SOURCE_CONNECTORS_FILE);
422
+ const sourceConnectors = connectorPath ? loadSourceConnectorFile(connectorPath) : [];
423
+ const sourceCredentialPath = option(parsed.options, "source_credentials", process.env.WAKEBRIDGE_SOURCE_CREDENTIALS_FILE);
424
+ const sourceCredentials = sourceCredentialPath ? loadSourceIngestCredentialFile(sourceCredentialPath) : [];
425
+ const hostAdapterPath = option(parsed.options, "host_adapters", process.env.WAKEBRIDGE_HOST_ADAPTERS_FILE);
426
+ const externalHosts = hostAdapterPath ? loadOutOfProcessHostFile(hostAdapterPath) : { adapters: [], credentials: [] };
427
+ const bridge = openBridge(config, parsed.options, true);
428
+ const daemon = await startDaemon(config, {
429
+ bridge,
430
+ host: option(parsed.options, "host", "127.0.0.1"),
431
+ port: Number(option(parsed.options, "port", "4311")),
432
+ scheduler_interval_ms: Number(option(parsed.options, "scheduler_interval_ms", "1000")),
433
+ unsafe_no_auth: unsafeNoAuth,
434
+ host_adapters: externalHosts.adapters,
435
+ host_credentials: externalHosts.credentials,
436
+ source_connectors: sourceConnectors,
437
+ source_credentials: sourceCredentials,
438
+ });
439
+ output({ ok: true, address: daemon.address, instance_id: config.instance_id, owner_id: config.owner_id });
440
+ const close = async () => { await daemon.close(); daemon.bridge.close(); };
441
+ process.once("SIGINT", () => { void close().finally(() => process.exit(0)); });
442
+ process.once("SIGTERM", () => { void close().finally(() => process.exit(0)); });
443
+ await new Promise(() => undefined);
444
+ return;
445
+ }
446
+ if (parsed.command === "mcp") {
447
+ // MCP is an agent-facing control plane sharing the daemon's DB. Opening
448
+ // it during a long wake turn must never reclaim the daemon's live lease.
449
+ const bridge = openBridge(config, parsed.options, false);
450
+ try {
451
+ const sourceControl = config.admin_token
452
+ ? new DaemonSourceControlClient(process.env.WAKEBRIDGE_DAEMON_URL || "http://127.0.0.1:4311", config.admin_token)
453
+ : undefined;
454
+ await runMcpStdio(new WakeBridgeMcpServer({ bridge, source_control: sourceControl }));
455
+ }
456
+ finally {
457
+ bridge.close();
458
+ }
459
+ return;
460
+ }
461
+ // The daemon and an explicit `dispatch` invocation are dispatcher owners.
462
+ // Inspect/schedule/ack/consume CLI processes must not recover their leases.
463
+ const bridge = openBridge(config, parsed.options, parsed.command === "dispatch");
464
+ try {
465
+ switch (parsed.command) {
466
+ case "source-bootstrap": {
467
+ const path = option(parsed.options, "connectors", process.env.WAKEBRIDGE_SOURCE_CONNECTORS_FILE);
468
+ const source = parsed.rest[0];
469
+ const mode = option(parsed.options, "mode");
470
+ const expectedSubjectRef = option(parsed.options, "expected_subject_ref");
471
+ const expectedBindingFingerprint = option(parsed.options, "expected_binding_fingerprint");
472
+ if (!path || !source || mode !== "from-now" || !expectedSubjectRef || !expectedBindingFingerprint) {
473
+ throw new Error("source-bootstrap requires SOURCE, --mode from-now, --connectors, --expected-subject-ref, and --expected-binding-fingerprint");
474
+ }
475
+ const config = loadSourceConnectorFile(path).find((item) => item.id === source);
476
+ if (!config)
477
+ throw new Error(`source connector ${source} is not configured`);
478
+ output({ checkpoint: await bootstrapSourceConnector(bridge, config, "from-now", process.env, fetch, {
479
+ subject_ref: expectedSubjectRef,
480
+ binding_fingerprint: expectedBindingFingerprint,
481
+ }) });
482
+ break;
483
+ }
484
+ case "source-once": {
485
+ const path = option(parsed.options, "connectors", process.env.WAKEBRIDGE_SOURCE_CONNECTORS_FILE);
486
+ const source = parsed.rest[0];
487
+ if (!path || !source)
488
+ throw new Error("source-once requires SOURCE and --connectors");
489
+ const supervisor = new SourceSupervisor(bridge, loadSourceConnectorFile(path));
490
+ const status = await supervisor.runOnce(source);
491
+ output(status);
492
+ if (status.state !== "healthy")
493
+ process.exitCode = 2;
494
+ await supervisor.stop();
495
+ break;
496
+ }
497
+ case "policy": {
498
+ const action = parsed.rest[0];
499
+ if (action === "list") {
500
+ output({ policies: bridge.listPolicies(), active: bridge.listActivePolicies(), status: bridge.listPolicyStatuses() });
501
+ break;
502
+ }
503
+ const filePath = option(parsed.options, "file");
504
+ if (action === "install") {
505
+ if (!filePath)
506
+ throw new Error("policy install requires --file");
507
+ const file = loadPolicyFile(filePath);
508
+ bridge.installPolicies(file.policies);
509
+ output({ ok: true, installed: file.policies.map((policy) => ({ id: policy.id, version: policy.version })), active: bridge.listActivePolicies(), status: bridge.listPolicyStatuses() });
510
+ break;
511
+ }
512
+ if (action === "test" || action === "preview") {
513
+ const source = option(parsed.options, "source");
514
+ const event = jsonOption(parsed.options, "event", undefined);
515
+ if (!source || !event || typeof event !== "object" || Array.isArray(event)) {
516
+ throw new Error(`policy ${action} requires --source and an object --event JSON value`);
517
+ }
518
+ const candidates = filePath ? loadPolicyFile(filePath).policies : undefined;
519
+ output(action === "test"
520
+ ? bridge.testPolicy(source, event, candidates)
521
+ : bridge.previewPolicy(source, event, candidates));
522
+ break;
523
+ }
524
+ throw new Error("policy requires one of: list, install, test, preview");
525
+ }
526
+ case "emit": {
527
+ const resource = option(parsed.options, "resource");
528
+ if (!resource)
529
+ throw new Error("--resource is required");
530
+ const event = jsonOption(parsed.options, "event", {
531
+ type: option(parsed.options, "type", "event"),
532
+ occurred_at: option(parsed.options, "occurred_at"),
533
+ dedupe_key: option(parsed.options, "dedupe_key", `cli:${Date.now()}`),
534
+ coalesce_key: option(parsed.options, "coalesce_key"),
535
+ priority_hint: option(parsed.options, "priority_hint", "normal"),
536
+ attention_channel_hint: option(parsed.options, "attention_channel_hint", "default"),
537
+ resource: { uri: resource },
538
+ metadata: jsonOption(parsed.options, "metadata", {}),
539
+ payload_preview: option(parsed.options, "payload_preview"),
540
+ });
541
+ output(bridge.emitEvent(option(parsed.options, "source", "manual"), event));
542
+ break;
543
+ }
544
+ case "claim-schedule": {
545
+ const resource = option(parsed.options, "resource");
546
+ const eligible = option(parsed.options, "eligible_after");
547
+ if (!resource || !eligible)
548
+ throw new Error("--resource and --eligible-after are required");
549
+ output(bridge.scheduleClaim({ resource: { uri: resource }, eligible_after: eligible, attention_channel: option(parsed.options, "attention_channel"), reason_code: option(parsed.options, "reason_code"), note: option(parsed.options, "note"), idempotency_key: option(parsed.options, "idempotency_key") }));
550
+ break;
551
+ }
552
+ case "claim-snooze":
553
+ output(bridge.snoozeClaim(parsed.rest[0], option(parsed.options, "until")));
554
+ break;
555
+ case "claim-dismiss":
556
+ output(bridge.dismissClaim(parsed.rest[0], option(parsed.options, "reason", "dismissed")));
557
+ break;
558
+ case "claim-consume":
559
+ output(bridge.consumeClaim(parsed.rest[0], jsonOption(parsed.options, "result", null)));
560
+ break;
561
+ case "endpoint-register":
562
+ {
563
+ const hostKind = option(parsed.options, "host_kind", "mock");
564
+ if (Object.prototype.hasOwnProperty.call(parsed.options, "cold_routes")) {
565
+ throw new Error("--cold-routes has been removed; use --routes");
566
+ }
567
+ const routes = option(parsed.options, "routes");
568
+ const parsedRoutes = routes === undefined ? undefined : jsonOption(parsed.options, "routes", undefined);
569
+ output(bridge.registerEndpoint({ id: option(parsed.options, "id"), host_kind: hostKind, session_ref: option(parsed.options, "session_ref", "cli"), capabilities: jsonOption(parsed.options, "capabilities", undefined), routes: parsedRoutes }));
570
+ }
571
+ break;
572
+ case "endpoint-renew":
573
+ output(bridge.renewEndpoint(parsed.rest[0], option(parsed.options, "lease_token"), Number(option(parsed.options, "lease_ms", "3600000"))));
574
+ break;
575
+ case "takeover":
576
+ output(bridge.takeover(option(parsed.options, "attention_channel", "default"), option(parsed.options, "endpoint_id"), option(parsed.options, "expected_generation") == null ? undefined : Number(option(parsed.options, "expected_generation"))));
577
+ break;
578
+ case "presence-renew":
579
+ output(bridge.renewPresence({ attention_channel: option(parsed.options, "attention_channel", "default"), endpoint_id: option(parsed.options, "endpoint_id"), generation: Number(option(parsed.options, "generation", "1")), lease_token: option(parsed.options, "lease_token"), ttl_ms: Number(option(parsed.options, "ttl_ms", "1200000")), observed_by: option(parsed.options, "observed_by", "cli"), observation: option(parsed.options, "observation", "user_message_accepted") }));
580
+ break;
581
+ case "tick":
582
+ output(bridge.tick());
583
+ break;
584
+ case "dispatch":
585
+ output(await bridge.dispatchDue());
586
+ break;
587
+ case "inspect":
588
+ output(bridge.inspect());
589
+ break;
590
+ case "status": {
591
+ const status = operatorStatus(bridge);
592
+ output(status);
593
+ if (status.attention_required)
594
+ process.exitCode = 2;
595
+ break;
596
+ }
597
+ case "batch-retry": {
598
+ const batchId = parsed.rest[0];
599
+ const expectedAttempt = Number(option(parsed.options, "expected_attempt"));
600
+ const reason = option(parsed.options, "reason");
601
+ if (!batchId || !Number.isSafeInteger(expectedAttempt) || expectedAttempt < 1 || !reason) {
602
+ throw new Error("batch-retry requires BATCH_ID, --expected-attempt, and --reason");
603
+ }
604
+ output(retryDeadLetter(bridge, { batch_id: batchId, expected_attempt: expectedAttempt, reason }));
605
+ break;
606
+ }
607
+ case "events":
608
+ output(bridge.listEvents());
609
+ break;
610
+ case "claims":
611
+ output(bridge.listClaims());
612
+ break;
613
+ case "batches":
614
+ output(bridge.listBatches());
615
+ break;
616
+ case "receipts":
617
+ output(bridge.listReceipts(parsed.rest[0]));
618
+ break;
619
+ case "bindings":
620
+ output(bridge.listBindings());
621
+ break;
622
+ case "endpoints":
623
+ output(bridge.listEndpoints());
624
+ break;
625
+ default:
626
+ throw new Error(`unknown command: ${parsed.command}`);
627
+ }
628
+ }
629
+ finally {
630
+ bridge.close();
631
+ }
632
+ }
633
+ main().catch((error) => {
634
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
635
+ process.exitCode = 1;
636
+ });
637
+ //# sourceMappingURL=cli.js.map