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

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 (43) hide show
  1. package/dist/main/app/app.js +11 -3
  2. package/dist/main/index.bundle.js +319 -81
  3. package/dist/main/ipc/settings.js +4 -0
  4. package/dist/main/services/mcp/server.d.ts +2 -1
  5. package/dist/main/services/mcp/server.js +6 -3
  6. package/dist/main/services/mcp/tools/project-tools.d.ts +49 -0
  7. package/dist/main/services/mcp/tools/project-tools.js +121 -0
  8. package/dist/main/services/notifications/renderer-notifier.d.ts +11 -0
  9. package/dist/main/services/notifications/renderer-notifier.js +3 -0
  10. package/dist/main/services/workbench-context.d.ts +14 -0
  11. package/dist/main/services/workbench-context.js +9 -1
  12. package/dist/main/services/workspace/compile-log-buffer.d.ts +40 -0
  13. package/dist/main/services/workspace/compile-log-buffer.js +41 -0
  14. package/dist/main/services/workspace/session-status-store.d.ts +47 -0
  15. package/dist/main/services/workspace/session-status-store.js +73 -0
  16. package/dist/main/services/workspace/status-tap.d.ts +20 -0
  17. package/dist/main/services/workspace/status-tap.js +32 -0
  18. package/dist/native-host/common/common.js +58 -154
  19. package/dist/native-host/container/pageFrame.css +1 -1
  20. package/dist/native-host/render/render.js +3297 -6602
  21. package/dist/native-host/service/service.js +2 -2
  22. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  23. package/dist/preload/windows/simulator.cjs.map +1 -1
  24. package/dist/renderer/assets/index-BJlFbXHb.js +49 -0
  25. package/dist/renderer/assets/{input-dsgeTmVX.js → input-xdmfv-eH.js} +2 -2
  26. package/dist/renderer/assets/{ipc-transport-BgFdT9bT.js → ipc-transport-B3EpmfNS.js} +2 -2
  27. package/dist/renderer/assets/popover-B88e2hkg.js +2 -0
  28. package/dist/renderer/assets/{select-DAw1hzkl.js → select-CD9CJyhI.js} +2 -2
  29. package/dist/renderer/assets/settings-44hf0QtO.js +2 -0
  30. package/dist/renderer/assets/settings-api-pJKPuP1M.js +2 -0
  31. package/dist/renderer/assets/workbenchSettings-DKD5U1Ql.js +8 -0
  32. package/dist/renderer/entries/main/index.html +5 -5
  33. package/dist/renderer/entries/popover/index.html +4 -4
  34. package/dist/renderer/entries/settings/index.html +4 -4
  35. package/dist/renderer/entries/workbench-settings/index.html +3 -3
  36. package/dist/shared/ipc-channels.d.ts +2 -0
  37. package/dist/shared/ipc-channels.js +2 -0
  38. package/package.json +5 -5
  39. package/dist/renderer/assets/index-M5NDq_vi.js +0 -49
  40. package/dist/renderer/assets/popover-Disfu1cx.js +0 -2
  41. package/dist/renderer/assets/settings-M_8GFW2M.js +0 -2
  42. package/dist/renderer/assets/settings-api-DFR5eDl_.js +0 -2
  43. 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");
@@ -10813,10 +10925,127 @@ function registerContextTools(server) {
10813
10925
  );
10814
10926
  }
10815
10927
 
10816
- // src/main/services/mcp/tools/simulator-tools.ts
10928
+ // src/main/services/mcp/tools/project-tools.ts
10929
+ import path18 from "node:path";
10817
10930
  import { z as z5 } from "zod";
10818
- function ok(text) {
10819
- return { content: [{ type: "text", text }] };
10931
+ var DEFAULT_OPEN_TIMEOUT_MS = 18e4;
10932
+ var DEFAULT_WAIT_TIMEOUT_MS = 6e4;
10933
+ function text(payload) {
10934
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
10935
+ }
10936
+ function errorText(message) {
10937
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
10938
+ }
10939
+ function publicPhase(snapshot, sessionActive) {
10940
+ if (!sessionActive && snapshot.phase === "ready") return "idle";
10941
+ return snapshot.phase;
10942
+ }
10943
+ function statusPayload(host) {
10944
+ const snapshot = host.sessionStatus.get();
10945
+ const sessionActive = host.workspace.hasActiveSession();
10946
+ return {
10947
+ phase: publicPhase(snapshot, sessionActive),
10948
+ message: snapshot.message,
10949
+ projectPath: host.workspace.getProjectPath(),
10950
+ sessionActive,
10951
+ watcherAlive: snapshot.watcherAlive,
10952
+ updatedAt: snapshot.updatedAt,
10953
+ generation: snapshot.generation
10954
+ };
10955
+ }
10956
+ function registerProjectTools(server, host) {
10957
+ server.tool(
10958
+ "project_open",
10959
+ "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.",
10960
+ {
10961
+ path: z5.string().describe("Absolute path to the mini-program project directory"),
10962
+ timeoutMs: z5.number().optional().default(DEFAULT_OPEN_TIMEOUT_MS).describe("Max time to wait for the compile to settle")
10963
+ },
10964
+ async ({ path: rawPath, timeoutMs }) => {
10965
+ const dir = path18.resolve(rawPath);
10966
+ const current = host.sessionStatus.get();
10967
+ if (host.workspace.getProjectPath() === dir && host.workspace.hasActiveSession() && current.phase === "ready") {
10968
+ return text({ ...statusPayload(host), note: "project already open" });
10969
+ }
10970
+ const invalid = await host.workspace.validateProjectDir(dir);
10971
+ if (invalid) return errorText(invalid);
10972
+ let project;
10973
+ if (await host.workspace.hasProject(dir)) {
10974
+ const known = (await host.workspace.listProjects()).find((p) => p.path === dir);
10975
+ project = known ?? { name: path18.basename(dir), path: dir };
10976
+ } else {
10977
+ project = await host.workspace.addProject(dir);
10978
+ }
10979
+ const afterGeneration = host.sessionStatus.get().generation;
10980
+ host.requestOpenInUi({ name: project.name, path: project.path });
10981
+ let settled;
10982
+ try {
10983
+ settled = await host.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
10984
+ } catch (err2) {
10985
+ return errorText(err2 instanceof Error ? err2.message : String(err2));
10986
+ }
10987
+ if (settled.phase === "error") {
10988
+ return errorText(
10989
+ `compile failed: ${settled.message} \u2014 call compile_logs (stream: "stderr") for details`
10990
+ );
10991
+ }
10992
+ return text(statusPayload(host));
10993
+ }
10994
+ );
10995
+ server.tool(
10996
+ "project_close",
10997
+ "Close the currently open project session and return the workbench to the project list.",
10998
+ {},
10999
+ async () => {
11000
+ if (!host.workspace.getProjectPath() && !host.workspace.hasActiveSession()) {
11001
+ return text({ closed: false, note: "no project is open" });
11002
+ }
11003
+ await host.workspace.closeProject();
11004
+ host.requestNavigateBack();
11005
+ return text({ closed: true });
11006
+ }
11007
+ );
11008
+ server.tool(
11009
+ "project_status",
11010
+ "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.",
11011
+ {},
11012
+ async () => text(statusPayload(host))
11013
+ );
11014
+ server.tool(
11015
+ "project_wait_ready",
11016
+ '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.',
11017
+ {
11018
+ afterGeneration: z5.number().optional().describe("Only accept states recorded after this generation (from project_status). Omit to accept the current state when already settled."),
11019
+ timeoutMs: z5.number().optional().default(DEFAULT_WAIT_TIMEOUT_MS).describe("Max time to wait")
11020
+ },
11021
+ async ({ afterGeneration, timeoutMs }) => {
11022
+ try {
11023
+ await host.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
11024
+ } catch (err2) {
11025
+ return errorText(err2 instanceof Error ? err2.message : String(err2));
11026
+ }
11027
+ return text(statusPayload(host));
11028
+ }
11029
+ );
11030
+ server.tool(
11031
+ "compile_logs",
11032
+ "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.",
11033
+ {
11034
+ cursor: z5.number().optional().default(0).describe("Return only lines after this cursor (0 = from the start of the buffer)"),
11035
+ limit: z5.number().optional().default(100).describe("Max lines to return"),
11036
+ stream: z5.enum(["stdout", "stderr"]).optional().describe("Only lines from this stream (stderr carries compile errors)")
11037
+ },
11038
+ async ({ cursor, limit, stream }) => {
11039
+ const read = host.compileLogs.read({ afterSeq: cursor, limit, stream });
11040
+ return text(read);
11041
+ }
11042
+ );
11043
+ }
11044
+
11045
+ // src/main/services/mcp/tools/simulator-tools.ts
11046
+ import { z as z6 } from "zod";
11047
+ function ok(text2) {
11048
+ return { content: [{ type: "text", text: text2 }] };
10820
11049
  }
10821
11050
  function err(msg) {
10822
11051
  return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
@@ -10872,8 +11101,8 @@ async function tapSelector(c, selector, nth) {
10872
11101
  await clickAt(c, value.x, value.y);
10873
11102
  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
11103
  }
10875
- async function typeText(c, selector, nth, text) {
10876
- if (!selector || typeof text !== "string") return err("type requires selector and text");
11104
+ async function typeText(c, selector, nth, text2) {
11105
+ if (!selector || typeof text2 !== "string") return err("type requires selector and text");
10877
11106
  const { root } = await c.DOM.getDocument({ depth: 0 });
10878
11107
  const { nodeIds } = await c.DOM.querySelectorAll({ nodeId: root.nodeId, selector });
10879
11108
  if (!nodeIds || nodeIds.length === 0) return err(`selector matched no elements: ${selector}`);
@@ -10881,13 +11110,13 @@ async function typeText(c, selector, nth, text) {
10881
11110
  if (index < 0 || index >= nodeIds.length) return err(`nth ${index} out of range (matches: ${nodeIds.length})`);
10882
11111
  await c.DOM.focus({ nodeId: nodeIds[index] });
10883
11112
  try {
10884
- await c.Input.insertText({ text });
11113
+ await c.Input.insertText({ text: text2 });
10885
11114
  } catch {
10886
- for (const ch of text) {
11115
+ for (const ch of text2) {
10887
11116
  await c.Input.dispatchKeyEvent({ type: "char", text: ch });
10888
11117
  }
10889
11118
  }
10890
- return ok(`Typed ${text.length} char(s) into ${selector}[${index}]`);
11119
+ return ok(`Typed ${text2.length} char(s) into ${selector}[${index}]`);
10891
11120
  }
10892
11121
  async function scrollAt(c, x, y, deltaX, deltaY) {
10893
11122
  if (typeof x !== "number" || typeof y !== "number" || typeof deltaX !== "number" || typeof deltaY !== "number") {
@@ -10907,8 +11136,8 @@ async function dispatchKey(c, key) {
10907
11136
  }
10908
11137
  function registerSimulatorTools(server) {
10909
11138
  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")
11139
+ url: z6.string().optional().describe("URL to navigate to"),
11140
+ reload: z6.boolean().optional().describe("If true, reload current page instead of navigating")
10912
11141
  }, async ({ url, reload }) => {
10913
11142
  const c = getClient("simulator");
10914
11143
  if (reload) {
@@ -10923,16 +11152,16 @@ function registerSimulatorTools(server) {
10923
11152
  return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
10924
11153
  });
10925
11154
  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 }) => {
11155
+ action: z6.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
11156
+ x: z6.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
11157
+ y: z6.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
11158
+ selector: z6.string().optional().describe("CSS selector for `tap_selector` or `type`"),
11159
+ nth: z6.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
11160
+ text: z6.string().optional().describe("Text to insert for `type`"),
11161
+ key: z6.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
11162
+ deltaX: z6.number().optional().describe("Horizontal wheel delta for `scroll`"),
11163
+ deltaY: z6.number().optional().describe("Vertical wheel delta for `scroll`")
11164
+ }, async ({ action, x, y, selector, nth, text: text2, key, deltaX, deltaY }) => {
10936
11165
  const c = getClient("simulator");
10937
11166
  switch (action) {
10938
11167
  case "tap_coord":
@@ -10940,7 +11169,7 @@ function registerSimulatorTools(server) {
10940
11169
  case "tap_selector":
10941
11170
  return tapSelector(c, selector, nth);
10942
11171
  case "type":
10943
- return typeText(c, selector, nth, text);
11172
+ return typeText(c, selector, nth, text2);
10944
11173
  case "scroll":
10945
11174
  return scrollAt(c, x, y, deltaX, deltaY);
10946
11175
  case "key":
@@ -10963,7 +11192,7 @@ function registerWorkbenchTools(server) {
10963
11192
  // src/main/services/mcp/server.ts
10964
11193
  var require2 = createRequire(import.meta.url);
10965
11194
  var pkg = require2("../../../../package.json");
10966
- function buildServer() {
11195
+ function buildServer(projectHost) {
10967
11196
  const server = new McpServer({
10968
11197
  name: "@dimina-kit/devtools",
10969
11198
  version: pkg.version
@@ -10973,9 +11202,10 @@ function buildServer() {
10973
11202
  registerCommonTargetTools(server, "workbench");
10974
11203
  registerWorkbenchTools(server);
10975
11204
  registerContextTools(server);
11205
+ registerProjectTools(server, projectHost);
10976
11206
  return server;
10977
11207
  }
10978
- function startMcpServer(resolvedCdpPort, mcpPort) {
11208
+ function startMcpServer(resolvedCdpPort, mcpPort, projectHost) {
10979
11209
  setCdpPort(resolvedCdpPort);
10980
11210
  connectTarget("simulator").catch(() => {
10981
11211
  });
@@ -10989,7 +11219,7 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
10989
11219
  transports.set(transport.sessionId, transport);
10990
11220
  res.on("close", () => transports.delete(transport.sessionId));
10991
11221
  try {
10992
- await buildServer().connect(transport);
11222
+ await buildServer(projectHost).connect(transport);
10993
11223
  } catch (err2) {
10994
11224
  transports.delete(transport.sessionId);
10995
11225
  if (!res.headersSent) res.writeHead(500).end("MCP connect failed");
@@ -11206,7 +11436,7 @@ function setupSimulatorCurrentPage(host, options) {
11206
11436
 
11207
11437
  // src/main/services/render-inspect/index.ts
11208
11438
  import { readFileSync } from "node:fs";
11209
- import path18 from "node:path";
11439
+ import path19 from "node:path";
11210
11440
  var DEFAULT_SOURCE_PATH = "dist/render-host/render-inspect.js";
11211
11441
  var HIGHLIGHT_CONFIG = {
11212
11442
  showInfo: true,
@@ -11232,7 +11462,7 @@ function withTimeout(p, ms) {
11232
11462
  });
11233
11463
  }
11234
11464
  function createRenderInspector(options = {}) {
11235
- const loadSource = options.loadSource ?? (() => readFileSync(path18.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
11465
+ const loadSource = options.loadSource ?? (() => readFileSync(path19.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
11236
11466
  let cachedSource = null;
11237
11467
  const injected = /* @__PURE__ */ new Set();
11238
11468
  const broker = options.broker ?? createCdpSessionBroker({ connections: options.connections });
@@ -11344,13 +11574,13 @@ function createRenderInspector(options = {}) {
11344
11574
  import { toDisposable as toDisposable6 } from "@dimina-kit/electron-deck/main";
11345
11575
 
11346
11576
  // src/main/services/simulator-temp-files/store.ts
11347
- function registerTempFile(store, path26, mime, bytes) {
11577
+ function registerTempFile(store, path27, mime, bytes) {
11348
11578
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
11349
- store.delete(path26);
11350
- store.set(path26, { bytes: buf, mime });
11579
+ store.delete(path27);
11580
+ store.set(path27, { bytes: buf, mime });
11351
11581
  }
11352
- function revokeTempFile(store, path26) {
11353
- store.delete(path26);
11582
+ function revokeTempFile(store, path27) {
11583
+ store.delete(path27);
11354
11584
  }
11355
11585
  function revokeAllTempFiles(store) {
11356
11586
  store.clear();
@@ -11358,14 +11588,14 @@ function revokeAllTempFiles(store) {
11358
11588
 
11359
11589
  // src/shared/vpath.ts
11360
11590
  import os from "node:os";
11361
- import path19 from "node:path";
11591
+ import path20 from "node:path";
11362
11592
  var DIFILE_PREFIX = "difile://";
11363
11593
  function sandboxBase() {
11364
11594
  const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
11365
11595
  if (env) {
11366
- return path19.join(env, "files");
11596
+ return path20.join(env, "files");
11367
11597
  }
11368
- return path19.join(os.homedir(), ".dimina", "files");
11598
+ return path20.join(os.homedir(), ".dimina", "files");
11369
11599
  }
11370
11600
  function resolveVPath(url) {
11371
11601
  if (typeof url !== "string") return null;
@@ -11399,15 +11629,15 @@ function resolveVPath(url) {
11399
11629
  return { kind, writable };
11400
11630
  }
11401
11631
  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;
11632
+ const joined = path20.join(base, decoded);
11633
+ const normalized = path20.normalize(joined);
11634
+ if (normalized !== base && !normalized.startsWith(base + path20.sep)) return null;
11405
11635
  return { kind, writable, realPath: normalized };
11406
11636
  }
11407
11637
 
11408
11638
  // src/main/services/simulator-temp-files/disk.ts
11409
11639
  import fs9 from "node:fs/promises";
11410
- import path20 from "node:path";
11640
+ import path21 from "node:path";
11411
11641
  var EXT_MIME = {
11412
11642
  ".txt": "text/plain",
11413
11643
  ".log": "text/plain",
@@ -11485,7 +11715,7 @@ function sniffMime(head) {
11485
11715
  return hit ? hit.mime : null;
11486
11716
  }
11487
11717
  function extMime(realPath) {
11488
- const ext = path20.extname(realPath).toLowerCase();
11718
+ const ext = path21.extname(realPath).toLowerCase();
11489
11719
  if (!ext) return null;
11490
11720
  return EXT_MIME[ext] ?? null;
11491
11721
  }
@@ -11559,7 +11789,7 @@ async function readDiskFile(realPath, opts) {
11559
11789
  }
11560
11790
  async function writeDiskFile(realPath, bytes) {
11561
11791
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
11562
- await fs9.mkdir(path20.dirname(realPath), { recursive: true });
11792
+ await fs9.mkdir(path21.dirname(realPath), { recursive: true });
11563
11793
  await fs9.writeFile(realPath, buf);
11564
11794
  }
11565
11795
  async function readDiskDir(realPath) {
@@ -11701,28 +11931,28 @@ async function handleDifileRequest(ctx, req) {
11701
11931
 
11702
11932
  // src/main/services/simulator-temp-files/fs-channels.ts
11703
11933
  import fs10 from "node:fs/promises";
11704
- import path21 from "node:path";
11934
+ import path22 from "node:path";
11705
11935
  async function enforceSandbox(realPath) {
11706
11936
  if (typeof realPath !== "string" || realPath.length === 0) {
11707
11937
  throw new Error("sandbox: realPath must be a non-empty string");
11708
11938
  }
11709
11939
  const base = sandboxBase();
11710
11940
  const baseReal = await fs10.realpath(base).catch(() => base);
11711
- const normalized = path21.normalize(realPath);
11712
- if (normalized !== base && !normalized.startsWith(base + path21.sep)) {
11941
+ const normalized = path22.normalize(realPath);
11942
+ if (normalized !== base && !normalized.startsWith(base + path22.sep)) {
11713
11943
  throw new Error("sandbox: realPath escapes the user-data base");
11714
11944
  }
11715
11945
  let probe = normalized;
11716
- while (probe !== path21.parse(probe).root) {
11946
+ while (probe !== path22.parse(probe).root) {
11717
11947
  try {
11718
11948
  const resolved = await fs10.realpath(probe);
11719
- if (resolved !== baseReal && !resolved.startsWith(baseReal + path21.sep)) {
11949
+ if (resolved !== baseReal && !resolved.startsWith(baseReal + path22.sep)) {
11720
11950
  throw new Error("sandbox: realPath escapes the user-data base via symlink");
11721
11951
  }
11722
11952
  break;
11723
11953
  } catch (err2) {
11724
11954
  if (err2.code === "ENOENT") {
11725
- probe = path21.dirname(probe);
11955
+ probe = path22.dirname(probe);
11726
11956
  continue;
11727
11957
  }
11728
11958
  throw err2;
@@ -11778,10 +12008,10 @@ function setupSimulatorTempFiles(simSession) {
11778
12008
  const store = /* @__PURE__ */ new Map();
11779
12009
  const pendingWaiters = /* @__PURE__ */ new Map();
11780
12010
  let disposed = false;
11781
- function drainWaiters(path26) {
11782
- const list = pendingWaiters.get(path26);
12011
+ function drainWaiters(path27) {
12012
+ const list = pendingWaiters.get(path27);
11783
12013
  if (!list) return;
11784
- pendingWaiters.delete(path26);
12014
+ pendingWaiters.delete(path27);
11785
12015
  for (const fn of list) fn();
11786
12016
  }
11787
12017
  function drainAllWaiters() {
@@ -11794,15 +12024,15 @@ function setupSimulatorTempFiles(simSession) {
11794
12024
  const registry = new IpcRegistry(simulatorOnlyPolicy);
11795
12025
  registry.on("simulator:temp-file:write", (_event, payload) => {
11796
12026
  if (disposed) return;
11797
- const { path: path26, mime, bytes } = payload;
11798
- registerTempFile(store, path26, mime, bytes);
12027
+ const { path: path27, mime, bytes } = payload;
12028
+ registerTempFile(store, path27, mime, bytes);
11799
12029
  enforceStoreCap(store);
11800
- drainWaiters(path26);
12030
+ drainWaiters(path27);
11801
12031
  });
11802
12032
  registry.on("simulator:temp-file:revoke", (_event, payload) => {
11803
12033
  if (disposed) return;
11804
- const { path: path26 } = payload;
11805
- revokeTempFile(store, path26);
12034
+ const { path: path27 } = payload;
12035
+ revokeTempFile(store, path27);
11806
12036
  });
11807
12037
  registry.on("simulator:temp-file:revoke-all", () => {
11808
12038
  if (disposed) return;
@@ -11948,11 +12178,11 @@ function setupSimulatorSessionPolicy() {
11948
12178
  import http2 from "node:http";
11949
12179
  import nodeFs3 from "node:fs";
11950
12180
  import fs11 from "node:fs/promises";
11951
- import path23 from "node:path";
12181
+ import path24 from "node:path";
11952
12182
 
11953
12183
  // src/main/services/fs-watch-sse.ts
11954
12184
  import nodeFs2 from "node:fs";
11955
- import path22 from "node:path";
12185
+ import path23 from "node:path";
11956
12186
 
11957
12187
  // src/main/services/http-json.ts
11958
12188
  function jsonRes(res, code, obj) {
@@ -11964,7 +12194,7 @@ function jsonRes(res, code, obj) {
11964
12194
  // src/main/services/fs-watch-sse.ts
11965
12195
  var WATCH_DEBOUNCE_MS = 80;
11966
12196
  function shouldReportWatchPath(rel) {
11967
- if (rel === "" || rel.startsWith("..") || path22.isAbsolute(rel)) return false;
12197
+ if (rel === "" || rel.startsWith("..") || path23.isAbsolute(rel)) return false;
11968
12198
  return !rel.split("/").some((segment) => SKIP_DIRS.has(segment));
11969
12199
  }
11970
12200
  function handleFsWatchRequest(req, res, getProjectRoot) {
@@ -12005,7 +12235,7 @@ function handleFsWatchRequest(req, res, getProjectRoot) {
12005
12235
  `);
12006
12236
  }
12007
12237
  function onChange(_event, filename) {
12008
- const rel = filename ? filename.toString().split(path22.sep).join("/") : ".";
12238
+ const rel = filename ? filename.toString().split(path23.sep).join("/") : ".";
12009
12239
  if (rel !== "." && !shouldReportWatchPath(rel)) return;
12010
12240
  pending.add(rel);
12011
12241
  if (!debounceTimer) {
@@ -12060,8 +12290,8 @@ function setIsolationHeaders(res) {
12060
12290
  }
12061
12291
  function containedStaticPath(root, pathname) {
12062
12292
  const rel = pathname.replace(/^\/+/, "") || "index.html";
12063
- const resolved = path23.resolve(root, rel);
12064
- if (resolved !== root && !resolved.startsWith(root + path23.sep)) return null;
12293
+ const resolved = path24.resolve(root, rel);
12294
+ if (resolved !== root && !resolved.startsWith(root + path24.sep)) return null;
12065
12295
  return resolved;
12066
12296
  }
12067
12297
  function serveStaticFile(res, root, pathname) {
@@ -12072,7 +12302,7 @@ function serveStaticFile(res, root, pathname) {
12072
12302
  return;
12073
12303
  }
12074
12304
  Promise.all([fs11.realpath(root), fs11.realpath(candidate)]).then(([realRoot, realFile]) => {
12075
- if (realFile !== realRoot && !realFile.startsWith(realRoot + path23.sep)) {
12305
+ if (realFile !== realRoot && !realFile.startsWith(realRoot + path24.sep)) {
12076
12306
  res.writeHead(403);
12077
12307
  res.end("Forbidden");
12078
12308
  return;
@@ -12084,7 +12314,7 @@ function serveStaticFile(res, root, pathname) {
12084
12314
  return;
12085
12315
  }
12086
12316
  res.writeHead(200, {
12087
- "Content-Type": MIME[path23.extname(realFile)] ?? "application/octet-stream",
12317
+ "Content-Type": MIME[path24.extname(realFile)] ?? "application/octet-stream",
12088
12318
  "Content-Length": stat.size,
12089
12319
  "Cache-Control": "no-store"
12090
12320
  });
@@ -12238,7 +12468,7 @@ async function listFilesRecursive(dir, base = "") {
12238
12468
  for (const e of entries) {
12239
12469
  const rel = base ? `${base}/${e.name}` : e.name;
12240
12470
  if (e.isDirectory()) {
12241
- out.push(...await listFilesRecursive(path23.join(dir, e.name), rel));
12471
+ out.push(...await listFilesRecursive(path24.join(dir, e.name), rel));
12242
12472
  } else if (e.isFile()) {
12243
12473
  out.push(rel);
12244
12474
  }
@@ -12255,9 +12485,9 @@ async function readContributedExtensions(extensionsDir) {
12255
12485
  }
12256
12486
  for (const e of entries) {
12257
12487
  if (!e.isDirectory()) continue;
12258
- const extDir = path23.join(extensionsDir, e.name);
12488
+ const extDir = path24.join(extensionsDir, e.name);
12259
12489
  try {
12260
- const pkgRaw = await fs11.readFile(path23.join(extDir, "package.json"), "utf8");
12490
+ const pkgRaw = await fs11.readFile(path24.join(extDir, "package.json"), "utf8");
12261
12491
  const files = await listFilesRecursive(extDir);
12262
12492
  out.push({ dir: e.name, packageJson: JSON.parse(pkgRaw), files });
12263
12493
  } catch {
@@ -12266,8 +12496,8 @@ async function readContributedExtensions(extensionsDir) {
12266
12496
  return out;
12267
12497
  }
12268
12498
  async function startWorkbenchCoiServer(options) {
12269
- const root = path23.resolve(options.rootDir);
12270
- const contribRoot = options.extensionsDir ? path23.resolve(options.extensionsDir) : null;
12499
+ const root = path24.resolve(options.rootDir);
12500
+ const contribRoot = options.extensionsDir ? path24.resolve(options.extensionsDir) : null;
12271
12501
  const server = http2.createServer((req, res) => {
12272
12502
  setIsolationHeaders(res);
12273
12503
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -12426,7 +12656,7 @@ var UpdateManager = class {
12426
12656
 
12427
12657
  // src/main/services/update/github-release-checker.ts
12428
12658
  import fs12 from "fs";
12429
- import path24 from "path";
12659
+ import path25 from "path";
12430
12660
  import https from "https";
12431
12661
  import { app as app14 } from "electron";
12432
12662
  function createGitHubReleaseChecker(opts) {
@@ -12452,10 +12682,10 @@ function createGitHubReleaseChecker(opts) {
12452
12682
  };
12453
12683
  },
12454
12684
  async downloadUpdate(info, onProgress) {
12455
- const tmpDir = path24.join(app14.getPath("temp"), `${opts.repo}-update`);
12685
+ const tmpDir = path25.join(app14.getPath("temp"), `${opts.repo}-update`);
12456
12686
  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);
12687
+ const fileName = path25.basename(new URL(info.downloadUrl).pathname);
12688
+ const filePath = path25.join(tmpDir, fileName);
12459
12689
  await downloadFile(info.downloadUrl, filePath, opts.token, onProgress);
12460
12690
  return filePath;
12461
12691
  }
@@ -12738,7 +12968,7 @@ async function disposeContext(ctx) {
12738
12968
  function createConfiguredMainWindow(config, rendererDir2) {
12739
12969
  const mainWindow = createMainWindow({
12740
12970
  title: config.appName ?? "Dimina DevTools",
12741
- indexHtml: path25.join(rendererDir2, "entries/main/index.html"),
12971
+ indexHtml: path26.join(rendererDir2, "entries/main/index.html"),
12742
12972
  width: config.window?.width,
12743
12973
  height: config.window?.height,
12744
12974
  minWidth: config.window?.minWidth,
@@ -12811,12 +13041,20 @@ async function setupAutomation(instance) {
12811
13041
  console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
12812
13042
  }
12813
13043
  }
12814
- function setupMcp() {
13044
+ function setupMcp(context) {
12815
13045
  const settings = loadWorkbenchSettings();
12816
13046
  if (!settings.mcp.enabled) return null;
12817
13047
  const cdpPortSwitch = app15.commandLine.getSwitchValue("remote-debugging-port");
12818
13048
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
12819
- return startMcpServer(cdpPort2, settings.mcp.port);
13049
+ return startMcpServer(cdpPort2, settings.mcp.port, {
13050
+ workspace: context.workspace,
13051
+ sessionStatus: context.sessionStatus,
13052
+ compileLogs: context.compileLogBuffer,
13053
+ // The renderer owns the open path (mounting ProjectRuntime compiles and
13054
+ // attaches the simulator), so project_open pushes it there.
13055
+ requestOpenInUi: (p) => context.notify.windowOpenProject(p),
13056
+ requestNavigateBack: () => context.notify.windowNavigateBack()
13057
+ });
12820
13058
  }
12821
13059
  function wireAppWindowEvents(config, instance, isOnProjectScreen) {
12822
13060
  const { mainWindow, context } = instance;
@@ -12952,7 +13190,7 @@ async function createDevtoolsRuntime(config = {}, onInstanceCreated) {
12952
13190
  context.registry.add(() => instance.updateManager.dispose());
12953
13191
  }
12954
13192
  await setupAutomation(instance);
12955
- const mcp = setupMcp();
13193
+ const mcp = setupMcp(context);
12956
13194
  if (mcp) context.registry.add(mcp);
12957
13195
  const getActiveAppId = () => {
12958
13196
  const session5 = context.workspace.getSession();
@@ -13063,8 +13301,8 @@ async function createDevtoolsRuntime(config = {}, onInstanceCreated) {
13063
13301
  bridge: context.bridge
13064
13302
  }));
13065
13303
  }
13066
- const bundleDir = config.editorViewConfig?.bundleDir ?? path25.join(devtoolsPackageRoot, "dist/vscode-workbench");
13067
- if (!fs13.existsSync(path25.join(bundleDir, "index.html"))) {
13304
+ const bundleDir = config.editorViewConfig?.bundleDir ?? path26.join(devtoolsPackageRoot, "dist/vscode-workbench");
13305
+ if (!fs13.existsSync(path26.join(bundleDir, "index.html"))) {
13068
13306
  console.warn(
13069
13307
  `[workbench] editor bundle not found at ${bundleDir} \u2014 skipping embedded editor assembly`
13070
13308
  );