minecodex 0.1.10 → 0.1.12
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.
- package/README.md +33 -32
- package/features/hide-upsell-banner/codex-feature.json +1 -1
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +19 -7
- package/features/images/codex-feature.json +1 -1
- package/features/model-slider/codex-feature.json +1 -1
- package/features/notes/codex-feature.json +1 -1
- package/package.json +1 -1
- package/packages/runtime-host/src/codex-runtime.mjs +71 -14
package/README.md
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/readme/hero.svg" width="100%" alt="MineCodex:给 Codex Desktop 增加轻量、本地优先的插件,包含模型滑条、笔记与图片库">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# MineCodex
|
|
2
6
|
|
|
3
|
-
MineCodex 给 Codex Desktop
|
|
7
|
+
MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。所有数据均保存在本地,不经过任何第三方服务器,也不会改变 Codex 的原有使用习惯。
|
|
8
|
+
|
|
9
|
+
## 插件列表
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
| 插件 | 说明 |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| **Model Slider** | 更清晰、更顺滑的模型切换面板:具备品牌标识、常用收藏与流畅交互 |
|
|
14
|
+
| **Notes** | 跨会话管理待办事项、常用提示词与文件,可随时从 Codex 工具栏快速复用 |
|
|
15
|
+
| **Images** | 集中浏览生成的图片、Prompt 和来源任务,轻松保存原图或快速返回原会话 |
|
|
16
|
+
| **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
|
|
8
17
|
|
|
9
18
|
## 安装
|
|
10
19
|
|
|
11
|
-
MineCodex
|
|
12
|
-
`/Applications/ChatGPT.app` 或 `~/Applications/ChatGPT.app` 查找 Codex Desktop;
|
|
13
|
-
如果两处都找不到,安装会停止并给出提示。
|
|
20
|
+
MineCodex 目前仅支持 macOS 系统,需要 Node.js 22.5 或更高版本。安装程序会自动扫描 `/Applications/ChatGPT.app` 或 `~/Applications/ChatGPT.app` 寻找 Codex Desktop;如果两处均未找到,安装将暂停并给出说明。
|
|
14
21
|
|
|
15
22
|
```bash
|
|
16
23
|
npm install -g minecodex
|
|
17
24
|
mcx install
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
`mcx install`
|
|
21
|
-
MineCodex 将在下次重启 Codex 时启用,并询问是否现在重启;默认答案是 **No**。
|
|
22
|
-
安装过程只有在用户明确确认,或之后主动运行 `mcx restart` 时,才会关闭并重新打开
|
|
23
|
-
Codex。安装后,macOS 的“登录项与扩展”中会显示名为 **MineCodex** 的后台项目。
|
|
24
|
-
这个后台项目负责插件与本地控制台:用户退出 Codex 后,它会保持停止;用户后来从
|
|
25
|
-
Dock 主动打开 Codex 时,这次新启动会被视为明确的启动意图,并切换为可注入的托管
|
|
26
|
-
实例。由于普通 Dock 启动本身没有 CDP,首次切换时 Codex 可能会短暂重开一次。
|
|
27
|
+
运行 `mcx install` 不会强制关闭或启动 Codex,也不会影响当前打开的窗口。安装完成后,终端会说明更改将在下一次重启 Codex 时生效,并询问是否立即重启;默认选项为 **No**。只有在您明确确认,或后续手动运行 `mcx restart` 时,才会重新打开 Codex。安装完成后,macOS 的“登录项与扩展”中会添加名为 **MineCodex** 的后台项目。
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
打开本地控制台:
|
|
29
30
|
|
|
30
31
|
```bash
|
|
31
32
|
mcx open
|
|
@@ -33,19 +34,22 @@ mcx open
|
|
|
33
34
|
|
|
34
35
|
常用命令:
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
mcx
|
|
39
|
-
mcx
|
|
40
|
-
mcx
|
|
41
|
-
mcx
|
|
42
|
-
mcx
|
|
43
|
-
|
|
37
|
+
| 命令 | 作用 |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `mcx install` | 安装或修复 MineCodex |
|
|
40
|
+
| `mcx uninstall [--purge]` | 移除扩展;默认保留个人数据,带 `--purge` 参数才会清理 |
|
|
41
|
+
| `mcx update` | 更新到最新的稳定版本 |
|
|
42
|
+
| `mcx open` | 打开 MineCodex 控制台 |
|
|
43
|
+
| `mcx status [--json]` | 查看安装与插件运行状态 |
|
|
44
|
+
| `mcx restart` | 重启 Codex 并应用插件开启/关闭状态 |
|
|
45
|
+
|
|
46
|
+
关闭某项功能仅停用对应的扩展服务,不会删除任何个人数据;修改设置后运行 `mcx restart` 即可应用更改。
|
|
47
|
+
|
|
48
|
+
运行 `mcx uninstall` 会移除名为 MineCodex 的后台项目,并默认保留配置设置、Notes 笔记和 Images 图片索引;只有显式加上 `--purge` 参数,才会清理 MineCodex 产生的本地配置。MineCodex 绝不会删除 `~/.codex/generated_images` 中的原始图片或 Notes 引用的本地文件。
|
|
49
|
+
|
|
50
|
+
## 工作原理
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
`mcx uninstall` 会移除名为 MineCodex 的后台项目,并默认保留 MineCodex 设置、
|
|
47
|
-
Notes 数据和 Images 索引;只有显式传入 `--purge` 才会删除 MineCodex 自有数据。
|
|
48
|
-
MineCodex 永远不会删除 `~/.codex/generated_images` 中的原图或 Notes 引用的源文件。
|
|
52
|
+
MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。每个插件(Model Slider、Notes、Images)独立管理各自的数据与操作页面;Hide Upsell Banner 专注于界面展示的优化,不影响实际的用量计算。所有功能按需开启,关闭后即停止运行。
|
|
49
53
|
|
|
50
54
|
## 开发
|
|
51
55
|
|
|
@@ -57,9 +61,6 @@ npm run pack:check
|
|
|
57
61
|
npm run pack:audit
|
|
58
62
|
```
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
Codex 中的入口、主题与交互适配。
|
|
64
|
+
## 许可
|
|
62
65
|
|
|
63
|
-
MineCodex
|
|
64
|
-
第三方图标、品牌和兼容资产遵循各自的权利说明,见
|
|
65
|
-
[`features/model-slider/THIRD_PARTY_NOTICES.md`](features/model-slider/THIRD_PARTY_NOTICES.md)。
|
|
66
|
+
MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有代码以 MIT License 授权;第三方图标、品牌和兼容资产遵循各自的权利说明,见 [`features/model-slider/THIRD_PARTY_NOTICES.md`](features/model-slider/THIRD_PARTY_NOTICES.md)。
|
|
@@ -37,18 +37,30 @@ function hideVisibleBanners(root = document) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
let rescanQueued = false;
|
|
41
|
+
function rescanBanners() {
|
|
42
|
+
// React can render the banner in stages: an empty <aside> shell first, then
|
|
43
|
+
// text and style updates on the same element. A childList-only observer
|
|
44
|
+
// misses those later mutations, so every mutation triggers a full rescan
|
|
45
|
+
// instead of only inspecting freshly added nodes.
|
|
46
|
+
if (rescanQueued) return;
|
|
47
|
+
rescanQueued = true;
|
|
48
|
+
queueMicrotask(() => {
|
|
49
|
+
rescanQueued = false;
|
|
50
|
+
hideVisibleBanners(document);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
hideVisibleBanners();
|
|
41
|
-
const observer = new MutationObserver((
|
|
42
|
-
|
|
43
|
-
for (const node of record.addedNodes) {
|
|
44
|
-
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
45
|
-
hideVisibleBanners(node);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
55
|
+
const observer = new MutationObserver(() => {
|
|
56
|
+
rescanBanners();
|
|
48
57
|
});
|
|
49
58
|
observer.observe(document.documentElement, {
|
|
50
59
|
childList: true,
|
|
51
60
|
subtree: true,
|
|
61
|
+
attributes: true,
|
|
62
|
+
attributeFilter: ["style", "class", "hidden"],
|
|
63
|
+
characterData: true,
|
|
52
64
|
});
|
|
53
65
|
|
|
54
66
|
return () => {
|
package/package.json
CHANGED
|
@@ -105,6 +105,14 @@ export function createInjectionSource(features, {
|
|
|
105
105
|
} = {}) {
|
|
106
106
|
function install(config) {
|
|
107
107
|
if (window.top !== window) return;
|
|
108
|
+
if (!document.documentElement) {
|
|
109
|
+
// The document bootstrap runs before the root element exists. Defer the
|
|
110
|
+
// whole install until the DOM is ready instead of crashing on a null
|
|
111
|
+
// documentElement (which previously left the runtime permanently absent
|
|
112
|
+
// after a renderer reload).
|
|
113
|
+
document.addEventListener("DOMContentLoaded", () => install(config), { once: true });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
108
116
|
const currentRuntime = window.__codexPersonalRuntime;
|
|
109
117
|
if (currentRuntime?.version === config.version && currentRuntime?.sessionId === config.sessionId) {
|
|
110
118
|
currentRuntime.ensure();
|
|
@@ -605,10 +613,42 @@ export function createInjectionSource(features, {
|
|
|
605
613
|
}
|
|
606
614
|
}
|
|
607
615
|
|
|
616
|
+
function nativeCssModuleClass(tokenName, root = document) {
|
|
617
|
+
const pattern = new RegExp(`^_${tokenName}_[A-Za-z0-9_-]+$`);
|
|
618
|
+
const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"]`))
|
|
619
|
+
.flatMap((element) => Array.from(element.classList))
|
|
620
|
+
.find((name) => pattern.test(name));
|
|
621
|
+
if (live) return live;
|
|
622
|
+
const selectorPattern = new RegExp(`\\.(_${tokenName}_[A-Za-z0-9_-]+)`);
|
|
623
|
+
const find = (rules) => {
|
|
624
|
+
for (const rule of rules ?? []) {
|
|
625
|
+
const match = String(rule.selectorText ?? "").match(selectorPattern);
|
|
626
|
+
if (match?.[1]) return match[1];
|
|
627
|
+
try {
|
|
628
|
+
const nested = find(rule.cssRules);
|
|
629
|
+
if (nested) return nested;
|
|
630
|
+
} catch {
|
|
631
|
+
// Ignore inaccessible nested rules and continue with Codex's readable sheets.
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
};
|
|
636
|
+
for (const sheet of document.styleSheets) {
|
|
637
|
+
try {
|
|
638
|
+
const match = find(sheet.cssRules);
|
|
639
|
+
if (match) return match;
|
|
640
|
+
} catch {
|
|
641
|
+
// Ignore cross-origin stylesheets.
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
|
|
608
647
|
function createNativeFastIcon(active) {
|
|
609
648
|
const definition = modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
|
|
610
649
|
const icon = createModelSelectorIcon(definition, { brand: true });
|
|
611
|
-
|
|
650
|
+
const iconClass = nativeCssModuleClass("FastModeIcon");
|
|
651
|
+
if (icon && iconClass) icon.classList.add(iconClass);
|
|
612
652
|
return icon;
|
|
613
653
|
}
|
|
614
654
|
|
|
@@ -1389,12 +1429,17 @@ export function createInjectionSource(features, {
|
|
|
1389
1429
|
if (!track) return;
|
|
1390
1430
|
track.querySelectorAll('[class*="_FastParticleClip_"]').forEach((element) => element.remove());
|
|
1391
1431
|
if (!enabled || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1432
|
+
const clipClass = nativeCssModuleClass("FastParticleClip");
|
|
1433
|
+
const particlesClass = nativeCssModuleClass("FastTrackParticles");
|
|
1434
|
+
const pathClass = nativeCssModuleClass("FastTrackParticlePath");
|
|
1435
|
+
const particleClass = nativeCssModuleClass("TrackParticle");
|
|
1436
|
+
if (!clipClass || !particlesClass || !pathClass || !particleClass) return;
|
|
1392
1437
|
const clip = document.createElement("span");
|
|
1393
|
-
clip.className =
|
|
1438
|
+
clip.className = clipClass;
|
|
1394
1439
|
clip.setAttribute("aria-hidden", "true");
|
|
1395
1440
|
clip.style.opacity = "1";
|
|
1396
1441
|
const particles = document.createElement("span");
|
|
1397
|
-
particles.className =
|
|
1442
|
+
particles.className = particlesClass;
|
|
1398
1443
|
particles.setAttribute("aria-hidden", "true");
|
|
1399
1444
|
particles.setAttribute("data-animation-active", "true");
|
|
1400
1445
|
const travelDuration = 1.9;
|
|
@@ -1407,12 +1452,12 @@ export function createInjectionSource(features, {
|
|
|
1407
1452
|
const speed = 1 + (nativeParticleSeed(index, 21) - 0.5) * 0.4;
|
|
1408
1453
|
const duration = travelDuration / speed;
|
|
1409
1454
|
const path = document.createElement("span");
|
|
1410
|
-
path.className =
|
|
1455
|
+
path.className = pathClass;
|
|
1411
1456
|
path.style.animationDelay = `${index * stagger - duration * remainingTravel}s`;
|
|
1412
1457
|
path.style.animationDuration = `${duration}s`;
|
|
1413
1458
|
path.style.top = `${12 + nativeParticleSeed(index, 23) * 76}%`;
|
|
1414
1459
|
const particle = document.createElement("span");
|
|
1415
|
-
particle.className =
|
|
1460
|
+
particle.className = particleClass;
|
|
1416
1461
|
particle.style.opacity = String(0.4 + nativeParticleSeed(index, 11) * 0.6);
|
|
1417
1462
|
particle.style.transform = `translate(-50%, -50%) scale(${0.5 + nativeParticleSeed(index, 12) * 0.45})`;
|
|
1418
1463
|
path.append(particle);
|
|
@@ -1501,26 +1546,37 @@ export function createInjectionSource(features, {
|
|
|
1501
1546
|
function createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort) {
|
|
1502
1547
|
const efforts = model.supportedReasoningLevels.map((level) => level.effort);
|
|
1503
1548
|
const selectedIndex = Math.max(0, efforts.indexOf(selectedEffort));
|
|
1549
|
+
const classes = Object.fromEntries([
|
|
1550
|
+
"Menu",
|
|
1551
|
+
"ViewToggle",
|
|
1552
|
+
"ViewToggleContent",
|
|
1553
|
+
"ViewToggleIcon",
|
|
1554
|
+
"ViewControls",
|
|
1555
|
+
"FastModeToggle",
|
|
1556
|
+
"FastModeToggleContent",
|
|
1557
|
+
"SimpleView",
|
|
1558
|
+
].map((tokenName) => [tokenName, nativeCssModuleClass(tokenName, parentMenu)]));
|
|
1559
|
+
if (Object.values(classes).some((name) => !name)) return null;
|
|
1504
1560
|
const shell = document.createElement("div");
|
|
1505
1561
|
shell.setAttribute("data-codex-model-slider-generic", `${identity.raw}:${selectedEffort}`);
|
|
1506
|
-
shell.className =
|
|
1562
|
+
shell.className = classes.Menu;
|
|
1507
1563
|
shell.setAttribute("data-view", "simple");
|
|
1508
1564
|
shell.setAttribute("data-reduced-motion", String(matchMedia("(prefers-reduced-motion: reduce)").matches));
|
|
1509
1565
|
shell.setAttribute("data-transitions-ready", "false");
|
|
1510
1566
|
const nativeRow = nativeModelPickerRow(parentMenu);
|
|
1511
1567
|
const modelButton = document.createElement("div");
|
|
1512
|
-
modelButton.className =
|
|
1568
|
+
modelButton.className = classes.ViewToggle;
|
|
1513
1569
|
modelButton.setAttribute("role", "button");
|
|
1514
1570
|
modelButton.setAttribute("tabindex", "0");
|
|
1515
1571
|
modelButton.setAttribute("data-codex-model-slider-model-button", "");
|
|
1516
1572
|
modelButton.setAttribute("aria-label", `Model ${modelDisplayLabel(identity, selector)}`);
|
|
1517
1573
|
const buttonContent = document.createElement("span");
|
|
1518
|
-
buttonContent.className =
|
|
1574
|
+
buttonContent.className = classes.ViewToggleContent;
|
|
1519
1575
|
const buttonLabel = document.createElement("span");
|
|
1520
1576
|
buttonLabel.textContent = modelDisplayLabel(identity, selector);
|
|
1521
1577
|
const nativeChevron = nativeRow?.querySelector("svg")?.cloneNode(true);
|
|
1522
1578
|
if (nativeChevron) {
|
|
1523
|
-
nativeChevron.className.baseVal =
|
|
1579
|
+
nativeChevron.className.baseVal = classes.ViewToggleIcon;
|
|
1524
1580
|
nativeChevron.setAttribute("aria-hidden", "true");
|
|
1525
1581
|
buttonContent.append(buttonLabel, nativeChevron);
|
|
1526
1582
|
} else {
|
|
@@ -1536,7 +1592,7 @@ export function createInjectionSource(features, {
|
|
|
1536
1592
|
});
|
|
1537
1593
|
|
|
1538
1594
|
const viewControls = document.createElement("div");
|
|
1539
|
-
viewControls.className =
|
|
1595
|
+
viewControls.className = classes.ViewControls;
|
|
1540
1596
|
viewControls.setAttribute("data-expanded", "false");
|
|
1541
1597
|
viewControls.setAttribute("data-ultra-warning-visible", "false");
|
|
1542
1598
|
const controller = nativeComposerModelController();
|
|
@@ -1552,7 +1608,7 @@ export function createInjectionSource(features, {
|
|
|
1552
1608
|
if (supportsFast) {
|
|
1553
1609
|
const fastButton = document.createElement("button");
|
|
1554
1610
|
fastButton.type = "button";
|
|
1555
|
-
fastButton.className =
|
|
1611
|
+
fastButton.className = classes.FastModeToggle;
|
|
1556
1612
|
fastButton.setAttribute("data-codex-model-slider-fast", "");
|
|
1557
1613
|
fastButton.setAttribute("aria-pressed", String(fastEnabled));
|
|
1558
1614
|
fastButton.setAttribute("data-fast-mode-enabled", String(fastEnabled));
|
|
@@ -1563,7 +1619,7 @@ export function createInjectionSource(features, {
|
|
|
1563
1619
|
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
1564
1620
|
const fastIcon = createNativeFastIcon(fastEnabled);
|
|
1565
1621
|
const fastContent = document.createElement("span");
|
|
1566
|
-
fastContent.className =
|
|
1622
|
+
fastContent.className = classes.FastModeToggleContent;
|
|
1567
1623
|
fastContent.setAttribute("data-codex-model-slider-fast-content", "");
|
|
1568
1624
|
if (fastIcon) fastContent.append(fastIcon);
|
|
1569
1625
|
fastButton.append(fastContent);
|
|
@@ -1669,7 +1725,7 @@ export function createInjectionSource(features, {
|
|
|
1669
1725
|
selectNativeEffort(parentMenu, efforts[pendingIndex]);
|
|
1670
1726
|
});
|
|
1671
1727
|
const simpleView = document.createElement("div");
|
|
1672
|
-
simpleView.className =
|
|
1728
|
+
simpleView.className = classes.SimpleView;
|
|
1673
1729
|
simpleView.append(nativeSlider ?? rail);
|
|
1674
1730
|
shell.append(viewControls, simpleView);
|
|
1675
1731
|
return shell;
|
|
@@ -1707,12 +1763,13 @@ export function createInjectionSource(features, {
|
|
|
1707
1763
|
existing?.remove();
|
|
1708
1764
|
const nativeContainer = parentMenu.firstElementChild;
|
|
1709
1765
|
if (!nativeContainer) return false;
|
|
1766
|
+
const shell = createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort);
|
|
1767
|
+
if (!shell) return false;
|
|
1710
1768
|
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
1711
1769
|
modelSelectorGenericContainers.set(nativeContainer, nativeContainer.style.display);
|
|
1712
1770
|
}
|
|
1713
1771
|
parentMenu.setAttribute("data-codex-model-slider-generic-menu", "");
|
|
1714
1772
|
nativeContainer.style.display = "none";
|
|
1715
|
-
const shell = createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort);
|
|
1716
1773
|
shell.setAttribute("data-codex-model-slider-generic", key);
|
|
1717
1774
|
parentMenu.append(shell);
|
|
1718
1775
|
return true;
|