-
-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathpreload.ts
More file actions
319 lines (317 loc) · 21 KB
/
preload.ts
File metadata and controls
319 lines (317 loc) · 21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron";
// Early setup wrapped in try/catch so contextBridge.exposeInMainWorld always runs
// even if DOM isn't ready or something else fails above it.
try {
// Apply platform + glass classes as early as possible (before React mounts)
ipcRenderer.invoke("app:getGlassEnabled").then((enabled: boolean) => {
// Platform class for platform-specific CSS (e.g. hiding macOS traffic-light padding on Windows)
document.documentElement.classList.add(`platform-${process.platform}`);
if (enabled) {
document.documentElement.classList.add("glass-enabled");
}
});
} catch (e) {
console.error("[preload] early setup failed:", e);
}
contextBridge.exposeInMainWorld("claude", {
getGlassEnabled: () => ipcRenderer.invoke("app:getGlassEnabled"),
setMinWidth: (width: number) => ipcRenderer.send("app:set-min-width", width),
start: (options: unknown) => ipcRenderer.invoke("claude:start", options),
send: (sessionId: string, message: unknown) => ipcRenderer.invoke("claude:send", { sessionId, message }),
stop: (sessionId: string, reason?: string) =>
ipcRenderer.invoke("claude:stop", { sessionId, reason }),
interrupt: (sessionId: string) => ipcRenderer.invoke("claude:interrupt", sessionId),
log: (label: string, data: unknown) => ipcRenderer.send("claude:log", label, data),
onEvent: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("claude:event", listener);
return () => ipcRenderer.removeListener("claude:event", listener);
},
onStderr: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("claude:stderr", listener);
return () => ipcRenderer.removeListener("claude:stderr", listener);
},
onExit: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("claude:exit", listener);
return () => ipcRenderer.removeListener("claude:exit", listener);
},
onPermissionRequest: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("claude:permission_request", listener);
return () => ipcRenderer.removeListener("claude:permission_request", listener);
},
respondPermission: (sessionId: string, requestId: string, behavior: string, toolUseId: string, toolInput: unknown, newPermissionMode?: string) =>
ipcRenderer.invoke("claude:permission_response", { sessionId, requestId, behavior, toolUseId, toolInput, newPermissionMode }),
setPermissionMode: (sessionId: string, permissionMode: string) =>
ipcRenderer.invoke("claude:set-permission-mode", { sessionId, permissionMode }),
setModel: (sessionId: string, model: string) =>
ipcRenderer.invoke("claude:set-model", { sessionId, model }),
setThinking: (sessionId: string, thinkingEnabled: boolean) =>
ipcRenderer.invoke("claude:set-thinking", { sessionId, thinkingEnabled }),
version: () => ipcRenderer.invoke("claude:version"),
binaryStatus: () => ipcRenderer.invoke("claude:binary-status"),
supportedModels: (sessionId: string) => ipcRenderer.invoke("claude:supported-models", sessionId),
slashCommands: (sessionId: string) => ipcRenderer.invoke("claude:slash-commands", sessionId),
modelsCacheGet: () => ipcRenderer.invoke("claude:models-cache:get"),
modelsCacheRevalidate: (options?: { cwd?: string }) => ipcRenderer.invoke("claude:models-cache:revalidate", options),
mcpStatus: (sessionId: string) => ipcRenderer.invoke("claude:mcp-status", sessionId),
mcpReconnect: (sessionId: string, serverName: string) =>
ipcRenderer.invoke("claude:mcp-reconnect", { sessionId, serverName }),
revertFiles: (sessionId: string, checkpointId: string) =>
ipcRenderer.invoke("claude:revert-files", { sessionId, checkpointId }),
restartSession: (sessionId: string, mcpServers?: unknown[], cwd?: string, effort?: string, model?: string) =>
ipcRenderer.invoke("claude:restart-session", { sessionId, mcpServers, cwd, effort, model }),
readFile: (filePath: string) => ipcRenderer.invoke("file:read", filePath),
openInEditor: (filePath: string, line?: number, editor?: string) => ipcRenderer.invoke("file:open-in-editor", { filePath, line, editor }),
openExternal: (url: string) => ipcRenderer.invoke("shell:open-external", url),
generateTitle: (message: string, cwd?: string, engine?: string, sessionId?: string) =>
ipcRenderer.invoke("claude:generate-title", { message, cwd, engine, sessionId }),
projects: {
list: () => ipcRenderer.invoke("projects:list"),
create: (spaceId?: string) => ipcRenderer.invoke("projects:create", spaceId),
createDev: (name: string, spaceId?: string) => ipcRenderer.invoke("projects:create-dev", name, spaceId),
delete: (projectId: string) => ipcRenderer.invoke("projects:delete", projectId),
rename: (projectId: string, name: string) => ipcRenderer.invoke("projects:rename", projectId, name),
updateSpace: (projectId: string, spaceId: string) => ipcRenderer.invoke("projects:update-space", projectId, spaceId),
updateIcon: (projectId: string, icon: string | null, iconType: "emoji" | "lucide" | null) => ipcRenderer.invoke("projects:update-icon", projectId, icon, iconType),
reorder: (projectId: string, targetProjectId: string) => ipcRenderer.invoke("projects:reorder", projectId, targetProjectId),
},
sessions: {
save: (data: unknown) => ipcRenderer.invoke("sessions:save", data),
load: (projectId: string, sessionId: string) => ipcRenderer.invoke("sessions:load", projectId, sessionId),
list: (projectId: string) => ipcRenderer.invoke("sessions:list", projectId),
delete: (projectId: string, sessionId: string) => ipcRenderer.invoke("sessions:delete", projectId, sessionId),
search: (projectIds: string[], query: string) => ipcRenderer.invoke("sessions:search", { projectIds, query }),
},
spaces: {
list: () => ipcRenderer.invoke("spaces:list"),
save: (spaces: unknown) => ipcRenderer.invoke("spaces:save", spaces),
},
ccSessions: {
list: (projectPath: string) => ipcRenderer.invoke("cc-sessions:list", projectPath),
import: (projectPath: string, ccSessionId: string) => ipcRenderer.invoke("cc-sessions:import", projectPath, ccSessionId),
},
files: {
list: (cwd: string) => ipcRenderer.invoke("files:list", cwd),
listAll: (cwd: string) => ipcRenderer.invoke("files:list-all", cwd),
watch: (cwd: string) => ipcRenderer.invoke("files:watch", cwd),
unwatch: (cwd: string) => ipcRenderer.invoke("files:unwatch", cwd),
readMultiple: (cwd: string, paths: string[]) => ipcRenderer.invoke("files:read-multiple", { cwd, paths }),
onChanged: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("files:changed", listener);
return () => ipcRenderer.removeListener("files:changed", listener);
},
},
git: {
discoverRepos: (projectPath: string) => ipcRenderer.invoke("git:discover-repos", projectPath),
status: (cwd: string) => ipcRenderer.invoke("git:status", cwd),
stage: (cwd: string, files: string[]) => ipcRenderer.invoke("git:stage", { cwd, files }),
unstage: (cwd: string, files: string[]) => ipcRenderer.invoke("git:unstage", { cwd, files }),
stageAll: (cwd: string) => ipcRenderer.invoke("git:stage-all", cwd),
unstageAll: (cwd: string) => ipcRenderer.invoke("git:unstage-all", cwd),
discard: (cwd: string, files: string[]) => ipcRenderer.invoke("git:discard", { cwd, files }),
commit: (cwd: string, message: string) => ipcRenderer.invoke("git:commit", { cwd, message }),
branches: (cwd: string) => ipcRenderer.invoke("git:branches", cwd),
checkout: (cwd: string, branch: string) => ipcRenderer.invoke("git:checkout", { cwd, branch }),
createBranch: (cwd: string, name: string) => ipcRenderer.invoke("git:create-branch", { cwd, name }),
createWorktree: (cwd: string, path: string, branch: string, fromRef?: string) => ipcRenderer.invoke("git:create-worktree", { cwd, path, branch, fromRef }),
removeWorktree: (cwd: string, path: string, force?: boolean) => ipcRenderer.invoke("git:remove-worktree", { cwd, path, force }),
pruneWorktrees: (cwd: string) => ipcRenderer.invoke("git:prune-worktrees", cwd),
push: (cwd: string) => ipcRenderer.invoke("git:push", cwd),
pull: (cwd: string) => ipcRenderer.invoke("git:pull", cwd),
fetch: (cwd: string) => ipcRenderer.invoke("git:fetch", cwd),
diffFile: (cwd: string, file: string, staged: boolean) => ipcRenderer.invoke("git:diff-file", { cwd, file, staged }),
diffStat: (cwd: string) => ipcRenderer.invoke("git:diff-stat", cwd) as Promise<{ additions: number; deletions: number }>,
log: (cwd: string, count?: number) => ipcRenderer.invoke("git:log", { cwd, count }),
getGitDirSize: (cwd: string) => ipcRenderer.invoke("git:get-git-dir-size", cwd) as Promise<{ sizeBytes: number; error?: string }>,
generateCommitMessage: (cwd: string, engine?: string, sessionId?: string) =>
ipcRenderer.invoke("git:generate-commit-message", { cwd, engine, sessionId }),
},
terminal: {
create: (options: { cwd?: string; cols?: number; rows?: number; spaceId?: string }) => ipcRenderer.invoke("terminal:create", options),
list: () => ipcRenderer.invoke("terminal:list"),
snapshot: (terminalId: string) => ipcRenderer.invoke("terminal:snapshot", terminalId),
write: (terminalId: string, data: string) => ipcRenderer.invoke("terminal:write", { terminalId, data }),
resize: (terminalId: string, cols: number, rows: number) => ipcRenderer.invoke("terminal:resize", { terminalId, cols, rows }),
destroy: (terminalId: string) => ipcRenderer.invoke("terminal:destroy", terminalId),
destroySpace: (spaceId: string) => ipcRenderer.invoke("terminal:destroy-space", spaceId),
onData: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("terminal:data", listener);
return () => ipcRenderer.removeListener("terminal:data", listener);
},
onExit: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("terminal:exit", listener);
return () => ipcRenderer.removeListener("terminal:exit", listener);
},
},
acp: {
log: (label: string, data: unknown) => ipcRenderer.send("acp:log", label, data),
start: (options: { agentId: string; cwd: string; mcpServers?: unknown[] }) => ipcRenderer.invoke("acp:start", options),
prompt: (sessionId: string, text: string, images?: unknown[]) =>
ipcRenderer.invoke("acp:prompt", { sessionId, text, images }),
stop: (sessionId: string) => ipcRenderer.invoke("acp:stop", sessionId),
reloadSession: (sessionId: string, mcpServers?: unknown[], cwd?: string) =>
ipcRenderer.invoke("acp:reload-session", { sessionId, mcpServers, cwd }),
reviveSession: (options: { agentId: string; cwd: string; agentSessionId?: string; mcpServers?: unknown[] }) =>
ipcRenderer.invoke("acp:revive-session", options),
cancel: (sessionId: string) => ipcRenderer.invoke("acp:cancel", sessionId),
abortPendingStart: () => ipcRenderer.invoke("acp:abort-pending-start"),
respondPermission: (sessionId: string, requestId: string, optionId: string) =>
ipcRenderer.invoke("acp:permission_response", { sessionId, requestId, optionId }),
setConfig: (sessionId: string, configId: string, value: string) =>
ipcRenderer.invoke("acp:set-config", { sessionId, configId, value }),
getConfigOptions: (sessionId: string) =>
ipcRenderer.invoke("acp:get-config-options", sessionId),
getAvailableCommands: (sessionId: string) =>
ipcRenderer.invoke("acp:get-available-commands", sessionId),
onEvent: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("acp:event", listener);
return () => ipcRenderer.removeListener("acp:event", listener);
},
onPermissionRequest: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("acp:permission_request", listener);
return () => ipcRenderer.removeListener("acp:permission_request", listener);
},
onTurnComplete: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("acp:turn_complete", listener);
return () => ipcRenderer.removeListener("acp:turn_complete", listener);
},
onExit: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("acp:exit", listener);
return () => ipcRenderer.removeListener("acp:exit", listener);
},
},
codex: {
log: (label: string, data: unknown) => ipcRenderer.send("codex:log", label, data),
start: (options: { cwd: string; model?: string; approvalPolicy?: string; sandbox?: "read-only" | "workspace-write" | "danger-full-access"; personality?: string; collaborationMode?: { mode: string; settings: { model: string; reasoning_effort: string | null; developer_instructions: string | null } } }) =>
ipcRenderer.invoke("codex:start", options),
send: (sessionId: string, text: string, images?: Array<{ type: "image"; url: string } | { type: "localImage"; path: string }>, effort?: string, collaborationMode?: { mode: string; settings: { model: string; reasoning_effort: string | null; developer_instructions: string | null } }) =>
ipcRenderer.invoke("codex:send", { sessionId, text, images, effort, collaborationMode }),
stop: (sessionId: string) => ipcRenderer.invoke("codex:stop", sessionId),
interrupt: (sessionId: string) => ipcRenderer.invoke("codex:interrupt", sessionId),
respondApproval: (sessionId: string, rpcId: string | number, decision: string, acceptSettings?: unknown) =>
ipcRenderer.invoke("codex:approval_response", { sessionId, rpcId, decision, acceptSettings }),
respondUserInput: (sessionId: string, rpcId: string | number, answers: Record<string, { answers: string[] }>) =>
ipcRenderer.invoke("codex:user_input_response", { sessionId, rpcId, answers }),
respondServerRequestError: (sessionId: string, rpcId: string | number, code: number, message: string) =>
ipcRenderer.invoke("codex:server_request_error", { sessionId, rpcId, code, message }),
compact: (sessionId: string) => ipcRenderer.invoke("codex:compact", sessionId),
listSkills: (sessionId: string) => ipcRenderer.invoke("codex:list-skills", sessionId),
listApps: (sessionId: string) => ipcRenderer.invoke("codex:list-apps", sessionId),
listModels: () => ipcRenderer.invoke("codex:list-models"),
authStatus: () => ipcRenderer.invoke("codex:auth-status"),
login: (sessionId: string, type: "apiKey" | "chatgpt", apiKey?: string) =>
ipcRenderer.invoke("codex:login", { sessionId, type, apiKey }),
resume: (options: { cwd: string; threadId: string; model?: string; approvalPolicy?: string; sandbox?: "read-only" | "workspace-write" | "danger-full-access" }) =>
ipcRenderer.invoke("codex:resume", options),
setModel: (sessionId: string, model: string) =>
ipcRenderer.invoke("codex:set-model", { sessionId, model }),
version: () => ipcRenderer.invoke("codex:version"),
binaryStatus: () => ipcRenderer.invoke("codex:binary-status"),
onEvent: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("codex:event", listener);
return () => ipcRenderer.removeListener("codex:event", listener);
},
onApprovalRequest: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("codex:approval_request", listener);
return () => ipcRenderer.removeListener("codex:approval_request", listener);
},
onExit: (callback: (data: unknown) => void) => {
const listener = (_event: IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on("codex:exit", listener);
return () => ipcRenderer.removeListener("codex:exit", listener);
},
},
mcp: {
list: (projectId: string) => ipcRenderer.invoke("mcp:list", projectId),
add: (projectId: string, server: unknown) => ipcRenderer.invoke("mcp:add", { projectId, server }),
remove: (projectId: string, name: string) => ipcRenderer.invoke("mcp:remove", { projectId, name }),
authenticate: (serverName: string, serverUrl: string) => ipcRenderer.invoke("mcp:authenticate", { serverName, serverUrl }),
authStatus: (serverName: string) => ipcRenderer.invoke("mcp:auth-status", serverName),
probe: (servers: unknown[]) => ipcRenderer.invoke("mcp:probe", servers),
},
agents: {
list: () => ipcRenderer.invoke("agents:list"),
save: (agent: unknown) => ipcRenderer.invoke("agents:save", agent),
delete: (id: string) => ipcRenderer.invoke("agents:delete", id),
updateCachedConfig: (agentId: string, configOptions: unknown[]) =>
ipcRenderer.invoke("agents:update-cached-config", agentId, configOptions),
checkBinaries: (agents: Array<{ id: string; binary: Record<string, { cmd: string; args?: string[] }> }>) =>
ipcRenderer.invoke("agents:check-binaries", agents),
},
settings: {
get: () => ipcRenderer.invoke("settings:get"),
set: (patch: Record<string, unknown>) => ipcRenderer.invoke("settings:set", patch),
},
jira: {
getConfig: (projectId: string) => ipcRenderer.invoke("jira:get-config", projectId),
saveConfig: (projectId: string, config: unknown) =>
ipcRenderer.invoke("jira:save-config", { projectId, config }),
deleteConfig: (projectId: string) => ipcRenderer.invoke("jira:delete-config", projectId),
authenticate: (instanceUrl: string, method: "oauth" | "apitoken", apiToken?: string, email?: string) =>
ipcRenderer.invoke("jira:authenticate", { instanceUrl, method, apiToken, email }),
authStatus: (instanceUrl: string) => ipcRenderer.invoke("jira:auth-status", instanceUrl),
logout: (instanceUrl: string) => ipcRenderer.invoke("jira:logout", instanceUrl),
getProjects: (instanceUrl: string) => ipcRenderer.invoke("jira:get-projects", instanceUrl),
getBoards: (params: { instanceUrl: string; projectKey?: string }) =>
ipcRenderer.invoke("jira:get-boards", params),
getBoardConfiguration: (params: { instanceUrl: string; boardId: string }) =>
ipcRenderer.invoke("jira:get-board-configuration", params),
getSprints: (params: { instanceUrl: string; boardId: string }) =>
ipcRenderer.invoke("jira:get-sprints", params),
getIssues: (params: { instanceUrl: string; boardId: string; sprintId?: string; maxResults?: number }) =>
ipcRenderer.invoke("jira:get-issues", params),
getComments: (params: { instanceUrl: string; issueKey: string }) =>
ipcRenderer.invoke("jira:get-comments", params),
getTransitions: (params: { instanceUrl: string; issueKey: string }) =>
ipcRenderer.invoke("jira:get-transitions", params),
transitionIssue: (params: { instanceUrl: string; issueKey: string; transitionId: string }) =>
ipcRenderer.invoke("jira:transition-issue", params),
},
analytics: {
capture: (event: string, properties?: Record<string, unknown>) =>
ipcRenderer.send("analytics:capture", event, properties),
},
speech: {
startNativeDictation: () => ipcRenderer.invoke("speech:start-native-dictation"),
getPlatform: () => ipcRenderer.invoke("speech:get-platform"),
requestMicPermission: () => ipcRenderer.invoke("speech:request-mic-permission"),
},
updater: {
onUpdateAvailable: (cb: (info: unknown) => void) => {
const listener = (_event: IpcRendererEvent, info: unknown) => cb(info);
ipcRenderer.on("updater:update-available", listener);
return () => ipcRenderer.removeListener("updater:update-available", listener);
},
onDownloadProgress: (cb: (progress: unknown) => void) => {
const listener = (_event: IpcRendererEvent, progress: unknown) => cb(progress);
ipcRenderer.on("updater:download-progress", listener);
return () => ipcRenderer.removeListener("updater:download-progress", listener);
},
onUpdateDownloaded: (cb: (info: unknown) => void) => {
const listener = (_event: IpcRendererEvent, info: unknown) => cb(info);
ipcRenderer.on("updater:update-downloaded", listener);
return () => ipcRenderer.removeListener("updater:update-downloaded", listener);
},
onInstallError: (cb: (error: { message: string }) => void) => {
const listener = (_event: IpcRendererEvent, error: { message: string }) => cb(error);
ipcRenderer.on("updater:install-error", listener);
return () => ipcRenderer.removeListener("updater:install-error", listener);
},
download: () => ipcRenderer.invoke("updater:download"),
install: () => ipcRenderer.invoke("updater:install"),
check: () => ipcRenderer.invoke("updater:check"),
currentVersion: () => ipcRenderer.invoke("updater:current-version") as Promise<string>,
},
});