@amaster.ai/employee-runtime-connector 0.1.0-beta.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.
@@ -0,0 +1,1005 @@
1
+ #!/usr/bin/env node
2
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { spawn, spawnSync } from "node:child_process";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { homedir, hostname } from "node:os";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const CONNECTOR_VERSION = "0.1.0-beta.0";
9
+
10
+ const CAPABILITIES = [
11
+ "remote_registration",
12
+ "heartbeat",
13
+ "executor_discovery",
14
+ "workspace_binding",
15
+ "run_wakeup",
16
+ "model_call",
17
+ "run_cancel",
18
+ "run_terminate",
19
+ "logs_cost_workspace_status",
20
+ ];
21
+
22
+ const CONFIG_KEY_MAP = new Map([
23
+ ["server_url", "AMASTER_EMPLOYEE_SERVER_URL"],
24
+ ["server", "AMASTER_EMPLOYEE_SERVER_URL"],
25
+ ["app_url", "AMASTER_EMPLOYEE_SERVER_URL"],
26
+ ["connector_token", "AMASTER_CONNECTOR_TOKEN"],
27
+ ["token", "AMASTER_CONNECTOR_TOKEN"],
28
+ ["auth_header", "AMASTER_CONNECTOR_AUTH_HEADER"],
29
+ ["company_id", "AMASTER_COMPANY_ID"],
30
+ ["company", "AMASTER_COMPANY_ID"],
31
+ ["device_name", "AMASTER_CONNECTOR_NAME"],
32
+ ["connector_name", "AMASTER_CONNECTOR_NAME"],
33
+ ["machine_id", "AMASTER_MACHINE_ID"],
34
+ ["workspace_allowlist", "AMASTER_WORKSPACE_ALLOWLIST"],
35
+ ["workspace_bindings", "AMASTER_WORKSPACE_ALLOWLIST"],
36
+ ["workspace", "AMASTER_WORKSPACE_ALLOWLIST"],
37
+ ["executors", "AMASTER_EXECUTORS"],
38
+ ["executor_paths", "AMASTER_EXECUTOR_PATHS"],
39
+ ["max_concurrent_commands", "AMASTER_MAX_CONCURRENT_COMMANDS"],
40
+ ["maxconcurrentcommands", "AMASTER_MAX_CONCURRENT_COMMANDS"],
41
+ ["capabilities", "AMASTER_CAPABILITIES"],
42
+ ["network_domains", "AMASTER_NETWORK_DOMAINS"],
43
+ ["state_file", "AMASTER_DAEMON_STATE_FILE"],
44
+ ["poll_interval_seconds", "AMASTER_POLL_INTERVAL_SECONDS"],
45
+ ["lease_seconds", "AMASTER_LEASE_SECONDS"],
46
+ ["executor_timeout_seconds", "AMASTER_EXECUTOR_TIMEOUT_SECONDS"],
47
+ ["runtime_workspaces_root", "AMASTER_RUNTIME_WORKSPACES_ROOT"],
48
+ ["pi_provider", "AMASTER_PI_PROVIDER"],
49
+ ["pi_model", "AMASTER_PI_MODEL"],
50
+ ["pi_extra_args", "AMASTER_PI_EXTRA_ARGS"],
51
+ ["pi_coding_agent_dir", "PI_CODING_AGENT_DIR"],
52
+ ["pi_agent_home", "PI_AGENT_HOME"],
53
+ ["pi_agent_node", "PI_AGENT_NODE"],
54
+ ["heartbeat_log_mode", "AMASTER_HEARTBEAT_LOG_MODE"],
55
+ ]);
56
+
57
+ const ENV_ORDER = [
58
+ "AMASTER_EMPLOYEE_SERVER_URL",
59
+ "AMASTER_CONNECTOR_TOKEN",
60
+ "AMASTER_CONNECTOR_AUTH_HEADER",
61
+ "AMASTER_COMPANY_ID",
62
+ "AMASTER_CONNECTOR_NAME",
63
+ "AMASTER_MACHINE_ID",
64
+ "AMASTER_WORKSPACE_ALLOWLIST",
65
+ "AMASTER_EXECUTORS",
66
+ "AMASTER_EXECUTOR_PATHS",
67
+ "AMASTER_MAX_CONCURRENT_COMMANDS",
68
+ "AMASTER_CAPABILITIES",
69
+ "AMASTER_NETWORK_DOMAINS",
70
+ "AMASTER_DAEMON_STATE_FILE",
71
+ "AMASTER_POLL_INTERVAL_SECONDS",
72
+ "AMASTER_LEASE_SECONDS",
73
+ "AMASTER_EXECUTOR_TIMEOUT_SECONDS",
74
+ "AMASTER_RUNTIME_WORKSPACES_ROOT",
75
+ "AMASTER_PI_PROVIDER",
76
+ "AMASTER_PI_MODEL",
77
+ "AMASTER_PI_EXTRA_ARGS",
78
+ "PI_CODING_AGENT_DIR",
79
+ "PI_AGENT_HOME",
80
+ "PI_AGENT_NODE",
81
+ "AMASTER_HEARTBEAT_LOG_MODE",
82
+ ];
83
+
84
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
85
+ const defaultStateHome = process.env.AMASTER_RUNTIME_STATE_HOME
86
+ || process.env.AMASTER_RUNTIME_HOME
87
+ || join(homedir(), ".amaster-employee");
88
+ const configDir = resolve(defaultStateHome);
89
+ const updateDir = resolve(process.env.AMASTER_RUNTIME_UPDATE_HOME || "/run/amaster-runtime/updates");
90
+ const envFile = process.env.AMASTER_RUNTIME_ENV_FILE || join(configDir, "runtime-daemon.env");
91
+ const pidFile = process.env.AMASTER_RUNTIME_PID_FILE || join(configDir, "runtime-daemon.pid");
92
+ const logFile = process.env.AMASTER_RUNTIME_LOG_FILE || join(configDir, "runtime-daemon.log");
93
+
94
+ function exactSemver(value) {
95
+ const normalized = typeof value === "string" ? value.trim() : "";
96
+ const match = normalized.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/);
97
+ if (!match) return null;
98
+ const invalidNumericPrerelease = (match[4]?.split(".") ?? [])
99
+ .some((identifier) => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"));
100
+ return invalidNumericPrerelease ? null : normalized;
101
+ }
102
+
103
+ function delegateToActiveConnectorVersion(argv) {
104
+ const activeVersionPath = join(updateDir, "active-connector-version");
105
+ if (!existsSync(activeVersionPath)) return false;
106
+ const activeVersion = exactSemver(readFileSync(activeVersionPath, "utf8"));
107
+ if (!activeVersion || activeVersion === CONNECTOR_VERSION) return false;
108
+ const activeCliPath = join(
109
+ updateDir,
110
+ "packages",
111
+ activeVersion,
112
+ "node_modules",
113
+ "@amaster.ai",
114
+ "employee-runtime-connector",
115
+ "dist",
116
+ "amaster-runtime.mjs",
117
+ );
118
+ if (!existsSync(activeCliPath)) return false;
119
+ const result = spawnSync(process.execPath, [activeCliPath, ...argv], {
120
+ env: process.env,
121
+ stdio: "inherit",
122
+ });
123
+ if (result.error) throw result.error;
124
+ process.exit(result.status ?? (result.signal ? 1 : 0));
125
+ }
126
+
127
+ function normalizeKey(key) {
128
+ return String(key ?? "").trim().toLowerCase().replaceAll("-", "_");
129
+ }
130
+
131
+ function envKeyFor(key) {
132
+ const normalized = normalizeKey(key);
133
+ if (CONFIG_KEY_MAP.has(normalized)) return CONFIG_KEY_MAP.get(normalized);
134
+ if (/^AMASTER_[A-Z0-9_]+$/.test(String(key))) return String(key);
135
+ throw new Error(`Unknown config key: ${key}`);
136
+ }
137
+
138
+ function parseFlags(argv) {
139
+ const flags = {};
140
+ const positional = [];
141
+ for (let index = 0; index < argv.length; index += 1) {
142
+ const token = argv[index];
143
+ if (!token.startsWith("--")) {
144
+ positional.push(token);
145
+ continue;
146
+ }
147
+ const eqIndex = token.indexOf("=");
148
+ if (eqIndex !== -1) {
149
+ flags[normalizeKey(token.slice(2, eqIndex))] = token.slice(eqIndex + 1);
150
+ continue;
151
+ }
152
+ const name = normalizeKey(token.slice(2));
153
+ const next = argv[index + 1];
154
+ if (next && !next.startsWith("--")) {
155
+ flags[name] = next;
156
+ index += 1;
157
+ } else {
158
+ flags[name] = "true";
159
+ }
160
+ }
161
+ return { flags, positional };
162
+ }
163
+
164
+ function unquoteEnvValue(value) {
165
+ const trimmed = String(value ?? "").trim();
166
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
167
+ return trimmed.slice(1, -1).replaceAll("'\\''", "'");
168
+ }
169
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
170
+ return trimmed.slice(1, -1).replace(/\\(["\\$`])/g, "$1");
171
+ }
172
+ return trimmed;
173
+ }
174
+
175
+ function readConfig() {
176
+ if (!existsSync(envFile)) return {};
177
+ const config = {};
178
+ for (const rawLine of readFileSync(envFile, "utf8").split(/\r?\n/)) {
179
+ const line = rawLine.trim();
180
+ if (!line || line.startsWith("#")) continue;
181
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
182
+ if (!match) continue;
183
+ config[match[1]] = unquoteEnvValue(match[2]);
184
+ }
185
+ return config;
186
+ }
187
+
188
+ function shellQuote(value) {
189
+ const text = String(value ?? "");
190
+ if (text && /^[A-Za-z0-9_@%+=:,./~-]+$/.test(text)) return text;
191
+ return `'${text.replaceAll("'", "'\\''")}'`;
192
+ }
193
+
194
+ function defaultMachineId() {
195
+ return `${hostname()}-${process.platform}-${process.arch}`;
196
+ }
197
+
198
+ function normalizeServerUrl(value) {
199
+ const text = String(value ?? "").trim();
200
+ if (!text) throw new Error("server URL is required");
201
+ return text.replace(/\/+$/, "");
202
+ }
203
+
204
+ function splitList(value) {
205
+ return String(value ?? "")
206
+ .split(",")
207
+ .map((entry) => entry.trim())
208
+ .filter(Boolean);
209
+ }
210
+
211
+ function uniqueList(entries) {
212
+ const seen = new Set();
213
+ const result = [];
214
+ for (const entry of entries) {
215
+ const trimmed = String(entry ?? "").trim();
216
+ if (!trimmed || seen.has(trimmed)) continue;
217
+ seen.add(trimmed);
218
+ result.push(trimmed);
219
+ }
220
+ return result;
221
+ }
222
+
223
+ function withDefaults(config) {
224
+ const runtimeWorkspacesRoot = config.AMASTER_RUNTIME_WORKSPACES_ROOT || join(configDir, "workspaces");
225
+ return {
226
+ AMASTER_EMPLOYEE_SERVER_URL: "http://127.0.0.1:3100",
227
+ AMASTER_CONNECTOR_NAME: `${hostname()}-amaster`,
228
+ AMASTER_MACHINE_ID: defaultMachineId(),
229
+ AMASTER_WORKSPACE_ALLOWLIST: runtimeWorkspacesRoot,
230
+ AMASTER_CAPABILITIES: CAPABILITIES.join(","),
231
+ AMASTER_NETWORK_DOMAINS: "self-host",
232
+ AMASTER_DAEMON_STATE_FILE: join(configDir, "runtime-connector-state.json"),
233
+ AMASTER_RUNTIME_WORKSPACES_ROOT: runtimeWorkspacesRoot,
234
+ AMASTER_POLL_INTERVAL_SECONDS: "10",
235
+ ...config,
236
+ };
237
+ }
238
+
239
+ function writeConfig(config) {
240
+ mkdirSync(dirname(envFile), { recursive: true });
241
+ const merged = withDefaults(config);
242
+ mkdirSync(merged.AMASTER_RUNTIME_WORKSPACES_ROOT, { recursive: true });
243
+ const lines = [
244
+ "# AMaster Runtime local daemon config",
245
+ "# Edit with `amaster-runtime config set <key> <value>` where possible.",
246
+ ];
247
+ for (const key of ENV_ORDER) {
248
+ if (merged[key] === undefined || merged[key] === null || merged[key] === "") continue;
249
+ lines.push(`${key}=${shellQuote(merged[key])}`);
250
+ }
251
+ writeFileSync(envFile, `${lines.join("\n")}\n`);
252
+ }
253
+
254
+ function readState(config) {
255
+ const statePath = config.AMASTER_DAEMON_STATE_FILE || join(configDir, "runtime-connector-state.json");
256
+ if (!existsSync(statePath)) return {};
257
+ try {
258
+ return JSON.parse(readFileSync(statePath, "utf8"));
259
+ } catch {
260
+ return {};
261
+ }
262
+ }
263
+
264
+ function writeState(config, state) {
265
+ const statePath = withDefaults(config).AMASTER_DAEMON_STATE_FILE || join(configDir, "runtime-connector-state.json");
266
+ mkdirSync(dirname(statePath), { recursive: true });
267
+ writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`);
268
+ }
269
+
270
+ function readPidInfo() {
271
+ if (!existsSync(pidFile)) return null;
272
+ try {
273
+ return JSON.parse(readFileSync(pidFile, "utf8"));
274
+ } catch {
275
+ const pid = Number(readFileSync(pidFile, "utf8").trim());
276
+ return Number.isFinite(pid) ? { pid } : null;
277
+ }
278
+ }
279
+
280
+ function writePidInfo(info) {
281
+ mkdirSync(dirname(pidFile), { recursive: true });
282
+ writeFileSync(pidFile, `${JSON.stringify(info, null, 2)}\n`);
283
+ }
284
+
285
+ function pidRunning(pid) {
286
+ if (!Number.isInteger(pid) || pid <= 0) return false;
287
+ try {
288
+ process.kill(pid, 0);
289
+ return true;
290
+ } catch {
291
+ return false;
292
+ }
293
+ }
294
+
295
+ function runningStatePid(state, config) {
296
+ const runtimeStatus = state && typeof state === "object" && state.runtimeStatus && typeof state.runtimeStatus === "object"
297
+ ? state.runtimeStatus
298
+ : null;
299
+ if (runtimeStatus?.daemon !== "running") return null;
300
+ const statusHostname = typeof runtimeStatus.hostname === "string" ? runtimeStatus.hostname.trim() : "";
301
+ if (statusHostname !== hostname()) return null;
302
+ const statusMachineId = typeof runtimeStatus.machineId === "string" ? runtimeStatus.machineId.trim() : "";
303
+ if (statusMachineId !== config.AMASTER_MACHINE_ID) return null;
304
+ const updatedAtMs = Date.parse(String(runtimeStatus.updatedAt ?? ""));
305
+ const pollIntervalSeconds = Number(runtimeStatus.pollIntervalSeconds);
306
+ const staleMs = Number.isFinite(pollIntervalSeconds) && pollIntervalSeconds > 0
307
+ ? Math.max(60_000, (pollIntervalSeconds * 2 + 30) * 1000)
308
+ : 60_000;
309
+ if (!Number.isFinite(updatedAtMs) || Date.now() - updatedAtMs > staleMs) return null;
310
+ const pid = Number(runtimeStatus.pid);
311
+ return pidRunning(pid) ? pid : null;
312
+ }
313
+
314
+ function daemonScriptPath() {
315
+ const candidates = [
316
+ join(scriptDir, "amaster-runtime-daemon.mjs"),
317
+ join(dirname(scriptDir), "scripts", "amaster-runtime-daemon.mjs"),
318
+ ];
319
+ const found = candidates.find((candidate) => existsSync(candidate));
320
+ if (!found) throw new Error("Cannot find amaster-runtime-daemon.mjs next to the runtime CLI.");
321
+ return found;
322
+ }
323
+
324
+ function daemonEnv(config = readConfig()) {
325
+ return {
326
+ ...process.env,
327
+ ...withDefaults(config),
328
+ };
329
+ }
330
+
331
+ function refreshManagedEnvConfig(config, env = process.env) {
332
+ for (const key of ENV_ORDER) {
333
+ if (env[key] !== undefined) config[key] = env[key];
334
+ }
335
+ }
336
+
337
+ function printConfig(config) {
338
+ const merged = withDefaults(config);
339
+ const safeEntries = [
340
+ ["server_url", merged.AMASTER_EMPLOYEE_SERVER_URL],
341
+ ["device_name", merged.AMASTER_CONNECTOR_NAME],
342
+ ["machine_id", merged.AMASTER_MACHINE_ID],
343
+ ["workspace_bindings", merged.AMASTER_WORKSPACE_ALLOWLIST],
344
+ ["managed_workspaces_root", merged.AMASTER_RUNTIME_WORKSPACES_ROOT],
345
+ ["executors", merged.AMASTER_EXECUTORS || "(auto discovery)"],
346
+ ["executor_paths", merged.AMASTER_EXECUTOR_PATHS || "(PATH only)"],
347
+ ["max_concurrent_commands", merged.AMASTER_MAX_CONCURRENT_COMMANDS || "(default: 1)"],
348
+ ["pi_provider", merged.AMASTER_PI_PROVIDER || "(default)"],
349
+ ["pi_model", merged.AMASTER_PI_MODEL || "(default)"],
350
+ ["pi_extra_args", merged.AMASTER_PI_EXTRA_ARGS || "(none)"],
351
+ ["pi_coding_agent_dir", merged.PI_CODING_AGENT_DIR || "(default)"],
352
+ ["pi_agent_home", merged.PI_AGENT_HOME || "(default)"],
353
+ ["pi_agent_node", merged.PI_AGENT_NODE || "(default)"],
354
+ ["heartbeat_log_mode", merged.AMASTER_HEARTBEAT_LOG_MODE || "(compact)"],
355
+ ["network_domains", merged.AMASTER_NETWORK_DOMAINS],
356
+ ["capabilities", merged.AMASTER_CAPABILITIES],
357
+ ["state_file", merged.AMASTER_DAEMON_STATE_FILE],
358
+ ["env_file", envFile],
359
+ ];
360
+ for (const [key, value] of safeEntries) {
361
+ process.stdout.write(`${key}: ${value}\n`);
362
+ }
363
+ process.stdout.write(`token: ${merged.AMASTER_CONNECTOR_TOKEN ? "(configured)" : "(not configured)"}\n`);
364
+ process.stdout.write(`auth_header: ${merged.AMASTER_CONNECTOR_AUTH_HEADER ? "(configured)" : "(not configured)"}\n`);
365
+ }
366
+
367
+ function workspaceBindingsFromConfig(config = readConfig()) {
368
+ return splitList(withDefaults(config).AMASTER_WORKSPACE_ALLOWLIST);
369
+ }
370
+
371
+ function writeWorkspaceBindings(config, bindings) {
372
+ config.AMASTER_WORKSPACE_ALLOWLIST = uniqueList(bindings).join(",");
373
+ writeConfig(config);
374
+ }
375
+
376
+ function commandSetup(argv) {
377
+ const { flags, positional } = parseFlags(argv);
378
+ const serverUrl = positional[0] || flags.server_url;
379
+ if (!serverUrl) throw new Error("Usage: amaster-runtime setup <server-url>");
380
+ const config = readConfig();
381
+ const envDefaults = {};
382
+ for (const key of ENV_ORDER) {
383
+ if (process.env[key] !== undefined) envDefaults[key] = process.env[key];
384
+ }
385
+ const defaultConfig = withDefaults({ ...envDefaults, ...config });
386
+ config.AMASTER_EMPLOYEE_SERVER_URL = normalizeServerUrl(serverUrl);
387
+ config.AMASTER_CONNECTOR_NAME = flags.device_name || flags.connector_name || defaultConfig.AMASTER_CONNECTOR_NAME;
388
+ config.AMASTER_MACHINE_ID = flags.machine_id || defaultConfig.AMASTER_MACHINE_ID;
389
+ config.AMASTER_RUNTIME_WORKSPACES_ROOT = flags.runtime_workspaces_root || defaultConfig.AMASTER_RUNTIME_WORKSPACES_ROOT;
390
+ config.AMASTER_WORKSPACE_ALLOWLIST = flags.workspace_allowlist || flags.workspace || defaultConfig.AMASTER_WORKSPACE_ALLOWLIST;
391
+ config.AMASTER_CAPABILITIES = flags.capabilities || defaultConfig.AMASTER_CAPABILITIES;
392
+ config.AMASTER_NETWORK_DOMAINS = flags.network_domains || defaultConfig.AMASTER_NETWORK_DOMAINS;
393
+ config.AMASTER_DAEMON_STATE_FILE = flags.state_file || defaultConfig.AMASTER_DAEMON_STATE_FILE;
394
+ if (flags.executors) config.AMASTER_EXECUTORS = flags.executors;
395
+ if (flags.executor_paths) config.AMASTER_EXECUTOR_PATHS = flags.executor_paths;
396
+ writeConfig(config);
397
+ process.stdout.write(`AMaster runtime config written: ${envFile}\n`);
398
+ }
399
+
400
+ function commandConfig(argv) {
401
+ const [action, key, ...valueParts] = argv;
402
+ const config = readConfig();
403
+ if (!action || action === "list" || action === "get") {
404
+ if (action === "get" && key) {
405
+ const envKey = envKeyFor(key);
406
+ process.stdout.write(`${withDefaults(config)[envKey] ?? ""}\n`);
407
+ return;
408
+ }
409
+ printConfig(config);
410
+ return;
411
+ }
412
+ if (action !== "set") {
413
+ throw new Error("Usage: amaster-runtime config set <key> <value>");
414
+ }
415
+ if (!key || valueParts.length === 0) {
416
+ throw new Error("Usage: amaster-runtime config set <key> <value>");
417
+ }
418
+ const envKey = envKeyFor(key);
419
+ const value = valueParts.join(" ").trim();
420
+ config[envKey] = envKey === "AMASTER_EMPLOYEE_SERVER_URL" ? normalizeServerUrl(value) : value;
421
+ writeConfig(config);
422
+ process.stdout.write(`${normalizeKey(key)} updated\n`);
423
+ }
424
+
425
+ async function commandWorkspace(argv) {
426
+ const [action = "list", maybePath, ...rest] = argv;
427
+ const config = readConfig();
428
+ const bindings = workspaceBindingsFromConfig(config);
429
+
430
+ if (action === "gc") {
431
+ const { flags } = parseFlags([maybePath, ...rest].filter(Boolean));
432
+ const dryRun = flags.dry_run === "true" || flags.dry === "true" || flags.n === "true";
433
+ if (!dryRun) {
434
+ throw new Error("Usage: amaster-runtime workspace gc --dry-run");
435
+ }
436
+ const passthroughArgs = ["workspace-gc-dry-run"];
437
+ if (flags.ttl_hours) passthroughArgs.push("--ttl-hours", flags.ttl_hours);
438
+ if (flags.root) passthroughArgs.push("--root", flags.root);
439
+ passthroughDaemon(passthroughArgs[0], passthroughArgs.slice(1));
440
+ return;
441
+ }
442
+
443
+ if (action === "list") {
444
+ if (bindings.length === 0) {
445
+ process.stdout.write("No workspace bindings configured\n");
446
+ return;
447
+ }
448
+ process.stdout.write("workspace_bindings:\n");
449
+ bindings.forEach((binding, index) => {
450
+ process.stdout.write(` ${index + 1}. ${binding}\n`);
451
+ });
452
+ return;
453
+ }
454
+
455
+ if (action === "add") {
456
+ if (!maybePath) {
457
+ throw new Error("Usage: amaster-runtime workspace add <path> [--name <name>]");
458
+ }
459
+ const { flags } = parseFlags(rest);
460
+ const nextPath = resolve(maybePath);
461
+ writeWorkspaceBindings(config, [...bindings, nextPath]);
462
+ process.stdout.write(`workspace binding added: ${nextPath}\n`);
463
+ if (flags.name) {
464
+ process.stdout.write(`workspace name noted for display: ${flags.name}\n`);
465
+ }
466
+ return;
467
+ }
468
+
469
+ if (action === "remove") {
470
+ if (!maybePath) {
471
+ throw new Error("Usage: amaster-runtime workspace remove <path-or-name> [--force]");
472
+ }
473
+ const { flags } = parseFlags(rest);
474
+ const target = String(maybePath).trim();
475
+ const exact = bindings.filter((binding) => binding === target || resolve(binding) === resolve(target));
476
+ const basenameMatches = bindings.filter((binding) => binding.split("/").filter(Boolean).at(-1) === target);
477
+ const matches = uniqueList([...exact, ...basenameMatches]);
478
+ if (matches.length === 0) {
479
+ throw new Error(`Workspace binding not found: ${target}`);
480
+ }
481
+ if (matches.length > 1) {
482
+ throw new Error(`Workspace binding name is ambiguous: ${target}`);
483
+ }
484
+ const force = flags.force === "true" || flags.yes === "true";
485
+ const impact = await fetchWorkspaceBindingImpact(config, matches[0]);
486
+ const affectedAgents = Array.isArray(impact?.affectedAgents) ? impact.affectedAgents : [];
487
+ if (affectedAgents.length > 0 && !force) {
488
+ throw new Error([
489
+ `Workspace binding is used by ${affectedAgents.length} agent(s):`,
490
+ ...affectedAgents.map((agent) => {
491
+ const executor = agent.executorKind || "unknown executor";
492
+ const workspacePath = agent.workspacePath || "unknown workspace";
493
+ return ` - ${agent.name} (${executor}, ${workspacePath})`;
494
+ }),
495
+ `Re-run with --force to remove ${matches[0]} anyway.`,
496
+ ].join("\n"));
497
+ }
498
+ writeWorkspaceBindings(config, bindings.filter((binding) => binding !== matches[0]));
499
+ process.stdout.write(`workspace binding removed: ${matches[0]}\n`);
500
+ if (affectedAgents.length > 0) {
501
+ process.stdout.write(`affected_agents: ${affectedAgents.length}\n`);
502
+ }
503
+ return;
504
+ }
505
+
506
+ throw new Error("Usage: amaster-runtime workspace <list|add|remove>");
507
+ }
508
+
509
+ function commandDiscover() {
510
+ const result = spawnSync(process.execPath, [daemonScriptPath(), "render-register-payload"], {
511
+ env: daemonEnv(),
512
+ encoding: "utf8",
513
+ stdio: ["ignore", "pipe", "pipe"],
514
+ });
515
+ if (result.status !== 0) {
516
+ throw new Error(result.stderr.trim() || "AMaster runtime discovery failed");
517
+ }
518
+ const payload = JSON.parse(result.stdout);
519
+ process.stdout.write("executors:\n");
520
+ for (const executor of payload.executors ?? []) {
521
+ const version = executor.version ? ` (${executor.version})` : "";
522
+ process.stdout.write(` - ${executor.kind}: ${executor.command}${version}\n`);
523
+ }
524
+ process.stdout.write("workspace_bindings:\n");
525
+ for (const binding of payload.workspaceBindings ?? []) {
526
+ process.stdout.write(` - ${binding}\n`);
527
+ }
528
+ }
529
+
530
+ async function postJson(serverUrl, path, payload, headers = {}) {
531
+ const res = await fetch(`${serverUrl}${path}`, {
532
+ method: "POST",
533
+ headers: { "content-type": "application/json", ...headers },
534
+ body: JSON.stringify(payload),
535
+ });
536
+ const text = await res.text();
537
+ const body = text ? JSON.parse(text) : null;
538
+ if (!res.ok) {
539
+ throw new Error(`POST ${path} returned HTTP ${res.status}: ${text}`);
540
+ }
541
+ return body;
542
+ }
543
+
544
+ async function getJson(serverUrl, path, headers = {}) {
545
+ const res = await fetch(`${serverUrl}${path}`, {
546
+ method: "GET",
547
+ headers,
548
+ });
549
+ const text = await res.text();
550
+ const body = text ? JSON.parse(text) : null;
551
+ if (!res.ok) {
552
+ throw new Error(`GET ${path} returned HTTP ${res.status}: ${text}`);
553
+ }
554
+ return body;
555
+ }
556
+
557
+ function connectorAuthHeaders(config, state = readState(withDefaults(config))) {
558
+ if (config.AMASTER_CONNECTOR_AUTH_HEADER) {
559
+ return { authorization: config.AMASTER_CONNECTOR_AUTH_HEADER };
560
+ }
561
+ const token = state.connectorToken || config.AMASTER_CONNECTOR_TOKEN;
562
+ return token ? { authorization: `Bearer ${token}` } : {};
563
+ }
564
+
565
+ async function fetchWorkspaceBindingImpact(config, binding) {
566
+ const merged = withDefaults(config);
567
+ const state = readState(merged);
568
+ if (!state.connectorId) return null;
569
+ const headers = connectorAuthHeaders(merged, state);
570
+ if (!headers.authorization) return null;
571
+ const serverUrl = normalizeServerUrl(merged.AMASTER_EMPLOYEE_SERVER_URL);
572
+ return getJson(
573
+ serverUrl,
574
+ `/api/amaster/runtime-connectors/${encodeURIComponent(state.connectorId)}/workspace-bindings/impact?path=${encodeURIComponent(binding)}`,
575
+ headers,
576
+ );
577
+ }
578
+
579
+ function renderRegisterPayload(config) {
580
+ const result = spawnSync(process.execPath, [daemonScriptPath(), "render-register-payload"], {
581
+ env: daemonEnv(config),
582
+ encoding: "utf8",
583
+ stdio: ["ignore", "pipe", "pipe"],
584
+ });
585
+ if (result.status !== 0) {
586
+ throw new Error(result.stderr.trim() || "AMaster runtime discovery failed");
587
+ }
588
+ return JSON.parse(result.stdout);
589
+ }
590
+
591
+ async function commandManagedEnsure(argv) {
592
+ const { flags } = parseFlags(argv);
593
+ const config = readConfig();
594
+ if (flags.server || flags.server_url) {
595
+ config.AMASTER_EMPLOYEE_SERVER_URL = normalizeServerUrl(flags.server || flags.server_url);
596
+ }
597
+ const merged = withDefaults(config);
598
+ const serverUrl = normalizeServerUrl(merged.AMASTER_EMPLOYEE_SERVER_URL);
599
+ const provisionerKey = String(
600
+ flags.provisioner_key
601
+ ?? flags.provisioner
602
+ ?? process.env.AMASTER_MANAGED_RUNTIME_PROVISIONER_KEY
603
+ ?? process.env.AMASTER_PI_CLI_RUNTIME_PROVISIONER_KEY
604
+ ?? merged.AMASTER_MACHINE_ID,
605
+ ).trim();
606
+ const provisionToken = String(
607
+ flags.token
608
+ ?? process.env.AMASTER_BUILTIN_RUNTIME_PROVISION_TOKEN
609
+ ?? process.env.AMASTER_MANAGED_RUNTIME_PROVISION_TOKEN
610
+ ?? "",
611
+ ).trim();
612
+ if (!provisionerKey) {
613
+ throw new Error("Usage: amaster-runtime managed-ensure --provisioner-key <key>");
614
+ }
615
+ if (!provisionToken) {
616
+ throw new Error("AMASTER_BUILTIN_RUNTIME_PROVISION_TOKEN is required for managed runtime provisioning");
617
+ }
618
+
619
+ const payload = renderRegisterPayload(config);
620
+ const result = await postJson(
621
+ serverUrl,
622
+ "/api/amaster/runtime-connectors/managed/ensure",
623
+ {
624
+ ...payload,
625
+ provisionerKey,
626
+ metadata: {
627
+ ...payload.metadata,
628
+ source: "amaster-runtime-managed-ensure",
629
+ },
630
+ },
631
+ { authorization: `Bearer ${provisionToken}` },
632
+ );
633
+
634
+ config.AMASTER_EMPLOYEE_SERVER_URL = serverUrl;
635
+ writeConfig(config);
636
+ writeState(config, {
637
+ ...readState(withDefaults(config)),
638
+ connectorId: result.connectorId,
639
+ connectorToken: result.connectorToken,
640
+ companyId: null,
641
+ managed: true,
642
+ provisionerKey,
643
+ serverUrl,
644
+ connectedAt: new Date().toISOString(),
645
+ });
646
+
647
+ process.stdout.write(`AMaster managed runtime ensured: ${result.connectorId}\n`);
648
+ process.stdout.write(`${JSON.stringify({
649
+ status: result.status,
650
+ created: result.created,
651
+ credentialReissued: result.credentialReissued,
652
+ recoveredFromRevoked: result.recoveredFromRevoked,
653
+ }, null, 2)}\n`);
654
+ }
655
+
656
+ function heartbeatAfterConnect(config) {
657
+ const result = spawnSync(process.execPath, [daemonScriptPath(), "heartbeat"], {
658
+ env: daemonEnv(config),
659
+ encoding: "utf8",
660
+ stdio: ["ignore", "pipe", "pipe"],
661
+ });
662
+ if (result.status !== 0) {
663
+ throw new Error(result.stderr.trim() || "AMaster runtime heartbeat after connect failed");
664
+ }
665
+ }
666
+
667
+ function readDaemonStatusSummary(config) {
668
+ const result = spawnSync(process.execPath, [daemonScriptPath(), "status-summary"], {
669
+ env: daemonEnv(config),
670
+ encoding: "utf8",
671
+ stdio: ["ignore", "pipe", "pipe"],
672
+ });
673
+ if (result.status !== 0) return null;
674
+ try {
675
+ return JSON.parse(result.stdout);
676
+ } catch {
677
+ return null;
678
+ }
679
+ }
680
+
681
+ function formatDaemonStatusValue(value, fallback = "-") {
682
+ return typeof value === "string" && value.trim() ? value : fallback;
683
+ }
684
+
685
+ function formatDaemonStatusLastOutput(entry) {
686
+ const lastOutputAt = formatDaemonStatusValue(entry?.lastOutputAt, null);
687
+ const stream = formatDaemonStatusValue(entry?.lastOutputStream, "unknown");
688
+ const bytes = typeof entry?.lastOutputBytes === "number" && Number.isFinite(entry.lastOutputBytes)
689
+ ? `${entry.lastOutputBytes}B`
690
+ : "0B";
691
+ return lastOutputAt ? `${lastOutputAt} ${stream} ${bytes}` : `${stream} ${bytes}`;
692
+ }
693
+
694
+ async function commandConnect(argv) {
695
+ const { flags, positional } = parseFlags(argv);
696
+ const code = positional[0] || flags.code;
697
+ if (!code) {
698
+ throw new Error("Usage: amaster-runtime connect <pairing-code>");
699
+ }
700
+
701
+ const config = readConfig();
702
+ if (flags.server || flags.server_url) {
703
+ config.AMASTER_EMPLOYEE_SERVER_URL = normalizeServerUrl(flags.server || flags.server_url);
704
+ }
705
+ const serverUrl = normalizeServerUrl(withDefaults(config).AMASTER_EMPLOYEE_SERVER_URL);
706
+ const payload = renderRegisterPayload(config);
707
+ const result = await postJson(
708
+ serverUrl,
709
+ `/api/amaster/runtime-connectors/pairing-codes/${encodeURIComponent(code)}/claim`,
710
+ payload,
711
+ );
712
+
713
+ config.AMASTER_EMPLOYEE_SERVER_URL = serverUrl;
714
+ if (result.companyId) config.AMASTER_COMPANY_ID = result.companyId;
715
+ if (Array.isArray(result.workspaceBindings) && result.workspaceBindings.length > 0) {
716
+ config.AMASTER_WORKSPACE_ALLOWLIST = result.workspaceBindings.join(",");
717
+ }
718
+ writeConfig(config);
719
+ writeState(config, {
720
+ ...readState(withDefaults(config)),
721
+ connectorId: result.connectorId,
722
+ connectorToken: result.connectorToken,
723
+ companyId: result.companyId ?? null,
724
+ serverUrl,
725
+ connectedAt: new Date().toISOString(),
726
+ });
727
+ heartbeatAfterConnect(config);
728
+
729
+ process.stdout.write(`AMaster runtime connected: ${result.connectorId}\n`);
730
+ if (Array.isArray(result.workspaceBindings)) {
731
+ process.stdout.write(`workspace_bindings: ${result.workspaceBindings.join(",")}\n`);
732
+ }
733
+ }
734
+
735
+ function commandLogin(argv) {
736
+ const { flags, positional } = parseFlags(argv);
737
+ const token = flags.token || positional[0];
738
+ const authHeader = flags.auth_header;
739
+ if (!token && !authHeader) {
740
+ throw new Error("Usage: amaster-runtime login --token <token>");
741
+ }
742
+ const config = readConfig();
743
+ if (token) {
744
+ config.AMASTER_CONNECTOR_TOKEN = token;
745
+ delete config.AMASTER_CONNECTOR_AUTH_HEADER;
746
+ }
747
+ if (authHeader) {
748
+ config.AMASTER_CONNECTOR_AUTH_HEADER = authHeader;
749
+ delete config.AMASTER_CONNECTOR_TOKEN;
750
+ }
751
+ writeConfig(config);
752
+ process.stdout.write("AMaster runtime auth saved\n");
753
+ }
754
+
755
+ function spawnDaemonForeground(args = []) {
756
+ const result = spawnSync(process.execPath, [daemonScriptPath(), ...args], {
757
+ env: {
758
+ ...daemonEnv(),
759
+ AMASTER_RUNTIME_DAEMON_FOREGROUND: "1",
760
+ },
761
+ stdio: "inherit",
762
+ });
763
+ process.exit(result.status ?? 1);
764
+ }
765
+
766
+ function commandDaemon(argv) {
767
+ const [action = "status", ...rest] = argv;
768
+ if (action === "status") {
769
+ commandStatus();
770
+ return;
771
+ }
772
+ if (action === "stop") {
773
+ commandStop();
774
+ return;
775
+ }
776
+ if (action === "run-once" || action === "once") {
777
+ spawnDaemonForeground(["run-once"]);
778
+ return;
779
+ }
780
+ if (action !== "start" && action !== "restart") {
781
+ throw new Error("Usage: amaster-runtime daemon <start|status|stop|restart|run-once>");
782
+ }
783
+
784
+ const { flags } = parseFlags(rest);
785
+ const config = readConfig();
786
+ refreshManagedEnvConfig(config);
787
+ if (flags.device_name) config.AMASTER_CONNECTOR_NAME = flags.device_name;
788
+ if (flags.machine_id) config.AMASTER_MACHINE_ID = flags.machine_id;
789
+ if (flags.workspace_allowlist) config.AMASTER_WORKSPACE_ALLOWLIST = flags.workspace_allowlist;
790
+ if (flags.executors) config.AMASTER_EXECUTORS = flags.executors;
791
+ if (flags.executor_paths) config.AMASTER_EXECUTOR_PATHS = flags.executor_paths;
792
+ writeConfig(config);
793
+
794
+ if (action === "restart") commandStop({ quiet: true });
795
+
796
+ const current = readPidInfo();
797
+ if (current?.pid && pidRunning(current.pid) && flags.force !== "true") {
798
+ process.stdout.write(`AMaster runtime daemon already running pid=${current.pid}\n`);
799
+ return;
800
+ }
801
+
802
+ if (flags.foreground === "true") {
803
+ spawnDaemonForeground(["start"]);
804
+ return;
805
+ }
806
+
807
+ mkdirSync(configDir, { recursive: true });
808
+ const fd = openSync(logFile, "a");
809
+ const child = spawn(process.execPath, [daemonScriptPath(), "start"], {
810
+ cwd: scriptDir,
811
+ detached: true,
812
+ env: {
813
+ ...daemonEnv(config),
814
+ AMASTER_RUNTIME_DAEMON_FOREGROUND: "0",
815
+ },
816
+ stdio: ["ignore", fd, fd],
817
+ });
818
+ child.unref();
819
+ closeSync(fd);
820
+ writePidInfo({
821
+ pid: child.pid,
822
+ startedAt: new Date().toISOString(),
823
+ serverUrl: withDefaults(config).AMASTER_EMPLOYEE_SERVER_URL,
824
+ logFile,
825
+ });
826
+ process.stdout.write(`AMaster runtime daemon started pid=${child.pid}\n`);
827
+ process.stdout.write(`log: ${logFile}\n`);
828
+ }
829
+
830
+ function commandStatus() {
831
+ const config = withDefaults(readConfig());
832
+ const pidInfo = readPidInfo();
833
+ const state = readState(config);
834
+ const foregroundPid = runningStatePid(state, config);
835
+ const runningPid = pidInfo?.pid && pidRunning(pidInfo.pid) ? pidInfo.pid : foregroundPid;
836
+ process.stdout.write("AMaster runtime daemon\n");
837
+ process.stdout.write(`status: ${runningPid ? "running" : "stopped"}\n`);
838
+ if (runningPid) process.stdout.write(`pid: ${runningPid}\n`);
839
+ else if (pidInfo?.pid) process.stdout.write(`pid: ${pidInfo.pid}\n`);
840
+ process.stdout.write(`server_url: ${config.AMASTER_EMPLOYEE_SERVER_URL}\n`);
841
+ process.stdout.write(`device_name: ${config.AMASTER_CONNECTOR_NAME}\n`);
842
+ process.stdout.write(`workspace_bindings: ${config.AMASTER_WORKSPACE_ALLOWLIST}\n`);
843
+ process.stdout.write(`managed_workspaces_root: ${config.AMASTER_RUNTIME_WORKSPACES_ROOT}\n`);
844
+ process.stdout.write(`executors: ${config.AMASTER_EXECUTORS || "(auto discovery)"}\n`);
845
+ process.stdout.write(`executor_paths: ${config.AMASTER_EXECUTOR_PATHS || "(PATH only)"}\n`);
846
+ process.stdout.write(`max_concurrent_commands: ${config.AMASTER_MAX_CONCURRENT_COMMANDS || "1"}\n`);
847
+ process.stdout.write(`connector_id: ${state.connectorId || "(not registered yet)"}\n`);
848
+ process.stdout.write(`connector_token: ${state.connectorToken || config.AMASTER_CONNECTOR_TOKEN ? "(configured)" : "(not configured)"}\n`);
849
+ const localSummary = readDaemonStatusSummary(config);
850
+ if (localSummary) {
851
+ process.stdout.write(`managed_workdirs: ${localSummary.managedWorkdirCount ?? 0}\n`);
852
+ process.stdout.write(`managed_workdir_bytes: ${localSummary.managedWorkdirBytes ?? 0}\n`);
853
+ process.stdout.write(`stale_managed_workdirs: ${localSummary.staleManagedWorkdirCount ?? 0}\n`);
854
+ process.stdout.write(`result_outbox_pending: ${localSummary.resultOutboxPending ?? 0}\n`);
855
+ process.stdout.write(`result_outbox_invalid: ${localSummary.resultOutboxInvalid ?? 0}\n`);
856
+ const recentManagedRuns = Array.isArray(localSummary.recentManagedRuns)
857
+ ? localSummary.recentManagedRuns
858
+ : [];
859
+ process.stdout.write(`recent_managed_runs: ${recentManagedRuns.length}\n`);
860
+ for (const entry of recentManagedRuns) {
861
+ process.stdout.write([
862
+ "recent_run:",
863
+ `run=${formatDaemonStatusValue(entry.runId)}`,
864
+ `command=${formatDaemonStatusValue(entry.commandId)}`,
865
+ `issue=${formatDaemonStatusValue(entry.issueId)}`,
866
+ `executor=${formatDaemonStatusValue(entry.executorKind)}`,
867
+ `last_output=${formatDaemonStatusLastOutput(entry)}`,
868
+ `outbox_pending=${entry.outboxPending ?? 0}`,
869
+ `manifest=${formatDaemonStatusValue(entry.manifestPath)}`,
870
+ ].join(" "));
871
+ process.stdout.write("\n");
872
+ }
873
+ }
874
+ process.stdout.write(`env_file: ${envFile}\n`);
875
+ process.stdout.write(`log_file: ${logFile}\n`);
876
+ }
877
+
878
+ function commandStop(opts = {}) {
879
+ const pidInfo = readPidInfo();
880
+ if (!pidInfo?.pid || !pidRunning(pidInfo.pid)) {
881
+ if (!opts.quiet) process.stdout.write("AMaster runtime daemon is not running\n");
882
+ rmSync(pidFile, { force: true });
883
+ return;
884
+ }
885
+ try {
886
+ process.kill(pidInfo.pid, "SIGTERM");
887
+ } catch {
888
+ // Process may exit between the status check and SIGTERM.
889
+ }
890
+ rmSync(pidFile, { force: true });
891
+ if (!opts.quiet) process.stdout.write(`AMaster runtime daemon stopped pid=${pidInfo.pid}\n`);
892
+ }
893
+
894
+ function passthroughDaemon(command, argv = []) {
895
+ const diagnosticEnv = command === "heartbeat"
896
+ ? {
897
+ AMASTER_HEARTBEAT_MODE: "diagnostic",
898
+ AMASTER_HEARTBEAT_DIAGNOSTIC_SOURCE: "amaster-runtime-cli",
899
+ }
900
+ : {};
901
+ const result = spawnSync(process.execPath, [daemonScriptPath(), command, ...argv], {
902
+ env: {
903
+ ...daemonEnv(),
904
+ ...diagnosticEnv,
905
+ },
906
+ stdio: "inherit",
907
+ });
908
+ process.exit(result.status ?? 1);
909
+ }
910
+
911
+ function commandSmoke(argv) {
912
+ const [action = "print", ...rest] = argv;
913
+ if (action !== "print" && action !== "print-smoke") {
914
+ throw new Error("Usage: amaster-runtime smoke print");
915
+ }
916
+ passthroughDaemon("print-smoke", rest);
917
+ }
918
+
919
+ function help() {
920
+ process.stdout.write(`Usage: amaster-runtime <command> [args]
921
+
922
+ Commands:
923
+ setup <server-url> Write the local daemon config.
924
+ config list Show local config without secrets.
925
+ config set <key> <value> Set server_url, workspace_bindings, executors, executor_paths, etc.
926
+ workspace list Show authorized local workspace bindings.
927
+ workspace add <path> [--name <name>] Add a local workspace binding.
928
+ workspace remove <path-or-name> [--force] Remove a local workspace binding after checking linked agents.
929
+ workspace gc --dry-run [--ttl-hours 72] Preview managed runtime workdirs eligible for cleanup.
930
+ discover Show detected executors and workspace bindings.
931
+ connect <pairing-code> Pair this terminal with a workspace code from the UI.
932
+ managed-ensure --provisioner-key <key> Ensure a deployment-managed shared runner and rotate its connector token.
933
+ login --token <token> Store a reserved connector bearer token for authenticated deployments.
934
+ daemon start [--device-name <name>] Start the local runtime daemon in the background.
935
+ daemon start --foreground Start the daemon in the foreground.
936
+ daemon status Show daemon pid, connector id, and config summary.
937
+ daemon stop Stop the background daemon.
938
+ smoke print Print register/poll/ack/result/ingest smoke commands.
939
+ register | heartbeat | poll | ack | result Pass through to amaster-runtime-daemon.mjs.
940
+
941
+ Config file:
942
+ ${envFile}
943
+
944
+ Advanced:
945
+ company_id is normally set by \`connect <pairing-code>\`; set it manually only for diagnostics or separate runtime homes.
946
+ `);
947
+ }
948
+
949
+ async function main(argv) {
950
+ delegateToActiveConnectorVersion(argv);
951
+ const [command = "help", ...rest] = argv;
952
+ switch (command) {
953
+ case "setup":
954
+ commandSetup(rest);
955
+ break;
956
+ case "config":
957
+ commandConfig(rest);
958
+ break;
959
+ case "workspace":
960
+ await commandWorkspace(rest);
961
+ break;
962
+ case "discover":
963
+ commandDiscover();
964
+ break;
965
+ case "connect":
966
+ await commandConnect(rest);
967
+ break;
968
+ case "managed-ensure":
969
+ await commandManagedEnsure(rest);
970
+ break;
971
+ case "login":
972
+ commandLogin(rest);
973
+ break;
974
+ case "daemon":
975
+ commandDaemon(rest);
976
+ break;
977
+ case "smoke":
978
+ commandSmoke(rest);
979
+ break;
980
+ case "register":
981
+ case "heartbeat":
982
+ case "poll":
983
+ case "ack":
984
+ case "result":
985
+ case "print-smoke":
986
+ passthroughDaemon(command, rest);
987
+ break;
988
+ case "help":
989
+ case "--help":
990
+ case "-h":
991
+ help();
992
+ break;
993
+ case "--version":
994
+ case "-v":
995
+ process.stdout.write(`${CONNECTOR_VERSION}\n`);
996
+ break;
997
+ default:
998
+ throw new Error(`Unknown command: ${command}`);
999
+ }
1000
+ }
1001
+
1002
+ main(process.argv.slice(2)).catch((err) => {
1003
+ process.stderr.write(`${err.stack ?? err.message}\n`);
1004
+ process.exit(1);
1005
+ });