@engineeros/connector 0.8.8 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # EngineerOS Connector
2
2
 
3
- The connector uses an authenticated EngineerOS WebSocket for pairing, workspace identity, run lifecycle, and evidence. Coding work can run through any ACP v1-compatible agent over stdio. Codex CLI remains available as the default built-in adapter.
3
+ The connector uses an authenticated EngineerOS WebSocket for pairing, workspace identity, run lifecycle, and evidence. Coding work can run through any agent in the official ACP registry or a custom ACP v1-compatible command over stdio. Direct Codex CLI remains available as the compatibility path.
4
+
5
+ ## Connect an official ACP agent
6
+
7
+ EngineerOS reads the curated [ACP agent registry](https://agentclientprotocol.com/registry), caches it for 24 hours, and uses the registry's pinned distribution for the current platform. The catalog includes Codex, Claude, Gemini, GitHub Copilot, Goose, OpenCode, Qwen Code, Cursor, and other compatible agents as they are published. List the current catalog, prepare the selected agent, then pair the workspace:
8
+
9
+ ```sh
10
+ npx --yes @engineeros/connector@latest agents
11
+ npx --yes @engineeros/connector@latest agent gemini install
12
+ npx --yes @engineeros/connector@latest pair PAIRING-CODE --url https://your-engineeros.example --workspace . --onboard --agent gemini
13
+ ```
14
+
15
+ Registry `npx` and `uvx` packages are prepared with their native package runner. Registry binaries are downloaded into `~/.engineeros/agents`, checked against the published SHA-256 digest when present, and launched from that managed location. The connector checks the selected distribution before pairing. Agent processes stay alive while the connector is active, project conversations reuse their ACP sessions, and response chunks reach Copilot as the agent produces them.
4
16
 
5
17
  ## Pair an ACP coding agent
6
18
 
@@ -10,7 +22,7 @@ Run the command from the repository the agent should work in:
10
22
  npx --yes @engineeros/connector@latest pair PAIRING-CODE --url http://localhost:8000 --workspace . --onboard --agent-name "My ACP agent" --agent-command my-agent --agent-args '["--acp"]'
11
23
  ```
12
24
 
13
- `--agent-command` must start an ACP v1 agent over NDJSON stdio. `--agent-args` is a JSON array so arguments are passed without invoking a shell. Omit both options to use the installed Codex CLI adapter.
25
+ `--agent-command` must start an ACP v1 agent over NDJSON stdio. `--agent-args` is a JSON array so arguments are passed without invoking a shell. Omit `--agent` and the custom command options to use the installed Codex CLI directly.
14
26
 
15
27
  Connect a local Codex CLI workspace to EngineerOS through an outbound WebSocket.
16
28
 
@@ -29,9 +41,9 @@ npx --yes @engineeros/connector@latest pair PAIRING-CODE --url https://your-engi
29
41
 
30
42
  The connector uploads a bounded ZIP snapshot for a safe file inventory, then stays online for deep assessments, rescans, and Goal Runs. Inventory never executes repository code. It excludes known secrets, dependency directories, build output, compiled binaries, files larger than 5 MB, agent-tool caches, Git metadata, and connector state before upload.
31
43
 
32
- From **Project steering -> Workspace**, choose **Assess with Codex** to run a read-only engineering assessment using the Codex CLI subscription already authenticated on that computer. EngineerOS stores the source-cited findings in System State and promotes the highest-return corrective action in Steering. Assessment cannot modify the workspace.
44
+ From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. EngineerOS stores the source-cited findings in System State and promotes the highest-return corrective action in Steering. Assessment cannot modify the workspace.
33
45
 
34
- After onboarding, every project prompt is routed to this connection. Copilot, shaping, planning, architecture, and experience generation use the Codex CLI subscription and connected workspace context. Prompt runs are read-only; only an explicitly registered Goal Run receives workspace-write access. If the connector is offline, EngineerOS asks the user to reconnect instead of silently switching models.
46
+ After onboarding, every project prompt is routed to this connection. Copilot, shaping, planning, architecture, and experience generation use the connected agent subscription and workspace context. Interactive prompts run independently from assessments and Goal scheduling. Prompt runs are read-only; only an explicitly registered Goal Run receives workspace-write access. If the connector is offline, EngineerOS asks the user to reconnect instead of silently switching models.
35
47
 
36
48
  Project prompts use resumable, purpose-specific coding-agent sessions. The connector keeps the external session identifiers in its local configuration, so Copilot and artifact conversations survive connector restarts. Changing the purpose, model, or reasoning effort starts a separate session. Goal implementation and verification remain isolated runs.
37
49
 
@@ -48,11 +60,11 @@ If a new pairing command is accidentally run from the same folder against the sa
48
60
 
49
61
  ## Run Goals
50
62
 
51
- Keep the connector online to receive Goals assigned from EngineerOS. Each Goal runs in an isolated worktree below `~/.engineeros/runs`. Cancellation stops Codex. The connector returns changed paths, a bounded diff, and the exact repository ZIP; a human still performs independent attestation.
63
+ Keep the connector online to receive Goals assigned from EngineerOS. Each Goal runs in an isolated worktree below `~/.engineeros/runs`. Cancellation stops the agent. The connector returns changed paths, a bounded diff, and the exact repository ZIP; a human still performs independent attestation.
52
64
 
53
65
  During the writable implementation phase, Codex receives an authenticated EngineerOS MCP server automatically. It can list, read, create, update, reclassify, soft-delete, and materialize project artifacts into canonical Product records through the same repository boundary used by Copilot. The backend accepts those calls only while the assigned Goal is running. Read-only project prompts and the independent verification phase do not receive mutation tools.
54
66
 
55
- Requirements: Node.js 22 or newer, Git, and an authenticated Codex CLI (`codex login`).
67
+ Requirements: Node.js 22 or newer, Git, and an authenticated agent. `npx` distributions use npm, `uvx` distributions require uv, and binary archives require `tar` (`unzip` for ZIP files on Linux). Registry agents report their own authentication prerequisites when they start.
56
68
 
57
69
  ## Execution profiles
58
70
 
@@ -18,11 +18,17 @@ import {
18
18
  executeConnectedPrompt,
19
19
  executeWorkspaceAssessment,
20
20
  inspectCodingAgent,
21
- promptProgressMessage,
22
- promptQueueMessage,
21
+ promptStreamEvent,
23
22
  stopProcess,
24
23
  workspaceSnapshot,
25
24
  } from "../src/runner.mjs";
25
+ import { disposeAcpRuntimes } from "../src/acp-client.mjs";
26
+ import {
27
+ installRegisteredAgent,
28
+ inspectRegisteredAgent,
29
+ registeredAgentConfig,
30
+ registeredAgents,
31
+ } from "../src/agent-registry.mjs";
26
32
  import {
27
33
  describeWebSocketError,
28
34
  startConnectionWatchdog,
@@ -32,13 +38,60 @@ import { parseConnectorArgs } from "../src/cli-args.mjs";
32
38
  import { runMcpServer } from "../src/mcp-server.mjs";
33
39
  import packageJson from "../package.json" with { type: "json" };
34
40
 
35
- const { command, positional, flags } = parseConnectorArgs(process.argv.slice(2));
41
+ const { command, positional, flags } = parseConnectorArgs(
42
+ process.argv.slice(2),
43
+ );
44
+
45
+ if (command === "agents") {
46
+ let agents;
47
+ try {
48
+ agents = await registeredAgents();
49
+ } catch (error) {
50
+ fail(error instanceof Error ? error.message : String(error));
51
+ }
52
+ const statuses = await Promise.all(
53
+ agents.map(async (agent) => {
54
+ try {
55
+ await inspectRegisteredAgent(agent.id);
56
+ return `${agent.id}: ready (${agent.name} ${agent.version}, ${agent.distribution_type})`;
57
+ } catch (error) {
58
+ const detail = error instanceof Error ? error.message : String(error);
59
+ return `${agent.id}: setup needed (${agent.name} ${agent.version}, ${agent.distribution_type}) - ${detail}`;
60
+ }
61
+ }),
62
+ );
63
+ for (const status of statuses) {
64
+ console.log(status);
65
+ }
66
+ process.exit(0);
67
+ }
68
+
69
+ if (command === "agent") {
70
+ const agentId = positional[0];
71
+ const action = positional[1] || "check";
72
+ if (!agentId || !["check", "install"].includes(action)) {
73
+ fail("Usage: engineeros-connector agent AGENT_ID [check|install]");
74
+ }
75
+ try {
76
+ const installed =
77
+ action === "install"
78
+ ? await installRegisteredAgent(agentId)
79
+ : await inspectRegisteredAgent(agentId);
80
+ console.log(
81
+ `${installed.name} is ready (${installed.version}, ${installed.distribution}).`,
82
+ );
83
+ } catch (error) {
84
+ fail(error instanceof Error ? error.message : String(error));
85
+ }
86
+ process.exit(0);
87
+ }
36
88
 
37
89
  if (command === "mcp") {
38
90
  const config = await loadConfig(flags.workspace || process.cwd());
39
91
  if (!config) fail("This workspace is not paired with EngineerOS.");
40
92
  const runId = flags["run-id"] || positional[0];
41
- if (!runId) fail("Usage: engineeros-connector mcp --run-id RUN_ID [--workspace PATH]");
93
+ if (!runId)
94
+ fail("Usage: engineeros-connector mcp --run-id RUN_ID [--workspace PATH]");
42
95
  await runMcpServer({
43
96
  config,
44
97
  runId,
@@ -66,10 +119,22 @@ if (command === "pair") {
66
119
  const pairingCode = positional[0];
67
120
  if (!pairingCode)
68
121
  fail(
69
- "Usage: engineeros-connector pair CODE --url URL [--name NAME] [--workspace PATH] [--skip-git-repo-check]",
122
+ "Usage: engineeros-connector pair CODE --url URL [--agent AGENT_ID] [--name NAME] [--workspace PATH] [--skip-git-repo-check]",
70
123
  );
71
124
  const url = flags.url;
72
125
  if (!url) fail("Pairing requires --url with the EngineerOS backend address.");
126
+ if (flags.agent && flags["agent-command"]) {
127
+ fail("Use either --agent or --agent-command, not both.");
128
+ }
129
+ let agentConfig = {};
130
+ try {
131
+ if (flags.agent) {
132
+ await inspectRegisteredAgent(flags.agent);
133
+ agentConfig = await registeredAgentConfig(flags.agent);
134
+ }
135
+ } catch (error) {
136
+ fail(error instanceof Error ? error.message : String(error));
137
+ }
73
138
  config = {
74
139
  server_url: socketUrl(url),
75
140
  workspace: path.resolve(flags.workspace || process.cwd()),
@@ -79,6 +144,7 @@ if (command === "pair") {
79
144
  agent_command: flags["agent-command"] || null,
80
145
  agent_args: parseAgentArgs(flags["agent-args"]),
81
146
  agent_name: flags["agent-name"] || null,
147
+ ...agentConfig,
82
148
  skip_git_repo_check: flags["skip-git-repo-check"] === true,
83
149
  name:
84
150
  flags.name ||
@@ -104,7 +170,9 @@ if (command === "pair") {
104
170
  token: config.token,
105
171
  };
106
172
  } else {
107
- fail("Use `engineeros-connector pair`, `start`, `status`, or `mcp`.");
173
+ fail(
174
+ "Use `engineeros-connector pair`, `start`, `status`, `agents`, `agent`, or `mcp`.",
175
+ );
108
176
  }
109
177
 
110
178
  let codingAgent;
@@ -113,7 +181,9 @@ try {
113
181
  } catch (error) {
114
182
  fail(error instanceof Error ? error.message : String(error));
115
183
  }
116
- console.log(`Using ${codingAgent.name} through ${codingAgent.protocol} (${codingAgent.version}).`);
184
+ console.log(
185
+ `Using ${codingAgent.name} through ${codingAgent.protocol} (${codingAgent.version}).`,
186
+ );
117
187
  const capabilities = advertisedCapabilities(config, codingAgent);
118
188
  firstMessage.capabilities = capabilities;
119
189
 
@@ -121,7 +191,7 @@ let stopped = false;
121
191
  let active = null;
122
192
  const available = [];
123
193
  const assessments = [];
124
- const prompts = [];
194
+ const activePrompts = new Map();
125
195
  let socket;
126
196
  let pingTimer;
127
197
  let reconnectTimer;
@@ -137,6 +207,8 @@ process.on("SIGINT", async () => {
137
207
  clearTimeout(reconnectTimer);
138
208
  clearInterval(pingTimer);
139
209
  await stopProcess(active?.child);
210
+ await Promise.all([...activePrompts.values()].map(cancelPrompt));
211
+ await disposeAcpRuntimes();
140
212
  socket?.close();
141
213
  process.exit(0);
142
214
  });
@@ -212,29 +284,12 @@ async function connect() {
212
284
  return;
213
285
  }
214
286
  if (message.type === "prompt.execute") {
215
- let added = false;
216
- if (
217
- active?.runId !== message.prompt_id &&
218
- !prompts.some((candidate) => candidate.prompt_id === message.prompt_id)
219
- ) {
220
- prompts.push(message);
221
- added = true;
222
- }
223
- if (added && active) {
224
- sendPromptProgress(message.prompt_id, promptQueueMessage(active.kind));
225
- }
226
- pump();
287
+ if (!activePrompts.has(message.prompt_id)) void executePrompt(message);
227
288
  return;
228
289
  }
229
290
  if (message.type === "prompt.cancel") {
230
- const queued = prompts.findIndex(
231
- (candidate) => candidate.prompt_id === message.prompt_id,
232
- );
233
- if (queued >= 0) prompts.splice(queued, 1);
234
- if (active?.kind === "prompt" && active.runId === message.prompt_id) {
235
- active.cancelled = true;
236
- await stopProcess(active.child);
237
- }
291
+ const promptState = activePrompts.get(message.prompt_id);
292
+ if (promptState) await cancelPrompt(promptState);
238
293
  return;
239
294
  }
240
295
  if (message.type === "run.available") {
@@ -274,10 +329,8 @@ async function connect() {
274
329
  socket.addEventListener("close", (event) => {
275
330
  clearConnectionWatchdog?.();
276
331
  clearInterval(pingTimer);
277
- if (active?.kind === "prompt") {
278
- active.cancelled = true;
279
- void stopProcess(active.child);
280
- }
332
+ for (const promptState of activePrompts.values())
333
+ void cancelPrompt(promptState);
281
334
  if (event.code === 4001 && active?.kind === "assessment") {
282
335
  void stopProcess(active.child);
283
336
  }
@@ -301,7 +354,9 @@ async function connect() {
301
354
 
302
355
  function scheduleReconnect() {
303
356
  if (stopped || connectionRejected || reconnectTimer) return;
304
- const detail = lastConnectionError ? ` Last error: ${lastConnectionError}` : "";
357
+ const detail = lastConnectionError
358
+ ? ` Last error: ${lastConnectionError}`
359
+ : "";
305
360
  console.error(
306
361
  `Connection unavailable.${detail} Retrying in ${Math.round(reconnectDelay / 1_000)}s.`,
307
362
  );
@@ -366,30 +421,21 @@ function startPings() {
366
421
  }, 10_000);
367
422
  }
368
423
 
369
- function sendPromptProgress(promptId, message) {
370
- if (!message || socket.readyState !== WebSocket.OPEN) return;
371
- socket.send(
372
- JSON.stringify({
373
- type: "prompt.progress",
374
- prompt_id: promptId,
375
- message: String(message).slice(0, 500),
376
- }),
377
- );
424
+ function sendPromptEvent(promptId, event) {
425
+ if (!event || socket.readyState !== WebSocket.OPEN) return;
426
+ const payload = {
427
+ type: "prompt.event",
428
+ prompt_id: promptId,
429
+ kind: event.kind,
430
+ };
431
+ if (event.message) payload.message = String(event.message).slice(0, 500);
432
+ if (event.delta) payload.delta = String(event.delta).slice(0, 50_000);
433
+ if (event.status) payload.status = String(event.status).slice(0, 100);
434
+ socket.send(JSON.stringify(payload));
378
435
  }
379
436
 
380
437
  function pump() {
381
438
  if (active || socket.readyState !== WebSocket.OPEN) return;
382
- const prompt = prompts.shift();
383
- if (prompt) {
384
- active = {
385
- kind: "prompt",
386
- runId: prompt.prompt_id,
387
- child: null,
388
- cancelled: false,
389
- };
390
- void executePrompt(prompt);
391
- return;
392
- }
393
439
  const assessment = assessments.shift();
394
440
  if (assessment) {
395
441
  active = {
@@ -418,36 +464,56 @@ function pump() {
418
464
 
419
465
  async function executePrompt(assignment) {
420
466
  const promptId = assignment.prompt_id;
421
- let lastProgressMessage;
422
- const reportProgress = (message) => {
467
+ const promptState = {
468
+ runId: promptId,
469
+ child: null,
470
+ controller: null,
471
+ cancelled: false,
472
+ };
473
+ activePrompts.set(promptId, promptState);
474
+ let lastEvent;
475
+ const reportEvent = (event) => {
476
+ const serialized = event ? JSON.stringify(event) : null;
423
477
  if (
424
- !message ||
425
- message === lastProgressMessage ||
478
+ !serialized ||
479
+ serialized === lastEvent ||
426
480
  socket.readyState !== WebSocket.OPEN ||
427
- active?.runId !== promptId
481
+ activePrompts.get(promptId) !== promptState
428
482
  ) {
429
483
  return;
430
484
  }
431
- lastProgressMessage = message;
432
- sendPromptProgress(promptId, message);
485
+ lastEvent = serialized;
486
+ sendPromptEvent(promptId, event);
433
487
  };
434
- sendPromptProgress(promptId, "Agent started this request");
488
+ sendPromptEvent(promptId, {
489
+ kind: "status",
490
+ message: "Agent started this request",
491
+ });
435
492
  console.log(
436
493
  `Answering ${assignment.purpose || "project"} prompt with ${codingAgent.name}.`,
437
494
  );
438
495
  try {
439
496
  const result = await executeConnectedPrompt(assignment, config, {
440
497
  onProcess: (child) => {
441
- if (active?.runId === promptId) active.child = child;
498
+ if (activePrompts.get(promptId) === promptState)
499
+ promptState.child = child;
442
500
  },
443
- onEvent: (event) => reportProgress(promptProgressMessage(event)),
501
+ onController: (controller) => {
502
+ if (activePrompts.get(promptId) === promptState) {
503
+ promptState.controller = controller;
504
+ }
505
+ },
506
+ onEvent: (event) => reportEvent(promptStreamEvent(event)),
444
507
  });
445
508
  config = {
446
509
  ...config,
447
- sessions: { ...(config.sessions || {}), [result.sessionKey]: result.sessionId },
510
+ sessions: {
511
+ ...(config.sessions || {}),
512
+ [result.sessionKey]: result.sessionId,
513
+ },
448
514
  };
449
515
  await saveConfig(config);
450
- if (active?.cancelled || socket.readyState !== WebSocket.OPEN) return;
516
+ if (promptState.cancelled || socket.readyState !== WebSocket.OPEN) return;
451
517
  socket.send(
452
518
  JSON.stringify({
453
519
  type: "prompt.completed",
@@ -458,7 +524,7 @@ async function executePrompt(assignment) {
458
524
  }),
459
525
  );
460
526
  } catch (error) {
461
- if (!active?.cancelled && socket.readyState === WebSocket.OPEN) {
527
+ if (!promptState.cancelled && socket.readyState === WebSocket.OPEN) {
462
528
  socket.send(
463
529
  JSON.stringify({
464
530
  type: "prompt.failed",
@@ -467,18 +533,29 @@ async function executePrompt(assignment) {
467
533
  }),
468
534
  );
469
535
  }
470
- if (!active?.cancelled) {
536
+ if (!promptState.cancelled) {
471
537
  console.error(error instanceof Error ? error.message : String(error));
472
538
  }
473
539
  } finally {
474
- active = null;
475
- pump();
540
+ if (activePrompts.get(promptId) === promptState)
541
+ activePrompts.delete(promptId);
542
+ }
543
+ }
544
+
545
+ async function cancelPrompt(promptState) {
546
+ promptState.cancelled = true;
547
+ if (typeof promptState.controller?.cancel === "function") {
548
+ await promptState.controller.cancel();
549
+ return;
476
550
  }
551
+ await stopProcess(promptState.child);
477
552
  }
478
553
 
479
554
  async function executeAssessment(assignment) {
480
555
  const assessmentId = assignment.assessment_id;
481
- console.log(`Assessing workspace with ${codingAgent.name} (${assessmentId}).`);
556
+ console.log(
557
+ `Assessing workspace with ${codingAgent.name} (${assessmentId}).`,
558
+ );
482
559
  let progress = 10;
483
560
  const reportedMilestones = new Set();
484
561
  const reportProgress = (message, { milestone = true } = {}) => {
@@ -530,7 +607,9 @@ async function executeAssessment(assignment) {
530
607
  `EngineerOS rejected the assessment (${response.status}): ${await response.text()}`,
531
608
  );
532
609
  }
533
- console.log(`${codingAgent.name} workspace assessment is current in EngineerOS.`);
610
+ console.log(
611
+ `${codingAgent.name} workspace assessment is current in EngineerOS.`,
612
+ );
534
613
  } catch (error) {
535
614
  if (socket.readyState === WebSocket.OPEN) {
536
615
  socket.send(
@@ -561,7 +640,7 @@ async function execute(assignment) {
561
640
  type: "run.progress",
562
641
  run_id: runId,
563
642
  progress_percent: progress,
564
- message: `${codingAgent.name} is working`,
643
+ message: "Agent is working",
565
644
  }),
566
645
  );
567
646
  }
@@ -573,7 +652,7 @@ async function execute(assignment) {
573
652
  },
574
653
  onEvent: (event) => {
575
654
  const message =
576
- event.message || event.item?.text || event.type || `${codingAgent.name} is working`;
655
+ event.message || event.item?.text || event.type || "Agent is working";
577
656
  if (socket.readyState === WebSocket.OPEN) {
578
657
  socket.send(
579
658
  JSON.stringify({
@@ -608,10 +687,16 @@ async function execute(assignment) {
608
687
  result.head_revision,
609
688
  );
610
689
  if (integration.applied) {
611
- console.log(`Run accepted and applied to the connected repository at ${integration.revision}.`);
690
+ console.log(
691
+ `Run accepted and applied to the connected repository at ${integration.revision}.`,
692
+ );
612
693
  } else {
613
- console.warn(`Run accepted but not applied locally. ${integration.reason}`);
614
- console.warn(`The verified run workspace remains at ${result.run_workspace}.`);
694
+ console.warn(
695
+ `Run accepted but not applied locally. ${integration.reason}`,
696
+ );
697
+ console.warn(
698
+ `The verified run workspace remains at ${result.run_workspace}.`,
699
+ );
615
700
  }
616
701
  } catch (error) {
617
702
  if (!active?.cancelled && socket.readyState === WebSocket.OPEN) {
@@ -636,12 +721,17 @@ function parseAgentArgs(value) {
636
721
  if (!value) return [];
637
722
  try {
638
723
  const parsed = JSON.parse(value);
639
- if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
724
+ if (
725
+ !Array.isArray(parsed) ||
726
+ parsed.some((item) => typeof item !== "string")
727
+ ) {
640
728
  throw new Error();
641
729
  }
642
730
  return parsed;
643
731
  } catch {
644
- fail('--agent-args must be a JSON array, for example: --agent-args "[\\"acp\\"]"');
732
+ fail(
733
+ '--agent-args must be a JSON array, for example: --agent-args "[\\"acp\\"]"',
734
+ );
645
735
  }
646
736
  }
647
737
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engineeros/connector",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "start": "node ./bin/engineeros-connector.mjs",
18
18
  "test": "node --test --test-concurrency=1",
19
- "type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
19
+ "type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/acp-client.mjs && node --check ./src/agent-registry.mjs && node --check ./src/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/config.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
20
20
  },
21
21
  "engines": {
22
22
  "node": ">=22"