codex-plus-patcher 0.6.0 → 0.7.0

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 (40) hide show
  1. package/README.md +2 -1
  2. package/package.json +4 -2
  3. package/src/patches/lib/common-patches.js +46 -66
  4. package/src/patches/lib/hooks/about.js +7 -0
  5. package/src/patches/lib/hooks/diagnostics.js +7 -0
  6. package/src/patches/lib/hooks/mermaid.js +7 -0
  7. package/src/patches/lib/hooks/message-composer.js +7 -0
  8. package/src/patches/lib/hooks/native-main.js +7 -0
  9. package/src/patches/lib/hooks/project-selector.js +12 -0
  10. package/src/patches/lib/hooks/review.js +7 -0
  11. package/src/patches/lib/hooks/settings-commands.js +12 -0
  12. package/src/patches/lib/hooks/sidebar.js +12 -0
  13. package/src/patches/lib/hooks/thread-header.js +7 -0
  14. package/src/patches/lib/hooks/worker.js +7 -0
  15. package/src/patches/lib/project-selector-shortcut-patch.js +7 -6
  16. package/src/runtime/api/about.js +16 -0
  17. package/src/runtime/api/commands.js +85 -0
  18. package/src/runtime/api/composer.js +14 -0
  19. package/src/runtime/api/diagnostics.js +38 -0
  20. package/src/runtime/api/errors.js +20 -0
  21. package/src/runtime/api/index.js +79 -0
  22. package/src/runtime/api/mermaid.js +14 -0
  23. package/src/runtime/api/message.js +14 -0
  24. package/src/runtime/api/modules.js +57 -0
  25. package/src/runtime/api/native.js +14 -0
  26. package/src/runtime/api/patches.js +31 -0
  27. package/src/runtime/api/review.js +20 -0
  28. package/src/runtime/api/settings.js +76 -0
  29. package/src/runtime/api/sidebar.js +24 -0
  30. package/src/runtime/api/styles.js +28 -0
  31. package/src/runtime/api/threadHeader.js +41 -0
  32. package/src/runtime/assets.js +58 -17
  33. package/src/runtime/host/messageComposer.js +16 -0
  34. package/src/runtime/host/nativeMain.js +159 -0
  35. package/src/runtime/host/projectSelector.js +58 -0
  36. package/src/runtime/host/review.js +62 -0
  37. package/src/runtime/host/sidebar.js +21 -0
  38. package/src/runtime/host/threadHeader.js +9 -0
  39. package/src/runtime/{worker.js → host/worker.js} +7 -0
  40. package/src/runtime/runtime.js +23 -441
@@ -0,0 +1,159 @@
1
+ const fs = require("node:fs");
2
+ const os = require("node:os");
3
+ const path = require("node:path");
4
+ const { randomUUID } = require("node:crypto");
5
+ const { pathToFileURL } = require("node:url");
6
+
7
+ function create({ electron }) {
8
+ let nativeMenuItems = [];
9
+ let refreshApplicationMenu = null;
10
+
11
+ function menuSnapshot(menu) {
12
+ return menu?.items?.map((item) => ({
13
+ id: item.id,
14
+ label: item.label,
15
+ enabled: item.enabled,
16
+ visible: item.visible,
17
+ accelerator: item.accelerator,
18
+ submenu: menuSnapshot(item.submenu),
19
+ }));
20
+ }
21
+
22
+ function logMenuDiagnostics() {
23
+ try {
24
+ const menu = menuSnapshot(electron.Menu.getApplicationMenu()) ?? [];
25
+ const text = JSON.stringify(menu);
26
+ const hasOpenDeveloperTools = text.includes("codexPlusOpenDevTools") || text.includes("Open Developer Tools");
27
+ if (process.env.CODEX_PLUS_MENU_DIAGNOSTICS === "1" || !hasOpenDeveloperTools) {
28
+ console.log(`[Codex Plus menu diagnostics] ${JSON.stringify({ hasOpenDeveloperTools, menu })}`);
29
+ }
30
+ } catch (error) {
31
+ console.log(`[Codex Plus menu diagnostics] ${JSON.stringify({ error: String(error?.message ?? error) })}`);
32
+ }
33
+ }
34
+
35
+ function openDevTools(event) {
36
+ try {
37
+ const webContents = event?.sender;
38
+ if (typeof webContents?.openDevTools !== "function") return { ok: false };
39
+ webContents.openDevTools();
40
+ return { ok: true };
41
+ } catch {
42
+ return { ok: false };
43
+ }
44
+ }
45
+
46
+ function focusedEvent() {
47
+ const window = electron.BrowserWindow.getFocusedWindow();
48
+ return window && !window.isDestroyed() ? { sender: window.webContents } : null;
49
+ }
50
+
51
+ function runNativeMenuRequest(request) {
52
+ switch (request?.method) {
53
+ case "devtools/open":
54
+ return openDevTools(focusedEvent());
55
+ default:
56
+ return { ok: false };
57
+ }
58
+ }
59
+
60
+ function templateItems(menuId) {
61
+ return nativeMenuItems
62
+ .filter((item) => item.menuId === menuId)
63
+ .map((item) => ({
64
+ id: item.id,
65
+ label: item.label,
66
+ click: () => {
67
+ runNativeMenuRequest(item.nativeRequest);
68
+ },
69
+ }));
70
+ }
71
+
72
+ function registerNativeMenuItem(item) {
73
+ if (item?.id == null || item?.menuId == null || item?.label == null || item?.nativeRequest?.method == null) {
74
+ return { ok: false };
75
+ }
76
+ const nextItem = {
77
+ id: String(item.id),
78
+ menuId: String(item.menuId),
79
+ label: String(item.label),
80
+ nativeRequest: {
81
+ method: String(item.nativeRequest.method),
82
+ params: item.nativeRequest.params,
83
+ },
84
+ afterId: item.afterId == null ? null : String(item.afterId),
85
+ afterLabel: item.afterLabel == null ? null : String(item.afterLabel),
86
+ };
87
+ nativeMenuItems = nativeMenuItems.filter((existing) => existing.id !== nextItem.id);
88
+ nativeMenuItems.push(nextItem);
89
+ try {
90
+ refreshApplicationMenu?.();
91
+ } catch {}
92
+ logMenuDiagnostics();
93
+ return { ok: true };
94
+ }
95
+
96
+ function openMermaidViewer(params) {
97
+ const html = params?.html;
98
+ if (typeof html !== "string" || html.length === 0) return { ok: false };
99
+ const filePath = path.join(os.tmpdir(), `codex-plus-mermaid-${randomUUID()}.html`);
100
+ fs.writeFileSync(filePath, html, "utf8");
101
+ const window = new electron.BrowserWindow({
102
+ height: 900,
103
+ resizable: true,
104
+ show: true,
105
+ title: "Mermaid diagram viewer",
106
+ webPreferences: {
107
+ contextIsolation: true,
108
+ nodeIntegration: false,
109
+ sandbox: true,
110
+ },
111
+ width: 1400,
112
+ });
113
+ window.webContents.setWindowOpenHandler((event) => {
114
+ try {
115
+ const url = new URL(event.url);
116
+ if (url.protocol === "https:" && url.hostname === "mermaid.live") electron.shell.openExternal(event.url);
117
+ } catch {}
118
+ return { action: "deny" };
119
+ });
120
+ window.on("closed", () => {
121
+ try {
122
+ fs.unlinkSync(filePath);
123
+ } catch {}
124
+ });
125
+ window.loadURL(pathToFileURL(filePath).toString()).catch(() => {});
126
+ return { ok: true };
127
+ }
128
+
129
+ function registerNativeRequest({ isTrustedIpcEvent }) {
130
+ return electron.ipcMain.handle("codex_plus:native-request", async (event, request) => {
131
+ if (!isTrustedIpcEvent(event)) return { ok: false };
132
+ switch (request?.method) {
133
+ case "native-menu/register-item":
134
+ return registerNativeMenuItem(request.params);
135
+ case "devtools/open":
136
+ return openDevTools(event);
137
+ case "mermaid/openViewer":
138
+ return openMermaidViewer(request.params);
139
+ default:
140
+ return { ok: false };
141
+ }
142
+ });
143
+ }
144
+
145
+ function setRefreshApplicationMenu(refresh) {
146
+ refreshApplicationMenu = refresh;
147
+ }
148
+
149
+ return {
150
+ logMenuDiagnostics,
151
+ registerNativeRequest,
152
+ setRefreshApplicationMenu,
153
+ templateItems,
154
+ };
155
+ }
156
+
157
+ module.exports = {
158
+ create,
159
+ };
@@ -0,0 +1,58 @@
1
+ (function () {
2
+ const globalObject = typeof window !== "undefined" ? window : globalThis;
3
+
4
+ function fuzzyFilter(projects, query) {
5
+ const needle = String(query ?? "").trim().toLowerCase();
6
+ return globalObject.CodexPlus?.ui?.projectSelector?.fuzzyFilter?.(projects, query) ??
7
+ (needle
8
+ ? projects.filter((project) =>
9
+ [project.label, project.repositoryData?.rootFolder ?? "", project.path ?? "", project.hostDisplayName ?? ""].some((value) =>
10
+ String(value ?? "").toLowerCase().includes(needle),
11
+ ),
12
+ )
13
+ : projects);
14
+ }
15
+
16
+ function fuzzyHighlight(text, query, jsx) {
17
+ return globalObject.CodexPlus?.ui?.projectSelector?.fuzzyHighlight?.({ text, query, jsx }) ?? text;
18
+ }
19
+
20
+ function closeDropdown(event) {
21
+ const KeyboardEventConstructor = globalObject.KeyboardEvent;
22
+ if (typeof KeyboardEventConstructor !== "function") return;
23
+
24
+ const target = event?.target;
25
+ const dispatchTarget = typeof target?.dispatchEvent === "function" ? target : globalObject.document;
26
+ dispatchTarget?.dispatchEvent?.(new KeyboardEventConstructor("keydown", {
27
+ bubbles: true,
28
+ cancelable: true,
29
+ key: "Escape",
30
+ }));
31
+ }
32
+
33
+ function acceptFirst(event, projects, selectProjectId, query) {
34
+ const project = projects?.[0];
35
+ if (event?.key !== "Enter" || String(query ?? "").trim().length === 0 || project == null) return;
36
+ event.preventDefault?.();
37
+ event.stopPropagation?.();
38
+ selectProjectId(project.projectId);
39
+ closeDropdown(event);
40
+ }
41
+
42
+ function trigger(element, variant, React) {
43
+ return React.isValidElement(element)
44
+ ? React.cloneElement(element, {
45
+ ...element.props,
46
+ "data-codex-plus-project-selector-trigger": true,
47
+ "data-codex-plus-project-selector-variant": variant,
48
+ })
49
+ : element;
50
+ }
51
+
52
+ globalObject.CodexPlusHost.adapters.projectSelector = {
53
+ acceptFirst,
54
+ fuzzyFilter,
55
+ fuzzyHighlight,
56
+ trigger,
57
+ };
58
+ })();
@@ -0,0 +1,62 @@
1
+ (function () {
2
+ const globalObject = typeof window !== "undefined" ? window : globalThis;
3
+
4
+ function renderBodyFromHost(props, hostDeps) {
5
+ const [
6
+ jsxRuntime,
7
+ React,
8
+ useStore,
9
+ useAtom,
10
+ routeAtom,
11
+ cwdAtom,
12
+ hostIdAtom,
13
+ hostConfigAtom,
14
+ conversationIdAtom,
15
+ gitRequest,
16
+ pathValue,
17
+ DefaultReview,
18
+ Button,
19
+ Tooltip,
20
+ Icon,
21
+ Dropdown,
22
+ DropdownMenu,
23
+ BranchPickerDropdownContent,
24
+ ReviewToolbar,
25
+ parseDiff,
26
+ DiffCard,
27
+ ] = hostDeps;
28
+ const deps = {
29
+ jsx: jsxRuntime.jsx,
30
+ jsxs: jsxRuntime.jsxs,
31
+ Fragment: jsxRuntime.Fragment,
32
+ createElement: React.createElement,
33
+ React,
34
+ useStore,
35
+ useAtom,
36
+ routeAtom,
37
+ cwdAtom,
38
+ hostIdAtom,
39
+ hostConfigAtom,
40
+ conversationIdAtom,
41
+ gitRequest,
42
+ pathValue,
43
+ DefaultReview,
44
+ Button,
45
+ Tooltip,
46
+ Icon,
47
+ Dropdown,
48
+ DropdownMenu,
49
+ BranchPickerDropdownContent,
50
+ ReviewToolbar,
51
+ parseDiff,
52
+ DiffCard,
53
+ };
54
+ return globalObject.CodexPlus.ui.review.renderBody({
55
+ props,
56
+ deps,
57
+ defaultBody: props.mainReviewContent ?? deps.jsx(DefaultReview, props),
58
+ });
59
+ }
60
+
61
+ globalObject.CodexPlusHost.adapters.review = { renderBodyFromHost };
62
+ })();
@@ -0,0 +1,21 @@
1
+ (function () {
2
+ const globalObject = typeof window !== "undefined" ? window : globalThis;
3
+
4
+ function projectRowProps(project) {
5
+ return globalObject.CodexPlus?.ui?.sidebar?.projectRowProps?.({ project });
6
+ }
7
+
8
+ function threadRowProps(project) {
9
+ return globalObject.CodexPlus?.ui?.sidebar?.threadRowProps?.({ project });
10
+ }
11
+
12
+ function mergeThreadRowAttributes(base, extra) {
13
+ return globalObject.CodexPlus?.ui?.sidebar?.mergeDataAttributes?.(base, extra);
14
+ }
15
+
16
+ globalObject.CodexPlusHost.adapters.sidebar = {
17
+ mergeThreadRowAttributes,
18
+ projectRowProps,
19
+ threadRowProps,
20
+ };
21
+ })();
@@ -0,0 +1,9 @@
1
+ (function () {
2
+ const globalObject = typeof window !== "undefined" ? window : globalThis;
3
+
4
+ function accessories(context, deps) {
5
+ return globalObject.CodexPlus?.ui?.threadHeader?.renderAccessories?.({ context, deps }) ?? null;
6
+ }
7
+
8
+ globalObject.CodexPlusHost.adapters.threadHeader = { accessories };
9
+ })();
@@ -221,8 +221,15 @@ function isReadOnlyBranchRequest(requestKind, source) {
221
221
  return source === "codex_plus_review" && (requestKind === "recent-branches" || requestKind === "search-branches");
222
222
  }
223
223
 
224
+ function repositoryTargetsFromHost(gitManager, params, platform, signal, getSubmodulePaths) {
225
+ return repositoryTargets(gitManager, params, platform, signal, (root, submoduleSignal) =>
226
+ getSubmodulePaths(gitManager.getWorktreeRepositoryForRoot(root, platform), submoduleSignal),
227
+ );
228
+ }
229
+
224
230
  module.exports = {
225
231
  isReadOnlyBranchRequest,
232
+ repositoryTargetsFromHost,
226
233
  repositoryTargets,
227
234
  traceRequest,
228
235
  };