@bugmole/cli 0.4.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.
Files changed (308) hide show
  1. package/.bugmole.env.example +20 -0
  2. package/LICENSE +7 -0
  3. package/README.md +293 -0
  4. package/TESTING.md +117 -0
  5. package/bugmole.config.yaml +39 -0
  6. package/package.json +85 -0
  7. package/scripts/billing/paypal-setup.mjs +121 -0
  8. package/scripts/bugmole-continue.ts +318 -0
  9. package/scripts/bugmole-init.ts +188 -0
  10. package/scripts/bugmole.cjs +17 -0
  11. package/scripts/bugmole.test.ts +344 -0
  12. package/scripts/bugmole.ts +657 -0
  13. package/scripts/ensure-maestro.cjs +79 -0
  14. package/scripts/ios-tunnel-keeper.sh +45 -0
  15. package/scripts/ios-wda-keeper.sh +66 -0
  16. package/scripts/sync-plan-catalog.d.mts +3 -0
  17. package/scripts/sync-plan-catalog.mjs +16 -0
  18. package/scripts/ui-parity-diff.py +65 -0
  19. package/scripts/ui-parity-requirements.txt +1 -0
  20. package/scripts/verify-manage-to-plans.mts +194 -0
  21. package/spec/app-ui-audit.schema.json +176 -0
  22. package/spec/blockers.yaml +79 -0
  23. package/spec/bugs.index.json +42 -0
  24. package/spec/design-dna.schema.json +38 -0
  25. package/spec/domain_rules.yaml +24 -0
  26. package/spec/flows/flow_manage-to-plans-64c3c61b.yaml +8 -0
  27. package/spec/flows/flow_screen-to-evidence-6c3ab309.yaml +7 -0
  28. package/spec/journey_graph.yaml +126 -0
  29. package/spec/journeys.graph.json +2618 -0
  30. package/spec/plans/dashboard-smoke.flow.yaml +20 -0
  31. package/spec/plans/example.flow.yaml +99 -0
  32. package/spec/plans/flow_api-keys-to-logout-7396cd5d.flow.yaml +33 -0
  33. package/spec/plans/flow_api-keys-to-screen-157e10a6.flow.yaml +33 -0
  34. package/spec/plans/flow_manage-to-audits-176c1eb8.flow.yaml +33 -0
  35. package/spec/plans/flow_manage-to-blockers-633282c8.flow.yaml +112 -0
  36. package/spec/plans/flow_manage-to-devices-ad57e202.flow.yaml +33 -0
  37. package/spec/plans/flow_manage-to-logout-86c54656.flow.yaml +33 -0
  38. package/spec/plans/flow_manage-to-manage-resource-action-6325f99a.flow.yaml +185 -0
  39. package/spec/plans/flow_manage-to-parity-issues-01fa2579.flow.yaml +34 -0
  40. package/spec/plans/flow_manage-to-plans-64c3c61b.flow.yaml +123 -0
  41. package/spec/plans/flow_manage-to-screen-6857f915.flow.yaml +106 -0
  42. package/spec/plans/flow_manage-to-tasks-c5791c95.flow.yaml +33 -0
  43. package/spec/plans/flow_profile-to-screen-6045aa3d.flow.yaml +33 -0
  44. package/spec/plans/flow_register-to-api-auth-login-460a9cc8.flow.yaml +34 -0
  45. package/spec/plans/flow_screen-to-audits-07666356.flow.yaml +33 -0
  46. package/spec/plans/flow_screen-to-blockers-091d258b.flow.yaml +33 -0
  47. package/spec/plans/flow_screen-to-devices-cad72f75.flow.yaml +33 -0
  48. package/spec/plans/flow_screen-to-evidence-6c3ab309.flow.yaml +126 -0
  49. package/spec/plans/flow_screen-to-parity-issues-2218e3ad.flow.yaml +34 -0
  50. package/spec/plans/flow_screen-to-plans-8232a94f.flow.yaml +33 -0
  51. package/spec/plans/flow_screen-to-tasks-540671d1.flow.yaml +33 -0
  52. package/spec/plans/login.flow.yaml +22 -0
  53. package/spec/plans/owner-operations.flow.yaml +20 -0
  54. package/spec/project_config.yaml +55 -0
  55. package/spec/roles.yaml +30 -0
  56. package/spec/schema.md +394 -0
  57. package/spec/test-case-results.schema.json +62 -0
  58. package/spec/test-cases.schema.json +85 -0
  59. package/spec/ui-parity-audit.schema.json +194 -0
  60. package/spec/ui-reverse-engineering.schema.json +94 -0
  61. package/src/billing/plan-catalog.test.ts +46 -0
  62. package/src/billing/plan-catalog.ts +199 -0
  63. package/src/integrations/aws-sigv4.test.ts +42 -0
  64. package/src/integrations/aws-sigv4.ts +72 -0
  65. package/src/integrations/device-farm.ts +155 -0
  66. package/src/integrations/github-app.test.ts +57 -0
  67. package/src/integrations/github-app.ts +143 -0
  68. package/src/integrations/gitlab.ts +81 -0
  69. package/src/integrations/temp-email.test.ts +123 -0
  70. package/src/integrations/temp-email.ts +175 -0
  71. package/src/integrations/testflight-feedback.test.ts +51 -0
  72. package/src/integrations/testflight-feedback.ts +173 -0
  73. package/src/integrations/webdriver-client.ts +131 -0
  74. package/src/mcp/server.test.ts +1220 -0
  75. package/src/mcp/server.ts +3064 -0
  76. package/src/mcp/write-test-cases.test.ts +287 -0
  77. package/src/registry/api-key-client.ts +39 -0
  78. package/src/registry/control-plane-client.ts +212 -0
  79. package/src/registry/migrations/0001_registry.sql +47 -0
  80. package/src/registry/migrations/0002_device_authorizations.sql +23 -0
  81. package/src/registry/migrations/0003_project_environments.sql +25 -0
  82. package/src/registry/migrations/0004_device_authorization_email.sql +1 -0
  83. package/src/registry/migrations/0005_testing_control_plane.sql +55 -0
  84. package/src/registry/migrations/0006_workspaces.sql +36 -0
  85. package/src/registry/migrations/0007_project_apps.sql +26 -0
  86. package/src/registry/migrations/0008_agent_tasks.sql +30 -0
  87. package/src/registry/migrations/0009_journey_revisions.sql +17 -0
  88. package/src/registry/migrations/0010_agent_task_journey.sql +2 -0
  89. package/src/registry/migrations/0011_run_journey_revision.sql +1 -0
  90. package/src/registry/migrations/0012_canonical_flow_execution.sql +10 -0
  91. package/src/registry/migrations/0013_device_sessions.sql +22 -0
  92. package/src/registry/migrations/0014_agent_task_step.sql +1 -0
  93. package/src/registry/migrations/0015_agent_task_attempts.sql +6 -0
  94. package/src/registry/migrations/0016_github_issue_tracker.sql +54 -0
  95. package/src/registry/migrations/0017_run_targets.sql +7 -0
  96. package/src/registry/migrations/0018_run_fix_from.sql +4 -0
  97. package/src/registry/migrations/0019_workspace_flags.sql +9 -0
  98. package/src/registry/migrations/0020_orgs.sql +40 -0
  99. package/src/registry/migrations/0021_billing_core.sql +58 -0
  100. package/src/registry/migrations/0022_cloud_runners.sql +19 -0
  101. package/src/registry/migrations/0023_signup.sql +4 -0
  102. package/src/registry/migrations/0024_billing.sql +67 -0
  103. package/src/registry/migrations/0025_notifications.sql +47 -0
  104. package/src/registry/migrations/0026_repo_bindings.sql +28 -0
  105. package/src/registry/migrations/0027_feedback.sql +29 -0
  106. package/src/registry/migrations/0028_devices.sql +48 -0
  107. package/src/registry/migrations/0029_sso.sql +31 -0
  108. package/src/registry/migrations/0030_workspace_domains.sql +18 -0
  109. package/src/registry/migrations/0031_gitlab_and_teams.sql +45 -0
  110. package/src/registry/migrations/0032_personas.sql +15 -0
  111. package/src/registry/migrations/0033_bugmole_rename.sql +11 -0
  112. package/src/registry/task-scheduling.test.ts +100 -0
  113. package/src/registry/task-scheduling.ts +80 -0
  114. package/src/registry-worker/ai/platform-model.ts +77 -0
  115. package/src/registry-worker/ai/routes.test.ts +88 -0
  116. package/src/registry-worker/ai/routes.ts +90 -0
  117. package/src/registry-worker/artifacts.test.ts +98 -0
  118. package/src/registry-worker/artifacts.ts +85 -0
  119. package/src/registry-worker/billing/billing-core.test.ts +175 -0
  120. package/src/registry-worker/billing/checkout-routes.ts +209 -0
  121. package/src/registry-worker/billing/enforcement.ts +69 -0
  122. package/src/registry-worker/billing/entitlements.ts +108 -0
  123. package/src/registry-worker/billing/ledger.ts +186 -0
  124. package/src/registry-worker/billing/paypal/api.ts +259 -0
  125. package/src/registry-worker/billing/paypal/client.ts +91 -0
  126. package/src/registry-worker/billing/paypal/provider.ts +143 -0
  127. package/src/registry-worker/billing/paypal.test.ts +466 -0
  128. package/src/registry-worker/billing/provider.ts +114 -0
  129. package/src/registry-worker/billing/routes.ts +66 -0
  130. package/src/registry-worker/billing/subscriptions.ts +780 -0
  131. package/src/registry-worker/billing/thresholds.ts +107 -0
  132. package/src/registry-worker/core.ts +308 -0
  133. package/src/registry-worker/devices/devices.test.ts +185 -0
  134. package/src/registry-worker/devices/policy.ts +71 -0
  135. package/src/registry-worker/devices/routes.ts +453 -0
  136. package/src/registry-worker/domains/domains.test.ts +210 -0
  137. package/src/registry-worker/domains/routes.ts +139 -0
  138. package/src/registry-worker/domains.ts +88 -0
  139. package/src/registry-worker/email/sender.ts +75 -0
  140. package/src/registry-worker/env.d.ts +14716 -0
  141. package/src/registry-worker/features.ts +20 -0
  142. package/src/registry-worker/feedback/feedback.test.ts +230 -0
  143. package/src/registry-worker/feedback/format.ts +148 -0
  144. package/src/registry-worker/feedback/routes.ts +386 -0
  145. package/src/registry-worker/flags.ts +39 -0
  146. package/src/registry-worker/github/checks.test.ts +177 -0
  147. package/src/registry-worker/github/checks.ts +374 -0
  148. package/src/registry-worker/gitlab/checks.test.ts +141 -0
  149. package/src/registry-worker/gitlab/checks.ts +349 -0
  150. package/src/registry-worker/hooks.ts +54 -0
  151. package/src/registry-worker/index.ts +2077 -0
  152. package/src/registry-worker/jobs/index.ts +29 -0
  153. package/src/registry-worker/jobs/retention.ts +68 -0
  154. package/src/registry-worker/mcp/mcp.test.ts +355 -0
  155. package/src/registry-worker/mcp/routes.ts +215 -0
  156. package/src/registry-worker/mcp/token.ts +126 -0
  157. package/src/registry-worker/mcp/tools.ts +563 -0
  158. package/src/registry-worker/notifications/alerts.ts +212 -0
  159. package/src/registry-worker/notifications/notifications.test.ts +298 -0
  160. package/src/registry-worker/notifications/outbox.ts +83 -0
  161. package/src/registry-worker/notifications/routes.ts +280 -0
  162. package/src/registry-worker/notifications/secrets.ts +49 -0
  163. package/src/registry-worker/notifications/slack.ts +96 -0
  164. package/src/registry-worker/notifications/teams.ts +46 -0
  165. package/src/registry-worker/org/audit.ts +116 -0
  166. package/src/registry-worker/org/routes.test.ts +163 -0
  167. package/src/registry-worker/org/routes.ts +302 -0
  168. package/src/registry-worker/personas/personas.test.ts +78 -0
  169. package/src/registry-worker/personas/routes.ts +100 -0
  170. package/src/registry-worker/repo-triggers.ts +20 -0
  171. package/src/registry-worker/routes/index.ts +74 -0
  172. package/src/registry-worker/run-events.ts +24 -0
  173. package/src/registry-worker/runner/dispatch.ts +219 -0
  174. package/src/registry-worker/runner/jobs.ts +43 -0
  175. package/src/registry-worker/runner/metering.ts +82 -0
  176. package/src/registry-worker/runner/policy.ts +59 -0
  177. package/src/registry-worker/runner/routes.ts +171 -0
  178. package/src/registry-worker/runner/runner.test.ts +358 -0
  179. package/src/registry-worker/runner/tokens.ts +93 -0
  180. package/src/registry-worker/runs.test.ts +60 -0
  181. package/src/registry-worker/signup/policy.ts +57 -0
  182. package/src/registry-worker/signup/routes.ts +106 -0
  183. package/src/registry-worker/signup/signup.test.ts +81 -0
  184. package/src/registry-worker/sso/aegis.ts +141 -0
  185. package/src/registry-worker/sso/membership.ts +157 -0
  186. package/src/registry-worker/sso/routes.ts +458 -0
  187. package/src/registry-worker/sso/sso.test.ts +344 -0
  188. package/src/registry-worker/testing/d1-shim.ts +180 -0
  189. package/src/registry-worker/testing/harness.ts +137 -0
  190. package/src/runner-worker/index.ts +108 -0
  191. package/src/runtime/ai-analysis.ts +97 -0
  192. package/src/runtime/ai-exploration.test.ts +32 -0
  193. package/src/runtime/ai-exploration.ts +69 -0
  194. package/src/runtime/ai-repair.ts +74 -0
  195. package/src/runtime/ai-work.test.ts +99 -0
  196. package/src/runtime/android-screen-record.test.ts +75 -0
  197. package/src/runtime/android-screen-record.ts +192 -0
  198. package/src/runtime/app-understanding.test.ts +123 -0
  199. package/src/runtime/app-understanding.ts +201 -0
  200. package/src/runtime/appium-driver.test.ts +179 -0
  201. package/src/runtime/appium-driver.ts +295 -0
  202. package/src/runtime/blocker-resolution.test.ts +113 -0
  203. package/src/runtime/blocker-resolution.ts +111 -0
  204. package/src/runtime/browser-matrix.integration.test.ts +212 -0
  205. package/src/runtime/browser-matrix.test.ts +143 -0
  206. package/src/runtime/browser-matrix.ts +200 -0
  207. package/src/runtime/canonical-flow.test.ts +52 -0
  208. package/src/runtime/config-validate.ts +185 -0
  209. package/src/runtime/continuous-execution.ts +291 -0
  210. package/src/runtime/cursor-applescript.ts +573 -0
  211. package/src/runtime/cursor-cli-driver.test.ts +78 -0
  212. package/src/runtime/cursor-cli-driver.ts +156 -0
  213. package/src/runtime/cursor-driver-example.ts +117 -0
  214. package/src/runtime/cursor-driver-index.ts +65 -0
  215. package/src/runtime/cursor-driver-init.ts +277 -0
  216. package/src/runtime/cursor-driver-run.test.ts +15 -0
  217. package/src/runtime/cursor-driver-run.ts +323 -0
  218. package/src/runtime/cursor-driver.ts +332 -0
  219. package/src/runtime/cursor-llm-example.ts +90 -0
  220. package/src/runtime/cursor-llm.ts +206 -0
  221. package/src/runtime/cursor-mcp-monitor.ts +386 -0
  222. package/src/runtime/device-clouds/browserstack.ts +73 -0
  223. package/src/runtime/device-clouds/device-farm.ts +52 -0
  224. package/src/runtime/device-clouds/index.ts +92 -0
  225. package/src/runtime/device-clouds/kobiton.ts +70 -0
  226. package/src/runtime/device-clouds/targets.ts +44 -0
  227. package/src/runtime/device-clouds/types.ts +62 -0
  228. package/src/runtime/diff-proposal.ts +84 -0
  229. package/src/runtime/discovery-task.test.ts +29 -0
  230. package/src/runtime/discovery-task.ts +284 -0
  231. package/src/runtime/driver-recovery.ts +69 -0
  232. package/src/runtime/driver.ts +79 -0
  233. package/src/runtime/environment.test.ts +104 -0
  234. package/src/runtime/environment.ts +137 -0
  235. package/src/runtime/executor.test.ts +509 -0
  236. package/src/runtime/executor.ts +921 -0
  237. package/src/runtime/explorer.test.ts +101 -0
  238. package/src/runtime/explorer.ts +1013 -0
  239. package/src/runtime/failure-analysis.test.ts +111 -0
  240. package/src/runtime/failure-analysis.ts +272 -0
  241. package/src/runtime/fixtures/fake-maestro.sh +36 -0
  242. package/src/runtime/flow-language.test.ts +268 -0
  243. package/src/runtime/flow-language.ts +414 -0
  244. package/src/runtime/init-wizard.ts +354 -0
  245. package/src/runtime/ios-screen-record.test.ts +68 -0
  246. package/src/runtime/ios-screen-record.ts +155 -0
  247. package/src/runtime/journey-editor.ts +452 -0
  248. package/src/runtime/journey-evidence.test.ts +161 -0
  249. package/src/runtime/journey-evidence.ts +180 -0
  250. package/src/runtime/journey-graph.test.ts +257 -0
  251. package/src/runtime/journey-graph.ts +170 -0
  252. package/src/runtime/legacy-names.ts +32 -0
  253. package/src/runtime/llm-example.ts +105 -0
  254. package/src/runtime/llm.ts +527 -0
  255. package/src/runtime/local-browser.test.ts +45 -0
  256. package/src/runtime/local-browser.ts +48 -0
  257. package/src/runtime/local-registry-stub.test.ts +325 -0
  258. package/src/runtime/local-registry-stub.ts +803 -0
  259. package/src/runtime/maestro-driver.test.ts +84 -0
  260. package/src/runtime/maestro-driver.ts +209 -0
  261. package/src/runtime/mole-voice.ts +21 -0
  262. package/src/runtime/nav-crawl.test.ts +100 -0
  263. package/src/runtime/nav-crawl.ts +153 -0
  264. package/src/runtime/pipeline.test.ts +405 -0
  265. package/src/runtime/pipeline.ts +833 -0
  266. package/src/runtime/planner.test.ts +37 -0
  267. package/src/runtime/planner.ts +274 -0
  268. package/src/runtime/platform-ai.ts +76 -0
  269. package/src/runtime/playwright-driver.test.ts +93 -0
  270. package/src/runtime/playwright-driver.ts +620 -0
  271. package/src/runtime/project-spec.ts +140 -0
  272. package/src/runtime/record-run-verdicts.ts +68 -0
  273. package/src/runtime/reporter.test.ts +56 -0
  274. package/src/runtime/reporter.ts +158 -0
  275. package/src/runtime/reset.test.ts +44 -0
  276. package/src/runtime/reset.ts +61 -0
  277. package/src/runtime/reviewer.test.ts +73 -0
  278. package/src/runtime/reviewer.ts +158 -0
  279. package/src/runtime/run-job.ts +136 -0
  280. package/src/runtime/run-once.test.ts +207 -0
  281. package/src/runtime/run-once.ts +168 -0
  282. package/src/runtime/run.ts +132 -0
  283. package/src/runtime/screen-recording.ts +34 -0
  284. package/src/runtime/serve-gateway.test.ts +74 -0
  285. package/src/runtime/serve-gateway.ts +164 -0
  286. package/src/runtime/serve-worker.test.ts +23 -0
  287. package/src/runtime/serve-worker.ts +278 -0
  288. package/src/runtime/site-discovery.test.ts +168 -0
  289. package/src/runtime/site-discovery.ts +308 -0
  290. package/src/runtime/target-runner.ts +144 -0
  291. package/src/runtime/test-case-verdicts.test.ts +94 -0
  292. package/src/runtime/test-case-verdicts.ts +120 -0
  293. package/src/runtime/ui-reverse-engineering/coordinator.test.ts +59 -0
  294. package/src/runtime/ui-reverse-engineering/coordinator.ts +391 -0
  295. package/src/runtime/ui-reverse-engineering/types.ts +197 -0
  296. package/src/runtime/web-suite.test.ts +97 -0
  297. package/src/runtime/web-suite.ts +176 -0
  298. package/src/storage/create-object-store.ts +144 -0
  299. package/src/storage/keys.ts +34 -0
  300. package/src/storage/local-artifact-server.test.ts +314 -0
  301. package/src/storage/local-artifact-server.ts +357 -0
  302. package/src/storage/object-store.test.ts +28 -0
  303. package/src/storage/object-store.ts +101 -0
  304. package/src/storage/registry-object-store.ts +88 -0
  305. package/src/storage/remote-object-store.ts +104 -0
  306. package/src/storage/storage-directory.test.ts +43 -0
  307. package/src/storage/storage-directory.ts +24 -0
  308. package/tsconfig.json +24 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Example: Using CURSOR_DRIVER as a simple LLM replacement
3
+ *
4
+ * This demonstrates the simplest way to use CURSOR_DRIVER - just like any LLM API.
5
+ */
6
+
7
+ import { cursorLLM, cursorLLMStructured } from "./cursor-llm.js";
8
+
9
+ /**
10
+ * Example 1: Simple text completion (like OpenAI API)
11
+ *
12
+ * Just pass a prompt, get back text. That's it.
13
+ */
14
+ export async function exampleSimple() {
15
+ const answer = await cursorLLM("Analyze the error handling patterns in this codebase");
16
+ console.log(answer);
17
+ return answer;
18
+ }
19
+
20
+ /**
21
+ * Example 2: Structured response with actions and artifacts
22
+ */
23
+ export async function exampleStructured() {
24
+ const response = await cursorLLMStructured(
25
+ "Review test failures and propose fixes. Focus on selector mismatches.",
26
+ );
27
+
28
+ if (response.success && response.payload) {
29
+ console.log("Status:", response.payload.status);
30
+ console.log("Summary:", response.payload.summary);
31
+ console.log("Actions:", response.payload.actions);
32
+ console.log("Artifacts:", response.payload.artifacts);
33
+ console.log("Full Answer:", response.payload.answer_markdown);
34
+ } else {
35
+ console.error("Error:", response.error);
36
+ }
37
+
38
+ return response;
39
+ }
40
+
41
+ /**
42
+ * Example 3: With custom options (optional)
43
+ */
44
+ export async function exampleWithOptions() {
45
+ const answer = await cursorLLM("Refactor the authentication module", {
46
+ timeout: 600000, // 10 minutes
47
+ requestId: `refactor_${Date.now()}`,
48
+ customInstructions: "Focus on security best practices",
49
+ });
50
+ return answer;
51
+ }
52
+
53
+ /**
54
+ * Example 4: Using in a workflow
55
+ */
56
+ export async function exampleWorkflow() {
57
+ // Step 1: Analyze
58
+ const analysis = await cursorLLM("Analyze the current test coverage");
59
+
60
+ // Step 2: Generate plan
61
+ const plan = await cursorLLMStructured(
62
+ "Based on this analysis, create a test plan:\n\n" + analysis,
63
+ );
64
+
65
+ // Step 3: Execute (if needed)
66
+ if (plan.success && plan.payload?.actions) {
67
+ console.log("Actions to take:", plan.payload.actions);
68
+ }
69
+
70
+ return { analysis, plan };
71
+ }
72
+
73
+ /**
74
+ * Example 5: Error handling
75
+ */
76
+ export async function exampleWithErrorHandling() {
77
+ try {
78
+ const answer = await cursorLLM("Your task here");
79
+ return answer;
80
+ } catch (error: any) {
81
+ if (error.message?.includes("MCP_UNAVAILABLE")) {
82
+ console.error("MCP server not available. Start it with: npm run bugmole:mcp");
83
+ } else if (error.message?.includes("Timeout")) {
84
+ console.error("Task timed out. Try increasing timeout or simplifying the task.");
85
+ } else {
86
+ console.error("Error:", error.message);
87
+ }
88
+ throw error;
89
+ }
90
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * CURSOR_DRIVER as a simple LLM API replacement
3
+ *
4
+ * This module provides a drop-in replacement for LLM API calls.
5
+ * Just pass a prompt, and it handles everything else using config.
6
+ *
7
+ * Usage:
8
+ * import { cursorLLM } from './cursor-llm.js';
9
+ * const response = await cursorLLM('Your prompt here');
10
+ */
11
+
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import dotenv from "dotenv";
16
+ import { preferExisting } from "./legacy-names.js";
17
+ import YAML from "yaml";
18
+ import { runCursorDriver } from "./cursor-driver-run.js";
19
+ import type { CursorDriverResponse, CursorDriverRunOptions } from "./cursor-driver.js";
20
+
21
+ // Cache config to avoid reloading
22
+ let cachedConfig: any = null;
23
+
24
+ /**
25
+ * Loads and caches the Bugmole MCP configuration
26
+ */
27
+ function loadConfig(configPath: string = preferExisting("bugmole.config.yaml", "valkyrie.config.yaml")): any {
28
+ if (cachedConfig) {
29
+ return cachedConfig;
30
+ }
31
+
32
+ const scriptPath = fileURLToPath(import.meta.url);
33
+ const scriptDir = path.dirname(scriptPath);
34
+ const projectRoot = path.resolve(scriptDir, "../..");
35
+
36
+ // Change to project root to ensure relative paths work
37
+ const originalCwd = process.cwd();
38
+ process.chdir(projectRoot);
39
+
40
+ try {
41
+ dotenv.config({ quiet: true });
42
+
43
+ const fullConfigPath = path.join(projectRoot, configPath);
44
+ if (!fs.existsSync(fullConfigPath)) {
45
+ throw new Error(`Config file not found: ${fullConfigPath}`);
46
+ }
47
+
48
+ const raw = fs.readFileSync(fullConfigPath, "utf-8");
49
+ const parsed = YAML.parse(raw);
50
+
51
+ // Interpolate environment variables
52
+ cachedConfig = interpolateEnv(parsed);
53
+ return cachedConfig;
54
+ } finally {
55
+ process.chdir(originalCwd);
56
+ }
57
+ }
58
+
59
+ // tiny ${ENV} interpolator for yaml configs
60
+ function interpolateEnv(obj: any): any {
61
+ if (typeof obj === "string") {
62
+ return obj.replace(/\$\{([A-Z0-9_]+)(?::-([^}]+))?\}/g, (_, k, defaultValue) => {
63
+ const envValue = process.env[k];
64
+ if (envValue !== undefined && envValue !== "") {
65
+ return envValue;
66
+ }
67
+ return defaultValue !== undefined ? defaultValue : "";
68
+ });
69
+ }
70
+ if (Array.isArray(obj)) return obj.map(interpolateEnv);
71
+ if (obj && typeof obj === "object") {
72
+ const out: any = {};
73
+ for (const [k, v] of Object.entries(obj)) out[k] = interpolateEnv(v);
74
+ return out;
75
+ }
76
+ return obj;
77
+ }
78
+
79
+ /**
80
+ * Options for cursorLLM (all optional, uses config defaults)
81
+ */
82
+ export interface CursorLLMOptions {
83
+ /**
84
+ * Custom config file path (default: bugmole.config.yaml)
85
+ */
86
+ configPath?: string;
87
+
88
+ /**
89
+ * Override MCP host (default: from config)
90
+ */
91
+ mcpHost?: string;
92
+
93
+ /**
94
+ * Override MCP port (default: from config)
95
+ */
96
+ mcpPort?: number;
97
+
98
+ /**
99
+ * Override artifacts directory (default: from config)
100
+ */
101
+ artifactsDir?: string;
102
+
103
+ /**
104
+ * Timeout in milliseconds (default: 300000 = 5 minutes)
105
+ */
106
+ timeout?: number;
107
+
108
+ /**
109
+ * Project directory to open in Cursor (default: from config)
110
+ */
111
+ projectDirectory?: string;
112
+
113
+ /**
114
+ * Request ID for tracking
115
+ */
116
+ requestId?: string;
117
+
118
+ /**
119
+ * Custom instructions to append before finalization protocol
120
+ */
121
+ customInstructions?: string;
122
+
123
+ /**
124
+ * Disable nudging (default: enabled)
125
+ */
126
+ disableNudging?: boolean;
127
+ }
128
+
129
+ /**
130
+ * Simple LLM-like interface - just pass a prompt
131
+ *
132
+ * @param prompt - The prompt/task to send to Cursor
133
+ * @param options - Optional overrides (uses config defaults)
134
+ * @returns The response text (markdown answer or summary)
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * import { cursorLLM } from './cursor-llm.js';
139
+ *
140
+ * const answer = await cursorLLM('Analyze the error handling in this codebase');
141
+ * console.log(answer);
142
+ * ```
143
+ */
144
+ export async function cursorLLM(prompt: string, options: CursorLLMOptions = {}): Promise<string> {
145
+ const cfg = loadConfig(options.configPath);
146
+
147
+ const response = await runCursorDriver(prompt, {
148
+ mcpHost: options.mcpHost || cfg.mcp?.host || "127.0.0.1",
149
+ mcpPort: options.mcpPort || cfg.mcp?.port || 3187,
150
+ artifactsDir: options.artifactsDir || cfg.runtime?.artifacts_dir || "./runs",
151
+ timeout: options.timeout || 300000,
152
+ projectDirectory: options.projectDirectory || cfg.cursor_driver?.project_directory,
153
+ requestId: options.requestId,
154
+ customInstructions: options.customInstructions,
155
+ nudgeOptions: options.disableNudging ? { enabled: false } : undefined,
156
+ });
157
+
158
+ if (!response.success || !response.payload) {
159
+ throw new Error(response.error || "Cursor driver failed to complete task");
160
+ }
161
+
162
+ return response.payload.answer_markdown || response.payload.summary;
163
+ }
164
+
165
+ /**
166
+ * LLM-like interface that returns structured response
167
+ *
168
+ * @param prompt - The prompt/task to send to Cursor
169
+ * @param options - Optional overrides (uses config defaults)
170
+ * @returns Full structured response with actions and artifacts
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * import { cursorLLMStructured } from './cursor-llm.js';
175
+ *
176
+ * const response = await cursorLLMStructured('Review test failures and propose fixes');
177
+ * if (response.success) {
178
+ * console.log('Actions:', response.payload?.actions);
179
+ * console.log('Answer:', response.payload?.answer_markdown);
180
+ * }
181
+ * ```
182
+ */
183
+ export async function cursorLLMStructured(
184
+ prompt: string,
185
+ options: CursorLLMOptions = {},
186
+ ): Promise<CursorDriverResponse> {
187
+ const cfg = loadConfig(options.configPath);
188
+
189
+ return runCursorDriver(prompt, {
190
+ mcpHost: options.mcpHost || cfg.mcp?.host || "127.0.0.1",
191
+ mcpPort: options.mcpPort || cfg.mcp?.port || 3187,
192
+ artifactsDir: options.artifactsDir || cfg.runtime?.artifacts_dir || "./runs",
193
+ timeout: options.timeout || 300000,
194
+ projectDirectory: options.projectDirectory || cfg.cursor_driver?.project_directory,
195
+ requestId: options.requestId,
196
+ customInstructions: options.customInstructions,
197
+ nudgeOptions: options.disableNudging ? { enabled: false } : undefined,
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Reset the config cache (useful for testing or when config changes)
203
+ */
204
+ export function resetConfigCache(): void {
205
+ cachedConfig = null;
206
+ }
@@ -0,0 +1,386 @@
1
+ /**
2
+ * MCP commit response monitor
3
+ *
4
+ * Polls the MCP server for commit responses from Cursor.
5
+ * This is how we receive structured output from Cursor tasks.
6
+ */
7
+
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import http from "node:http";
11
+
12
+ export interface CommitResponse {
13
+ id: string;
14
+ timestamp: string;
15
+ payload: {
16
+ status: "ok" | "error";
17
+ summary: string;
18
+ answer_markdown?: string;
19
+ actions?: Array<{
20
+ type: "edit" | "create" | "delete" | "command";
21
+ target: string;
22
+ details: string;
23
+ }>;
24
+ artifacts?: Array<{
25
+ type: "patch" | "config" | "command" | "note";
26
+ content: string;
27
+ }>;
28
+ next_steps?: string[];
29
+ };
30
+ }
31
+
32
+ export interface ProgressReport {
33
+ id: string;
34
+ timestamp: string;
35
+ status: "working" | "blocked" | "completed";
36
+ message: string;
37
+ details?: string;
38
+ estimated_time_remaining?: string;
39
+ }
40
+
41
+ export interface MonitorOptions {
42
+ /**
43
+ * MCP server host
44
+ * @default '127.0.0.1'
45
+ */
46
+ host?: string;
47
+
48
+ /**
49
+ * MCP server port
50
+ * @default 3187
51
+ */
52
+ port?: number;
53
+
54
+ /**
55
+ * Artifacts directory where commits are stored
56
+ */
57
+ artifactsDir: string;
58
+
59
+ /**
60
+ * Polling interval in ms
61
+ * @default 1000
62
+ */
63
+ pollInterval?: number;
64
+
65
+ /**
66
+ * Maximum wait time in ms
67
+ * @default 300000 (5 minutes)
68
+ */
69
+ timeout?: number;
70
+
71
+ /**
72
+ * Request ID to filter commits (optional)
73
+ */
74
+ requestId?: string;
75
+
76
+ /**
77
+ * Nudge configuration for inactivity detection
78
+ */
79
+ nudgeOptions?: {
80
+ enabled?: boolean;
81
+ /**
82
+ * Time without progress report before nudging (default: 300000 = 5 minutes)
83
+ */
84
+ progressReportTimeout?: number;
85
+ /**
86
+ * Minimum time between nudges (default: 120000 = 2 minutes)
87
+ */
88
+ nudgeInterval?: number;
89
+ maxNudges?: number;
90
+ customMessage?: string;
91
+ onNudge?: (nudgeCount: number) => Promise<void>;
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Reads a commit file from the artifacts directory
97
+ */
98
+ function readCommitFile(filePath: string): CommitResponse | null {
99
+ try {
100
+ if (!fs.existsSync(filePath)) {
101
+ return null;
102
+ }
103
+
104
+ const content = fs.readFileSync(filePath, "utf-8");
105
+ return JSON.parse(content) as CommitResponse;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Lists all commit files in the cursor_commits directory
113
+ */
114
+ function listCommitFiles(artifactsDir: string): string[] {
115
+ const commitDir = path.join(artifactsDir, "cursor_commits");
116
+
117
+ if (!fs.existsSync(commitDir)) {
118
+ return [];
119
+ }
120
+
121
+ try {
122
+ return fs
123
+ .readdirSync(commitDir)
124
+ .filter((f) => f.endsWith(".json"))
125
+ .map((f) => path.join(commitDir, f))
126
+ .sort((a, b) => {
127
+ // Sort by modification time (newest first)
128
+ const statA = fs.statSync(a);
129
+ const statB = fs.statSync(b);
130
+ return statB.mtimeMs - statA.mtimeMs;
131
+ });
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Lists all progress report files in the progress_reports directory
139
+ */
140
+ function listProgressReportFiles(artifactsDir: string): string[] {
141
+ const progressDir = path.join(artifactsDir, "progress_reports");
142
+
143
+ if (!fs.existsSync(progressDir)) {
144
+ return [];
145
+ }
146
+
147
+ try {
148
+ return fs
149
+ .readdirSync(progressDir)
150
+ .filter((f) => f.endsWith(".json"))
151
+ .map((f) => path.join(progressDir, f))
152
+ .sort((a, b) => {
153
+ // Sort by modification time (newest first)
154
+ const statA = fs.statSync(a);
155
+ const statB = fs.statSync(b);
156
+ return statB.mtimeMs - statA.mtimeMs;
157
+ });
158
+ } catch {
159
+ return [];
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Reads a progress report file
165
+ */
166
+ function readProgressReportFile(filePath: string): ProgressReport | null {
167
+ try {
168
+ if (!fs.existsSync(filePath)) {
169
+ return null;
170
+ }
171
+
172
+ const content = fs.readFileSync(filePath, "utf-8");
173
+ return JSON.parse(content) as ProgressReport;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Gets the most recent progress report after a given timestamp
181
+ */
182
+ function getLatestProgressReport(
183
+ artifactsDir: string,
184
+ afterTimestamp: number,
185
+ ): ProgressReport | null {
186
+ const progressFiles = listProgressReportFiles(artifactsDir);
187
+
188
+ for (const filePath of progressFiles) {
189
+ const report = readProgressReportFile(filePath);
190
+ if (!report) continue;
191
+
192
+ const reportTime = new Date(report.timestamp).getTime();
193
+ if (reportTime >= afterTimestamp) {
194
+ return report;
195
+ }
196
+ }
197
+
198
+ return null;
199
+ }
200
+
201
+ /**
202
+ * Checks MCP server health
203
+ */
204
+ async function checkMcpHealth(host: string, port: number): Promise<boolean> {
205
+ return new Promise((resolve) => {
206
+ const req = http.get(`http://${host}:${port}/health`, (res) => {
207
+ resolve(res.statusCode === 200);
208
+ });
209
+
210
+ req.on("error", () => resolve(false));
211
+ req.setTimeout(2000, () => {
212
+ req.destroy();
213
+ resolve(false);
214
+ });
215
+ });
216
+ }
217
+
218
+ /**
219
+ * Monitors for a commit response from Cursor
220
+ *
221
+ * Polls the artifacts directory for new commit files.
222
+ * Returns the first commit that appears after the start time.
223
+ * Includes nudge mechanism to detect and handle inactivity.
224
+ */
225
+ export async function waitForCommitResponse(options: MonitorOptions): Promise<CommitResponse> {
226
+ const {
227
+ host = "127.0.0.1",
228
+ port = 3187,
229
+ artifactsDir,
230
+ pollInterval = 1000,
231
+ timeout = 300000,
232
+ requestId,
233
+ nudgeOptions,
234
+ } = options;
235
+
236
+ const startTime = Date.now();
237
+ const seenCommitIds = new Set<string>();
238
+
239
+ // Nudge tracking - now based on progress reports
240
+ const nudgeEnabled = nudgeOptions?.enabled !== false;
241
+ const progressReportTimeout = nudgeOptions?.progressReportTimeout ?? 300000; // 5 minutes default - only nudge if no progress report for this long
242
+ const nudgeInterval = nudgeOptions?.nudgeInterval ?? 120000; // 2 minutes minimum between nudges
243
+ const maxNudges = nudgeOptions?.maxNudges ?? 10;
244
+ const customMessage = nudgeOptions?.customMessage;
245
+ const onNudge = nudgeOptions?.onNudge;
246
+
247
+ let lastProgressReportTime = startTime;
248
+ let lastNudgeTime = 0;
249
+ let nudgeCount = 0;
250
+
251
+ // First, verify MCP server is running
252
+ const isHealthy = await checkMcpHealth(host, port);
253
+ if (!isHealthy) {
254
+ throw new Error(`MCP server at ${host}:${port} is not responding`);
255
+ }
256
+
257
+ return new Promise((resolve, reject) => {
258
+ const poll = async () => {
259
+ const now = Date.now();
260
+
261
+ // Check timeout
262
+ if (now - startTime > timeout) {
263
+ reject(new Error(`Timeout waiting for commit response after ${timeout}ms`));
264
+ return;
265
+ }
266
+
267
+ // List commit files
268
+ const commitFiles = listCommitFiles(artifactsDir);
269
+
270
+ // Check each commit file
271
+ let foundNewCommit = false;
272
+ for (const filePath of commitFiles) {
273
+ const commit = readCommitFile(filePath);
274
+ if (!commit) continue;
275
+
276
+ // Skip if we've already seen this commit
277
+ if (seenCommitIds.has(commit.id)) {
278
+ continue;
279
+ }
280
+
281
+ // Check if commit is after start time (new commit)
282
+ const commitTime = new Date(commit.timestamp).getTime();
283
+ if (commitTime >= startTime) {
284
+ // If requestId is specified, check if it matches
285
+ // (This would require requestId to be embedded in the commit payload)
286
+ // For now, we just return the first new commit
287
+
288
+ seenCommitIds.add(commit.id);
289
+ foundNewCommit = true;
290
+ resolve(commit);
291
+ return;
292
+ }
293
+ }
294
+
295
+ // Check for progress reports (even if no commit yet)
296
+ const latestProgressReport = getLatestProgressReport(artifactsDir, startTime);
297
+ if (latestProgressReport) {
298
+ const reportTime = new Date(latestProgressReport.timestamp).getTime();
299
+ if (reportTime > lastProgressReportTime) {
300
+ lastProgressReportTime = reportTime;
301
+ console.log(
302
+ `[CURSOR_DRIVER] Progress report received: ${latestProgressReport.status} - ${latestProgressReport.message}`,
303
+ );
304
+ }
305
+ }
306
+
307
+ if (!foundNewCommit) {
308
+ // Add grace period before checking for MCP tool calls
309
+ // Planning models may take 10-30 seconds to think before calling tools
310
+ // Only start checking after initial grace period has passed
311
+ const gracePeriod = 15000; // 15 seconds grace period for planning models
312
+ const timeSinceStart = now - startTime;
313
+
314
+ if (timeSinceStart >= gracePeriod) {
315
+ // Periodically try to approve MCP tool dialogs that might be waiting
316
+ // Cursor sometimes shows confirmation dialogs for MCP tool execution
317
+ // But only after grace period to avoid interrupting model's thinking
318
+ try {
319
+ const { approveMcpTool } = await import("./cursor-applescript.js");
320
+ await approveMcpTool();
321
+ } catch (error: any) {
322
+ // Non-critical - approval might not be needed
323
+ }
324
+ }
325
+
326
+ // Check if we haven't received a progress report for too long
327
+ // Only nudge if no progress report has been received for the timeout period
328
+ const timeSinceLastProgressReport = now - lastProgressReportTime;
329
+ const timeSinceLastNudge = now - lastNudgeTime;
330
+
331
+ if (
332
+ nudgeEnabled &&
333
+ timeSinceLastProgressReport >= progressReportTimeout &&
334
+ timeSinceLastNudge >= nudgeInterval &&
335
+ nudgeCount < maxNudges
336
+ ) {
337
+ // Send nudge - agent hasn't reported progress for a while
338
+ nudgeCount++;
339
+ lastNudgeTime = now;
340
+
341
+ console.log(
342
+ `[CURSOR_DRIVER] Sending nudge #${nudgeCount} (no progress report for ${Math.round(
343
+ timeSinceLastProgressReport / 1000,
344
+ )}s)`,
345
+ );
346
+
347
+ try {
348
+ if (onNudge) {
349
+ await onNudge(nudgeCount);
350
+ } else {
351
+ // Default nudge implementation
352
+ const { sendNudge } = await import("./cursor-applescript.js");
353
+ const message =
354
+ customMessage ||
355
+ "Please continue working on the task. If you are still working, please call bugmole_report_progress to let me know. Remember to call bugmole_commit_response when you are fully finished.";
356
+ await sendNudge(message);
357
+ }
358
+ } catch (error: any) {
359
+ console.warn(`[CURSOR_DRIVER] Failed to send nudge: ${error.message}`);
360
+ }
361
+ }
362
+ }
363
+
364
+ // No new commit yet, poll again
365
+ setTimeout(poll, pollInterval);
366
+ };
367
+
368
+ // Start polling
369
+ poll();
370
+ });
371
+ }
372
+
373
+ /**
374
+ * Gets the most recent commit response
375
+ */
376
+ export function getLatestCommit(artifactsDir: string): CommitResponse | null {
377
+ const commitFiles = listCommitFiles(artifactsDir);
378
+
379
+ if (commitFiles.length === 0) {
380
+ return null;
381
+ }
382
+
383
+ // Get the most recent file
384
+ const latestFile = commitFiles[0];
385
+ return readCommitFile(latestFile);
386
+ }