minecodex 2.0.14 → 2.0.16

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 (44) hide show
  1. package/README.md +9 -0
  2. package/apps/dashboard/app.js +8 -5
  3. package/apps/dashboard/assets/data-transfer.js +2 -1
  4. package/apps/dashboard/transport.js +18 -0
  5. package/assets/brand/minecodex-icon-selected.svg +4 -0
  6. package/features/markdown-reading/src/adapters/codex/rich-text.mjs +12 -5
  7. package/features/markdown-reading/src/adapters/codex/table-actions.mjs +13 -2
  8. package/features/markdown-reading/src/domain/footnotes.mjs +4 -2
  9. package/features/markdown-reading/src/injection-source.mjs +2 -0
  10. package/features/markdown-reading/src/reading.css +3 -3
  11. package/features/notes/assets/icons/prompt-entry-selected.svg +1 -0
  12. package/features/notes/assets/icons/prompt-entry.svg +16 -0
  13. package/features/notes/codex-feature.json +3 -2
  14. package/features/notes/src/adapters/codex/insertion.mjs +92 -0
  15. package/features/notes/src/adapters/codex/requests.mjs +7 -0
  16. package/features/notes/src/frontend/entry.mjs +39 -137
  17. package/features/notes/src/frontend/icons.json +0 -2
  18. package/features/notes/src/injection-source.mjs +5 -0
  19. package/package.json +18 -5
  20. package/packages/cli/src/commands.mjs +10 -0
  21. package/packages/codex-adapter/src/renderer/environment.mjs +30 -1
  22. package/packages/plugin/assets/app.js +116 -0
  23. package/packages/plugin/assets/prompts.js +437 -0
  24. package/packages/plugin/plugin.json +16 -0
  25. package/packages/plugin/src/app.mjs +17 -0
  26. package/packages/plugin/src/control-client.mjs +41 -0
  27. package/packages/plugin/src/install.mjs +26 -0
  28. package/packages/plugin/src/navigation-icons.mjs +8 -0
  29. package/packages/plugin/src/prompts-app.mjs +45 -0
  30. package/packages/plugin/src/prompts-client.mjs +16 -0
  31. package/packages/plugin/src/server.mjs +74 -0
  32. package/packages/plugin/src/ui-resource.mjs +22 -0
  33. package/packages/runtime-host/src/codex-injection.mjs +5 -12
  34. package/packages/runtime-host/src/codex-runtime.mjs +3 -16
  35. package/packages/ui/src/install.mjs +4 -3
  36. package/packages/ui/src/native/mcp-app.mjs +32 -0
  37. package/packages/ui/src/native/mcp-navigation.mjs +52 -0
  38. package/packages/ui/src/native/mcp-theme.css +4 -0
  39. package/packages/ui/src/native/mcp-theme.mjs +20 -0
  40. package/packages/ui/assets/arrow-up-right.svg +0 -13
  41. package/packages/ui/assets/minecodex-menu-16.svg +0 -7
  42. package/packages/ui/assets/minecodex-menu-20.svg +0 -7
  43. package/packages/ui/src/components/account-menu.mjs +0 -88
  44. package/packages/ui/src/native/account-menu.mjs +0 -21
@@ -10,6 +10,7 @@ let addButton = null;
10
10
  let composerRoot = null;
11
11
  let composerThread = null;
12
12
  let composerEditable = null;
13
+ let focusedEditable = null;
13
14
  let panel = null;
14
15
  let items = [];
15
16
  let generation = 0;
@@ -25,6 +26,9 @@ function appearanceLabel(key) {
25
26
  }
26
27
  function errorMessage(error) {
27
28
  const messages = {
29
+ "No available input in the focused window": "当前窗口没有可用输入框,请先打开聊天并确认 MCX 增强服务已开启。",
30
+ "Input changed before insertion": "输入框已切换,请重新选择提示词。",
31
+ "Enable Prompts in MineCodex first": "请先在 MineCodex 中开启提示词。",
28
32
  "Open a chat before using a prompt.": "请先打开一个对话,再使用提示词。",
29
33
  "Category was not found": "未找到该分类",
30
34
  "Prompt was not found": "未找到该提示词",
@@ -281,6 +285,8 @@ function renderList() {
281
285
  async () => {
282
286
  const current = generation;
283
287
  try {
288
+ if (!composerEditable?.isConnected || !config.codex.composer(composerEditable,{purpose:"insertion"}))
289
+ throw new Error("Input changed before insertion");
284
290
  window.__codexPersonalRuntime.insertText(item.body, composerEditable);
285
291
  // 插入同步成功后再记一次调用;失败和单纯浏览不计数。
286
292
  close();
@@ -338,10 +344,15 @@ function isOpenComposerMenu(node) {
338
344
  && (node.getAttribute("data-composer-navigation-target") === "add-context"
339
345
  || node.getAttribute("aria-haspopup") === "menu");
340
346
  }
347
+ function insertionComposer() {
348
+ // 官方 App 获得 iframe 焦点时,保留用户最后操作的同窗口输入框。
349
+ const target = document.activeElement?.tagName === "IFRAME" ? focusedEditable : null;
350
+ return config.codex.composer(target,{purpose:"insertion"});
351
+ }
341
352
  function sync() {
342
353
  if (disposed || signal.aborted) return;
343
- syncNavigation();
344
- const nativeComposer = config.codex.composer(null,{purpose:"insertion"});
354
+ if (config.management) return;
355
+ const nativeComposer = panel ? config.codex.composer(composerEditable,{purpose:"insertion"}) : insertionComposer();
345
356
  const add = nativeComposer?.add;
346
357
  const card = nativeComposer?.root;
347
358
  const thread = nativeComposer?.threadId ?? null;
@@ -357,6 +368,7 @@ function sync() {
357
368
  composerRoot = card;
358
369
  }
359
370
  composerEditable = nativeComposer?.editable ?? null;
371
+ insertionBridge?.remember(nativeComposer);
360
372
  if (!add) return;
361
373
  if (
362
374
  panel &&
@@ -402,8 +414,6 @@ function sync() {
402
414
  }
403
415
  // 页面与快捷调用面板共享数据;所有编辑只存在于页面。
404
416
  // 管理页面与编辑弹窗
405
- let navButton = null;
406
- let ownNavGroup = null;
407
417
  let page = null;
408
418
  let pageGeneration = 0;
409
419
  let activeCategory = "all";
@@ -414,8 +424,6 @@ let cardResizeObserver = null;
414
424
  let searchText = "";
415
425
  let editor = null;
416
426
  let pageFocus = null;
417
- const hiddenSurfaces = new Map();
418
- let navigationRail = null;
419
427
 
420
428
  function pageControl(label, handler, primary = false, tone = "secondary") {
421
429
  return config.ui.action(control(label, handler, `pm-command${primary ? " pm-primary" : ""}`), primary ? "primary" : tone);
@@ -428,23 +436,7 @@ function pageError(error) {
428
436
  (editor ?? page.querySelector(".pm-wrap") ?? page).append(message);
429
437
  }
430
438
  function positionPage() {
431
- if (!page) return;
432
- const rail = config.codex.rail();
433
- if (!rail) {
434
- closePage();
435
- return;
436
- }
437
- const rect = rail.getBoundingClientRect();
438
- page.style.left = `${rect.right}px`;
439
- page.style.top = `${rect.top}px`;
440
- page.style.right = "4px";
441
- page.style.bottom = "4px";
442
- }
443
- function syncNavigationSelection() {
444
- navigationRail?.select(Boolean(page));
445
- }
446
- function restoreNavigationSelection() {
447
- navigationRail?.select(false);
439
+ if (page) Object.assign(page.style, {inset:"0",borderRadius:"0"});
448
440
  }
449
441
  function closePage(restoreFocus = false, restoreNavigation = true) {
450
442
  dragState?.cancel();
@@ -456,12 +448,7 @@ function closePage(restoreFocus = false, restoreNavigation = true) {
456
448
  if (page) config.ui.releasePage(page, { restoreNavigation });
457
449
  page?.remove();
458
450
  page = null;
459
- restoreNavigationSelection();
460
451
  editor = null;
461
- for (const [surface, inert] of hiddenSurfaces) surface.inert = inert;
462
- hiddenSurfaces.clear();
463
- navButton?.removeAttribute("data-selected");
464
- navButton?.removeAttribute("aria-current");
465
452
  document.documentElement.removeAttribute("data-mcx-prompts-open");
466
453
  if (restoreFocus && pageFocus?.isConnected)
467
454
  pageFocus.focus({ preventScroll: true });
@@ -487,19 +474,9 @@ function openPage() {
487
474
  page = element("section");
488
475
  page.setAttribute(MARKER, "page");
489
476
  config.ui.page(page, { sidebar:"none", onNavigate:() => closePage(false, false) });
490
- page.setAttribute("aria-label", text("Prompts", "提示词"));
477
+ page.setAttribute("aria-label", text("Prompt", "提示词"));
491
478
  document.body.append(page);
492
- // 只隔离主内容的焦点,保留导航栏与原生窗口控件。
493
- for (const surface of document.querySelectorAll(
494
- '[class*="_PageSurface_"], [class*="_Workspace_"]',
495
- )) {
496
- hiddenSurfaces.set(surface, surface.inert);
497
- surface.inert = true;
498
- }
499
479
  document.documentElement.setAttribute("data-mcx-prompts-open", "");
500
- navButton?.setAttribute("data-selected", "");
501
- navButton?.setAttribute("aria-current", "page");
502
- syncNavigationSelection();
503
480
  positionPage();
504
481
  page.append(element("div", "pm-empty", text("Loading…", "加载中…")));
505
482
  return loadPage();
@@ -666,7 +643,7 @@ function renderPage() {
666
643
  searchField,
667
644
  pageControl(text("New prompt", "新建提示词"), () => editPrompt(), true),
668
645
  );
669
- heading.append(element("h1", "", text("Prompts", "提示词")), actions);
646
+ heading.append(element("h1", "", text("Prompt", "提示词")), actions);
670
647
  const tabs = element("div", "pm-tabs");
671
648
  tabs.setAttribute("role", "tablist");
672
649
  tabs.setAttribute("aria-label", text("Categories", "分类"));
@@ -818,31 +795,9 @@ function cardPreview(item) {
818
795
  }
819
796
  async function usePagePrompt(item, trigger) {
820
797
  trigger.disabled = true;
821
- closePage();
822
- try {
823
- // Page 归还原路由后等待输入框挂载;不自动发送消息。
824
- let editable = null;
825
- for (let attempt = 0; attempt < 40 && !signal.aborted; attempt++) {
826
- editable = config.codex.composer(null, { purpose: "insertion" })?.editable;
827
- if (editable) break;
828
- await new Promise((resolve) => setTimeout(resolve, 50));
829
- }
830
- if (signal.aborted) return;
831
- if (!editable) throw new Error("Open a chat before using a prompt.");
832
- window.__codexPersonalRuntime.insertText(item.body, editable);
833
- } catch (error) {
834
- await openPage();
835
- pageError(error);
836
- return;
837
- }
838
- try {
839
- await api(`/api/prompts/${encodeURIComponent(item.id)}/use`, {
840
- method: "POST", body: { action: "insert", success: true },
841
- });
842
- } catch (error) {
843
- // 正文已插入,计数失败不能再次插入。
844
- console.warn("[Prompts] Usage could not be saved", error);
845
- }
798
+ try { await config.insertPrompt(item.id); if (!signal.aborted) await loadPage(); }
799
+ catch (error) { if (!signal.aborted) pageError(error); }
800
+ finally { trigger.disabled = false; }
846
801
  }
847
802
  let imagePreview = null;
848
803
  let imagePreviewAnchor = null;
@@ -1031,7 +986,7 @@ function emptyState(searching) {
1031
986
  const empty = element("div", "pm-empty-state");
1032
987
  const art = element("span", "pm-empty-art", searching ? "🔎" : "📖");
1033
988
  art.setAttribute("role", "img");
1034
- art.setAttribute("aria-label", text("Prompts", "提示词"));
989
+ art.setAttribute("aria-label", text("Prompt", "提示词"));
1035
990
  empty.append(
1036
991
  art,
1037
992
  element(
@@ -1620,70 +1575,16 @@ function editCategory(category = null) {
1620
1575
  name.focus();
1621
1576
  }
1622
1577
  // 原生导航挂载与卸载
1623
- function syncNavigation() {
1624
- const rail = config.codex.rail();
1625
- if (!rail) {
1626
- navButton?.remove();
1627
- navButton = null;
1628
- if (page) closePage();
1629
- return;
1630
- }
1631
- const slot = config.codex.railSlot(ownNavGroup);
1632
- if (!slot) return;
1633
- const { template:source, scroll } = slot;
1634
- let group = slot.group;
1635
- if (!group) {
1636
- if (!ownNavGroup) {
1637
- ownNavGroup = element("div", "flex shrink-0 flex-col items-center gap-2");
1638
- ownNavGroup.setAttribute(MARKER, "nav-group");
1639
- }
1640
- if (ownNavGroup.parentElement !== scroll) scroll.append(ownNavGroup);
1641
- group = ownNavGroup;
1642
- }
1643
- if (!navButton?.isConnected) {
1644
- navigationRail?.restore();
1645
- navButton = source.cloneNode(false);
1646
- navButton.removeAttribute("id");
1647
- navButton.removeAttribute("title");
1648
- navButton.removeAttribute("aria-describedby");
1649
- navButton.removeAttribute("data-sidebar-destination");
1650
- navButton.removeAttribute("data-selected");
1651
- navButton.removeAttribute("aria-current");
1652
- navButton.removeAttribute("data-suppress-active-style");
1653
- navButton.setAttribute(MARKER, "nav");
1654
- navButton.setAttribute("aria-label", text("Prompts", "提示词"));
1655
- navButton.setAttribute("data-minecodex-tooltip", text("Prompts", "提示词"));
1656
- const inner = element("span", source.firstElementChild?.className ?? "");
1657
- const glyph = icon("prompt-nav");
1658
- glyph.setAttribute("aria-hidden", "true");
1659
- inner.append(glyph, element("span", "sr-only", text("Prompts", "提示词")));
1660
- navButton.append(inner);
1661
- navigationRail = config.ui.navigationRail(navButton, {
1662
- defaultIcon:glyph, selectedIcon:icon("prompt-nav-selected"),
1663
- });
1664
- navButton.addEventListener("click", openPage);
1665
- }
1666
- if (navButton.parentElement !== group || navButton !== group.lastElementChild)
1667
- group.append(navButton);
1668
- if (group !== ownNavGroup) ownNavGroup?.remove();
1669
- syncNavigationSelection();
1670
- positionPage();
1671
- }
1672
- document.addEventListener(
1673
- "click",
1674
- (event) => {
1675
- if (
1676
- page &&
1677
- event.target.closest("[data-app-navigation-rail] button") &&
1678
- !navButton?.contains(event.target)
1679
- )
1680
- closePage(false, false);
1681
- },
1682
- { signal, capture: true },
1683
- );
1684
- window.addEventListener("popstate", () => closePage(false, false), { signal });
1685
1578
  window.addEventListener("resize", positionPage, { signal });
1686
1579
 
1580
+ document.addEventListener("focusin", event => {
1581
+ if (config.management) return;
1582
+ const target = event.target.closest?.('[contenteditable="true"]');
1583
+ const focused = target ? config.codex.composer(target,{purpose:"insertion"}) : null;
1584
+ if (focused) focusedEditable = focused.editable;
1585
+ sync();
1586
+ }, {signal});
1587
+
1687
1588
  const observer = new MutationObserver(() => {
1688
1589
  if (syncQueued) return;
1689
1590
  syncQueued = true;
@@ -1692,7 +1593,7 @@ const observer = new MutationObserver(() => {
1692
1593
  sync();
1693
1594
  });
1694
1595
  });
1695
- observer.observe(document.documentElement, {
1596
+ if (!config.management) observer.observe(document.documentElement, {
1696
1597
  childList: true,
1697
1598
  subtree: true,
1698
1599
  attributes: true,
@@ -1745,24 +1646,25 @@ document.addEventListener(
1745
1646
  );
1746
1647
  window.addEventListener("resize", () => { clearQuickPreview(true); position(); }, { signal });
1747
1648
  window.addEventListener("scroll", position, { signal, capture: true });
1748
- sync();
1649
+ const insertionBridge = config.management ? null : installPromptInsertion(signal, {
1650
+ codex:config.codex, api, insertText:(body,editable) => window.__codexPersonalRuntime.insertText(body,editable),
1651
+ });
1652
+ if (config.management) openPage(); else sync();
1749
1653
 
1750
1654
  config.registerSelfCheck?.(() => {
1751
- const rail=config.codex.railSlot(ownNavGroup), composer=config.codex.composer(null,{purpose:"insertion"});
1752
- const checks=[{name:"prompts-navigation-anchor",available:Boolean(rail && navButton?.isConnected)}];
1753
- if (composer) checks.push({name:"quick-prompts-anchor",available:Boolean(composer.add && button?.isConnected && composerRoot === composer.root)});
1655
+ const composer=config.codex.composer(null,{purpose:"insertion"});
1656
+ const checks=composer ? [{name:"quick-prompts-anchor",available:Boolean(composer.add && button?.isConnected && composerEditable === composer.editable)}] : [];
1754
1657
  const available=checks.every(check=>check.available);
1755
- return {state:available ? "available" : "unavailable",reason:available ? null : "prompts-anchor-unavailable",checks,
1756
- scopes:[{name:"navigation",element:navButton},{name:"composer",element:composer?.root},{name:"prompts-page",element:page},{name:"prompts-popup",element:pagePopup},{name:"quick-prompts",element:panel}]};
1658
+ return {state:composer ? available ? "available" : "unavailable" : "not-applicable",reason:available ? null : "prompts-anchor-unavailable",checks,
1659
+ scopes:[{name:"composer",element:composer?.root},{name:"quick-prompts",element:panel}]};
1757
1660
  });
1758
1661
 
1759
1662
  return () => {
1760
1663
  disposed = true;
1664
+
1761
1665
  observer.disconnect();
1762
1666
  close();
1763
1667
  button?.remove();
1764
1668
  closePage();
1765
- navButton?.remove();
1766
- ownNavGroup?.remove();
1767
1669
  style.remove();
1768
1670
  };
@@ -8,8 +8,6 @@
8
8
  "back": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\"><path d=\"M9.87117 11.6289C10.0762 11.8339 10.0762 12.166 9.87117 12.3711C9.66614 12.5761 9.334 12.5761 9.12898 12.3711L5.36433 8.60641C5.0296 8.27129 5.0296 7.72863 5.36433 7.39351L9.12898 3.62887C9.334 3.42384 9.66614 3.42384 9.87117 3.62887C10.0762 3.83389 10.0762 4.16603 9.87117 4.37105L6.24226 7.99996L9.87117 11.6289Z\" fill=\"currentColor\"/></svg>",
9
9
  "settings": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6.33374 8.97461C7.54786 8.97484 8.56163 9.83236 8.80347 10.9746H13.3337C13.6235 10.9749 13.8591 11.2102 13.8591 11.5C13.8591 11.7898 13.6235 12.0251 13.3337 12.0254H8.80347C8.56163 13.1676 7.54786 14.0252 6.33374 14.0254C5.11907 14.0254 4.10455 13.1673 3.86304 12.0244L2.66675 12.0254C2.37691 12.0253 2.14138 11.7899 2.14136 11.5C2.14136 11.2102 2.37693 10.9748 2.66675 10.9746H3.86304C4.10491 9.83221 5.11941 8.97461 6.33374 8.97461ZM6.33374 10.0254C5.51912 10.0254 4.85913 10.6854 4.85913 11.5C4.85913 12.3146 5.51912 12.9746 6.33374 12.9746C7.14814 12.9743 7.80835 12.3145 7.80835 11.5C7.80835 10.6855 7.14814 10.0257 6.33374 10.0254Z\" fill=\"currentColor\"/> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9.66675 1.97461C10.881 1.97461 11.8935 2.83236 12.1355 3.97461H13.3328C13.6227 3.97459 13.8581 4.21005 13.8582 4.5C13.8581 4.78993 13.6227 5.02537 13.3328 5.02539H12.1365C11.8946 6.16774 10.881 7.02539 9.66675 7.02539C8.45245 7.02539 7.43893 6.16774 7.19702 5.02539H2.66675C2.3768 5.02539 2.14136 4.78995 2.14136 4.5C2.14136 4.21005 2.3768 3.97461 2.66675 3.97461H7.19702C7.43893 2.83226 8.45245 1.97461 9.66675 1.97461ZM9.66675 3.02539C8.85213 3.02539 8.19214 3.68538 8.19214 4.5C8.19214 5.31462 8.85213 5.97461 9.66675 5.97461C10.4814 5.97461 11.1414 5.31462 11.1414 4.5C11.1414 3.68538 10.4814 3.02539 9.66675 3.02539Z\" fill=\"currentColor\"/></svg>",
10
10
  "drag": "<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M4 5h16\" />\n <path d=\"M4 12h16\" />\n <path d=\"M4 19h16\" />\n</svg>\n",
11
- "prompt-nav": "<svg\n class=\"lucide lucide-book-open\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"-1.5 -1.5 27 27\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M12 7v14 M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\" />\n</svg>",
12
- "prompt-nav-selected": "<svg width=\"20\" height=\"20\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M240,56V200a8,8,0,0,1-8,8H160a24,24,0,0,0-24,23.94,7.9,7.9,0,0,1-5.12,7.55A8,8,0,0,1,120,232a24,24,0,0,0-24-24H24a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H88a32,32,0,0,1,32,32v87.73a8.17,8.17,0,0,0,7.47,8.25,8,8,0,0,0,8.53-8V80a32,32,0,0,1,32-32h64A8,8,0,0,1,240,56Z\"/></svg>",
13
11
  "layout-grid": "<!-- Lucide, ISC license; see assets/brand/references/lucide-LICENSE.txt -->\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\" />\n</svg>\n",
14
12
  "list": "<!-- Lucide, ISC license; see assets/brand/references/lucide-LICENSE.txt -->\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M3 5h.01\" />\n <path d=\"M3 12h.01\" />\n <path d=\"M3 19h.01\" />\n <path d=\"M8 5h13\" />\n <path d=\"M8 12h13\" />\n <path d=\"M8 19h13\" />\n</svg>\n",
15
13
  "square-pen": "<!-- Lucide, ISC license; see assets/brand/references/lucide-LICENSE.txt -->\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z\" />\n</svg>\n"
@@ -0,0 +1,5 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import {installPromptInsertion} from './adapters/codex/insertion.mjs';
3
+ export async function createPageScriptSource() {
4
+ return `const installPromptInsertion = ${installPromptInsertion.toString()};\n${await readFile(new URL('./frontend/entry.mjs',import.meta.url),'utf8')}`;
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.14",
3
+ "version": "2.0.16",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,6 +57,7 @@
57
57
  "assets/brand/minecodex-icon.svg",
58
58
  "assets/brand/minecodex-icon-light.svg",
59
59
  "assets/brand/minecodex-icon-dark.svg",
60
+ "assets/brand/minecodex-icon-selected.svg",
60
61
  "assets/brand/references/lucide-LICENSE.txt",
61
62
  "assets/readme/hero.svg",
62
63
  "packages/ui/assets",
@@ -67,14 +68,18 @@
67
68
  "features/file-navigation/src",
68
69
  "features/file-navigation/codex-feature.json",
69
70
  "features/file-navigation/assets",
70
- "features/file-navigation/vendor/lucide-LICENSE"
71
+ "features/file-navigation/vendor/lucide-LICENSE",
72
+ "apps/dashboard/transport.js",
73
+ "packages/plugin/src",
74
+ "packages/plugin/assets",
75
+ "packages/plugin/plugin.json"
71
76
  ],
72
77
  "engines": {
73
78
  "node": ">=22.5.0"
74
79
  },
75
80
  "scripts": {
76
81
  "start": "node packages/cli/bin/mcx.mjs serve",
77
- "test": "node --test packages/cli/test/*.test.mjs packages/runtime-host/test/*.test.mjs features/model-slider/test/*.test.mjs features/notes/test/*.test.mjs features/images/test/*.test.mjs features/hide-upsell-banner/test/*.test.mjs features/session-status/test/*.test.mjs features/usage-meter/test/*.test.mjs apps/dashboard/test/*.test.mjs features/unread-inbox/test/*.test.mjs features/sidebar-focus/test/*.test.mjs features/sites/test/*.test.mjs packages/ui/test/*.test.mjs packages/codex-adapter/test/*.test.mjs features/markdown-reading/test/*.test.mjs features/file-navigation/test/*.test.mjs",
82
+ "test": "node --test packages/cli/test/*.test.mjs packages/runtime-host/test/*.test.mjs features/model-slider/test/*.test.mjs features/notes/test/*.test.mjs features/images/test/*.test.mjs features/hide-upsell-banner/test/*.test.mjs features/session-status/test/*.test.mjs features/usage-meter/test/*.test.mjs apps/dashboard/test/*.test.mjs features/unread-inbox/test/*.test.mjs features/sidebar-focus/test/*.test.mjs features/sites/test/*.test.mjs packages/ui/test/*.test.mjs packages/codex-adapter/test/*.test.mjs features/markdown-reading/test/*.test.mjs features/file-navigation/test/*.test.mjs packages/plugin/test/*.test.mjs",
78
83
  "test:cli": "node --test packages/cli/test/*.test.mjs",
79
84
  "test:runtime": "node --test packages/runtime-host/test/*.test.mjs",
80
85
  "check": "node scripts/check-syntax.mjs",
@@ -83,7 +88,9 @@
83
88
  "pack:audit": "node scripts/audit-package.mjs",
84
89
  "prepublishOnly": "npm test && npm run check && npm run pack:audit",
85
90
  "test:ui": "node --test packages/ui/test/*.test.mjs",
86
- "build:icons": "node scripts/build-brand-icons.mjs"
91
+ "build:icons": "node scripts/build-brand-icons.mjs",
92
+ "build:plugin": "node scripts/build-plugin.mjs",
93
+ "test:plugin": "node --test packages/plugin/test/*.test.mjs"
87
94
  },
88
95
  "repository": {
89
96
  "type": "git",
@@ -105,6 +112,12 @@
105
112
  "win32"
106
113
  ],
107
114
  "dependencies": {
108
- "acorn": "^8.18.0"
115
+ "@modelcontextprotocol/ext-apps": "^2.0.3",
116
+ "@modelcontextprotocol/sdk": "^1.32.0",
117
+ "acorn": "^8.18.0",
118
+ "zod": "^4.6.5"
119
+ },
120
+ "devDependencies": {
121
+ "esbuild": "^0.28.2"
109
122
  }
110
123
  }
@@ -85,6 +85,7 @@ Commands:
85
85
  mcx uninstall [--purge] Remove integration; preserve data unless --purge is used
86
86
  mcx update Update to the latest stable npm release
87
87
  mcx open Open the MineCodex plugin control panel
88
+ mcx plugin install Register the official Codex plugin entry
88
89
  mcx status [--json] Show installation and plugin status
89
90
  mcx restart Restart Codex and apply saved plugin switches
90
91
  mcx --version Print the installed version
@@ -447,6 +448,15 @@ export async function runCli(argv, {
447
448
  const [command = "--help", ...args] = argv;
448
449
  if (["--help", "-h", "help"].includes(command)) return printHelp(output);
449
450
  if (["--version", "-v"].includes(command)) return output(packageMetadata.version);
451
+ if (command === "plugin" && args[0] === "serve") {
452
+ const { servePlugin } = await import("../../plugin/src/server.mjs");
453
+ return servePlugin({ paths });
454
+ }
455
+ if (command === "plugin" && args[0] === "install") {
456
+ const { installPlugin } = await import("../../plugin/src/install.mjs");
457
+ await installPlugin({ paths });
458
+ return output("MineCodex plugin installed. Open Explore in Codex to find MineCodex.");
459
+ }
450
460
  if (command === "install") return installCommand({ paths, platform, cliPath, output, serviceProbe, fetchImpl, confirmRestart, nodeVersion });
451
461
  if (command === "open" || command === "gui") return openCommand({ paths, platform, output, serviceProbe, fetchImpl });
452
462
  if (command === "status") return statusCommand({ paths, platform, jsonOutput: args.includes("--json"), output, fetchImpl });
@@ -65,12 +65,41 @@ export function createCodexEnvironment() {
65
65
  const group = [...(scroll?.children ?? [])].find(child => child !== ownGroup && child.classList.contains('flex-col') && child.classList.contains('shrink-0'));
66
66
  return node && template && scroll ? { rail:node, template, scroll, group } : null;
67
67
  }
68
+ // 导航与 MCP iframe 身份在 Adapter 内解释,功能不接触 React props。
69
+ function navigation() {
70
+ const scope = props(rail(), value => Boolean((value.router ?? value.value?.router)?.navigate));
71
+ const router = scope?.router ?? scope?.value?.router;
72
+ if (!router?.subscribe) return null;
73
+ const read = () => {
74
+ const location = router.state.location;
75
+ return {path:location.pathname + (location.search ?? "") + (location.hash ?? ""), key:location.key};
76
+ };
77
+ return {location:read, navigate:path => router.navigate(path, {replace:true}),
78
+ subscribe:callback => router.subscribe(state => {
79
+ if (!state.navigation?.state || state.navigation.state === "idle") callback(read());
80
+ })};
81
+ }
82
+ function mcpAppSource(sourceWindow, {server, pluginId, resourceUri}) {
83
+ for (const frame of document.querySelectorAll('[data-mcp-extension-surface] iframe')) {
84
+ let source = sourceWindow, matches = false;
85
+ // 官方 sandbox proxy 包含资源 iframe;WindowProxy.parent 可跨源读取。
86
+ for (let depth = 0; source && depth < 4; depth++) {
87
+ if (source === frame.contentWindow) {matches = true;break;}
88
+ try {if (source.parent === source) break; source = source.parent;} catch {break;}
89
+ }
90
+ if (!matches) continue;
91
+ const view = props(frame, value => value.view?.resourceUri != null)?.view;
92
+ if (view?.resourceUri === resourceUri && (view.server === server || view.pluginId === pluginId))
93
+ return {connected:() => frame.isConnected};
94
+ }
95
+ return null;
96
+ }
68
97
  function route() {
69
98
  const node = rail();
70
99
  const scope = node && props(node, value => Boolean((value.router ?? value.value?.router)?.state?.location));
71
100
  return (scope?.router ?? scope?.value?.router)?.state?.location?.pathname ?? null;
72
101
  }
73
- return { composer, composerSurface, rail, railSlot, route, currentThreadId:element => composer(element)?.threadId ?? null,
102
+ return { composer, composerSurface, rail, railSlot, route, navigation, mcpAppSource, currentThreadId:element => composer(element)?.threadId ?? null,
74
103
  // 仅供 UI/插件 native Adapter 使用,交互层不读取 React props。
75
104
  currentFiber, props, report, diagnostics:() => [...capabilities.values()].map(item => ({...item})) };
76
105
  }