@dimina-kit/devtools 0.4.0-dev.20260728080958 → 0.4.0-dev.20260728110120

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 (56) hide show
  1. package/dist/main/app/app.js +11 -3
  2. package/dist/main/index.bundle.js +320 -83
  3. package/dist/main/ipc/bridge-router.js +1 -2
  4. package/dist/main/ipc/settings.js +4 -0
  5. package/dist/main/services/mcp/server.d.ts +2 -1
  6. package/dist/main/services/mcp/server.js +6 -3
  7. package/dist/main/services/mcp/tools/project-tools.d.ts +49 -0
  8. package/dist/main/services/mcp/tools/project-tools.js +121 -0
  9. package/dist/main/services/notifications/renderer-notifier.d.ts +11 -0
  10. package/dist/main/services/notifications/renderer-notifier.js +3 -0
  11. package/dist/main/services/views/native-simulator-view.js +4 -0
  12. package/dist/main/services/workbench-context.d.ts +14 -0
  13. package/dist/main/services/workbench-context.js +9 -1
  14. package/dist/main/services/workspace/compile-log-buffer.d.ts +40 -0
  15. package/dist/main/services/workspace/compile-log-buffer.js +41 -0
  16. package/dist/main/services/workspace/session-status-store.d.ts +47 -0
  17. package/dist/main/services/workspace/session-status-store.js +73 -0
  18. package/dist/main/services/workspace/status-tap.d.ts +20 -0
  19. package/dist/main/services/workspace/status-tap.js +32 -0
  20. package/dist/native-host/common/common.js +58 -154
  21. package/dist/native-host/container/pageFrame.css +1 -1
  22. package/dist/native-host/render/render.js +3297 -6602
  23. package/dist/native-host/service/service.js +2 -2
  24. package/dist/preload/runtime/native-host.d.ts +8 -0
  25. package/dist/preload/runtime/native-host.js +2 -0
  26. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  27. package/dist/preload/windows/main.cjs.map +1 -1
  28. package/dist/preload/windows/simulator.cjs +1 -0
  29. package/dist/preload/windows/simulator.cjs.map +2 -2
  30. package/dist/preload/windows/simulator.js +1 -0
  31. package/dist/render-host/preload.cjs +18 -0
  32. package/dist/renderer/assets/index-BJlFbXHb.js +49 -0
  33. package/dist/renderer/assets/{input-dsgeTmVX.js → input-xdmfv-eH.js} +2 -2
  34. package/dist/renderer/assets/{ipc-transport-BgFdT9bT.js → ipc-transport-B3EpmfNS.js} +2 -2
  35. package/dist/renderer/assets/popover-B88e2hkg.js +2 -0
  36. package/dist/renderer/assets/{select-DAw1hzkl.js → select-CD9CJyhI.js} +2 -2
  37. package/dist/renderer/assets/settings-44hf0QtO.js +2 -0
  38. package/dist/renderer/assets/settings-api-pJKPuP1M.js +2 -0
  39. package/dist/renderer/assets/workbenchSettings-DKD5U1Ql.js +8 -0
  40. package/dist/renderer/entries/main/index.html +5 -5
  41. package/dist/renderer/entries/popover/index.html +4 -4
  42. package/dist/renderer/entries/settings/index.html +4 -4
  43. package/dist/renderer/entries/workbench-settings/index.html +3 -3
  44. package/dist/shared/bridge-channels.d.ts +5 -2
  45. package/dist/shared/ipc-channels.d.ts +2 -0
  46. package/dist/shared/ipc-channels.js +2 -0
  47. package/dist/simulator/assets/{device-shell-Cs-rmP1f.js → device-shell-Sbkjw8Nb.js} +2 -2
  48. package/dist/simulator/assets/{simulator-BG-5CADK.js → simulator-CRyLSz1N.js} +3 -3
  49. package/dist/simulator/assets/{simulator-mini-app-DzfMfnVM.js → simulator-mini-app-w1Bhwkld.js} +2 -2
  50. package/dist/simulator/simulator.html +1 -1
  51. package/package.json +5 -5
  52. package/dist/renderer/assets/index-M5NDq_vi.js +0 -49
  53. package/dist/renderer/assets/popover-Disfu1cx.js +0 -2
  54. package/dist/renderer/assets/settings-M_8GFW2M.js +0 -2
  55. package/dist/renderer/assets/settings-api-DFR5eDl_.js +0 -2
  56. package/dist/renderer/assets/workbenchSettings-CxdXeoOu.js +0 -8
@@ -419,6 +419,7 @@ var WorkbenchSettingsChannel = {
419
419
  Get: "workbenchSettings:get",
420
420
  Save: "workbenchSettings:save",
421
421
  SetTheme: "workbenchSettings:setTheme",
422
+ Restart: "workbenchSettings:restart",
422
423
  GetCdpStatus: "workbenchSettings:getCdpStatus",
423
424
  GetMcpStatus: "workbenchSettings:getMcpStatus",
424
425
  Init: "workbenchSettings:init",
@@ -531,6 +532,7 @@ var PopoverChannel = {
531
532
  };
532
533
  var WindowChannel = {
533
534
  NavigateBack: "window:navigateBack",
535
+ OpenProject: "window:openProject",
534
536
  // Renderer → main: the renderer's current top-level screen ('project' when
535
537
  // inside a project screen, 'list' on the project list / landing). The
536
538
  // renderer pushes this on every screen change, including the moment it enters
@@ -899,7 +901,7 @@ function registerProjectFsIpc(ctx) {
899
901
  // src/main/app/app.ts
900
902
  import { app as app15, BrowserWindow as BrowserWindow9, nativeImage, session as session4 } from "electron";
901
903
  import fs13 from "fs";
902
- import path25 from "path";
904
+ import path26 from "path";
903
905
 
904
906
  // src/main/utils/paths.ts
905
907
  import fs3 from "fs";
@@ -1597,6 +1599,9 @@ function createRendererNotifier(ctx) {
1597
1599
  windowNavigateBack() {
1598
1600
  sendToMain(WindowChannel.NavigateBack);
1599
1601
  },
1602
+ windowOpenProject(payload) {
1603
+ sendToMain(WindowChannel.OpenProject, payload);
1604
+ },
1600
1605
  popoverClosed() {
1601
1606
  sendToMain(PopoverChannel.Closed);
1602
1607
  },
@@ -1628,6 +1633,104 @@ function createRendererNotifier(ctx) {
1628
1633
  };
1629
1634
  }
1630
1635
 
1636
+ // src/main/services/workspace/compile-log-buffer.ts
1637
+ var DEFAULT_CAPACITY = 1e3;
1638
+ var DEFAULT_READ_LIMIT = 100;
1639
+ function createCompileLogBuffer(capacity = DEFAULT_CAPACITY) {
1640
+ const entries = [];
1641
+ let seq = 0;
1642
+ return {
1643
+ append(entry) {
1644
+ entries.push({ seq: ++seq, ...entry });
1645
+ if (entries.length > capacity) entries.splice(0, entries.length - capacity);
1646
+ },
1647
+ read({ afterSeq = 0, limit = DEFAULT_READ_LIMIT, stream } = {}) {
1648
+ const matched = [];
1649
+ for (const entry of entries) {
1650
+ if (entry.seq <= afterSeq) continue;
1651
+ if (stream && entry.stream !== stream) continue;
1652
+ matched.push(entry);
1653
+ if (matched.length >= limit) break;
1654
+ }
1655
+ const last = matched[matched.length - 1];
1656
+ return { entries: matched, nextCursor: last ? last.seq : afterSeq };
1657
+ },
1658
+ clear() {
1659
+ entries.length = 0;
1660
+ }
1661
+ };
1662
+ }
1663
+
1664
+ // src/main/services/workspace/session-status-store.ts
1665
+ function toPhase(status2) {
1666
+ if (status2 === "compiling") return "compiling";
1667
+ if (status2 === "error") return "error";
1668
+ return "ready";
1669
+ }
1670
+ function createSessionStatusStore() {
1671
+ let snapshot = {
1672
+ phase: "idle",
1673
+ message: "",
1674
+ watcherAlive: true,
1675
+ updatedAt: 0,
1676
+ generation: 0
1677
+ };
1678
+ const waiters = /* @__PURE__ */ new Set();
1679
+ return {
1680
+ get: () => snapshot,
1681
+ record(payload) {
1682
+ const phase = toPhase(payload.status);
1683
+ snapshot = {
1684
+ phase,
1685
+ message: payload.message,
1686
+ // A dead watcher is a session-lifetime fact; only a NEW compile
1687
+ // (fresh open) resets it. Rebuild 'ready' chatter keeps it dead.
1688
+ watcherAlive: payload.watcher === "dead" ? false : phase === "compiling" ? true : snapshot.watcherAlive,
1689
+ updatedAt: Date.now(),
1690
+ generation: snapshot.generation + 1
1691
+ };
1692
+ for (const waiter of [...waiters]) waiter(snapshot);
1693
+ },
1694
+ waitForSettled({ afterGeneration = -1, timeoutMs }) {
1695
+ const isSettled = (s) => s.phase !== "compiling" && s.generation > afterGeneration;
1696
+ if (isSettled(snapshot)) return Promise.resolve(snapshot);
1697
+ return new Promise((resolve, reject) => {
1698
+ const timer = setTimeout(() => {
1699
+ waiters.delete(onRecord);
1700
+ reject(new Error(
1701
+ `timed out after ${timeoutMs}ms waiting for the compile to settle (last phase: ${snapshot.phase})`
1702
+ ));
1703
+ }, timeoutMs);
1704
+ const onRecord = (s) => {
1705
+ if (!isSettled(s)) return;
1706
+ clearTimeout(timer);
1707
+ waiters.delete(onRecord);
1708
+ resolve(s);
1709
+ };
1710
+ waiters.add(onRecord);
1711
+ });
1712
+ }
1713
+ };
1714
+ }
1715
+
1716
+ // src/main/services/workspace/status-tap.ts
1717
+ function tapNotifierIntoStores(notify, status2, compileLogs) {
1718
+ return {
1719
+ ...notify,
1720
+ projectStatus(payload) {
1721
+ if (payload.status === "compiling" && payload.hotReload !== true) {
1722
+ compileLogs.clear();
1723
+ }
1724
+ status2.record(payload);
1725
+ notify.projectStatus(payload);
1726
+ },
1727
+ compileLog(payload) {
1728
+ compileLogs.append(payload);
1729
+ notify.compileLog(payload);
1730
+ }
1731
+ };
1732
+ }
1733
+
1631
1734
  // src/main/services/safe-area/index.ts
1632
1735
  function guestInsets(device, isTabPage2) {
1633
1736
  const top = device?.safeAreaInsets.top ?? 0;
@@ -4724,8 +4827,8 @@ function createHostToolbarView(ctx, reconciler, deps) {
4724
4827
  hide() {
4725
4828
  hideHostToolbar();
4726
4829
  },
4727
- setPreloadPath(path26) {
4728
- hostToolbarPreloadOverride = path26;
4830
+ setPreloadPath(path27) {
4831
+ hostToolbarPreloadOverride = path27;
4729
4832
  },
4730
4833
  setHeightMode(mode) {
4731
4834
  if (mode !== "auto" && !(Number.isFinite(mode.fixed) && mode.fixed >= 0)) {
@@ -6153,7 +6256,13 @@ function createWorkbenchContext(opts) {
6153
6256
  ctx.windows = createWindowService(opts.mainWindow);
6154
6257
  ctx.views = createViewManager(ctx);
6155
6258
  ctx.registry.add(() => ctx.views.disposeAll());
6156
- ctx.notify = createRendererNotifier(ctx);
6259
+ ctx.sessionStatus = createSessionStatusStore();
6260
+ ctx.compileLogBuffer = createCompileLogBuffer();
6261
+ ctx.notify = tapNotifierIntoStores(
6262
+ createRendererNotifier(ctx),
6263
+ ctx.sessionStatus,
6264
+ ctx.compileLogBuffer
6265
+ );
6157
6266
  ctx.openSettings = () => openSettingsWindow(ctx);
6158
6267
  ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
6159
6268
  ctx.projectTemplates = resolveTemplates(
@@ -6734,6 +6843,9 @@ function registerSettingsIpc(ctx) {
6734
6843
  args
6735
6844
  );
6736
6845
  applyTheme(theme);
6846
+ }).handle(WorkbenchSettingsChannel.Restart, () => {
6847
+ app9.relaunch();
6848
+ app9.quit();
6737
6849
  }).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
6738
6850
  const settings = loadWorkbenchSettings();
6739
6851
  const switchValue = app9.commandLine.getSwitchValue("remote-debugging-port");
@@ -9506,8 +9618,7 @@ function resolveRootPagePath(manifest, requestedPagePath) {
9506
9618
  function resolvePageWindowConfig(appConfig, pagePath) {
9507
9619
  const normalized = normalizePagePath(pagePath);
9508
9620
  const appWindow = appConfig.app?.window ?? {};
9509
- const pageEntry = appConfig.modules?.[normalized];
9510
- const pageWindow = pageEntry?.window ?? {};
9621
+ const pageWindow = appConfig.modules?.[normalized] ?? {};
9511
9622
  return {
9512
9623
  navigationBarTitleText: pageWindow.navigationBarTitleText ?? appWindow.navigationBarTitleText ?? "",
9513
9624
  navigationBarBackgroundColor: pageWindow.navigationBarBackgroundColor ?? appWindow.navigationBarBackgroundColor ?? "#ffffff",
@@ -10813,10 +10924,127 @@ function registerContextTools(server) {
10813
10924
  );
10814
10925
  }
10815
10926
 
10816
- // src/main/services/mcp/tools/simulator-tools.ts
10927
+ // src/main/services/mcp/tools/project-tools.ts
10928
+ import path18 from "node:path";
10817
10929
  import { z as z5 } from "zod";
10818
- function ok(text) {
10819
- return { content: [{ type: "text", text }] };
10930
+ var DEFAULT_OPEN_TIMEOUT_MS = 18e4;
10931
+ var DEFAULT_WAIT_TIMEOUT_MS = 6e4;
10932
+ function text(payload) {
10933
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
10934
+ }
10935
+ function errorText(message) {
10936
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
10937
+ }
10938
+ function publicPhase(snapshot, sessionActive) {
10939
+ if (!sessionActive && snapshot.phase === "ready") return "idle";
10940
+ return snapshot.phase;
10941
+ }
10942
+ function statusPayload(host) {
10943
+ const snapshot = host.sessionStatus.get();
10944
+ const sessionActive = host.workspace.hasActiveSession();
10945
+ return {
10946
+ phase: publicPhase(snapshot, sessionActive),
10947
+ message: snapshot.message,
10948
+ projectPath: host.workspace.getProjectPath(),
10949
+ sessionActive,
10950
+ watcherAlive: snapshot.watcherAlive,
10951
+ updatedAt: snapshot.updatedAt,
10952
+ generation: snapshot.generation
10953
+ };
10954
+ }
10955
+ function registerProjectTools(server, host) {
10956
+ server.tool(
10957
+ "project_open",
10958
+ "Open a mini-program project: registers the directory if needed, launches it in the simulator (compile + attach), and waits until the compile settles. Returns the settled status; on compile error, pull stderr via compile_logs.",
10959
+ {
10960
+ path: z5.string().describe("Absolute path to the mini-program project directory"),
10961
+ timeoutMs: z5.number().optional().default(DEFAULT_OPEN_TIMEOUT_MS).describe("Max time to wait for the compile to settle")
10962
+ },
10963
+ async ({ path: rawPath, timeoutMs }) => {
10964
+ const dir = path18.resolve(rawPath);
10965
+ const current = host.sessionStatus.get();
10966
+ if (host.workspace.getProjectPath() === dir && host.workspace.hasActiveSession() && current.phase === "ready") {
10967
+ return text({ ...statusPayload(host), note: "project already open" });
10968
+ }
10969
+ const invalid = await host.workspace.validateProjectDir(dir);
10970
+ if (invalid) return errorText(invalid);
10971
+ let project;
10972
+ if (await host.workspace.hasProject(dir)) {
10973
+ const known = (await host.workspace.listProjects()).find((p) => p.path === dir);
10974
+ project = known ?? { name: path18.basename(dir), path: dir };
10975
+ } else {
10976
+ project = await host.workspace.addProject(dir);
10977
+ }
10978
+ const afterGeneration = host.sessionStatus.get().generation;
10979
+ host.requestOpenInUi({ name: project.name, path: project.path });
10980
+ let settled;
10981
+ try {
10982
+ settled = await host.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
10983
+ } catch (err2) {
10984
+ return errorText(err2 instanceof Error ? err2.message : String(err2));
10985
+ }
10986
+ if (settled.phase === "error") {
10987
+ return errorText(
10988
+ `compile failed: ${settled.message} \u2014 call compile_logs (stream: "stderr") for details`
10989
+ );
10990
+ }
10991
+ return text(statusPayload(host));
10992
+ }
10993
+ );
10994
+ server.tool(
10995
+ "project_close",
10996
+ "Close the currently open project session and return the workbench to the project list.",
10997
+ {},
10998
+ async () => {
10999
+ if (!host.workspace.getProjectPath() && !host.workspace.hasActiveSession()) {
11000
+ return text({ closed: false, note: "no project is open" });
11001
+ }
11002
+ await host.workspace.closeProject();
11003
+ host.requestNavigateBack();
11004
+ return text({ closed: true });
11005
+ }
11006
+ );
11007
+ server.tool(
11008
+ "project_status",
11009
+ "Query the current project compile status (phase: idle | compiling | ready | error). Includes `generation` \u2014 pass it to project_wait_ready to await the NEXT settled state.",
11010
+ {},
11011
+ async () => text(statusPayload(host))
11012
+ );
11013
+ server.tool(
11014
+ "project_wait_ready",
11015
+ 'Wait until the compile pipeline settles (phase leaves "compiling"). Watcher rebuilds emit no "compiling" phase, so to await a rebuild after editing files pass the `generation` from a prior project_status.',
11016
+ {
11017
+ afterGeneration: z5.number().optional().describe("Only accept states recorded after this generation (from project_status). Omit to accept the current state when already settled."),
11018
+ timeoutMs: z5.number().optional().default(DEFAULT_WAIT_TIMEOUT_MS).describe("Max time to wait")
11019
+ },
11020
+ async ({ afterGeneration, timeoutMs }) => {
11021
+ try {
11022
+ await host.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
11023
+ } catch (err2) {
11024
+ return errorText(err2 instanceof Error ? err2.message : String(err2));
11025
+ }
11026
+ return text(statusPayload(host));
11027
+ }
11028
+ );
11029
+ server.tool(
11030
+ "compile_logs",
11031
+ "Pull compile log lines incrementally. Pass the previous call's nextCursor as `cursor` to receive only new lines. The buffer resets on each fresh project open; watcher rebuilds append to the same timeline.",
11032
+ {
11033
+ cursor: z5.number().optional().default(0).describe("Return only lines after this cursor (0 = from the start of the buffer)"),
11034
+ limit: z5.number().optional().default(100).describe("Max lines to return"),
11035
+ stream: z5.enum(["stdout", "stderr"]).optional().describe("Only lines from this stream (stderr carries compile errors)")
11036
+ },
11037
+ async ({ cursor, limit, stream }) => {
11038
+ const read = host.compileLogs.read({ afterSeq: cursor, limit, stream });
11039
+ return text(read);
11040
+ }
11041
+ );
11042
+ }
11043
+
11044
+ // src/main/services/mcp/tools/simulator-tools.ts
11045
+ import { z as z6 } from "zod";
11046
+ function ok(text2) {
11047
+ return { content: [{ type: "text", text: text2 }] };
10820
11048
  }
10821
11049
  function err(msg) {
10822
11050
  return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
@@ -10872,8 +11100,8 @@ async function tapSelector(c, selector, nth) {
10872
11100
  await clickAt(c, value.x, value.y);
10873
11101
  return ok(`Tapped selector ${value.selector}[${value.index}] at (${value.x}, ${value.y}) within rect (${value.rect.left}, ${value.rect.top}, ${value.rect.width} x ${value.rect.height})`);
10874
11102
  }
10875
- async function typeText(c, selector, nth, text) {
10876
- if (!selector || typeof text !== "string") return err("type requires selector and text");
11103
+ async function typeText(c, selector, nth, text2) {
11104
+ if (!selector || typeof text2 !== "string") return err("type requires selector and text");
10877
11105
  const { root } = await c.DOM.getDocument({ depth: 0 });
10878
11106
  const { nodeIds } = await c.DOM.querySelectorAll({ nodeId: root.nodeId, selector });
10879
11107
  if (!nodeIds || nodeIds.length === 0) return err(`selector matched no elements: ${selector}`);
@@ -10881,13 +11109,13 @@ async function typeText(c, selector, nth, text) {
10881
11109
  if (index < 0 || index >= nodeIds.length) return err(`nth ${index} out of range (matches: ${nodeIds.length})`);
10882
11110
  await c.DOM.focus({ nodeId: nodeIds[index] });
10883
11111
  try {
10884
- await c.Input.insertText({ text });
11112
+ await c.Input.insertText({ text: text2 });
10885
11113
  } catch {
10886
- for (const ch of text) {
11114
+ for (const ch of text2) {
10887
11115
  await c.Input.dispatchKeyEvent({ type: "char", text: ch });
10888
11116
  }
10889
11117
  }
10890
- return ok(`Typed ${text.length} char(s) into ${selector}[${index}]`);
11118
+ return ok(`Typed ${text2.length} char(s) into ${selector}[${index}]`);
10891
11119
  }
10892
11120
  async function scrollAt(c, x, y, deltaX, deltaY) {
10893
11121
  if (typeof x !== "number" || typeof y !== "number" || typeof deltaX !== "number" || typeof deltaY !== "number") {
@@ -10907,8 +11135,8 @@ async function dispatchKey(c, key) {
10907
11135
  }
10908
11136
  function registerSimulatorTools(server) {
10909
11137
  server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
10910
- url: z5.string().optional().describe("URL to navigate to"),
10911
- reload: z5.boolean().optional().describe("If true, reload current page instead of navigating")
11138
+ url: z6.string().optional().describe("URL to navigate to"),
11139
+ reload: z6.boolean().optional().describe("If true, reload current page instead of navigating")
10912
11140
  }, async ({ url, reload }) => {
10913
11141
  const c = getClient("simulator");
10914
11142
  if (reload) {
@@ -10923,16 +11151,16 @@ function registerSimulatorTools(server) {
10923
11151
  return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
10924
11152
  });
10925
11153
  server.tool("simulator_input", "Dispatch input to the simulator. `tap_coord` requires x and y. `tap_selector` requires selector and optional nth. `type` requires selector and text. `scroll` requires x, y, deltaX, and deltaY. `key` is best-effort for common keys such as Enter, Escape, Tab, Arrow* and Space.", {
10926
- action: z5.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
10927
- x: z5.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
10928
- y: z5.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
10929
- selector: z5.string().optional().describe("CSS selector for `tap_selector` or `type`"),
10930
- nth: z5.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
10931
- text: z5.string().optional().describe("Text to insert for `type`"),
10932
- key: z5.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
10933
- deltaX: z5.number().optional().describe("Horizontal wheel delta for `scroll`"),
10934
- deltaY: z5.number().optional().describe("Vertical wheel delta for `scroll`")
10935
- }, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
11154
+ action: z6.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
11155
+ x: z6.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
11156
+ y: z6.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
11157
+ selector: z6.string().optional().describe("CSS selector for `tap_selector` or `type`"),
11158
+ nth: z6.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
11159
+ text: z6.string().optional().describe("Text to insert for `type`"),
11160
+ key: z6.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
11161
+ deltaX: z6.number().optional().describe("Horizontal wheel delta for `scroll`"),
11162
+ deltaY: z6.number().optional().describe("Vertical wheel delta for `scroll`")
11163
+ }, async ({ action, x, y, selector, nth, text: text2, key, deltaX, deltaY }) => {
10936
11164
  const c = getClient("simulator");
10937
11165
  switch (action) {
10938
11166
  case "tap_coord":
@@ -10940,7 +11168,7 @@ function registerSimulatorTools(server) {
10940
11168
  case "tap_selector":
10941
11169
  return tapSelector(c, selector, nth);
10942
11170
  case "type":
10943
- return typeText(c, selector, nth, text);
11171
+ return typeText(c, selector, nth, text2);
10944
11172
  case "scroll":
10945
11173
  return scrollAt(c, x, y, deltaX, deltaY);
10946
11174
  case "key":
@@ -10963,7 +11191,7 @@ function registerWorkbenchTools(server) {
10963
11191
  // src/main/services/mcp/server.ts
10964
11192
  var require2 = createRequire(import.meta.url);
10965
11193
  var pkg = require2("../../../../package.json");
10966
- function buildServer() {
11194
+ function buildServer(projectHost) {
10967
11195
  const server = new McpServer({
10968
11196
  name: "@dimina-kit/devtools",
10969
11197
  version: pkg.version
@@ -10973,9 +11201,10 @@ function buildServer() {
10973
11201
  registerCommonTargetTools(server, "workbench");
10974
11202
  registerWorkbenchTools(server);
10975
11203
  registerContextTools(server);
11204
+ registerProjectTools(server, projectHost);
10976
11205
  return server;
10977
11206
  }
10978
- function startMcpServer(resolvedCdpPort, mcpPort) {
11207
+ function startMcpServer(resolvedCdpPort, mcpPort, projectHost) {
10979
11208
  setCdpPort(resolvedCdpPort);
10980
11209
  connectTarget("simulator").catch(() => {
10981
11210
  });
@@ -10989,7 +11218,7 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
10989
11218
  transports.set(transport.sessionId, transport);
10990
11219
  res.on("close", () => transports.delete(transport.sessionId));
10991
11220
  try {
10992
- await buildServer().connect(transport);
11221
+ await buildServer(projectHost).connect(transport);
10993
11222
  } catch (err2) {
10994
11223
  transports.delete(transport.sessionId);
10995
11224
  if (!res.headersSent) res.writeHead(500).end("MCP connect failed");
@@ -11206,7 +11435,7 @@ function setupSimulatorCurrentPage(host, options) {
11206
11435
 
11207
11436
  // src/main/services/render-inspect/index.ts
11208
11437
  import { readFileSync } from "node:fs";
11209
- import path18 from "node:path";
11438
+ import path19 from "node:path";
11210
11439
  var DEFAULT_SOURCE_PATH = "dist/render-host/render-inspect.js";
11211
11440
  var HIGHLIGHT_CONFIG = {
11212
11441
  showInfo: true,
@@ -11232,7 +11461,7 @@ function withTimeout(p, ms) {
11232
11461
  });
11233
11462
  }
11234
11463
  function createRenderInspector(options = {}) {
11235
- const loadSource = options.loadSource ?? (() => readFileSync(path18.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
11464
+ const loadSource = options.loadSource ?? (() => readFileSync(path19.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
11236
11465
  let cachedSource = null;
11237
11466
  const injected = /* @__PURE__ */ new Set();
11238
11467
  const broker = options.broker ?? createCdpSessionBroker({ connections: options.connections });
@@ -11344,13 +11573,13 @@ function createRenderInspector(options = {}) {
11344
11573
  import { toDisposable as toDisposable6 } from "@dimina-kit/electron-deck/main";
11345
11574
 
11346
11575
  // src/main/services/simulator-temp-files/store.ts
11347
- function registerTempFile(store, path26, mime, bytes) {
11576
+ function registerTempFile(store, path27, mime, bytes) {
11348
11577
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
11349
- store.delete(path26);
11350
- store.set(path26, { bytes: buf, mime });
11578
+ store.delete(path27);
11579
+ store.set(path27, { bytes: buf, mime });
11351
11580
  }
11352
- function revokeTempFile(store, path26) {
11353
- store.delete(path26);
11581
+ function revokeTempFile(store, path27) {
11582
+ store.delete(path27);
11354
11583
  }
11355
11584
  function revokeAllTempFiles(store) {
11356
11585
  store.clear();
@@ -11358,14 +11587,14 @@ function revokeAllTempFiles(store) {
11358
11587
 
11359
11588
  // src/shared/vpath.ts
11360
11589
  import os from "node:os";
11361
- import path19 from "node:path";
11590
+ import path20 from "node:path";
11362
11591
  var DIFILE_PREFIX = "difile://";
11363
11592
  function sandboxBase() {
11364
11593
  const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
11365
11594
  if (env) {
11366
- return path19.join(env, "files");
11595
+ return path20.join(env, "files");
11367
11596
  }
11368
- return path19.join(os.homedir(), ".dimina", "files");
11597
+ return path20.join(os.homedir(), ".dimina", "files");
11369
11598
  }
11370
11599
  function resolveVPath(url) {
11371
11600
  if (typeof url !== "string") return null;
@@ -11399,15 +11628,15 @@ function resolveVPath(url) {
11399
11628
  return { kind, writable };
11400
11629
  }
11401
11630
  const base = sandboxBase();
11402
- const joined = path19.join(base, decoded);
11403
- const normalized = path19.normalize(joined);
11404
- if (normalized !== base && !normalized.startsWith(base + path19.sep)) return null;
11631
+ const joined = path20.join(base, decoded);
11632
+ const normalized = path20.normalize(joined);
11633
+ if (normalized !== base && !normalized.startsWith(base + path20.sep)) return null;
11405
11634
  return { kind, writable, realPath: normalized };
11406
11635
  }
11407
11636
 
11408
11637
  // src/main/services/simulator-temp-files/disk.ts
11409
11638
  import fs9 from "node:fs/promises";
11410
- import path20 from "node:path";
11639
+ import path21 from "node:path";
11411
11640
  var EXT_MIME = {
11412
11641
  ".txt": "text/plain",
11413
11642
  ".log": "text/plain",
@@ -11485,7 +11714,7 @@ function sniffMime(head) {
11485
11714
  return hit ? hit.mime : null;
11486
11715
  }
11487
11716
  function extMime(realPath) {
11488
- const ext = path20.extname(realPath).toLowerCase();
11717
+ const ext = path21.extname(realPath).toLowerCase();
11489
11718
  if (!ext) return null;
11490
11719
  return EXT_MIME[ext] ?? null;
11491
11720
  }
@@ -11559,7 +11788,7 @@ async function readDiskFile(realPath, opts) {
11559
11788
  }
11560
11789
  async function writeDiskFile(realPath, bytes) {
11561
11790
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
11562
- await fs9.mkdir(path20.dirname(realPath), { recursive: true });
11791
+ await fs9.mkdir(path21.dirname(realPath), { recursive: true });
11563
11792
  await fs9.writeFile(realPath, buf);
11564
11793
  }
11565
11794
  async function readDiskDir(realPath) {
@@ -11701,28 +11930,28 @@ async function handleDifileRequest(ctx, req) {
11701
11930
 
11702
11931
  // src/main/services/simulator-temp-files/fs-channels.ts
11703
11932
  import fs10 from "node:fs/promises";
11704
- import path21 from "node:path";
11933
+ import path22 from "node:path";
11705
11934
  async function enforceSandbox(realPath) {
11706
11935
  if (typeof realPath !== "string" || realPath.length === 0) {
11707
11936
  throw new Error("sandbox: realPath must be a non-empty string");
11708
11937
  }
11709
11938
  const base = sandboxBase();
11710
11939
  const baseReal = await fs10.realpath(base).catch(() => base);
11711
- const normalized = path21.normalize(realPath);
11712
- if (normalized !== base && !normalized.startsWith(base + path21.sep)) {
11940
+ const normalized = path22.normalize(realPath);
11941
+ if (normalized !== base && !normalized.startsWith(base + path22.sep)) {
11713
11942
  throw new Error("sandbox: realPath escapes the user-data base");
11714
11943
  }
11715
11944
  let probe = normalized;
11716
- while (probe !== path21.parse(probe).root) {
11945
+ while (probe !== path22.parse(probe).root) {
11717
11946
  try {
11718
11947
  const resolved = await fs10.realpath(probe);
11719
- if (resolved !== baseReal && !resolved.startsWith(baseReal + path21.sep)) {
11948
+ if (resolved !== baseReal && !resolved.startsWith(baseReal + path22.sep)) {
11720
11949
  throw new Error("sandbox: realPath escapes the user-data base via symlink");
11721
11950
  }
11722
11951
  break;
11723
11952
  } catch (err2) {
11724
11953
  if (err2.code === "ENOENT") {
11725
- probe = path21.dirname(probe);
11954
+ probe = path22.dirname(probe);
11726
11955
  continue;
11727
11956
  }
11728
11957
  throw err2;
@@ -11778,10 +12007,10 @@ function setupSimulatorTempFiles(simSession) {
11778
12007
  const store = /* @__PURE__ */ new Map();
11779
12008
  const pendingWaiters = /* @__PURE__ */ new Map();
11780
12009
  let disposed = false;
11781
- function drainWaiters(path26) {
11782
- const list = pendingWaiters.get(path26);
12010
+ function drainWaiters(path27) {
12011
+ const list = pendingWaiters.get(path27);
11783
12012
  if (!list) return;
11784
- pendingWaiters.delete(path26);
12013
+ pendingWaiters.delete(path27);
11785
12014
  for (const fn of list) fn();
11786
12015
  }
11787
12016
  function drainAllWaiters() {
@@ -11794,15 +12023,15 @@ function setupSimulatorTempFiles(simSession) {
11794
12023
  const registry = new IpcRegistry(simulatorOnlyPolicy);
11795
12024
  registry.on("simulator:temp-file:write", (_event, payload) => {
11796
12025
  if (disposed) return;
11797
- const { path: path26, mime, bytes } = payload;
11798
- registerTempFile(store, path26, mime, bytes);
12026
+ const { path: path27, mime, bytes } = payload;
12027
+ registerTempFile(store, path27, mime, bytes);
11799
12028
  enforceStoreCap(store);
11800
- drainWaiters(path26);
12029
+ drainWaiters(path27);
11801
12030
  });
11802
12031
  registry.on("simulator:temp-file:revoke", (_event, payload) => {
11803
12032
  if (disposed) return;
11804
- const { path: path26 } = payload;
11805
- revokeTempFile(store, path26);
12033
+ const { path: path27 } = payload;
12034
+ revokeTempFile(store, path27);
11806
12035
  });
11807
12036
  registry.on("simulator:temp-file:revoke-all", () => {
11808
12037
  if (disposed) return;
@@ -11948,11 +12177,11 @@ function setupSimulatorSessionPolicy() {
11948
12177
  import http2 from "node:http";
11949
12178
  import nodeFs3 from "node:fs";
11950
12179
  import fs11 from "node:fs/promises";
11951
- import path23 from "node:path";
12180
+ import path24 from "node:path";
11952
12181
 
11953
12182
  // src/main/services/fs-watch-sse.ts
11954
12183
  import nodeFs2 from "node:fs";
11955
- import path22 from "node:path";
12184
+ import path23 from "node:path";
11956
12185
 
11957
12186
  // src/main/services/http-json.ts
11958
12187
  function jsonRes(res, code, obj) {
@@ -11964,7 +12193,7 @@ function jsonRes(res, code, obj) {
11964
12193
  // src/main/services/fs-watch-sse.ts
11965
12194
  var WATCH_DEBOUNCE_MS = 80;
11966
12195
  function shouldReportWatchPath(rel) {
11967
- if (rel === "" || rel.startsWith("..") || path22.isAbsolute(rel)) return false;
12196
+ if (rel === "" || rel.startsWith("..") || path23.isAbsolute(rel)) return false;
11968
12197
  return !rel.split("/").some((segment) => SKIP_DIRS.has(segment));
11969
12198
  }
11970
12199
  function handleFsWatchRequest(req, res, getProjectRoot) {
@@ -12005,7 +12234,7 @@ function handleFsWatchRequest(req, res, getProjectRoot) {
12005
12234
  `);
12006
12235
  }
12007
12236
  function onChange(_event, filename) {
12008
- const rel = filename ? filename.toString().split(path22.sep).join("/") : ".";
12237
+ const rel = filename ? filename.toString().split(path23.sep).join("/") : ".";
12009
12238
  if (rel !== "." && !shouldReportWatchPath(rel)) return;
12010
12239
  pending.add(rel);
12011
12240
  if (!debounceTimer) {
@@ -12060,8 +12289,8 @@ function setIsolationHeaders(res) {
12060
12289
  }
12061
12290
  function containedStaticPath(root, pathname) {
12062
12291
  const rel = pathname.replace(/^\/+/, "") || "index.html";
12063
- const resolved = path23.resolve(root, rel);
12064
- if (resolved !== root && !resolved.startsWith(root + path23.sep)) return null;
12292
+ const resolved = path24.resolve(root, rel);
12293
+ if (resolved !== root && !resolved.startsWith(root + path24.sep)) return null;
12065
12294
  return resolved;
12066
12295
  }
12067
12296
  function serveStaticFile(res, root, pathname) {
@@ -12072,7 +12301,7 @@ function serveStaticFile(res, root, pathname) {
12072
12301
  return;
12073
12302
  }
12074
12303
  Promise.all([fs11.realpath(root), fs11.realpath(candidate)]).then(([realRoot, realFile]) => {
12075
- if (realFile !== realRoot && !realFile.startsWith(realRoot + path23.sep)) {
12304
+ if (realFile !== realRoot && !realFile.startsWith(realRoot + path24.sep)) {
12076
12305
  res.writeHead(403);
12077
12306
  res.end("Forbidden");
12078
12307
  return;
@@ -12084,7 +12313,7 @@ function serveStaticFile(res, root, pathname) {
12084
12313
  return;
12085
12314
  }
12086
12315
  res.writeHead(200, {
12087
- "Content-Type": MIME[path23.extname(realFile)] ?? "application/octet-stream",
12316
+ "Content-Type": MIME[path24.extname(realFile)] ?? "application/octet-stream",
12088
12317
  "Content-Length": stat.size,
12089
12318
  "Cache-Control": "no-store"
12090
12319
  });
@@ -12238,7 +12467,7 @@ async function listFilesRecursive(dir, base = "") {
12238
12467
  for (const e of entries) {
12239
12468
  const rel = base ? `${base}/${e.name}` : e.name;
12240
12469
  if (e.isDirectory()) {
12241
- out.push(...await listFilesRecursive(path23.join(dir, e.name), rel));
12470
+ out.push(...await listFilesRecursive(path24.join(dir, e.name), rel));
12242
12471
  } else if (e.isFile()) {
12243
12472
  out.push(rel);
12244
12473
  }
@@ -12255,9 +12484,9 @@ async function readContributedExtensions(extensionsDir) {
12255
12484
  }
12256
12485
  for (const e of entries) {
12257
12486
  if (!e.isDirectory()) continue;
12258
- const extDir = path23.join(extensionsDir, e.name);
12487
+ const extDir = path24.join(extensionsDir, e.name);
12259
12488
  try {
12260
- const pkgRaw = await fs11.readFile(path23.join(extDir, "package.json"), "utf8");
12489
+ const pkgRaw = await fs11.readFile(path24.join(extDir, "package.json"), "utf8");
12261
12490
  const files = await listFilesRecursive(extDir);
12262
12491
  out.push({ dir: e.name, packageJson: JSON.parse(pkgRaw), files });
12263
12492
  } catch {
@@ -12266,8 +12495,8 @@ async function readContributedExtensions(extensionsDir) {
12266
12495
  return out;
12267
12496
  }
12268
12497
  async function startWorkbenchCoiServer(options) {
12269
- const root = path23.resolve(options.rootDir);
12270
- const contribRoot = options.extensionsDir ? path23.resolve(options.extensionsDir) : null;
12498
+ const root = path24.resolve(options.rootDir);
12499
+ const contribRoot = options.extensionsDir ? path24.resolve(options.extensionsDir) : null;
12271
12500
  const server = http2.createServer((req, res) => {
12272
12501
  setIsolationHeaders(res);
12273
12502
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -12426,7 +12655,7 @@ var UpdateManager = class {
12426
12655
 
12427
12656
  // src/main/services/update/github-release-checker.ts
12428
12657
  import fs12 from "fs";
12429
- import path24 from "path";
12658
+ import path25 from "path";
12430
12659
  import https from "https";
12431
12660
  import { app as app14 } from "electron";
12432
12661
  function createGitHubReleaseChecker(opts) {
@@ -12452,10 +12681,10 @@ function createGitHubReleaseChecker(opts) {
12452
12681
  };
12453
12682
  },
12454
12683
  async downloadUpdate(info, onProgress) {
12455
- const tmpDir = path24.join(app14.getPath("temp"), `${opts.repo}-update`);
12684
+ const tmpDir = path25.join(app14.getPath("temp"), `${opts.repo}-update`);
12456
12685
  if (!fs12.existsSync(tmpDir)) fs12.mkdirSync(tmpDir, { recursive: true });
12457
- const fileName = path24.basename(new URL(info.downloadUrl).pathname);
12458
- const filePath = path24.join(tmpDir, fileName);
12686
+ const fileName = path25.basename(new URL(info.downloadUrl).pathname);
12687
+ const filePath = path25.join(tmpDir, fileName);
12459
12688
  await downloadFile(info.downloadUrl, filePath, opts.token, onProgress);
12460
12689
  return filePath;
12461
12690
  }
@@ -12738,7 +12967,7 @@ async function disposeContext(ctx) {
12738
12967
  function createConfiguredMainWindow(config, rendererDir2) {
12739
12968
  const mainWindow = createMainWindow({
12740
12969
  title: config.appName ?? "Dimina DevTools",
12741
- indexHtml: path25.join(rendererDir2, "entries/main/index.html"),
12970
+ indexHtml: path26.join(rendererDir2, "entries/main/index.html"),
12742
12971
  width: config.window?.width,
12743
12972
  height: config.window?.height,
12744
12973
  minWidth: config.window?.minWidth,
@@ -12811,12 +13040,20 @@ async function setupAutomation(instance) {
12811
13040
  console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
12812
13041
  }
12813
13042
  }
12814
- function setupMcp() {
13043
+ function setupMcp(context) {
12815
13044
  const settings = loadWorkbenchSettings();
12816
13045
  if (!settings.mcp.enabled) return null;
12817
13046
  const cdpPortSwitch = app15.commandLine.getSwitchValue("remote-debugging-port");
12818
13047
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
12819
- return startMcpServer(cdpPort2, settings.mcp.port);
13048
+ return startMcpServer(cdpPort2, settings.mcp.port, {
13049
+ workspace: context.workspace,
13050
+ sessionStatus: context.sessionStatus,
13051
+ compileLogs: context.compileLogBuffer,
13052
+ // The renderer owns the open path (mounting ProjectRuntime compiles and
13053
+ // attaches the simulator), so project_open pushes it there.
13054
+ requestOpenInUi: (p) => context.notify.windowOpenProject(p),
13055
+ requestNavigateBack: () => context.notify.windowNavigateBack()
13056
+ });
12820
13057
  }
12821
13058
  function wireAppWindowEvents(config, instance, isOnProjectScreen) {
12822
13059
  const { mainWindow, context } = instance;
@@ -12952,7 +13189,7 @@ async function createDevtoolsRuntime(config = {}, onInstanceCreated) {
12952
13189
  context.registry.add(() => instance.updateManager.dispose());
12953
13190
  }
12954
13191
  await setupAutomation(instance);
12955
- const mcp = setupMcp();
13192
+ const mcp = setupMcp(context);
12956
13193
  if (mcp) context.registry.add(mcp);
12957
13194
  const getActiveAppId = () => {
12958
13195
  const session5 = context.workspace.getSession();
@@ -13063,8 +13300,8 @@ async function createDevtoolsRuntime(config = {}, onInstanceCreated) {
13063
13300
  bridge: context.bridge
13064
13301
  }));
13065
13302
  }
13066
- const bundleDir = config.editorViewConfig?.bundleDir ?? path25.join(devtoolsPackageRoot, "dist/vscode-workbench");
13067
- if (!fs13.existsSync(path25.join(bundleDir, "index.html"))) {
13303
+ const bundleDir = config.editorViewConfig?.bundleDir ?? path26.join(devtoolsPackageRoot, "dist/vscode-workbench");
13304
+ if (!fs13.existsSync(path26.join(bundleDir, "index.html"))) {
13068
13305
  console.warn(
13069
13306
  `[workbench] editor bundle not found at ${bundleDir} \u2014 skipping embedded editor assembly`
13070
13307
  );