@maintainer-pro/ai-bridge 0.1.29 → 0.1.30

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/daemon.mjs +169 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainer-pro/ai-bridge",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "Local bridge daemon that pairs a machine to Maintainer Pro and configures multiple client sandboxes.",
5
5
  "keywords": [
6
6
  "maintainer-pro",
@@ -28,7 +28,7 @@
28
28
  "node": ">=22"
29
29
  },
30
30
  "dependencies": {
31
- "@maintainer-pro/ai-cli": "^0.1.15",
31
+ "@maintainer-pro/ai-cli": "^0.1.16",
32
32
  "@maintainer-pro/ai-server": "^0.1.8"
33
33
  }
34
34
  }
package/src/daemon.mjs CHANGED
@@ -451,13 +451,30 @@ async function loadAiCli() {
451
451
  /** @type {{ at: number, ids: string[] } | null} */
452
452
  let cliProviderCache = null;
453
453
 
454
- async function detectCliProviders() {
455
- if (cliProviderCache && Date.now() - cliProviderCache.at < 60_000) {
454
+ function missingCliSetupText(folder) {
455
+ const folderLine = folder
456
+ ? `No coding agent CLI is available in this folder:\n ${folder}`
457
+ : "No coding agent CLI found on this computer.";
458
+ return [
459
+ `${folderLine} Chat will not work until one is installed and reachable from this folder.`,
460
+ "",
461
+ " • Cursor Agent CLI — https://cursor.com (command: agent)",
462
+ " • Claude Code — https://docs.anthropic.com/en/docs/claude-code (command: claude)",
463
+ " • Antigravity — https://antigravity.google/ (command: agy)",
464
+ "",
465
+ "Then either sign in and re-run the Maintainer Pro command from a terminal where that CLI works, or set the full path in this folder's .env:",
466
+ " AI_CLI_PROVIDER=cursor",
467
+ " AI_CLI_COMMAND=C:\\\\Users\\\\you\\\\AppData\\\\Local\\\\cursor-agent\\\\agent.cmd",
468
+ ].join("\n");
469
+ }
470
+
471
+ async function detectCliProviders(force = false) {
472
+ if (!force && cliProviderCache && Date.now() - cliProviderCache.at < 60_000) {
456
473
  return cliProviderCache.ids;
457
474
  }
458
475
  try {
459
- const { resolveProvider } = await loadAiCli();
460
- const provider = await resolveProvider({ preference: "auto" });
476
+ const cli = await loadAiCli();
477
+ const provider = await cli.resolveProvider({ preference: "auto" });
461
478
  cliProviderCache = { at: Date.now(), ids: [provider.id] };
462
479
  return cliProviderCache.ids;
463
480
  } catch {
@@ -466,6 +483,65 @@ async function detectCliProviders() {
466
483
  }
467
484
  }
468
485
 
486
+ async function withCliProbe(result, folders) {
487
+ const roots = [...new Set((folders || []).filter(Boolean).map((row) => path.resolve(row)))];
488
+ let probe = { available: false };
489
+ for (const folder of roots) {
490
+ probe = await probeFolderCli(folder);
491
+ if (probe.available) break;
492
+ }
493
+ const folder = probe.folder || roots[0] || result.folderPath;
494
+ if (probe.available) {
495
+ log(
496
+ `coding agent CLI ready in ${folder} (${probe.providerId || "auto"}${
497
+ probe.source ? ` via ${probe.source}` : ""
498
+ })`
499
+ );
500
+ } else if (folder) {
501
+ activity(
502
+ result.sandboxId,
503
+ "warn",
504
+ `No coding agent CLI in ${folder}. Chat needs agent, claude, or agy — or AI_CLI_COMMAND in this folder's .env.`
505
+ );
506
+ }
507
+ return {
508
+ ...result,
509
+ missingCli: !probe.available,
510
+ cliProvider: probe.providerId || null,
511
+ cliCommand: probe.command || null,
512
+ warning: !probe.available
513
+ ? [result.warning, missingCliSetupText(folder)].filter(Boolean).join("\n\n")
514
+ : result.warning,
515
+ };
516
+ }
517
+
518
+ async function probeFolderCli(folder) {
519
+ const root = folder ? path.resolve(folder) : "";
520
+ if (!root) return { available: false };
521
+ try {
522
+ const cli = await loadAiCli();
523
+ if (typeof cli.probeCliInFolder === "function") {
524
+ return await cli.probeCliInFolder(root);
525
+ }
526
+ const provider = await cli.resolveProvider({
527
+ preference: "auto",
528
+ workspaceDir: root,
529
+ });
530
+ return { available: true, providerId: provider.id, folder: root };
531
+ } catch {
532
+ return { available: false, folder: root };
533
+ }
534
+ }
535
+
536
+ async function warnIfMissingCli() {
537
+ const ids = await detectCliProviders(true);
538
+ if (ids.length) {
539
+ log(`coding agent CLI ready (${ids.join(", ")})`);
540
+ return;
541
+ }
542
+ warn(missingCliSetupText());
543
+ }
544
+
469
545
  async function resolveWorkspaceHostApps(ws, opts = {}) {
470
546
  const folders = workspaceFolders(ws);
471
547
  const folder = folders[0] || path.resolve(ws.folderPath || "");
@@ -3714,11 +3790,18 @@ async function startAiServerForWorkspace(ws, opts = {}) {
3714
3790
 
3715
3791
  const storeEnv = await loadSandboxStoreEnv(ws, cfg);
3716
3792
  const folderEnv = readProjectEnvValues(folder);
3793
+ const cliProbe = await probeFolderCli(folder);
3717
3794
  const dataDir = dataDirFor(folder, ws.sandboxId);
3718
3795
  const instanceEnv = {
3719
3796
  ...folderEnv,
3720
3797
  ...overrideEnv,
3721
3798
  ...storeEnv,
3799
+ ...(cliProbe.command && !folderEnv.AI_CLI_COMMAND
3800
+ ? { AI_CLI_COMMAND: cliProbe.command }
3801
+ : {}),
3802
+ ...(cliProbe.providerId && !folderEnv.AI_CLI_PROVIDER
3803
+ ? { AI_CLI_PROVIDER: cliProbe.providerId }
3804
+ : {}),
3722
3805
  AI_SERVER_URL: localAi,
3723
3806
  CORS_ORIGIN: overrideEnv.CORS_ORIGIN || folderEnv.CORS_ORIGIN || "",
3724
3807
  CORS_ORIGINS: overrideEnv.CORS_ORIGINS || folderEnv.CORS_ORIGINS || "",
@@ -4448,7 +4531,7 @@ async function setupWorkspace(cfg, action) {
4448
4531
  const host = workspaceHostReport(existingWs);
4449
4532
  const aiServerUp = await isChatServerOnPort(existingWs.port);
4450
4533
  log(`setup added folder ${resolved} sandbox=${shortId(sandboxId)}`);
4451
- return {
4534
+ return await withCliProbe({
4452
4535
  sandboxId,
4453
4536
  folderPath: existingWs.folderPath,
4454
4537
  extraFolders: extra,
@@ -4476,7 +4559,9 @@ async function setupWorkspace(cfg, action) {
4476
4559
  reasons: ports.reasons || [],
4477
4560
  projectInfo: existingWs.projectInfo || null,
4478
4561
  ignorePaths: access.ignorePaths,
4479
- };
4562
+ },
4563
+ workspaceFolders(existingWs)
4564
+ );
4480
4565
  }
4481
4566
 
4482
4567
  const client = configureClient({
@@ -4566,32 +4651,35 @@ async function setupWorkspace(cfg, action) {
4566
4651
  );
4567
4652
 
4568
4653
  const host = workspaceHostReport(entry);
4569
- return {
4570
- sandboxId,
4571
- folderPath: entry.folderPath,
4572
- extraFolders: entry.extraFolders || [],
4573
- addFolder: false,
4574
- port: entry.port,
4575
- appUrl: host.appUrl || entry.appUrl || appUrl,
4576
- origins: host.origins.length
4577
- ? host.origins
4578
- : [...new Set([corsOrigin, aiOrigin, entry.appUrl].filter(Boolean))],
4579
- wroteEnv: false,
4580
- clientKind: client.kind,
4581
- clientFiles: client.filesWritten,
4582
- clientNotes: client.notes,
4583
- aiServerUp,
4584
- startedHosts: [],
4585
- openUrl,
4586
- processIssues,
4587
- warning,
4588
- waitingForStart,
4589
- needsReview,
4590
- hostApps: ports.apps || entry.hostApps || [],
4591
- reasons: ports.reasons || [],
4592
- projectInfo,
4593
- ignorePaths: access.ignorePaths,
4594
- };
4654
+ return await withCliProbe(
4655
+ {
4656
+ sandboxId,
4657
+ folderPath: entry.folderPath,
4658
+ extraFolders: entry.extraFolders || [],
4659
+ addFolder: false,
4660
+ port: entry.port,
4661
+ appUrl: host.appUrl || entry.appUrl || appUrl,
4662
+ origins: host.origins.length
4663
+ ? host.origins
4664
+ : [...new Set([corsOrigin, aiOrigin, entry.appUrl].filter(Boolean))],
4665
+ wroteEnv: false,
4666
+ clientKind: client.kind,
4667
+ clientFiles: client.filesWritten,
4668
+ clientNotes: client.notes,
4669
+ aiServerUp,
4670
+ startedHosts: [],
4671
+ openUrl,
4672
+ processIssues,
4673
+ warning,
4674
+ waitingForStart,
4675
+ needsReview,
4676
+ hostApps: ports.apps || entry.hostApps || [],
4677
+ reasons: ports.reasons || [],
4678
+ projectInfo,
4679
+ ignorePaths: access.ignorePaths,
4680
+ },
4681
+ workspaceFolders(entry)
4682
+ );
4595
4683
  }
4596
4684
 
4597
4685
  async function proposeSetupForWorkspace(ws, cfg, opts = {}) {
@@ -4738,16 +4826,28 @@ async function runActions(cfg, actions) {
4738
4826
  };
4739
4827
  }
4740
4828
  } else if (action.code === "recheck") {
4829
+ cliProviderCache = null;
4741
4830
  const sandboxId = action.sandboxId || action.payload?.sandboxId;
4742
4831
  const ws = (cfg.workspaces || []).find((w) => w.sandboxId === sandboxId);
4743
4832
  if (!ws) {
4744
4833
  log(`${label} recheck: no local workspace`);
4745
4834
  }
4835
+ const cli = await detectCliProviders(true);
4836
+ const folderProbe = ws
4837
+ ? await withCliProbe(
4838
+ { sandboxId, folderPath: ws.folderPath },
4839
+ workspaceFolders(ws)
4840
+ )
4841
+ : { missingCli: cli.length === 0 };
4746
4842
  const hostApps = ws
4747
4843
  ? await resolveWorkspaceHostApps(ws, { cfg, force: true, allowAi: false })
4748
4844
  : null;
4749
4845
  result = {
4750
4846
  recheckedAt: new Date().toISOString(),
4847
+ cliProviders: cli,
4848
+ missingCli: folderProbe.missingCli === true,
4849
+ cliProvider: folderProbe.cliProvider || null,
4850
+ cliCommand: folderProbe.cliCommand || null,
4751
4851
  hostApps: hostApps?.apps || [],
4752
4852
  projectInfo: ws?.projectInfo || null,
4753
4853
  };
@@ -5216,18 +5316,41 @@ function adminWsUrl(adminUrl, token) {
5216
5316
  async function buildIssues(cfg, workspaceStates) {
5217
5317
  /** @type {Array<Record<string, unknown>>} */
5218
5318
  const issues = [];
5219
- const cli = await detectCliProviders();
5220
- if (!cli.length) {
5221
- issues.push({
5222
- code: "missing_cli",
5223
- severity: "error",
5224
- title: "No coding agent CLI found",
5225
- message:
5226
- "PATH has no agent, claude, or agy. Chat turns will fail until one is installed.",
5227
- resolution: "Install and authenticate a CLI, then Recheck.",
5228
- actionCode: "recheck",
5229
- sandboxId: null,
5230
- });
5319
+ if (workspaceStates.length) {
5320
+ for (const st of workspaceStates) {
5321
+ const ws = (cfg.workspaces || []).find((row) => row.sandboxId === st.sandboxId);
5322
+ const folders = ws ? workspaceFolders(ws) : [st.folderPath].filter(Boolean);
5323
+ let probe = { available: false, folder: folders[0] || st.folderPath };
5324
+ for (const folder of folders) {
5325
+ probe = await probeFolderCli(folder);
5326
+ if (probe.available) break;
5327
+ }
5328
+ if (probe.available) continue;
5329
+ issues.push({
5330
+ code: "missing_cli",
5331
+ severity: "error",
5332
+ title: "No coding agent CLI in this folder",
5333
+ message: missingCliSetupText(probe.folder || folders[0] || st.folderPath),
5334
+ resolution:
5335
+ "Install a CLI and re-run the Maintainer Pro command from a terminal where it works, or set AI_CLI_COMMAND in this folder's .env, then Check again.",
5336
+ actionCode: "recheck",
5337
+ sandboxId: st.sandboxId,
5338
+ });
5339
+ }
5340
+ } else {
5341
+ const cli = await detectCliProviders();
5342
+ if (!cli.length) {
5343
+ issues.push({
5344
+ code: "missing_cli",
5345
+ severity: "error",
5346
+ title: "No coding agent CLI found",
5347
+ message: missingCliSetupText(),
5348
+ resolution:
5349
+ "Install Cursor (agent), Claude Code (claude), or Antigravity (agy), sign in, then stop and re-run the Maintainer Pro command. Use Check again after it is on PATH.",
5350
+ actionCode: "recheck",
5351
+ sandboxId: null,
5352
+ });
5353
+ }
5231
5354
  }
5232
5355
  for (const st of workspaceStates) {
5233
5356
  if (st.aiServerUp || st.appsRunning || st.startingAi) continue;
@@ -5377,6 +5500,7 @@ async function main() {
5377
5500
  log(
5378
5501
  `online via websocket (presence every ${HEARTBEAT_MS / 1000}s, ping every ${WS_PING_MS / 1000}s; reconnects until stopped)`
5379
5502
  );
5503
+ await warnIfMissingCli();
5380
5504
  await restoreHostsAfterReconnect(cfg);
5381
5505
 
5382
5506
  /** @type {unknown[]} */
@@ -5391,6 +5515,7 @@ async function main() {
5391
5515
  const batch = claimedActions.splice(0, claimedActions.length);
5392
5516
  await runActions(cfg, batch);
5393
5517
  }
5518
+ await sendPresenceOverWs();
5394
5519
  } catch (err) {
5395
5520
  warn(err instanceof Error ? err.message : String(err));
5396
5521
  } finally {