@skyramp/mcp 0.2.1 → 0.2.3

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.
@@ -29,7 +29,9 @@
29
29
  */
30
30
  import * as fs from "fs";
31
31
  import * as path from "path";
32
+ import { extractDartRoutes } from "./dartRouteExtractor.js";
32
33
  import { extractSourceRoutes } from "./sourceRouteExtractor.js";
34
+ import { hasFlutterSdkDep } from "../prompts/test-recommendation/scopeAssessment.js";
33
35
  import { readWorkspaceConfigRaw } from "./workspaceAuth.js";
34
36
  // ── Strategy 1: framework route grep ────────────────────────────────────────
35
37
  /**
@@ -228,6 +230,68 @@ export function findCandidatePagesBySourceRoute(frontendFiles, baseUrl, reposito
228
230
  }
229
231
  return Array.from(byUrl.values());
230
232
  }
233
+ // ── Strategy 2.5: Dart GoRouter routes ─────────────────────────────────────
234
+ /**
235
+ * Strategy 2.5: walk the repo's Dart files for GoRoute(path: '...') calls.
236
+ * Gated on `hasFlutterSdkDep(repoPath)` so non-Flutter Dart projects (e.g.
237
+ * shelf_router servers) don't accidentally emit URLs.
238
+ *
239
+ * Diff-matching: each GoRoute references its screen widget via
240
+ * `builder:`/`pageBuilder:` (e.g. `AuthorsScreen`). The extractor resolves
241
+ * those identifiers through the route file's imports to absolute screen
242
+ * file paths. We surface only routes whose screen file appears in the
243
+ * changed-file list. Falls back to all-routes when no route's screen
244
+ * file matches — preserves coverage for "router untouched, screen edited"
245
+ * cases when the import-resolution heuristic doesn't find a match.
246
+ *
247
+ * Filters applied:
248
+ * 1. Drop redirect-only routes (`redirect:` with no `builder:`/`pageBuilder:`).
249
+ * These have no UI to render — navigating there is wasted work.
250
+ * 2. Drop nested relative children (paths that don't start with `/`).
251
+ * Without parent-path composition the URL is meaningless.
252
+ *
253
+ * Returns empty when the repo isn't Flutter or no GoRoute calls are
254
+ * found — caller falls through to root-fallback.
255
+ */
256
+ export function findCandidatePagesByDartRoute(repositoryPath, baseUrl, frontendFiles) {
257
+ if (!hasFlutterSdkDep(repositoryPath))
258
+ return [];
259
+ const dartRoutes = extractDartRoutes(repositoryPath);
260
+ if (dartRoutes.length === 0)
261
+ return [];
262
+ // Strip redirect-only and relative routes — they're never valid candidates.
263
+ const renderable = dartRoutes.filter((r) => !r.isRedirectOnly && r.path.startsWith("/"));
264
+ if (renderable.length === 0)
265
+ return [];
266
+ // Diff-match each route against frontendFiles via its screenFiles.
267
+ const changedAbs = new Set(frontendFiles.map((f) => path.resolve(repositoryPath, f)));
268
+ const matched = renderable.filter((r) => r.screenFiles.some((sf) => changedAbs.has(sf)));
269
+ // Fall back to all renderable routes when nothing diff-matches. The
270
+ // import-resolution heuristic isn't perfect (custom widget naming, multiple
271
+ // screens per file, factory functions); a zero-match should not collapse
272
+ // the candidate list to empty when the repo clearly has UI changes.
273
+ const surfaced = matched.length > 0 ? matched : renderable;
274
+ const normalizedBase = baseUrl.replace(/\/$/, "");
275
+ const byUrl = new Map();
276
+ for (const route of surfaced) {
277
+ const fullUrl = normalizedBase + route.path;
278
+ const sourceFile = path.relative(repositoryPath, route.declaredIn);
279
+ const existing = byUrl.get(fullUrl);
280
+ if (existing) {
281
+ if (!existing.sourcedFrom.includes(sourceFile)) {
282
+ existing.sourcedFrom.push(sourceFile);
283
+ }
284
+ }
285
+ else {
286
+ byUrl.set(fullUrl, {
287
+ url: fullUrl,
288
+ sourcedFrom: [sourceFile],
289
+ strategy: "dart-go-router",
290
+ });
291
+ }
292
+ }
293
+ return Array.from(byUrl.values());
294
+ }
231
295
  // ── Strategy 3: root fallback ───────────────────────────────────────────────
232
296
  /**
233
297
  * Strategy 3: when strategies 1+2 yield nothing, fall back to the workspace's
@@ -314,6 +378,9 @@ export async function enumerateCandidateUiPages(repositoryPath, frontendFiles) {
314
378
  const fromSource = findCandidatePagesBySourceRoute(frontendFiles, baseUrl, repositoryPath);
315
379
  if (fromSource.length > 0)
316
380
  return fromSource;
381
+ const fromDart = findCandidatePagesByDartRoute(repositoryPath, baseUrl, frontendFiles);
382
+ if (fromDart.length > 0)
383
+ return fromDart;
317
384
  const fallback = await findRootFallbackPage(repositoryPath);
318
385
  return fallback ? [fallback] : [];
319
386
  }
@@ -240,6 +240,48 @@ describe("pickFrontendBaseUrl", () => {
240
240
  });
241
241
  expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8000");
242
242
  });
243
+ // Flutter support — the load-bearing assumption from the Confluence plan.
244
+ // init_workspace writes a single service with framework: playwright for a
245
+ // Flutter web app. Heuristic 1 must match by serviceName suffix
246
+ // (e.g. "birdle-frontend") because Heuristic 2 only knows JS/TS frontend
247
+ // frameworks. Without this, the agent never reaches the browser regardless
248
+ // of the .dart classifier fix.
249
+ it("picks the Flutter playwright-framework service by name suffix", async () => {
250
+ readSpy.mockResolvedValue({
251
+ services: [
252
+ {
253
+ serviceName: "birdle-frontend",
254
+ language: "typescript",
255
+ framework: "playwright",
256
+ api: { baseUrl: "http://localhost:8080" },
257
+ },
258
+ ],
259
+ });
260
+ expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
261
+ });
262
+ // Edge case: Flutter service has no "frontend"/"ui"/"app"/"web" suffix
263
+ // (so H1 misses) and `framework: playwright` isn't in H2's TS/JS framework
264
+ // allow-list. The chosen URL must come from H3 (first service with baseUrl).
265
+ // We add a second service to prove H3 picked the first, not H1 by accident.
266
+ it("falls back to first service when name+framework don't match H1 or H2", async () => {
267
+ readSpy.mockResolvedValue({
268
+ services: [
269
+ {
270
+ serviceName: "birdle",
271
+ language: "typescript",
272
+ framework: "playwright",
273
+ api: { baseUrl: "http://localhost:8080" },
274
+ },
275
+ {
276
+ serviceName: "other-service",
277
+ language: "go",
278
+ framework: "gin",
279
+ api: { baseUrl: "http://localhost:9000" },
280
+ },
281
+ ],
282
+ });
283
+ expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
284
+ });
243
285
  });
244
286
  // ---------------------------------------------------------------------------
245
287
  // findRootFallbackPage
@@ -375,6 +417,186 @@ export default function App() { return <Routes><Route path="/cart" element={<Car
375
417
  });
376
418
  fs.rmSync(repo, { recursive: true, force: true });
377
419
  });
420
+ // Flutter end-to-end: pubspec.yaml declares the Flutter SDK, .dart files
421
+ // appear in the diff (passed in by callers after isFrontendFile filtering),
422
+ // workspace.yml has a baseUrl. Strategies 1, 2, and 2.5 don't match
423
+ // (no framework config; TS extractor doesn't see .dart; no GoRoute calls).
424
+ // Result: root-fallback with the workspace baseUrl.
425
+ it("returns root-fallback for a Flutter repo with no GoRoute declarations", async () => {
426
+ readSpy.mockResolvedValue({
427
+ services: [
428
+ {
429
+ serviceName: "birdle-frontend",
430
+ framework: "playwright",
431
+ api: { baseUrl: "http://localhost:8080" },
432
+ },
433
+ ],
434
+ });
435
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-flutter-enum-"));
436
+ fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: birdle\ndependencies:\n flutter:\n sdk: flutter\n");
437
+ fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
438
+ fs.writeFileSync(path.join(repo, "lib", "main.dart"), "void main() => runApp(MyApp());");
439
+ const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
440
+ expect(pages).toHaveLength(1);
441
+ expect(pages[0]).toMatchObject({
442
+ url: "http://localhost:8080",
443
+ strategy: "root-fallback",
444
+ });
445
+ fs.rmSync(repo, { recursive: true, force: true });
446
+ });
447
+ // Strategy 2.5: Flutter repo with GoRouter declarations and no diff-match
448
+ // (frontendFiles names a screen file that's not imported by the router)
449
+ // surfaces every absolute route as a candidate URL via the all-routes
450
+ // fallback. Diff-match narrowing is exercised by the next test.
451
+ it("returns dart-go-router URLs for a Flutter repo with GoRoute declarations", async () => {
452
+ readSpy.mockResolvedValue({
453
+ services: [
454
+ {
455
+ serviceName: "books-frontend",
456
+ framework: "playwright",
457
+ api: { baseUrl: "http://localhost:8080" },
458
+ },
459
+ ],
460
+ });
461
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-"));
462
+ fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: books\ndependencies:\n flutter:\n sdk: flutter\n go_router: ^14.0.0\n");
463
+ fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
464
+ fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
465
+ final router = GoRouter(routes: [
466
+ GoRoute(path: '/signin', builder: (c, s) => SignIn()),
467
+ GoRoute(path: '/authors', builder: (c, s) => Authors()),
468
+ GoRoute(path: '/settings', builder: (c, s) => Settings()),
469
+ ]);
470
+ `);
471
+ const pages = await enumerateCandidateUiPages(repo, ["lib/screens/authors.dart"]);
472
+ const urls = pages.map((p) => p.url).sort();
473
+ expect(urls).toEqual([
474
+ "http://localhost:8080/authors",
475
+ "http://localhost:8080/settings",
476
+ "http://localhost:8080/signin",
477
+ ]);
478
+ expect(pages.every((p) => p.strategy === "dart-go-router")).toBe(true);
479
+ fs.rmSync(repo, { recursive: true, force: true });
480
+ });
481
+ // Diff-matching: Strategy 2.5 should narrow candidate URLs to only those
482
+ // whose screen file is in frontendFiles. The fixture has 3 routes; only
483
+ // /authors's screen file (lib/src/screens/authors.dart) is in the diff.
484
+ it("narrows GoRouter candidates to routes whose screen file is in the diff", async () => {
485
+ readSpy.mockResolvedValue({
486
+ services: [
487
+ {
488
+ serviceName: "books-frontend",
489
+ framework: "playwright",
490
+ api: { baseUrl: "http://localhost:8080" },
491
+ },
492
+ ],
493
+ });
494
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-diff-"));
495
+ fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: books\ndependencies:\n flutter:\n sdk: flutter\n");
496
+ fs.mkdirSync(path.join(repo, "lib", "src", "screens"), { recursive: true });
497
+ fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
498
+ import 'src/screens/authors.dart';
499
+ import 'src/screens/settings.dart';
500
+ import 'src/screens/sign_in.dart';
501
+
502
+ final router = GoRouter(routes: [
503
+ GoRoute(path: '/sign_in', builder: (c, s) => SignInScreen()),
504
+ GoRoute(path: '/authors', builder: (c, s) => AuthorsScreen()),
505
+ GoRoute(path: '/settings', builder: (c, s) => SettingsScreen()),
506
+ ]);
507
+ `);
508
+ fs.writeFileSync(path.join(repo, "lib", "src", "screens", "authors.dart"), "// authors");
509
+ fs.writeFileSync(path.join(repo, "lib", "src", "screens", "settings.dart"), "// settings");
510
+ fs.writeFileSync(path.join(repo, "lib", "src", "screens", "sign_in.dart"), "// signin");
511
+ const pages = await enumerateCandidateUiPages(repo, ["lib/src/screens/authors.dart"]);
512
+ const urls = pages.map((p) => p.url).sort();
513
+ // Only /authors should surface — its screen file is the only one in the diff.
514
+ expect(urls).toEqual(["http://localhost:8080/authors"]);
515
+ fs.rmSync(repo, { recursive: true, force: true });
516
+ });
517
+ // Redirect-only routes (`redirect:` with no builder) must not surface — they
518
+ // have no UI to navigate to. Same fixture shape but the only diff-matching
519
+ // route is redirect-only, so we should fall back to the non-redirect-only
520
+ // routes.
521
+ it("filters out redirect-only GoRoutes", async () => {
522
+ readSpy.mockResolvedValue({
523
+ services: [
524
+ {
525
+ serviceName: "frontend",
526
+ framework: "playwright",
527
+ api: { baseUrl: "http://localhost:8080" },
528
+ },
529
+ ],
530
+ });
531
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-redirect-"));
532
+ fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: app\ndependencies:\n flutter:\n sdk: flutter\n");
533
+ fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
534
+ fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
535
+ final router = GoRouter(routes: [
536
+ GoRoute(path: '/', redirect: (_, _) => '/home'),
537
+ GoRoute(path: '/home', builder: (c, s) => Home()),
538
+ ]);
539
+ `);
540
+ const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
541
+ const urls = pages.map((p) => p.url).sort();
542
+ // / is redirect-only — must not appear. Only /home is renderable.
543
+ expect(urls).toEqual(["http://localhost:8080/home"]);
544
+ fs.rmSync(repo, { recursive: true, force: true });
545
+ });
546
+ // Fallback path: when no route's screen file matches the diff (e.g. router
547
+ // file edited but our import-resolution heuristic missed the screen), surface
548
+ // ALL renderable routes rather than collapsing to empty.
549
+ it("falls back to all renderable routes when no screen file matches the diff", async () => {
550
+ readSpy.mockResolvedValue({
551
+ services: [
552
+ {
553
+ serviceName: "frontend",
554
+ framework: "playwright",
555
+ api: { baseUrl: "http://localhost:8080" },
556
+ },
557
+ ],
558
+ });
559
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-fallback-"));
560
+ fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: app\ndependencies:\n flutter:\n sdk: flutter\n");
561
+ fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
562
+ fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
563
+ final router = GoRouter(routes: [
564
+ GoRoute(path: '/foo', builder: (c, s) => FooScreen()),
565
+ GoRoute(path: '/bar', builder: (c, s) => BarScreen()),
566
+ ]);
567
+ `);
568
+ // Diff touches only main.dart (the router itself, not any screen). The
569
+ // import-resolution heuristic finds zero matches against frontendFiles, so
570
+ // we surface all renderable routes.
571
+ const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
572
+ const urls = pages.map((p) => p.url).sort();
573
+ expect(urls).toEqual([
574
+ "http://localhost:8080/bar",
575
+ "http://localhost:8080/foo",
576
+ ]);
577
+ fs.rmSync(repo, { recursive: true, force: true });
578
+ });
579
+ // Negative gate: pure-Dart server with a `GoRoute(`-shaped string in source
580
+ // (e.g. shelf_router uses similar names in tests) must NOT trigger Strategy
581
+ // 2.5 — pubspec.yaml lacks `sdk: flutter`, hasFlutterSdkDep returns false.
582
+ it("does not invoke Dart extractor when pubspec.yaml lacks sdk: flutter", async () => {
583
+ readSpy.mockResolvedValue({
584
+ services: [
585
+ {
586
+ serviceName: "server-frontend",
587
+ framework: "playwright",
588
+ api: { baseUrl: "http://localhost:8080" },
589
+ },
590
+ ],
591
+ });
592
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-dart-server-"));
593
+ fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: server\ndependencies:\n shelf: ^1.0.0\n");
594
+ fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
595
+ fs.writeFileSync(path.join(repo, "lib", "routes.dart"), `GoRoute(path: '/api/v1', handler: (req) => Response.ok(''))`);
596
+ const pages = await enumerateCandidateUiPages(repo, ["lib/routes.dart"]);
597
+ expect(pages.map((p) => p.strategy)).toEqual(["root-fallback"]);
598
+ fs.rmSync(repo, { recursive: true, force: true });
599
+ });
378
600
  });
379
601
  // ---------------------------------------------------------------------------
380
602
  // detectsFilesystemRouting
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var mouseActions_exports = {};
20
+ __export(mouseActions_exports, {
21
+ DEFAULT_DRAG_STEPS: () => DEFAULT_DRAG_STEPS,
22
+ decodeModifierKeys: () => decodeModifierKeys,
23
+ decomposeDrag: () => decomposeDrag,
24
+ mouseActionToJsonl: () => mouseActionToJsonl,
25
+ mouseJsonlToCode: () => mouseJsonlToCode
26
+ });
27
+ module.exports = __toCommonJS(mouseActions_exports);
28
+ const DEFAULT_DRAG_STEPS = 10;
29
+ function decodeModifierKeys(mask, platform = typeof process !== "undefined" ? process.platform : "linux") {
30
+ if (!mask)
31
+ return [];
32
+ const keys = [];
33
+ if (mask & 1) keys.push("Alt");
34
+ if ((mask & 6) === 6) {
35
+ keys.push(platform === "darwin" ? "Meta" : "Control");
36
+ } else {
37
+ if (mask & 2) keys.push("Control");
38
+ if (mask & 4) keys.push("Meta");
39
+ }
40
+ if (mask & 8) keys.push("Shift");
41
+ return keys;
42
+ }
43
+ function decomposeDrag(start, end, steps = DEFAULT_DRAG_STEPS) {
44
+ return [
45
+ { name: "mouse.move", position: start },
46
+ { name: "mouse.down" },
47
+ { name: "mouse.move", position: end, steps },
48
+ { name: "mouse.up" }
49
+ ];
50
+ }
51
+ function mouseActionToJsonl(params) {
52
+ const button = params.button ?? "left";
53
+ const modifiers = params.modifiers ?? 0;
54
+ switch (params.action) {
55
+ case "move": {
56
+ if (params.x === void 0 || params.y === void 0)
57
+ return { error: 'mouse action "move" requires x and y.' };
58
+ const move = params.steps !== void 0 ? { name: "mouse.move", position: { x: params.x, y: params.y }, steps: params.steps } : { name: "mouse.move", position: { x: params.x, y: params.y } };
59
+ return { actions: [move] };
60
+ }
61
+ case "down":
62
+ return { actions: [button === "left" ? { name: "mouse.down" } : { name: "mouse.down", button }] };
63
+ case "up":
64
+ return { actions: [button === "left" ? { name: "mouse.up" } : { name: "mouse.up", button }] };
65
+ case "wheel": {
66
+ if (params.deltaX === void 0 && params.deltaY === void 0)
67
+ return { error: 'mouse action "wheel" requires deltaX and/or deltaY.' };
68
+ if (params.x === void 0 || params.y === void 0)
69
+ return { error: 'mouse action "wheel" requires x and y (the scroll target) so the pointer is moved there before scrolling.' };
70
+ const position = { x: params.x, y: params.y };
71
+ return { actions: [
72
+ { name: "mouse.move", position },
73
+ { name: "mouse.wheel", position, deltaX: params.deltaX ?? 0, deltaY: params.deltaY ?? 0, modifiers }
74
+ ] };
75
+ }
76
+ case "click": {
77
+ if (params.x === void 0 || params.y === void 0)
78
+ return { error: 'mouse action "click" requires x and y.' };
79
+ return { actions: [{ name: "click", selector: "body", position: { x: params.x, y: params.y }, button, modifiers, clickCount: params.clickCount ?? 1 }] };
80
+ }
81
+ case "drag": {
82
+ if (params.x === void 0 || params.y === void 0 || params.endX === void 0 || params.endY === void 0)
83
+ return { error: 'mouse action "drag" requires x, y, endX and endY.' };
84
+ return { actions: decomposeDrag({ x: params.x, y: params.y }, { x: params.endX, y: params.endY }, params.steps) };
85
+ }
86
+ default:
87
+ return { error: `Unknown mouse action: ${params.action}` };
88
+ }
89
+ }
90
+ function mouseJsonlToCode(a) {
91
+ switch (a.name) {
92
+ case "mouse.move":
93
+ return [a.steps !== void 0 ? `await page.mouse.move(${a.position.x}, ${a.position.y}, { steps: ${a.steps} });` : `await page.mouse.move(${a.position.x}, ${a.position.y});`];
94
+ case "mouse.down":
95
+ return [a.button && a.button !== "left" ? `await page.mouse.down({ button: '${a.button}' });` : `await page.mouse.down();`];
96
+ case "mouse.up":
97
+ return [a.button && a.button !== "left" ? `await page.mouse.up({ button: '${a.button}' });` : `await page.mouse.up();`];
98
+ case "mouse.wheel":
99
+ return withModifierLines(a.modifiers, `await page.mouse.wheel(${a.deltaX}, ${a.deltaY});`);
100
+ case "click": {
101
+ const opts = a.button !== "left" || a.clickCount !== 1 ? `, { button: '${a.button}', clickCount: ${a.clickCount} }` : "";
102
+ return withModifierLines(a.modifiers, `await page.mouse.click(${a.position.x}, ${a.position.y}${opts});`);
103
+ }
104
+ }
105
+ }
106
+ function withModifierLines(mask, line) {
107
+ const keys = decodeModifierKeys(mask);
108
+ if (!keys.length)
109
+ return [line];
110
+ return [
111
+ ...keys.map((k) => `await page.keyboard.down('${k}');`),
112
+ line,
113
+ ...[...keys].reverse().map((k) => `await page.keyboard.up('${k}');`)
114
+ ];
115
+ }
116
+ // Annotate the CommonJS export names for ESM import in node:
117
+ 0 && (module.exports = {
118
+ DEFAULT_DRAG_STEPS,
119
+ decodeModifierKeys,
120
+ decomposeDrag,
121
+ mouseActionToJsonl,
122
+ mouseJsonlToCode
123
+ });
@@ -20,17 +20,27 @@ var skyramp_exports = {};
20
20
  __export(skyramp_exports, {
21
21
  ARGS_ONLY_TOOLS: () => import_types.ARGS_ONLY_TOOLS,
22
22
  TraceRecordingBackend: () => import_traceRecordingBackend.TraceRecordingBackend,
23
+ actionsFromPath: () => import_skyRampImport.actionsFromPath,
23
24
  buildJsonlContent: () => import_skyRampExport.buildJsonlContent,
25
+ loadTraceMcpTool: () => import_loadTraceTool.loadTraceMcpTool,
26
+ loadTraceSchema: () => import_loadTraceTool.loadTraceSchema,
27
+ parseJsonl: () => import_skyRampImport.parseJsonl,
24
28
  writeSkyrampZip: () => import_skyRampExport.writeSkyrampZip
25
29
  });
26
30
  module.exports = __toCommonJS(skyramp_exports);
27
31
  var import_traceRecordingBackend = require("./traceRecordingBackend");
28
32
  var import_types = require("./types");
29
33
  var import_skyRampExport = require("../test/skyRampExport");
34
+ var import_loadTraceTool = require("./loadTraceTool");
35
+ var import_skyRampImport = require("./skyRampImport");
30
36
  // Annotate the CommonJS export names for ESM import in node:
31
37
  0 && (module.exports = {
32
38
  ARGS_ONLY_TOOLS,
33
39
  TraceRecordingBackend,
40
+ actionsFromPath,
34
41
  buildJsonlContent,
42
+ loadTraceMcpTool,
43
+ loadTraceSchema,
44
+ parseJsonl,
35
45
  writeSkyrampZip
36
46
  });