minecodex 2.0.8 → 2.0.10
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 +18 -4
- package/apps/dashboard/app.js +59 -10
- package/apps/dashboard/assets/diagnostics.js +18 -0
- package/apps/dashboard/index.html +5 -0
- package/apps/dashboard/styles.css +5 -1
- package/assets/brand/README.md +2 -2
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +2 -0
- package/features/images/src/adapters/codex/image-sources.mjs +8 -1
- package/features/images/src/adapters/codex/native-image-source.mjs +2 -2
- package/features/images/src/frontend/local-image-source.mjs +7 -0
- package/features/images/src/library-prompt-page.mjs +9 -0
- package/features/model-slider/src/adapters/codex/controller.mjs +14 -1
- package/features/model-slider/src/frontend/install.mjs +4 -1
- package/features/notes/src/frontend/entry.mjs +10 -0
- package/features/session-status/src/session-status.mjs +11 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +8 -0
- package/features/sites/src/frontend/entry.mjs +8 -0
- package/features/usage-meter/src/adapters/codex/renderer.mjs +2 -1
- package/features/usage-meter/src/usage-meter.mjs +8 -0
- package/package.json +1 -14
- package/packages/cli/src/commands.mjs +6 -1
- package/packages/cli/src/control-server.mjs +4 -1
- package/packages/cli/src/platform.mjs +10 -0
- package/packages/cli/src/runtime-manager.mjs +11 -3
- package/packages/codex-adapter/src/renderer/environment.mjs +6 -1
- package/packages/runtime-host/src/codex-injection.mjs +49 -16
- package/packages/runtime-host/src/codex-runtime.mjs +51 -0
- package/packages/runtime-host/src/feature-registry.mjs +2 -3
- package/packages/runtime-host/src/main.mjs +8 -4
- package/packages/runtime-host/src/renderer-self-check.mjs +59 -0
- package/packages/ui/src/components/composer-menu.mjs +7 -2
- package/packages/ui/src/native/theme.mjs +16 -1
- package/DESIGN.md +0 -192
- package/apps/dashboard/DESIGN.md +0 -67
- package/features/hide-upsell-banner/README.md +0 -30
- package/features/images/DESIGN.md +0 -151
- package/features/images/README.md +0 -37
- package/features/model-slider/DESIGN.md +0 -36
- package/features/model-slider/README.md +0 -45
- package/features/notes/DESIGN.md +0 -74
- package/features/notes/README.md +0 -33
- package/features/session-status/DESIGN.md +0 -45
- package/features/session-status/README.md +0 -43
- package/features/sidebar-focus/DESIGN.md +0 -17
- package/features/sites/DESIGN.md +0 -82
- package/features/sites/README.md +0 -31
- package/features/unread-inbox/DESIGN.md +0 -41
- package/features/unread-inbox/codex-feature.json +0 -23
- package/features/unread-inbox/src/adapters/codex/modules.mjs +0 -58
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +0 -198
- package/features/unread-inbox/src/domain/notifications.mjs +0 -21
- package/features/unread-inbox/src/injection-source.mjs +0 -9
- package/features/unread-inbox/src/unread-inbox.mjs +0 -307
- package/features/usage-meter/DESIGN.md +0 -49
- package/features/usage-meter/README.md +0 -19
- package/packages/cli/native/README.md +0 -49
- package/packages/codex-adapter/README.md +0 -16
- package/packages/runtime-host/README.md +0 -213
- package/packages/ui/DESIGN.md +0 -38
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# MineCodex
|
|
6
6
|
|
|
7
|
-
MineCodex 给 Codex Desktop
|
|
7
|
+
MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。配置、提示词和图片索引保存在本机。Images 的本地浏览不上传图片;使用云端收藏、加入文件夹或部分生成编辑操作时,所选图片会通过 Codex 原生能力上传至 OpenAI。
|
|
8
8
|
|
|
9
9
|
## 插件列表
|
|
10
10
|
|
|
@@ -16,6 +16,8 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
|
|
|
16
16
|
| **Images** | 在原生 Images 中浏览本地 Image Gen 图片,查看并复制提示词 |
|
|
17
17
|
| **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
|
|
18
18
|
| **Session Status** | 在输入框下方查看当前任务的步骤、观测速度、Token 用量与上下文明细 |
|
|
19
|
+
| **Sidebar Focus** | 展开侧栏时聚焦当前任务路径,再次点击恢复布局 |
|
|
20
|
+
| **Sites** | 在原生 Sites 中收录项目网页与自定义网址,按需打开或启动 |
|
|
19
21
|
|
|
20
22
|
## 安装
|
|
21
23
|
|
|
@@ -53,6 +55,18 @@ mcx open
|
|
|
53
55
|
|
|
54
56
|
运行 `mcx uninstall` 会移除名为 MineCodex 的后台项目,并默认保留配置设置、Notes 笔记和 Images 图片索引;只有显式加上 `--purge` 参数,才会清理 MineCodex 产生的本地配置。MineCodex 绝不会删除 `~/.codex/generated_images` 中的原始图片或 Notes 引用的本地文件。
|
|
55
57
|
|
|
58
|
+
## 数据与图片操作
|
|
59
|
+
|
|
60
|
+
本地 Image Gen 浏览、搜索、提示词查看和下载不会创建云端图片。首次收藏或加入文件夹时,会将所选图片上传到 OpenAI 的 Library;收藏和文件夹关系由云端保存。首次上传后取消文件夹选择,已产生的云端副本仍会保留,界面会提示“图片已保存到 Library,但未加入文件夹”。
|
|
61
|
+
|
|
62
|
+
图片生成编辑可能通过原生能力准备临时上传附件;查看图片不会触发这类操作。从 Image Gen 删除仅隐藏本地索引条目,保留原始图片和云端副本。卸载或 `--purge` 不会删除云端图片,也不会删除 Codex 的原始生成图片。
|
|
63
|
+
|
|
64
|
+
## 更新后的自检
|
|
65
|
+
|
|
66
|
+
MineCodex 在每个 Codex 窗口启动并完成注入后,后台检查一次功能安装、原生锚点与实际使用的设计变量。新窗口、页面重载或重新接入时重新检查;结果代表检查时的页面,不持续监测后续页面切换。控制台区分“正在自检”“部分窗口可用”“外观适配需要更新”“暂不可用”和“当前页面不适用”。没有输入框或未打开 Sites / Images 时,相关功能可能不适用,并不直接表示版本不兼容。
|
|
67
|
+
|
|
68
|
+
可使用控制台的“复制诊断信息”提供自检结果。诊断包含功能、窗口、锚点与设计变量证据,不包含提示词、聊天内容、账号资料或安装路径。自检可用表示当前检查项通过,实际保存、上传、模型切换等操作仍以各自的结果为准。
|
|
69
|
+
|
|
56
70
|
## 工作原理
|
|
57
71
|
|
|
58
72
|
MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Prompts 各自管理操作界面,Images 在原生 Images 中接入本地生成图片和提示词查看;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
|
|
@@ -67,16 +81,16 @@ npm run pack:check
|
|
|
67
81
|
npm run pack:audit
|
|
68
82
|
```
|
|
69
83
|
|
|
70
|
-
维护者发布流程见 [GitHub Actions 发布说明](docs/npm-publishing.md)。发布工作流仅允许维护者本人触发,使用 npm 可信发布,无需长期访问令牌。
|
|
84
|
+
维护者发布流程见 [GitHub Actions 发布说明](https://github.com/Vontean/MineCodex/blob/main/docs/npm-publishing.md)。发布工作流仅允许维护者本人触发,使用 npm 可信发布,无需长期访问令牌。
|
|
71
85
|
|
|
72
86
|
## 许可
|
|
73
87
|
|
|
74
88
|
MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有代码以 MIT License 授权;第三方图标、品牌和兼容资产遵循各自的权利说明,见 [`features/model-slider/THIRD_PARTY_NOTICES.md`](features/model-slider/THIRD_PARTY_NOTICES.md) 与 [`features/session-status/vendor/lucide-LICENSE`](features/session-status/vendor/lucide-LICENSE)。
|
|
75
89
|
|
|
76
|
-
|
|
90
|
+
## 侧栏聚焦
|
|
77
91
|
|
|
78
92
|
展开侧栏时,点击铃铛与搜索之间的聚焦按钮,保留当前任务路径并收起其他分支。再次点击恢复原先布局;切换任务时跟随路径,手动调整分支时退出聚焦。
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
## 从账号菜单打开控制台
|
|
81
95
|
|
|
82
96
|
在 Codex Navbar 底部打开账号菜单,点击 Settings 下方的 **MineCodex ↗**,即可在外部默认浏览器打开本地控制台,等同于 `mcx gui`。macOS 默认 Safari、Chrome/Chromium、Edge、Brave 和 Vivaldi 会定位已有控制台标签页,没有时才新建;首次定位可能需要系统自动化权限。其他浏览器与 Windows 仍由系统打开 URL。
|
package/apps/dashboard/app.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import {diagnosticReport} from "./assets/diagnostics.js";
|
|
2
|
+
let savedLocale;
|
|
3
|
+
try { savedLocale = localStorage.getItem("minecodex-language"); } catch { /* 存储不可用时跟随浏览器语言。 */ }
|
|
4
|
+
const locale = ["en", "zh-CN"].includes(savedLocale) ? savedLocale
|
|
5
|
+
: navigator.language.toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
2
6
|
document.documentElement.lang = locale;
|
|
3
7
|
|
|
4
8
|
const copy = {
|
|
@@ -19,8 +23,13 @@ const copy = {
|
|
|
19
23
|
configured: "Configured — starting in Codex…",
|
|
20
24
|
launching: "Launching in Codex…",
|
|
21
25
|
injected: "Active in Codex",
|
|
22
|
-
healthy: "
|
|
23
|
-
|
|
26
|
+
healthy: "Active in Codex",
|
|
27
|
+
checking: "Checking compatibility…",
|
|
28
|
+
partial: "Available in some windows",
|
|
29
|
+
degraded: "Appearance needs an update",
|
|
30
|
+
unavailable: "Temporarily unavailable — check for updates",
|
|
31
|
+
"not-applicable": "Not needed on this page",
|
|
32
|
+
failed: "Couldn’t start — try again",
|
|
24
33
|
stopped: "Stopped",
|
|
25
34
|
"restart-required": "Restart required — restart Codex",
|
|
26
35
|
},
|
|
@@ -31,13 +40,16 @@ const copy = {
|
|
|
31
40
|
darkAppearance: "Dark appearance",
|
|
32
41
|
lightAppearance: "Light appearance",
|
|
33
42
|
updateFailed: "Couldn’t check for updates. Please try again.",
|
|
43
|
+
copyDiagnostics: "Copy diagnostics",
|
|
44
|
+
diagnosticsCopied: "Diagnostics copied",
|
|
45
|
+
diagnosticsFailed: "Couldn’t copy diagnostics. Please try again.",
|
|
34
46
|
checkUpdates: "Check for updates",
|
|
35
47
|
checkingUpdates: "Checking…",
|
|
36
48
|
current: "You’re up to date.",
|
|
37
49
|
updateAvailable: (version) => `MineCodex ${version} is available. Run mcx update in Terminal.`,
|
|
38
50
|
changeFailed: "The plugin setting could not be saved.",
|
|
39
|
-
restartFailed: "MineCodex could not restart Codex.
|
|
40
|
-
failureDetail: "
|
|
51
|
+
restartFailed: "MineCodex could not restart Codex. Please try again or copy diagnostics.",
|
|
52
|
+
failureDetail: "This extension could not be confirmed. Copy diagnostics to help investigate.",
|
|
41
53
|
},
|
|
42
54
|
"zh-CN": {
|
|
43
55
|
connecting: "正在连接…",
|
|
@@ -56,8 +68,13 @@ const copy = {
|
|
|
56
68
|
configured: "已配置——正在 Codex 中启动…",
|
|
57
69
|
launching: "正在 Codex 中启动…",
|
|
58
70
|
injected: "已在 Codex 中生效",
|
|
59
|
-
healthy: "Codex
|
|
60
|
-
|
|
71
|
+
healthy: "已在 Codex 中生效",
|
|
72
|
+
checking: "正在自检…",
|
|
73
|
+
partial: "部分窗口可用",
|
|
74
|
+
degraded: "外观适配需要更新",
|
|
75
|
+
unavailable: "暂不可用——请检查更新",
|
|
76
|
+
"not-applicable": "当前页面不适用",
|
|
77
|
+
failed: "暂时无法启动——请重试",
|
|
61
78
|
stopped: "已停止",
|
|
62
79
|
"restart-required": "需要重启——请重启 Codex",
|
|
63
80
|
},
|
|
@@ -68,13 +85,16 @@ const copy = {
|
|
|
68
85
|
darkAppearance: "深色外观",
|
|
69
86
|
lightAppearance: "浅色外观",
|
|
70
87
|
updateFailed: "暂时无法检查更新,请稍后重试。",
|
|
88
|
+
copyDiagnostics: "复制诊断信息",
|
|
89
|
+
diagnosticsCopied: "诊断信息已复制",
|
|
90
|
+
diagnosticsFailed: "暂时无法复制诊断信息,请重试。",
|
|
71
91
|
checkUpdates: "检查更新",
|
|
72
92
|
checkingUpdates: "正在检查…",
|
|
73
93
|
current: "当前已是最新版本。",
|
|
74
94
|
updateAvailable: (version) => `发现 MineCodex ${version},请在终端运行 mcx update。`,
|
|
75
95
|
changeFailed: "插件设置保存失败。",
|
|
76
|
-
restartFailed: "MineCodex 无法重启 Codex
|
|
77
|
-
failureDetail: "
|
|
96
|
+
restartFailed: "MineCodex 无法重启 Codex。请重试或复制诊断信息协助排查。",
|
|
97
|
+
failureDetail: "暂时无法确认这项功能的状态,可复制诊断信息协助排查。",
|
|
78
98
|
},
|
|
79
99
|
}[locale];
|
|
80
100
|
|
|
@@ -94,10 +114,13 @@ const elements = {
|
|
|
94
114
|
retryButton: document.querySelector("#retry-button"),
|
|
95
115
|
versionLabel: document.querySelector("#version-label"),
|
|
96
116
|
versionValue: document.querySelector("#version-value"),
|
|
117
|
+
languageToggle: document.querySelector(".language-toggle"),
|
|
118
|
+
languageButtons: [...document.querySelectorAll("[data-language-choice]")],
|
|
97
119
|
themeToggle: document.querySelector(".theme-toggle"),
|
|
98
120
|
themeButtons: [...document.querySelectorAll("[data-theme-choice]")],
|
|
99
121
|
updateResult: document.querySelector("#update-result"),
|
|
100
122
|
updateButton: document.querySelector("#update-button"),
|
|
123
|
+
diagnosticsButton: document.querySelector("#diagnostics-button"),
|
|
101
124
|
toast: document.querySelector("#toast"),
|
|
102
125
|
};
|
|
103
126
|
|
|
@@ -106,6 +129,8 @@ let toastTimer = null;
|
|
|
106
129
|
const pendingFeatureIds = new Set();
|
|
107
130
|
|
|
108
131
|
function applyStaticCopy() {
|
|
132
|
+
elements.languageToggle.setAttribute("aria-label", locale === "zh-CN" ? "语言" : "Language");
|
|
133
|
+
for (const button of elements.languageButtons) button.setAttribute("aria-pressed", String(button.dataset.languageChoice === locale));
|
|
109
134
|
elements.runtimeLabel.textContent = copy.connecting;
|
|
110
135
|
elements.restartTitle.textContent = copy.restartTitle;
|
|
111
136
|
elements.restartCopy.textContent = copy.restartCopy;
|
|
@@ -115,6 +140,7 @@ function applyStaticCopy() {
|
|
|
115
140
|
elements.errorCopy.textContent = copy.loadErrorCopy;
|
|
116
141
|
elements.retryButton.textContent = copy.retry;
|
|
117
142
|
elements.versionLabel.textContent = copy.version;
|
|
143
|
+
elements.diagnosticsButton.textContent = copy.copyDiagnostics;
|
|
118
144
|
elements.updateButton.querySelector(".button-label").textContent = copy.checkUpdates;
|
|
119
145
|
}
|
|
120
146
|
|
|
@@ -186,6 +212,7 @@ function createPluginCard(feature) {
|
|
|
186
212
|
}
|
|
187
213
|
|
|
188
214
|
function updatePluginCard(card, feature) {
|
|
215
|
+
for (const button of elements.languageButtons) button.disabled = pendingFeatureIds.size > 0;
|
|
189
216
|
const status = featureState(feature);
|
|
190
217
|
const input = card.querySelector('input[type="checkbox"]');
|
|
191
218
|
const image = card.querySelector(".plugin-icon img");
|
|
@@ -285,7 +312,10 @@ async function load() {
|
|
|
285
312
|
try {
|
|
286
313
|
const response = await fetch("/api/status", { headers: { "Accept-Language": locale } });
|
|
287
314
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
288
|
-
|
|
315
|
+
const fresh = await response.json();
|
|
316
|
+
if (pendingFeatureIds.size && state) fresh.features = fresh.features.map(feature =>
|
|
317
|
+
pendingFeatureIds.has(feature.id) ? state.features.find(previous=>previous.id === feature.id) ?? feature : feature);
|
|
318
|
+
state = fresh;
|
|
289
319
|
elements.pluginList.hidden = false;
|
|
290
320
|
render();
|
|
291
321
|
} catch {
|
|
@@ -359,6 +389,16 @@ elements.retryButton.addEventListener("click", () => {
|
|
|
359
389
|
void load();
|
|
360
390
|
});
|
|
361
391
|
|
|
392
|
+
elements.diagnosticsButton.addEventListener("click", async () => {
|
|
393
|
+
try {
|
|
394
|
+
await navigator.clipboard.writeText(JSON.stringify(diagnosticReport(state),null,2));
|
|
395
|
+
showToast(copy.diagnosticsCopied);
|
|
396
|
+
} catch { showToast(copy.diagnosticsFailed); }
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// 页面可见时更新自检结果;保留正在保存的卡片和键盘焦点。
|
|
400
|
+
setInterval(() => { if (!document.hidden && !pendingFeatureIds.size) void load(); }, 5000);
|
|
401
|
+
|
|
362
402
|
elements.updateButton.addEventListener("click", async () => {
|
|
363
403
|
elements.updateButton.disabled = true;
|
|
364
404
|
elements.updateButton.querySelector(".button-label").textContent = copy.checkingUpdates;
|
|
@@ -397,6 +437,15 @@ elements.themeToggle.addEventListener("click", (event) => {
|
|
|
397
437
|
try { localStorage.setItem("minecodex-appearance", selectedTheme); } catch { /* 保留内存中的选择。 */ }
|
|
398
438
|
applyTheme();
|
|
399
439
|
});
|
|
440
|
+
elements.languageToggle.addEventListener("click", (event) => {
|
|
441
|
+
const button = event.target.closest("[data-language-choice]");
|
|
442
|
+
if (!button || pendingFeatureIds.size || elements.restartButton.disabled) return;
|
|
443
|
+
const choice = button.dataset.languageChoice;
|
|
444
|
+
if (choice === locale) return;
|
|
445
|
+
try { localStorage.setItem("minecodex-language", choice); } catch { return; }
|
|
446
|
+
// 重载后以同一语言请求卡片内容,不影响功能配置与外观选择。
|
|
447
|
+
location.reload();
|
|
448
|
+
});
|
|
400
449
|
systemTheme.addEventListener("change", applyTheme);
|
|
401
450
|
document.addEventListener("keydown", () => { document.documentElement.dataset.input = "keyboard"; });
|
|
402
451
|
document.addEventListener("pointerdown", () => { document.documentElement.dataset.input = "pointer"; });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 只复制自检证据;不包含账号、路径、会话内容、绑定令牌或原始错误文本。
|
|
2
|
+
export function diagnosticReport(state) {
|
|
3
|
+
return {
|
|
4
|
+
minecodexVersion:state?.version ?? null,
|
|
5
|
+
codexBuild:state?.renderer?.codexBuild ?? state?.runtime?.codex?.version ?? null,
|
|
6
|
+
buildSource:state?.renderer?.buildSource ?? null,
|
|
7
|
+
verification:"runtime-observation-only",
|
|
8
|
+
targets:(state?.renderer?.targets ?? []).map(target => ({
|
|
9
|
+
targetId:target.targetId,checkedAt:target.checkedAt,reason:target.reason ?? null,
|
|
10
|
+
tokens:target.tokens,capabilities:target.capabilities,
|
|
11
|
+
plugins:(target.plugins ?? []).map(plugin => ({
|
|
12
|
+
featureId:plugin.featureId,installation:plugin.installation,state:plugin.state,reason:plugin.reason,
|
|
13
|
+
checks:plugin.checks,tokens:plugin.tokens,
|
|
14
|
+
})),
|
|
15
|
+
})),
|
|
16
|
+
services:(state?.features ?? []).map(feature => ({featureId:feature.id,state:feature.state,failureCode:feature.failure?.code ?? null})),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
<h1><span class="brand-icon" aria-hidden="true"></span><span class="brand-name">MineCodex</span></h1>
|
|
19
19
|
</div>
|
|
20
20
|
<div class="header-actions">
|
|
21
|
+
<div class="language-toggle" role="group" aria-label="Language">
|
|
22
|
+
<button data-language-choice="zh-CN" type="button" lang="zh-CN" aria-pressed="false">中文</button>
|
|
23
|
+
<button data-language-choice="en" type="button" lang="en" aria-pressed="false">EN</button>
|
|
24
|
+
</div>
|
|
21
25
|
<div class="theme-toggle" role="group" aria-label="Appearance">
|
|
22
26
|
<span class="theme-selection" aria-hidden="true"></span>
|
|
23
27
|
<button data-theme-choice="light" type="button" aria-label="Light appearance" aria-pressed="false"><span class="ui-icon sun-icon" aria-hidden="true"></span></button>
|
|
@@ -67,6 +71,7 @@
|
|
|
67
71
|
<dl>
|
|
68
72
|
<div><dt id="version-label">Version</dt><dd id="version-value">—</dd></div>
|
|
69
73
|
</dl>
|
|
74
|
+
<button id="diagnostics-button" class="button quiet" type="button">Copy diagnostics</button>
|
|
70
75
|
<button id="update-button" class="button quiet" type="button"><span class="ui-icon refresh-icon" aria-hidden="true"></span><span class="button-label">Check for updates</span></button>
|
|
71
76
|
</footer>
|
|
72
77
|
<p id="update-result" class="update-result" role="status" hidden></p>
|
|
@@ -145,7 +145,11 @@ h2 { font-size: 14px; font-weight: 620; }
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
.overview { margin: 0 0 32px; }
|
|
148
|
-
.header-actions { flex-shrink: 0; }
|
|
148
|
+
.header-actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
|
|
149
|
+
.language-toggle { display: flex; gap: 4px; padding: 4px; border-radius: 999px; background: var(--surface-muted); box-shadow: inset 0 0 0 1px var(--border); }
|
|
150
|
+
.language-toggle button { min-width: 40px; height: 32px; padding: 0 8px; border: 0; border-radius: 999px; background: transparent; color: var(--ink-secondary); font: inherit; font-size: 12px; cursor: pointer; }
|
|
151
|
+
.language-toggle button[aria-pressed="true"] { background: var(--surface); color: var(--ink); box-shadow: 0 1px 3px #0002; }
|
|
152
|
+
.language-toggle button:disabled { cursor: wait; }
|
|
149
153
|
|
|
150
154
|
.runtime-state, .plugin-state {
|
|
151
155
|
display: inline-flex;
|
package/assets/brand/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MineCodex 图标
|
|
2
2
|
|
|
3
|
-
`minecodex-icon.svg` 是透明底、单色的可编辑矢量图标,使用 `viewBox="0 0 24 24"` 和 `currentColor`。外圈与镐子分别位于 `codex-outline`、`pickaxe
|
|
3
|
+
`minecodex-icon.svg` 是透明底、单色的可编辑矢量图标,使用 `viewBox="0 0 24 24"` 和 `currentColor`。外圈与镐子分别位于 `codex-outline`、`pickaxe`,可单独编辑。本机 PNG 预览用于透明底、明暗主题及 16/24/32/48px 尺寸对比,已加入 Git 忽略规则。当前主文件为实心版;`minecodex-icon-filled.svg` 是实心版副本,`minecodex-icon-outline.svg` 保留上一版空心设计。
|
|
4
4
|
|
|
5
5
|
## 设计与来源
|
|
6
6
|
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
|
|
17
17
|
`minecodex-icon.svg` 是唯一可编辑的生产源;运行 `npm run build:icons` 生成明暗 SVG、Dashboard favicon 和 README 顶部标识。`minecodex-icon-light.svg` 用于浅色背景,`minecodex-icon-dark.svg` 用于深色背景。Dashboard 标题通过 favicon 的 CSS mask 消费同一几何,颜色继承页面文字色。主题 SVG 与生产源、Lucide 许可、README 展示图均进入 npm 发布名单。
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
历史空心版仅用于设计参考,不进入 npm 发布包;PNG 对比图保留本机,不进入 GitHub 或 npm 发布包。macOS 后台服务 1.0.1 已接入 ICNS 图标;`minecodex-service-icon.svg` 为同一几何配浅色圆角底板的原生图标源,更新遵循原生服务文档的注销、替换和重新注册流程。
|
|
@@ -83,6 +83,8 @@ observer.observe(document.documentElement, {
|
|
|
83
83
|
characterData: true,
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
config.registerSelfCheck?.(() => ({state:hiddenBanners.size ? "available" : "not-applicable",reason:hiddenBanners.size ? null : "no-usage-banner",
|
|
87
|
+
checks:[...hiddenBanners.keys()].filter(node=>node.isConnected).map(()=>({name:"usage-banner-hidden",available:true}))}));
|
|
86
88
|
return () => {
|
|
87
89
|
disposed = true;
|
|
88
90
|
observer.disconnect();
|
|
@@ -3,7 +3,14 @@ export function createImageSourcesAdapter(codex) {
|
|
|
3
3
|
&& props.tabs?.some(item => item.key === "generated")
|
|
4
4
|
&& props.tabs?.some(item => item.key === "uploaded");
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
function applicability() {
|
|
7
|
+
const props=codex.props(codex.rail(),value=>Boolean((value.router ?? value.value?.router)?.state?.location));
|
|
8
|
+
const location=(props?.router ?? props?.value?.router)?.state?.location;
|
|
9
|
+
if (!location) return "unknown";
|
|
10
|
+
return location.pathname === "/space/files" && new URLSearchParams(location.search).get("tab") === "images"
|
|
11
|
+
? "applicable" : "not-applicable";
|
|
12
|
+
}
|
|
13
|
+
return {applicability,read() {
|
|
7
14
|
const tabs = [...document.querySelectorAll('[role="tablist"]')].find(node =>
|
|
8
15
|
!node.closest("[data-minecodex-image-navigation]")
|
|
9
16
|
&& codex.props(node, isImageNavigation));
|
|
@@ -423,13 +423,13 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
423
423
|
{id:"view-prompts", icon:promptIcon, text:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"}),onSelect:() => {void open(image).then(opened=>{if(opened&&isCurrent())onPrompt(image);}).catch(operationError);}},
|
|
424
424
|
{id:"favorite",icon:native.favoriteIcon(saved),text:saved
|
|
425
425
|
? message("library.favorites.remove","Remove from Favorites")
|
|
426
|
-
: message("
|
|
426
|
+
: message("minecodex.images.addToCloudFavorites",(context.intl.locale?.startsWith("zh") ? "加入收藏(上传至 Library)" : "Add to Favorites (uploads to Library)")),onSelect:() => {void perform(async () => {
|
|
427
427
|
favoriteRevision.current++;
|
|
428
428
|
const value=await cloud.favorite(image);
|
|
429
429
|
if(active.current&&isCurrent()){favoriteRevision.current++;setSaved(value);}
|
|
430
430
|
});}},
|
|
431
431
|
action("download-a-copy", native.downloadIcon, {id:"libraryNext.actions.download", defaultMessage:"Download"}),
|
|
432
|
-
{id:"folder",icon:native.folderIcon,text:message("
|
|
432
|
+
{id:"folder",icon:native.folderIcon,text:message("minecodex.images.addToCloudFolder",(context.intl.locale?.startsWith("zh") ? "加入文件夹(上传至 Library)" : "Add to folder (uploads to Library)")),onSelect:() => {void perform(() => folder(image));}},
|
|
433
433
|
{id:"delete",icon:native.deleteIcon,tone:"danger",text:message("libraryNext.actions.delete","Delete"),onSelect:()=>confirmRemove([image])},
|
|
434
434
|
].filter(Boolean);
|
|
435
435
|
const dropdown = h(native.Dropdown, {
|
|
@@ -188,6 +188,13 @@ export function installLocalImageSource(config, signal, sources, native) {
|
|
|
188
188
|
signal.addEventListener("abort", cleanup, {once:true});
|
|
189
189
|
return {
|
|
190
190
|
cleanup,
|
|
191
|
+
selfCheck() {
|
|
192
|
+
const entry=sources.read(),scope=sources.applicability();
|
|
193
|
+
if (!entry) return {state:scope === "applicable" ? "unavailable" : scope,reason:scope === "not-applicable" ? "outside-image-sources" : "image-sources-anchor-unavailable"};
|
|
194
|
+
const available=Boolean(mounted?.nav && mounted?.view && mounted.navigation.isConnected);
|
|
195
|
+
return {state:available ? "available" : mounted ? "unknown" : "unavailable",reason:available ? null : "native-image-mount-unavailable",
|
|
196
|
+
checks:[{name:"image-sources-anchor",available:true},{name:"native-image-mount",available}],scopes:[{name:"image-sources",element:entry.shell}]};
|
|
197
|
+
},
|
|
191
198
|
readViewer:header => native.readViewer(header),
|
|
192
199
|
pendingPrompt:() => pendingPrompt,
|
|
193
200
|
consumePrompt:() => {pendingPrompt = null;},
|
|
@@ -262,6 +262,15 @@ localeObserver.observe(document.documentElement, { attributes:true, attributeFil
|
|
|
262
262
|
localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex), createNativeImageSource(config.codex, {
|
|
263
263
|
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal,
|
|
264
264
|
}));
|
|
265
|
+
config.registerSelfCheck?.(() => {
|
|
266
|
+
const source=localImageSource.selfCheck();
|
|
267
|
+
if (source.state !== "not-applicable") return source;
|
|
268
|
+
const header=document.querySelector('[data-testid="viewer-header"]');
|
|
269
|
+
if (!header || !libraryImage(header)) return source;
|
|
270
|
+
const available=Boolean(mounted?.button.isConnected);
|
|
271
|
+
return {state:available ? "available" : "unknown",reason:available ? null : "image-viewer-mount-pending",
|
|
272
|
+
checks:[{name:"image-viewer-anchor",available:true},{name:"prompt-control",available}],scopes:[{name:"image-viewer",element:header}]};
|
|
273
|
+
});
|
|
265
274
|
const observer = new MutationObserver(() => {
|
|
266
275
|
if (scheduled) return;
|
|
267
276
|
scheduled = true;
|
|
@@ -89,5 +89,18 @@ function invokeNativeMenuItemSelect(item) {
|
|
|
89
89
|
codex.report("fast-icons",Boolean(active && inactive),active && inactive ? null : "icon-resource-contract-changed");
|
|
90
90
|
return {active:{path:active,viewBox:"0 0 24 24"},inactive:{path:inactive,viewBox:"0 0 20 20"}};
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
function selfCheck() {
|
|
93
|
+
const trigger=nativeComposerTrigger();
|
|
94
|
+
if (!trigger) {
|
|
95
|
+
const path=codex.route();
|
|
96
|
+
const applicable=Boolean(codex.composer(null,{threadOnly:false})) || path?.startsWith("/local/");
|
|
97
|
+
return {state:applicable ? "unavailable" : path ? "not-applicable" : "unknown",reason:applicable ? "model-trigger-unavailable" : "no-task-composer",checks:[{name:"model-trigger",available:false}]};
|
|
98
|
+
}
|
|
99
|
+
const generic=codex.props(trigger,p=>!!p.selectedModel && typeof p.onModelChange === "function",40);
|
|
100
|
+
if (generic) return {state:"not-applicable",reason:"chat-model-picker"};
|
|
101
|
+
const available=Boolean(nativeComposerModelController());
|
|
102
|
+
return {state:available ? "available" : "unavailable",reason:available ? null : "model-controller-unavailable",
|
|
103
|
+
scopes:[{name:"model-trigger",element:trigger}],checks:[{name:"model-trigger",available:true},{name:"model-controller",available}]};
|
|
104
|
+
}
|
|
105
|
+
return {selfCheck,fastIcons,trigger:nativeComposerTrigger,read:nativeComposerModelController,descriptor:nativeModelDescriptorForItem,setOpen:setReactControlledMenuOpen,selectItem:invokeNativeMenuItemSelect};
|
|
93
106
|
}
|
|
@@ -2173,5 +2173,8 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2173
2173
|
void loadNativeFastIconFromBundle();
|
|
2174
2174
|
}
|
|
2175
2175
|
|
|
2176
|
-
return {
|
|
2176
|
+
return { selfCheck: () => {
|
|
2177
|
+
const result=nativeModel.selfCheck();
|
|
2178
|
+
return {...result,scopes:[...(result.scopes ?? []),{name:"model-menu",element:modelSelectorControllerMenu?.menu}]};
|
|
2179
|
+
}, ensure: ensureModelSelector, dispose: restoreModelSelectorEnhancements };
|
|
2177
2180
|
}
|
|
@@ -1376,6 +1376,16 @@ document.addEventListener(
|
|
|
1376
1376
|
window.addEventListener("resize", position, { signal });
|
|
1377
1377
|
window.addEventListener("scroll", position, { signal, capture: true });
|
|
1378
1378
|
sync();
|
|
1379
|
+
|
|
1380
|
+
config.registerSelfCheck?.(() => {
|
|
1381
|
+
const rail=config.codex.railSlot(ownNavGroup), composer=config.codex.composer(null,{purpose:"insertion"});
|
|
1382
|
+
const checks=[{name:"prompts-navigation-anchor",available:Boolean(rail && navButton?.isConnected)}];
|
|
1383
|
+
if (composer) checks.push({name:"quick-prompts-anchor",available:Boolean(composer.add && button?.isConnected && composerRoot === composer.root)});
|
|
1384
|
+
const available=checks.every(check=>check.available);
|
|
1385
|
+
return {state:available ? "available" : "unavailable",reason:available ? null : "prompts-anchor-unavailable",checks,
|
|
1386
|
+
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}]};
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1379
1389
|
return () => {
|
|
1380
1390
|
disposed = true;
|
|
1381
1391
|
observer.disconnect();
|
|
@@ -612,6 +612,17 @@ instanceObserver.observe(document.documentElement, {
|
|
|
612
612
|
attributeFilter:["class", "style", "hidden", "inert", "aria-hidden", "data-composer-placement"],
|
|
613
613
|
});
|
|
614
614
|
syncInstances();
|
|
615
|
+
config.registerSelfCheck?.(() => {
|
|
616
|
+
const surfaces=[...document.querySelectorAll('[data-codex-composer-root]')].map(root=>config.codex.composerSurface(root)).filter(Boolean);
|
|
617
|
+
if (!surfaces.length) {
|
|
618
|
+
const path=config.codex.route();
|
|
619
|
+
return {state:path?.startsWith("/local/") ? "unavailable" : path ? "not-applicable" : "unknown",reason:"no-thread-surface"};
|
|
620
|
+
}
|
|
621
|
+
const identity=surfaces.every(surface=>!!surface.threadId),mounted=surfaces.every(surface=>instances.has(surface.root));
|
|
622
|
+
const available=identity && mounted;
|
|
623
|
+
return {state:available ? "available" : "unavailable",reason:available ? null : "thread-identity-or-status-anchor-unavailable",
|
|
624
|
+
checks:[{name:"thread-identity",available:identity},{name:"session-status-mount",available:mounted}],scopes:surfaces.map((surface,index)=>({name:`composer-${index+1}`,element:surface.root}))};
|
|
625
|
+
});
|
|
615
626
|
return () => {
|
|
616
627
|
instanceObserver.disconnect();
|
|
617
628
|
for (const instance of instances.values()) instance.dispose();
|
|
@@ -86,4 +86,12 @@ observer.observe(document.documentElement, { subtree:true, childList:true, attri
|
|
|
86
86
|
"data-app-action-sidebar-section-collapsed", "data-above-composer-conversation-id", "lang"
|
|
87
87
|
] });
|
|
88
88
|
reconcile();
|
|
89
|
+
config.registerSelfCheck?.(() => {
|
|
90
|
+
const sidebar=document.querySelector("#app-shell-sidebar");
|
|
91
|
+
if (!sidebar) return {state:"unknown",reason:"sidebar-anchor-unavailable"};
|
|
92
|
+
if (!sidebar.checkVisibility({visibilityProperty:true})) return {state:"not-applicable",reason:"sidebar-hidden"};
|
|
93
|
+
const available=Boolean(button?.isConnected);
|
|
94
|
+
return {state:available ? "available" : "unavailable",reason:available ? null : "sidebar-focus-anchor-unavailable",
|
|
95
|
+
checks:[{name:"sidebar-focus-anchor",available}],scopes:[{name:"sidebar",element:sidebar}]};
|
|
96
|
+
});
|
|
89
97
|
return () => { disposed = true; clearTimeout(timer); observer.disconnect(); button?.remove(); };
|
|
@@ -413,6 +413,14 @@ const observer = new MutationObserver(records => { if (records.some(r => !r.targ
|
|
|
413
413
|
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["aria-pressed"] });
|
|
414
414
|
document.addEventListener("input", schedule); document.addEventListener("click", onClick, true); document.addEventListener("keydown", onKey, true); document.addEventListener("pointerdown", onPointer, true);
|
|
415
415
|
refresh().catch(e => notice(e.message)); schedule();
|
|
416
|
+
config.registerSelfCheck?.(() => {
|
|
417
|
+
const path=nativeSites.path();
|
|
418
|
+
if (!path) return {state:"unknown",reason:"navigation-context-unavailable"};
|
|
419
|
+
if (path !== "/space/sites") return {state:"not-applicable",reason:"outside-sites-page"};
|
|
420
|
+
const page=nativeSites.pageInfo(),available=Boolean(page?.create && addButton?.isConnected);
|
|
421
|
+
return {state:available ? "available" : "unavailable",reason:available ? null : "sites-anchor-unavailable",
|
|
422
|
+
checks:[{name:"sites-page",available:Boolean(page)},{name:"sites-create-anchor",available}],scopes:[{name:"sites-page",element:page?.page},{name:"sites-dialog",element:dialog},{name:"sites-menu",element:menu}]};
|
|
423
|
+
});
|
|
416
424
|
return () => {
|
|
417
425
|
disposed = true; pendingLayout = null; clearTimeout(timer); observer.disconnect();
|
|
418
426
|
document.removeEventListener("input", schedule); document.removeEventListener("click", onClick, true); document.removeEventListener("keydown", onKey, true); document.removeEventListener("pointerdown", onPointer, true);
|
|
@@ -53,7 +53,8 @@ function avatarAnchor(avatar, rail) {
|
|
|
53
53
|
const windows=currentWindows(context), avatar=findAvatar(rail);
|
|
54
54
|
const available=Boolean(avatar && context.router && windows);
|
|
55
55
|
codex.report("usage-meter",available,available ? null : "account-quota-or-anchor-unavailable");
|
|
56
|
-
return { rail,avatar,windows,available
|
|
56
|
+
return { rail,avatar,windows,available,anchorAvailable:Boolean(avatar && context.router),
|
|
57
|
+
applicable:context.auth?.isLoading ? null : context.auth ? ["chatgpt","chatgptAuthTokens"].includes(context.auth.authMethod) : null };
|
|
57
58
|
}
|
|
58
59
|
function open(rail) { const {router}=nativeContext(rail); return router?.navigate("/settings/usage?tab=overview",{state:router.state?.location?.state}); }
|
|
59
60
|
signal.addEventListener("abort",()=>unsubscribe?.(),{once:true});
|
|
@@ -158,4 +158,12 @@ const cleanup = () => {
|
|
|
158
158
|
};
|
|
159
159
|
signal.addEventListener("abort", cleanup, { once: true });
|
|
160
160
|
sync();
|
|
161
|
+
config.registerSelfCheck?.(() => {
|
|
162
|
+
const snapshot=nativeUsage.read();
|
|
163
|
+
const available=Boolean(snapshot.available && record?.button.isConnected);
|
|
164
|
+
return {state:available ? "available" : snapshot.applicable === false ? "not-applicable" : !snapshot.anchorAvailable && snapshot.rail ? "unavailable" : "unknown",reason:available ? null : "quota-data-or-anchor-unavailable",
|
|
165
|
+
checks:[{name:"account-anchor",available:Boolean(snapshot.avatar)},{name:"account-quota",available:Boolean(snapshot.windows)},{name:"usage-meter-mount",available:Boolean(record?.button.isConnected)}],
|
|
166
|
+
scopes:[{name:"account",element:snapshot.avatar}]};
|
|
167
|
+
});
|
|
168
|
+
|
|
161
169
|
return cleanup;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecodex",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "Lightweight, local-first plugins for the Codex desktop app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,8 +27,6 @@
|
|
|
27
27
|
"features/notes/assets",
|
|
28
28
|
"features/notes/vendor/phosphor-LICENSE",
|
|
29
29
|
"features/notes/codex-feature.json",
|
|
30
|
-
"features/notes/DESIGN.md",
|
|
31
|
-
"features/notes/README.md",
|
|
32
30
|
"features/images/src",
|
|
33
31
|
"features/images/web",
|
|
34
32
|
"features/images/codex-feature.json",
|
|
@@ -36,8 +34,6 @@
|
|
|
36
34
|
"features/hide-upsell-banner/codex-feature.json",
|
|
37
35
|
"features/session-status/src",
|
|
38
36
|
"features/session-status/codex-feature.json",
|
|
39
|
-
"features/session-status/DESIGN.md",
|
|
40
|
-
"features/session-status/README.md",
|
|
41
37
|
"features/session-status/vendor/lucide-LICENSE",
|
|
42
38
|
"features/session-status/vendor/phosphor-LICENSE",
|
|
43
39
|
"apps/dashboard/app.js",
|
|
@@ -48,25 +44,16 @@
|
|
|
48
44
|
"README.md",
|
|
49
45
|
"features/usage-meter/src",
|
|
50
46
|
"features/usage-meter/codex-feature.json",
|
|
51
|
-
"features/usage-meter/DESIGN.md",
|
|
52
|
-
"features/usage-meter/README.md",
|
|
53
|
-
"features/unread-inbox/src",
|
|
54
|
-
"features/unread-inbox/codex-feature.json",
|
|
55
|
-
"features/unread-inbox/DESIGN.md",
|
|
56
47
|
"features/sidebar-focus/src",
|
|
57
48
|
"features/sidebar-focus/codex-feature.json",
|
|
58
|
-
"features/sidebar-focus/DESIGN.md",
|
|
59
49
|
"features/sidebar-focus/vendor/lucide-LICENSE",
|
|
60
50
|
"features/sites/src",
|
|
61
51
|
"features/sites/codex-feature.json",
|
|
62
|
-
"features/sites/DESIGN.md",
|
|
63
|
-
"features/sites/README.md",
|
|
64
52
|
"features/sites/vendor/lucide-LICENSE",
|
|
65
53
|
"packages/ui/src",
|
|
66
54
|
"packages/codex-adapter/src",
|
|
67
55
|
"packages/codex-adapter/package.json",
|
|
68
56
|
"packages/ui/package.json",
|
|
69
|
-
"DESIGN.md",
|
|
70
57
|
"assets/brand/minecodex-icon.svg",
|
|
71
58
|
"assets/brand/minecodex-icon-light.svg",
|
|
72
59
|
"assets/brand/minecodex-icon-dark.svg",
|
|
@@ -202,7 +202,12 @@ function featureStatusText(feature) {
|
|
|
202
202
|
saved: "saved; restart required",
|
|
203
203
|
configured: "configured; launching",
|
|
204
204
|
launching: "launching",
|
|
205
|
-
injected: "
|
|
205
|
+
injected: "available in Codex",
|
|
206
|
+
checking: "checking compatibility",
|
|
207
|
+
partial: "available in some windows",
|
|
208
|
+
degraded: "appearance needs an update",
|
|
209
|
+
unavailable: "temporarily unavailable; check for updates",
|
|
210
|
+
"not-applicable": "not needed on this page",
|
|
206
211
|
healthy: "healthy in Codex",
|
|
207
212
|
failed: "failed; run `mcx restart`",
|
|
208
213
|
stopped: "stopped",
|
|
@@ -129,7 +129,7 @@ async function featureStatuses(paths, runtimeManager, locale, runtimeState = nul
|
|
|
129
129
|
const plugin = ready.plugins.find((candidate) => candidate.featureId === feature.id) ?? null;
|
|
130
130
|
const pluginState = plugin?.state ?? null;
|
|
131
131
|
const applied = appliedFeatures.includes(feature.id)
|
|
132
|
-
|| ["configured", "launching", "ready", "injected"].includes(pluginState);
|
|
132
|
+
|| ["configured", "launching", "ready", "injected", "checking", "partial", "degraded", "unavailable", "not-applicable"].includes(pluginState);
|
|
133
133
|
const injected = Boolean(
|
|
134
134
|
pluginState === "injected" && rendererCurrent,
|
|
135
135
|
);
|
|
@@ -152,6 +152,8 @@ async function featureStatuses(paths, runtimeManager, locale, runtimeState = nul
|
|
|
152
152
|
state = "failed";
|
|
153
153
|
} else if (config.features[feature.id] !== applied) {
|
|
154
154
|
state = "restart-required";
|
|
155
|
+
} else if (["checking","partial","degraded","unavailable","not-applicable"].includes(pluginState)) {
|
|
156
|
+
state = rendererCurrent ? pluginState : "checking";
|
|
155
157
|
} else if (pluginState === "configured" || pluginState === "launching" || pluginState === "ready") {
|
|
156
158
|
state = "launching";
|
|
157
159
|
} else if (injected && healthy) {
|
|
@@ -178,6 +180,7 @@ async function featureStatuses(paths, runtimeManager, locale, runtimeState = nul
|
|
|
178
180
|
injected,
|
|
179
181
|
healthy,
|
|
180
182
|
state,
|
|
183
|
+
targets: plugin?.targets ?? [],
|
|
181
184
|
health: plugin?.health ?? null,
|
|
182
185
|
failure,
|
|
183
186
|
};
|
|
@@ -377,6 +377,14 @@ export class MacPlatformAdapter {
|
|
|
377
377
|
return this.codexInstallation;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
async codexBuild() {
|
|
381
|
+
const installation=await this.codex();
|
|
382
|
+
try {
|
|
383
|
+
const {stdout}=await this.execFile("/usr/bin/plutil",["-extract","CFBundleShortVersionString","raw","-o","-",path.join(installation.appPath,"Contents","Info.plist")]);
|
|
384
|
+
return String(stdout).trim() || null;
|
|
385
|
+
} catch { return null; }
|
|
386
|
+
}
|
|
387
|
+
|
|
380
388
|
async readCodexInstallationFingerprint(installation) {
|
|
381
389
|
if (typeof this.fileSystem.stat !== "function") return null;
|
|
382
390
|
const paths = [
|
|
@@ -838,6 +846,8 @@ export class WindowsPlatformAdapter {
|
|
|
838
846
|
return this.codexInstallation;
|
|
839
847
|
}
|
|
840
848
|
|
|
849
|
+
async codexBuild() { return (await this.codex()).version ?? null; }
|
|
850
|
+
|
|
841
851
|
async readCodexInstallationFingerprint(installation) {
|
|
842
852
|
if (typeof this.fileSystem.stat !== "function") return null;
|
|
843
853
|
const paths = [
|