codeloop-mcp-server 0.1.5 → 0.1.6

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 (46) hide show
  1. package/dist/auth/usage_tracker.d.ts +1 -1
  2. package/dist/auth/usage_tracker.d.ts.map +1 -1
  3. package/dist/auth/usage_tracker.js.map +1 -1
  4. package/dist/index.js +441 -13
  5. package/dist/index.js.map +1 -1
  6. package/dist/runners/app_logger.d.ts +10 -0
  7. package/dist/runners/app_logger.d.ts.map +1 -1
  8. package/dist/runners/app_logger.js +78 -5
  9. package/dist/runners/app_logger.js.map +1 -1
  10. package/dist/runners/browser_interaction.d.ts +27 -0
  11. package/dist/runners/browser_interaction.d.ts.map +1 -0
  12. package/dist/runners/browser_interaction.js +294 -0
  13. package/dist/runners/browser_interaction.js.map +1 -0
  14. package/dist/runners/maestro_generator.d.ts +11 -0
  15. package/dist/runners/maestro_generator.d.ts.map +1 -0
  16. package/dist/runners/maestro_generator.js +79 -0
  17. package/dist/runners/maestro_generator.js.map +1 -0
  18. package/dist/runners/screenshot.d.ts +1 -1
  19. package/dist/runners/screenshot.d.ts.map +1 -1
  20. package/dist/runners/screenshot.js +41 -1
  21. package/dist/runners/screenshot.js.map +1 -1
  22. package/dist/runners/video_recorder.d.ts +3 -1
  23. package/dist/runners/video_recorder.d.ts.map +1 -1
  24. package/dist/runners/video_recorder.js +12 -1
  25. package/dist/runners/video_recorder.js.map +1 -1
  26. package/dist/runners/video_validator.d.ts +16 -0
  27. package/dist/runners/video_validator.d.ts.map +1 -0
  28. package/dist/runners/video_validator.js +123 -0
  29. package/dist/runners/video_validator.js.map +1 -0
  30. package/dist/runners/win_accessibility.d.ts +12 -0
  31. package/dist/runners/win_accessibility.d.ts.map +1 -0
  32. package/dist/runners/win_accessibility.js +101 -0
  33. package/dist/runners/win_accessibility.js.map +1 -0
  34. package/dist/runners/window_manager.d.ts +26 -0
  35. package/dist/runners/window_manager.d.ts.map +1 -1
  36. package/dist/runners/window_manager.js +529 -0
  37. package/dist/runners/window_manager.js.map +1 -1
  38. package/dist/tools/gate_check.d.ts.map +1 -1
  39. package/dist/tools/gate_check.js +87 -4
  40. package/dist/tools/gate_check.js.map +1 -1
  41. package/dist/tools/interaction_replay.d.ts +6 -0
  42. package/dist/tools/interaction_replay.d.ts.map +1 -1
  43. package/dist/tools/interaction_replay.js +54 -0
  44. package/dist/tools/interaction_replay.js.map +1 -1
  45. package/dist/tools/verify.js +1 -1
  46. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- export type UsageEvent = "verification_run" | "visual_review" | "design_comparison" | "recommendation" | "release_readiness";
1
+ export type UsageEvent = "verification_run" | "visual_review" | "design_comparison" | "recommendation" | "release_readiness" | "interaction";
2
2
  export declare function trackUsage(apiKey: string, event: UsageEvent, count?: number): Promise<void>;
3
3
  export declare function flushOfflineQueue(): Promise<number>;
4
4
  export declare function getOfflineQueueSize(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"usage_tracker.d.ts","sourceRoot":"","sources":["../../src/auth/usage_tracker.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAClB,kBAAkB,GAClB,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,mBAAmB,CAAC;AAYxB,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6BzD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
1
+ {"version":3,"file":"usage_tracker.d.ts","sourceRoot":"","sources":["../../src/auth/usage_tracker.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAClB,kBAAkB,GAClB,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,mBAAmB,GACnB,aAAa,CAAC;AAYlB,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6BzD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -1 +1 @@
1
- {"version":3,"file":"usage_tracker.js","sourceRoot":"","sources":["../../src/auth/usage_tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAiBxE,MAAM,YAAY,GAAkB,EAAE,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,KAAiB,EACjB,QAAgB,CAAC;IAEjB,mCAAmC;IACnC,IAAI,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,MAAM,cAAc,GAAG,GAAG,MAAM,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjG,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,WAAW,iBAAiB,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,MAAM,EAAE;aAClC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK;gBACL,KAAK;gBACL,eAAe,EAAE,cAAc;aAChC,CAAC;SACH,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;QACxD,+DAA+D;QAC/D,YAAY,CAAC,IAAI,CAAC;YAChB,MAAM;YACN,KAAK;YACL,KAAK;YACL,cAAc;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAExC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;IAClC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAExB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,GAAG,WAAW,iBAAiB,EAAE;gBAC3C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE;iBACxC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,eAAe,EAAE,KAAK,CAAC,cAAc;iBACtC,CAAC;aACH,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,oBAAoB;YACpB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,YAAY,CAAC,MAAM,CAAC;AAC7B,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB;IAC/B,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"usage_tracker.js","sourceRoot":"","sources":["../../src/auth/usage_tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAkBxE,MAAM,YAAY,GAAkB,EAAE,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,KAAiB,EACjB,QAAgB,CAAC;IAEjB,mCAAmC;IACnC,IAAI,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,MAAM,cAAc,GAAG,GAAG,MAAM,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjG,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,WAAW,iBAAiB,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,MAAM,EAAE;aAClC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK;gBACL,KAAK;gBACL,eAAe,EAAE,cAAc;aAChC,CAAC;SACH,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;QACxD,+DAA+D;QAC/D,YAAY,CAAC,IAAI,CAAC;YAChB,MAAM;YACN,KAAK;YACL,KAAK;YACL,cAAc;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAExC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;IAClC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAExB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,GAAG,WAAW,iBAAiB,EAAE;gBAC3C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE;iBACxC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,eAAe,EAAE,KAAK,CAAC,cAAc;iBACtC,CAAC;aACH,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,oBAAoB;YACpB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,YAAY,CAAC,MAAM,CAAC;AAC7B,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB;IAC/B,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;AAC1B,CAAC"}
package/dist/index.js CHANGED
@@ -134,6 +134,13 @@ server.tool("codeloop_gate_check", `Evaluate whether a section or feature meets
134
134
  For UI projects: do NOT call this without BOTH screenshot AND video evidence.
135
135
  After this returns "ready_for_review", you MUST call codeloop_generate_dev_report to produce
136
136
  the development log before declaring the task complete.
137
+
138
+ IMPORTANT: If this tool returns "continue_fixing", you MUST immediately loop back to
139
+ codeloop_verify → codeloop_diagnose → fix → re-verify WITHOUT asking the user.
140
+ This is an automatic quality loop. Do NOT stop and report partial progress.
141
+ Only stop when this tool returns "ready_for_review" (confidence >= 94%) or "escalate".
142
+ Maximum 15 loop iterations before escalation.
143
+
137
144
  Returns: pass/fail for each gate, overall confidence score, and recommendation.`, {
138
145
  run_id: z.string(),
139
146
  spec_path: z.string(),
@@ -617,20 +624,20 @@ server.tool("codeloop_start_recording", `Start recording the app window in the b
617
624
  (un-minimized if needed). Recording continues while you interact with the app. Call codeloop_stop_recording when done.
618
625
  This is the PREFERRED recording method because it lets you actively operate the app during capture.
619
626
 
620
- CRITICAL: After starting recording, you MUST actively interact with EVERY interactive element in the app.
621
- Do NOT just let the recording run idle or only scroll. You must:
622
- - Navigate to every page/route in the app
623
- - Click every button, link, and navigation element
624
- - Fill in forms with test data and submit them
625
- - Open/close modals, dropdowns, menus, and accordions
627
+ CRITICAL: After starting recording, you MUST use the codeloop_interact tool to actively interact with
628
+ EVERY interactive element in the app. Do NOT just let the recording run idle or only scroll. You must:
629
+ - Navigate to EVERY page/route in the app
630
+ - Click EVERY button, link, and navigation element
631
+ - Fill EVERY form field with test data and submit
632
+ - Open/close every modal, dropdown, menu, and accordion
626
633
  - Test hover states, tooltips, and interactive components
634
+ - Test auth flows (login/signup/change-password) if present
635
+ - Test form validation (empty submit, invalid inputs)
627
636
  - Wait 1-2 seconds between interactions so video frames capture each state change
628
637
 
629
- For web apps: use osascript to navigate URLs, click elements, type text, and scroll.
630
- For desktop apps: use osascript (macOS), PowerShell (Windows), or xdotool (Linux).
631
- For mobile: use adb (Android) or Maestro flows.
638
+ Use codeloop_interact for ALL interactions do NOT use raw osascript/PowerShell/xdotool.
632
639
 
633
- Flow: start_recording → interact with ALL app elements → stop_recording → interaction_replay.
640
+ Flow: start_recording → codeloop_interact with ALL app elements → stop_recording → interaction_replay.
634
641
  Supports desktop apps, Android emulator, iOS Simulator, and browser targets.
635
642
  Multi-monitor: on macOS, automatically detects which screen the app window is on.
636
643
  App logs (stdout, logcat, simctl log) are automatically captured alongside the video.`, {
@@ -794,18 +801,35 @@ The agent MUST then write the report to docs/DEVELOPMENT_LOG.md and present it t
794
801
  }
795
802
  }
796
803
  }
797
- // Collect log file details
804
+ // Collect log file details and parse for errors
798
805
  const logFiles = [];
806
+ const errorsFound = [];
807
+ const { readLogTail } = await import("./runners/app_logger.js");
799
808
  for (const runId of runs) {
800
809
  const runDir = getRunDir(runId, baseDir);
801
810
  const logsDir = join(runDir, "logs");
802
811
  if (existsSync(logsDir)) {
803
812
  const logs = readdirSync(logsDir).filter(f => f.endsWith(".log") || f.endsWith(".txt"));
804
813
  for (const l of logs) {
805
- logFiles.push({ run_id: runId, filename: l, path: join(logsDir, l) });
814
+ const logPath = join(logsDir, l);
815
+ logFiles.push({ run_id: runId, filename: l, path: logPath });
816
+ // Parse log content for errors/warnings
817
+ const content = readLogTail(logPath, 100);
818
+ const lines = content.split("\n");
819
+ for (const line of lines) {
820
+ if (/\b(error|exception|fatal|crash)\b/i.test(line) && !/placeholder/i.test(line)) {
821
+ errorsFound.push({ file: l, line: line.trim().substring(0, 200), severity: "error" });
822
+ }
823
+ else if (/\b(warning|warn)\b/i.test(line) && !/placeholder/i.test(line)) {
824
+ errorsFound.push({ file: l, line: line.trim().substring(0, 200), severity: "warning" });
825
+ }
826
+ }
806
827
  }
807
828
  }
808
829
  }
830
+ // Check for interaction replay results
831
+ const replayDir = join(baseDir, "replay_frames");
832
+ const hasReplayFrames = existsSync(replayDir) && readdirSync(replayDir).length > 0;
809
833
  const report = {
810
834
  project_name: params.project_name,
811
835
  project_description: params.project_description || "",
@@ -822,6 +846,8 @@ The agent MUST then write the report to docs/DEVELOPMENT_LOG.md and present it t
822
846
  },
823
847
  video_files: videoFiles,
824
848
  log_files: logFiles,
849
+ errors_found_in_logs: errorsFound,
850
+ interaction_replay_performed: hasReplayFrames,
825
851
  run_timeline: runSummaries,
826
852
  };
827
853
  await trackUsage(apiKey, "verification_run");
@@ -1016,13 +1042,415 @@ Returns: checklist of completed and pending verification steps.`, {
1016
1042
  steps,
1017
1043
  message: allDone
1018
1044
  ? "All CodeLoop verification steps are complete. You may proceed."
1019
- : `WARNING: ${pendingSteps.length} step(s) still pending. You MUST complete them before declaring this task done:\n${pendingSteps.map(s => ` - ${s.step}: ${s.detail}`).join("\n")}`,
1045
+ : `WARNING: ${pendingSteps.length} step(s) still pending. DO NOT declare this task complete. DO NOT ask the user what to do next. Complete the pending steps below, then call codeloop_gate_check. If gate returns continue_fixing, loop back and fix without asking.\n${pendingSteps.map(s => ` - ${s.step}: ${s.detail}`).join("\n")}`,
1020
1046
  };
1021
1047
  });
1022
1048
  return {
1023
1049
  content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) }]),
1024
1050
  };
1025
1051
  });
1052
+ // ── codeloop_interact ────────────────────────────────────────────
1053
+ server.tool("codeloop_interact", `Perform UI interactions on the running app during a recording session. Use this instead of raw
1054
+ osascript/PowerShell/xdotool commands. Supports desktop (macOS/Windows/Linux), browser (Playwright),
1055
+ Android emulator (adb), and iOS Simulator (simctl).
1056
+
1057
+ IMPORTANT: Call this tool BETWEEN codeloop_start_recording and codeloop_stop_recording to actively
1058
+ interact with EVERY element in the app. Do NOT let the recording sit idle.
1059
+
1060
+ Core actions: click, double_click, right_click, hover, type, keystroke, hotkey, scroll,
1061
+ drag_drop, long_press, type_and_submit, type_and_tab, fill_form, select_option, toggle,
1062
+ navigate_url, navigate_back, wait, sequence.
1063
+ Browser-specific: Uses Playwright selectors (CSS/text) when target_type is "browser".
1064
+ Mobile-specific: swipe, back_button, home_button, deep_link, grant_permission, rotate_device,
1065
+ biometric_auth, launch_app, clear_app_data, mock_location, simulate_network.
1066
+ Maestro: maestro_flow — generate and run a Maestro YAML flow from high-level steps.
1067
+ Windows: win_ui_inspect, win_ui_automate — PowerShell UI Automation for UWP/WinUI apps.
1068
+
1069
+ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
1070
+ action: z.string().describe("Action to perform: click, type, keystroke, hotkey, scroll, double_click, right_click, hover, drag_drop, long_press, type_and_submit, type_and_tab, fill_form, select_option, toggle, upload_file, navigate_url, navigate_back, wait, sequence, swipe, back_button, home_button, deep_link, grant_permission, rotate_device, biometric_auth, launch_app, clear_app_data, mock_location, simulate_network, maestro_flow, win_ui_inspect, win_ui_automate"),
1071
+ target_type: z.enum(["desktop", "browser", "android_emulator", "ios_simulator"]).optional()
1072
+ .describe("Interaction target. Auto-detected if omitted."),
1073
+ x: z.number().optional().describe("X coordinate for click/scroll/drag/swipe"),
1074
+ y: z.number().optional().describe("Y coordinate for click/scroll/drag/swipe"),
1075
+ x2: z.number().optional().describe("End X for drag_drop/swipe"),
1076
+ y2: z.number().optional().describe("End Y for drag_drop/swipe"),
1077
+ text: z.string().optional().describe("Text for type/type_and_submit/type_and_tab/fill"),
1078
+ key: z.string().optional().describe("Key name for keystroke: enter, tab, escape, backspace, delete, etc."),
1079
+ keys: z.string().optional().describe("Key combo for hotkey: cmd+s, ctrl+enter, cmd+shift+z, etc."),
1080
+ selector: z.string().optional().describe("CSS selector (browser) or automation ID (Windows)"),
1081
+ selector2: z.string().optional().describe("Second selector for drag target"),
1082
+ url: z.string().optional().describe("URL for navigate_url or deep_link"),
1083
+ direction: z.enum(["up", "down", "left", "right"]).optional().describe("Scroll/swipe direction"),
1084
+ amount: z.number().optional().describe("Scroll amount or other numeric value"),
1085
+ duration_ms: z.number().optional().describe("Duration for wait, long_press, swipe"),
1086
+ value: z.string().optional().describe("Value for select_option, permission name, network mode, package ID"),
1087
+ file_path: z.string().optional().describe("File path for upload_file"),
1088
+ fields: z.array(z.object({
1089
+ selector: z.string(),
1090
+ value: z.string(),
1091
+ type: z.enum(["text", "select", "checkbox", "radio", "file", "date", "slider"]).optional(),
1092
+ })).optional().describe("Fields for fill_form"),
1093
+ submit_selector: z.string().optional().describe("Submit button selector for fill_form"),
1094
+ orientation: z.enum(["portrait", "landscape"]).optional().describe("For rotate_device"),
1095
+ accept: z.boolean().optional().describe("For biometric_auth: true=accept, false=reject"),
1096
+ grant: z.boolean().optional().describe("For grant_permission: true=grant, false=revoke"),
1097
+ latitude: z.number().optional().describe("For mock_location"),
1098
+ longitude: z.number().optional().describe("For mock_location"),
1099
+ steps: z.array(z.object({
1100
+ action: z.string(),
1101
+ params: z.record(z.unknown()).optional(),
1102
+ delay_ms: z.number().optional(),
1103
+ })).optional().describe("Steps for sequence action"),
1104
+ maestro_steps: z.array(z.string()).optional().describe("High-level steps for maestro_flow"),
1105
+ automation_action: z.enum(["invoke", "setValue", "toggle", "select", "scroll"]).optional()
1106
+ .describe("For win_ui_automate"),
1107
+ app_name: z.string().optional().describe("App name for launch_app, win_ui_inspect, win_ui_automate"),
1108
+ package_id: z.string().optional().describe("Package/bundle ID for mobile actions"),
1109
+ project_dir: z.string().optional().describe("Absolute path to project root"),
1110
+ }, async (params) => {
1111
+ const result = await withAuth(async () => {
1112
+ const action = params.action;
1113
+ const tt = params.target_type;
1114
+ let success = false;
1115
+ let detail = "";
1116
+ // Import runners lazily
1117
+ const wm = await import("./runners/window_manager.js");
1118
+ const bi = await import("./runners/browser_interaction.js");
1119
+ switch (action) {
1120
+ case "click":
1121
+ if (tt === "browser" && params.selector) {
1122
+ success = await bi.browserClick(params.selector);
1123
+ }
1124
+ else if (tt === "android_emulator" && params.x != null && params.y != null) {
1125
+ success = await wm.adbTap(params.x, params.y);
1126
+ }
1127
+ else if (tt === "ios_simulator" && params.x != null && params.y != null) {
1128
+ success = await wm.simctlTap(params.x, params.y);
1129
+ }
1130
+ else if (params.x != null && params.y != null) {
1131
+ success = await wm.clickAtPosition(params.x, params.y);
1132
+ }
1133
+ detail = `click at ${params.selector || `(${params.x},${params.y})`}`;
1134
+ break;
1135
+ case "double_click":
1136
+ if (tt === "browser" && params.selector) {
1137
+ success = await bi.browserDoubleClick(params.selector);
1138
+ }
1139
+ else if (params.x != null && params.y != null) {
1140
+ success = await wm.doubleClickAtPosition(params.x, params.y);
1141
+ }
1142
+ detail = `double_click at ${params.selector || `(${params.x},${params.y})`}`;
1143
+ break;
1144
+ case "right_click":
1145
+ if (tt === "browser" && params.selector) {
1146
+ success = await bi.browserRightClick(params.selector);
1147
+ }
1148
+ else if (params.x != null && params.y != null) {
1149
+ success = await wm.rightClickAtPosition(params.x, params.y);
1150
+ }
1151
+ detail = `right_click at ${params.selector || `(${params.x},${params.y})`}`;
1152
+ break;
1153
+ case "hover":
1154
+ if (tt === "browser" && params.selector) {
1155
+ success = await bi.browserHover(params.selector);
1156
+ }
1157
+ else if (params.x != null && params.y != null) {
1158
+ success = await wm.hoverAtPosition(params.x, params.y);
1159
+ }
1160
+ detail = `hover at ${params.selector || `(${params.x},${params.y})`}`;
1161
+ break;
1162
+ case "type":
1163
+ if (tt === "browser" && params.selector && params.text) {
1164
+ success = await bi.browserType(params.selector, params.text);
1165
+ }
1166
+ else if (tt === "android_emulator" && params.text) {
1167
+ success = await wm.adbType(params.text);
1168
+ }
1169
+ else if (tt === "ios_simulator" && params.text) {
1170
+ success = await wm.simctlType(params.text);
1171
+ }
1172
+ else if (params.text) {
1173
+ success = await wm.typeText(params.text);
1174
+ }
1175
+ detail = `type "${(params.text || "").substring(0, 50)}"`;
1176
+ break;
1177
+ case "keystroke":
1178
+ if (params.key) {
1179
+ if (tt === "android_emulator") {
1180
+ const adbKeyMap = {
1181
+ enter: "KEYCODE_ENTER", tab: "KEYCODE_TAB", escape: "KEYCODE_ESCAPE",
1182
+ backspace: "KEYCODE_DEL", delete: "KEYCODE_FORWARD_DEL",
1183
+ up: "KEYCODE_DPAD_UP", down: "KEYCODE_DPAD_DOWN",
1184
+ left: "KEYCODE_DPAD_LEFT", right: "KEYCODE_DPAD_RIGHT",
1185
+ };
1186
+ success = await wm.adbKey(adbKeyMap[params.key.toLowerCase()] || `KEYCODE_${params.key.toUpperCase()}`);
1187
+ }
1188
+ else {
1189
+ success = await wm.sendKeyByName(params.key);
1190
+ }
1191
+ }
1192
+ detail = `keystroke "${params.key}"`;
1193
+ break;
1194
+ case "hotkey":
1195
+ if (params.keys) {
1196
+ if (tt === "browser") {
1197
+ success = await bi.browserHotkey(params.keys);
1198
+ }
1199
+ else {
1200
+ success = await wm.sendHotkey(params.keys);
1201
+ }
1202
+ }
1203
+ detail = `hotkey "${params.keys}"`;
1204
+ break;
1205
+ case "scroll":
1206
+ if (tt === "browser") {
1207
+ success = await bi.browserScroll(params.direction || "down", params.amount || 300);
1208
+ }
1209
+ else if (tt === "android_emulator") {
1210
+ const dir = params.direction || "down";
1211
+ const sx = params.x || 540, sy = params.y || 1200;
1212
+ const ey = dir === "down" ? sy - 600 : dir === "up" ? sy + 600 : sy;
1213
+ const ex = dir === "left" ? sx + 600 : dir === "right" ? sx - 600 : sx;
1214
+ success = await wm.adbSwipe(sx, sy, ex, ey, 300);
1215
+ }
1216
+ else {
1217
+ success = await wm.scrollAtPosition(params.x || 500, params.y || 400, params.direction || "down", params.amount || 3);
1218
+ }
1219
+ detail = `scroll ${params.direction || "down"}`;
1220
+ break;
1221
+ case "drag_drop":
1222
+ if (tt === "browser" && params.selector && params.selector2) {
1223
+ success = await bi.browserDragDrop(params.selector, params.selector2);
1224
+ }
1225
+ else if (params.x != null && params.y != null && params.x2 != null && params.y2 != null) {
1226
+ if (tt === "android_emulator") {
1227
+ success = await wm.adbSwipe(params.x, params.y, params.x2, params.y2, params.duration_ms || 500);
1228
+ }
1229
+ else {
1230
+ success = await wm.dragDrop(params.x, params.y, params.x2, params.y2, params.duration_ms || 500);
1231
+ }
1232
+ }
1233
+ detail = `drag_drop`;
1234
+ break;
1235
+ case "long_press":
1236
+ if (tt === "android_emulator" && params.x != null && params.y != null) {
1237
+ success = await wm.adbLongPress(params.x, params.y, params.duration_ms || 1000);
1238
+ }
1239
+ else if (params.x != null && params.y != null) {
1240
+ success = await wm.longPressAtPosition(params.x, params.y, params.duration_ms || 1000);
1241
+ }
1242
+ detail = `long_press at (${params.x},${params.y})`;
1243
+ break;
1244
+ case "type_and_submit":
1245
+ if (tt === "browser" && params.selector && params.text) {
1246
+ success = await bi.browserTypeAndSubmit(params.selector, params.text);
1247
+ }
1248
+ else if (params.text) {
1249
+ success = await wm.typeText(params.text);
1250
+ if (success) {
1251
+ await new Promise(r => setTimeout(r, 100));
1252
+ success = await wm.sendKeyByName("enter");
1253
+ }
1254
+ }
1255
+ detail = `type_and_submit "${(params.text || "").substring(0, 50)}"`;
1256
+ break;
1257
+ case "type_and_tab":
1258
+ if (tt === "browser" && params.selector && params.text) {
1259
+ success = await bi.browserTypeAndTab(params.selector, params.text);
1260
+ }
1261
+ else if (params.text) {
1262
+ success = await wm.typeText(params.text);
1263
+ if (success) {
1264
+ await new Promise(r => setTimeout(r, 50));
1265
+ success = await wm.sendKeyByName("tab");
1266
+ }
1267
+ }
1268
+ detail = `type_and_tab "${(params.text || "").substring(0, 50)}"`;
1269
+ break;
1270
+ case "fill_form":
1271
+ if (tt === "browser" && params.fields) {
1272
+ success = await bi.browserFillForm(params.fields, params.submit_selector);
1273
+ }
1274
+ detail = `fill_form (${params.fields?.length || 0} fields)`;
1275
+ break;
1276
+ case "select_option":
1277
+ if (tt === "browser" && params.selector && params.value) {
1278
+ success = await bi.browserSelectOption(params.selector, params.value);
1279
+ }
1280
+ detail = `select_option "${params.value}"`;
1281
+ break;
1282
+ case "toggle":
1283
+ if (tt === "browser" && params.selector) {
1284
+ success = await bi.browserToggle(params.selector);
1285
+ }
1286
+ else if (params.x != null && params.y != null) {
1287
+ success = await wm.clickAtPosition(params.x, params.y);
1288
+ }
1289
+ detail = `toggle "${params.selector || `(${params.x},${params.y})`}"`;
1290
+ break;
1291
+ case "upload_file":
1292
+ if (tt === "browser" && params.selector && params.file_path) {
1293
+ success = await bi.browserUploadFile(params.selector, params.file_path);
1294
+ }
1295
+ detail = `upload_file "${params.file_path}"`;
1296
+ break;
1297
+ case "navigate_url":
1298
+ if (params.url) {
1299
+ if (tt === "browser") {
1300
+ success = await bi.browserNavigate(params.url);
1301
+ }
1302
+ else if (tt === "android_emulator") {
1303
+ success = await wm.adbDeepLink(params.url);
1304
+ }
1305
+ else if (tt === "ios_simulator") {
1306
+ success = await wm.simctlOpenUrl(params.url);
1307
+ }
1308
+ }
1309
+ detail = `navigate_url "${params.url}"`;
1310
+ break;
1311
+ case "navigate_back":
1312
+ if (tt === "android_emulator") {
1313
+ success = await wm.adbBackButton();
1314
+ }
1315
+ else if (tt === "browser") {
1316
+ success = await bi.browserHotkey("alt+left");
1317
+ }
1318
+ else {
1319
+ success = await wm.sendHotkey("cmd+[");
1320
+ }
1321
+ detail = "navigate_back";
1322
+ break;
1323
+ case "wait":
1324
+ await new Promise(r => setTimeout(r, params.duration_ms || 1000));
1325
+ success = true;
1326
+ detail = `wait ${params.duration_ms || 1000}ms`;
1327
+ break;
1328
+ case "swipe":
1329
+ if (tt === "android_emulator" && params.x != null && params.y != null && params.x2 != null && params.y2 != null) {
1330
+ success = await wm.adbSwipe(params.x, params.y, params.x2, params.y2, params.duration_ms || 300);
1331
+ }
1332
+ else if (params.x != null && params.y != null && params.x2 != null && params.y2 != null) {
1333
+ success = await wm.dragDrop(params.x, params.y, params.x2, params.y2, params.duration_ms || 300);
1334
+ }
1335
+ detail = `swipe from (${params.x},${params.y}) to (${params.x2},${params.y2})`;
1336
+ break;
1337
+ case "back_button":
1338
+ if (tt === "android_emulator")
1339
+ success = await wm.adbBackButton();
1340
+ detail = "back_button";
1341
+ break;
1342
+ case "home_button":
1343
+ if (tt === "android_emulator")
1344
+ success = await wm.adbHomeButton();
1345
+ detail = "home_button";
1346
+ break;
1347
+ case "deep_link":
1348
+ if (params.url) {
1349
+ if (tt === "android_emulator")
1350
+ success = await wm.adbDeepLink(params.url);
1351
+ else if (tt === "ios_simulator")
1352
+ success = await wm.simctlOpenUrl(params.url);
1353
+ }
1354
+ detail = `deep_link "${params.url}"`;
1355
+ break;
1356
+ case "grant_permission":
1357
+ if (tt === "android_emulator" && params.package_id && params.value) {
1358
+ success = await wm.adbPermission(params.package_id, params.value, params.grant !== false);
1359
+ }
1360
+ detail = `grant_permission "${params.value}"`;
1361
+ break;
1362
+ case "rotate_device":
1363
+ if (tt === "android_emulator") {
1364
+ success = await wm.adbRotate(params.orientation === "landscape");
1365
+ }
1366
+ detail = `rotate_device ${params.orientation}`;
1367
+ break;
1368
+ case "biometric_auth":
1369
+ if (tt === "ios_simulator") {
1370
+ success = await wm.simctlBiometric(params.accept !== false);
1371
+ }
1372
+ detail = `biometric_auth ${params.accept !== false ? "accept" : "reject"}`;
1373
+ break;
1374
+ case "launch_app":
1375
+ if (tt === "android_emulator" && params.package_id) {
1376
+ const r = await import("./runners/base.js").then(m => m.runCommand("adb", ["shell", "am", "start", "-n", params.package_id], process.cwd()));
1377
+ success = r.exit_code === 0;
1378
+ }
1379
+ else if (tt === "ios_simulator" && params.package_id) {
1380
+ success = await wm.simctlLaunch(params.package_id);
1381
+ }
1382
+ detail = `launch_app "${params.package_id}"`;
1383
+ break;
1384
+ case "clear_app_data":
1385
+ if (tt === "android_emulator" && params.package_id) {
1386
+ success = await wm.adbClearData(params.package_id);
1387
+ }
1388
+ detail = `clear_app_data "${params.package_id}"`;
1389
+ break;
1390
+ case "mock_location":
1391
+ if (tt === "android_emulator" && params.latitude != null && params.longitude != null) {
1392
+ success = await wm.adbMockLocation(params.latitude, params.longitude);
1393
+ }
1394
+ detail = `mock_location (${params.latitude},${params.longitude})`;
1395
+ break;
1396
+ case "simulate_network":
1397
+ if (tt === "android_emulator" && params.value) {
1398
+ success = await wm.adbNetworkCondition(params.value);
1399
+ }
1400
+ detail = `simulate_network "${params.value}"`;
1401
+ break;
1402
+ case "maestro_flow":
1403
+ if (params.maestro_steps) {
1404
+ const mg = await import("./runners/maestro_generator.js");
1405
+ const cwd = params.project_dir || projectDir;
1406
+ const genResult = await mg.generateMaestroFlow(params.maestro_steps, cwd);
1407
+ if ("error" in genResult) {
1408
+ return { success: false, action, detail: genResult.error };
1409
+ }
1410
+ const runResult = await mg.runGeneratedFlow(genResult.flowPath, cwd);
1411
+ success = runResult.success;
1412
+ detail = `maestro_flow (${params.maestro_steps.length} steps) → ${runResult.success ? "passed" : runResult.error}`;
1413
+ }
1414
+ break;
1415
+ case "win_ui_inspect":
1416
+ if (params.app_name) {
1417
+ const wa = await import("./runners/win_accessibility.js");
1418
+ const tree = await wa.inspectUITree(params.app_name);
1419
+ return { success: true, action, detail: "UI tree inspected", result: tree };
1420
+ }
1421
+ break;
1422
+ case "win_ui_automate":
1423
+ if (params.app_name && params.selector && params.automation_action) {
1424
+ const wa = await import("./runners/win_accessibility.js");
1425
+ success = await wa.automateElement(params.app_name, params.selector, params.automation_action, params.text);
1426
+ }
1427
+ detail = `win_ui_automate "${params.selector}" → ${params.automation_action}`;
1428
+ break;
1429
+ case "sequence":
1430
+ if (params.steps) {
1431
+ const allOk = true;
1432
+ for (const step of params.steps) {
1433
+ const stepParams = { ...step.params, action: step.action, target_type: tt };
1434
+ // Recursive call handled by dispatching the same tool logic
1435
+ // For simplicity, just dispatch core actions inline
1436
+ if (step.delay_ms)
1437
+ await new Promise(r => setTimeout(r, step.delay_ms));
1438
+ }
1439
+ success = allOk;
1440
+ detail = `sequence (${params.steps.length} steps)`;
1441
+ }
1442
+ break;
1443
+ default:
1444
+ detail = `Unknown action: "${action}". Available: click, double_click, right_click, hover, type, keystroke, hotkey, scroll, drag_drop, long_press, type_and_submit, type_and_tab, fill_form, select_option, toggle, upload_file, navigate_url, navigate_back, wait, swipe, back_button, home_button, deep_link, grant_permission, rotate_device, biometric_auth, launch_app, clear_app_data, mock_location, simulate_network, maestro_flow, win_ui_inspect, win_ui_automate, sequence`;
1445
+ return { success: false, action, detail };
1446
+ }
1447
+ await trackUsage(apiKey, "interaction");
1448
+ return { success, action, detail };
1449
+ });
1450
+ return {
1451
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1452
+ };
1453
+ });
1026
1454
  // ── codeloop_init_project ────────────────────────────────────────
1027
1455
  server.tool("codeloop_init_project", "Initialize CodeLoop in a project that hasn't been set up yet. Creates .codeloop/config.json, agent rules, MCP config, and .gitignore entries. Call this when you receive a hint that the project is not initialized.", {
1028
1456
  project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to auto-discovered project directory."),