@weavetab/mcp 2.5.0-beta.2 → 2.5.0-beta.4

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 (113) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +1 -1
  4. package/README.md +158 -961
  5. package/SECURITY.md +33 -22
  6. package/TROUBLESHOOTING.md +2 -2
  7. package/dist/cdp/bridge.js +2 -1
  8. package/dist/cdp/connector.d.ts +2 -0
  9. package/dist/cdp/connector.js +8 -4
  10. package/dist/cdp/helpers.d.ts +4 -0
  11. package/dist/cdp/helpers.js +181 -0
  12. package/dist/cdp/omni_broker.d.ts +1 -1
  13. package/dist/cdp/omni_broker.js +1 -1
  14. package/dist/cdp/walker.d.ts +25 -1
  15. package/dist/cdp/walker.js +304 -7
  16. package/dist/cli/plugin.js +24 -17
  17. package/dist/cli/updater.d.ts +1 -1
  18. package/dist/cli/updater.js +27 -7
  19. package/dist/config/loader.d.ts +8 -0
  20. package/dist/config/loader.js +41 -2
  21. package/dist/desktop/discovery.d.ts +44 -0
  22. package/dist/desktop/discovery.js +364 -0
  23. package/dist/index.js +95 -15
  24. package/dist/intelligence/marks.d.ts +36 -0
  25. package/dist/intelligence/marks.js +30 -0
  26. package/dist/intelligence/patternCompiler.d.ts +118 -0
  27. package/dist/intelligence/patternCompiler.js +259 -0
  28. package/dist/intelligence/sessionMemory.d.ts +2 -0
  29. package/dist/intelligence/sessionMemory.js +18 -8
  30. package/dist/overlay/injector.js +2 -2
  31. package/dist/plugin/interface.d.ts +76 -2
  32. package/dist/plugin/loader.d.ts +15 -1
  33. package/dist/plugin/loader.js +217 -70
  34. package/dist/plugin/pipeline.d.ts +75 -0
  35. package/dist/plugin/pipeline.js +183 -0
  36. package/dist/plugin/registry.js +3 -2
  37. package/dist/security/secrets.d.ts +1 -0
  38. package/dist/security/secrets.js +13 -0
  39. package/dist/sensors/actionability.js +1 -2
  40. package/dist/sensors/guard.js +4 -10
  41. package/dist/sensors/mutations.js +12 -0
  42. package/dist/sensors/operation_monitor.js +1 -0
  43. package/dist/server.d.ts +1 -1
  44. package/dist/server.js +428 -156
  45. package/dist/state/agent.d.ts +1 -0
  46. package/dist/state/agent.js +3 -0
  47. package/dist/state/delta.d.ts +12 -4
  48. package/dist/state/delta.js +67 -14
  49. package/dist/state/session.d.ts +6 -0
  50. package/dist/state/session.js +27 -0
  51. package/dist/tools/browser_detect.d.ts +16 -3
  52. package/dist/tools/browser_detect.js +50 -5
  53. package/dist/tools/burst.d.ts +17 -8
  54. package/dist/tools/burst.js +236 -77
  55. package/dist/tools/checkpoint.d.ts +38 -0
  56. package/dist/tools/checkpoint.js +271 -0
  57. package/dist/tools/click.d.ts +1 -0
  58. package/dist/tools/click.js +74 -70
  59. package/dist/tools/design_extract.js +1 -1
  60. package/dist/tools/eval.js +1 -1
  61. package/dist/tools/fill.d.ts +20 -1
  62. package/dist/tools/fill.js +199 -40
  63. package/dist/tools/find.d.ts +16 -0
  64. package/dist/tools/find.js +67 -44
  65. package/dist/tools/github/index.d.ts +19 -4
  66. package/dist/tools/github/index.js +40 -4
  67. package/dist/tools/highlight.d.ts +6 -2
  68. package/dist/tools/highlight.js +158 -72
  69. package/dist/tools/inspect.js +1 -1
  70. package/dist/tools/key.js +65 -56
  71. package/dist/tools/navigate.js +29 -34
  72. package/dist/tools/pdf.js +21 -13
  73. package/dist/tools/pointer.d.ts +18 -3
  74. package/dist/tools/pointer.js +89 -1
  75. package/dist/tools/read.d.ts +28 -8
  76. package/dist/tools/read.js +230 -56
  77. package/dist/tools/scrape.js +67 -13
  78. package/dist/tools/screenshot.js +21 -16
  79. package/dist/tools/scroll.js +6 -11
  80. package/dist/tools/select.js +8 -2
  81. package/dist/tools/snapshot.js +3 -34
  82. package/dist/tools/tabs.js +14 -6
  83. package/dist/tools/thought.d.ts +2 -0
  84. package/dist/tools/thought.js +13 -14
  85. package/dist/tools/type.d.ts +2 -0
  86. package/dist/tools/type.js +107 -58
  87. package/dist/tools/type_secret.js +42 -76
  88. package/dist/tools/upload.js +1 -1
  89. package/dist/tools/video_compiler.d.ts +1 -1
  90. package/dist/tools/video_compiler.js +11 -12
  91. package/dist/version.d.ts +5 -0
  92. package/dist/version.js +5 -0
  93. package/docs/DOCUMENTATION.md +21 -32
  94. package/docs/PLUGINS.md +121 -0
  95. package/docs/TOOLS.md +56 -0
  96. package/docs/architecture/blind-injection.md +83 -20
  97. package/docs/architecture/overview.md +49 -49
  98. package/docs/configuration/folders.md +55 -54
  99. package/docs/desktop-automation/electron-apps.md +217 -162
  100. package/docs/get-started/introduction.md +60 -60
  101. package/docs/intelligence/site-profiles.md +151 -0
  102. package/docs/tools/tools.md +631 -293
  103. package/docs/troubleshooting/trouble-loop.md +31 -31
  104. package/ext-dist/background.js +10 -62
  105. package/ext-dist/content.js +333 -920
  106. package/ext-dist/hud/hud.js +9 -9
  107. package/ext-dist/manifest.json +2 -6
  108. package/ext-dist/styles.css +6 -1
  109. package/mcp.json +101 -67
  110. package/package.json +5 -6
  111. package/server.json +3 -3
  112. package/ext-dist/sidepanel/sidepanel.js +0 -25
  113. package/ext-dist/sidepanel.html +0 -547
@@ -44,7 +44,7 @@ export const DEFAULT_CONFIG = {
44
44
  sessionTimeout: 3600,
45
45
  navigationTimeout: 30000,
46
46
  idleTimeout: 60000,
47
- actionabilityTimeoutMs: 15000,
47
+ actionabilityTimeoutMs: 300,
48
48
  maxTabs: 10,
49
49
  maxActionsPerMinute: 60,
50
50
  githubCacheTtlMs: 30 * 60 * 1000,
@@ -761,6 +761,19 @@ export function getGlobalSystemDir(subdir) {
761
761
  }
762
762
  return dir;
763
763
  }
764
+ /**
765
+ * Returns the canonical directory for persistent site profiles and learned workflow patterns.
766
+ * Lives in ~/.weavetab-system/memory/sites/ — strictly machine-managed, never user-authored.
767
+ * Separated from the legacy selector-memory dir (~/.weavetab-system/memory/) so existing
768
+ * per-domain selector caches are not disrupted.
769
+ */
770
+ export function getSystemMemorySitesDir() {
771
+ const dir = path.join(GLOBAL_SYSTEM_DIR, "memory", "sites");
772
+ if (!fs.existsSync(dir)) {
773
+ fs.mkdirSync(dir, { recursive: true });
774
+ }
775
+ return dir;
776
+ }
764
777
  export function saveConfig(config) {
765
778
  const { dir, file } = getActiveConfigPath();
766
779
  ensureConfigDir(dir);
@@ -789,11 +802,18 @@ export function saveConfig(config) {
789
802
  }
790
803
  // ─── Live-reload watcher ───────────────────────────────────────────────────────
791
804
  let watchTimeout = null;
805
+ let _configWatcher = null;
792
806
  function startWatcher(dir) {
793
807
  try {
794
808
  if (!fs.existsSync(dir))
795
809
  return;
796
- fs.watch(dir, (eventType, filename) => {
810
+ if (_configWatcher) {
811
+ try {
812
+ _configWatcher.close();
813
+ }
814
+ catch { /* ignore */ }
815
+ }
816
+ _configWatcher = fs.watch(dir, (eventType, filename) => {
797
817
  if (filename === "config.json" && eventType === "change") {
798
818
  if (watchTimeout)
799
819
  clearTimeout(watchTimeout);
@@ -806,12 +826,31 @@ function startWatcher(dir) {
806
826
  logDebug("[Weavetab] Failed to reload config", e);
807
827
  }
808
828
  }, 150);
829
+ if (watchTimeout && typeof watchTimeout.unref === "function") {
830
+ watchTimeout.unref();
831
+ }
809
832
  }
810
833
  });
834
+ if (_configWatcher && typeof _configWatcher.unref === "function") {
835
+ _configWatcher.unref();
836
+ }
811
837
  }
812
838
  catch {
813
839
  // Ignore watch errors on some OS/network mounts
814
840
  }
815
841
  }
842
+ export function stopConfigWatcher() {
843
+ if (watchTimeout) {
844
+ clearTimeout(watchTimeout);
845
+ watchTimeout = null;
846
+ }
847
+ if (_configWatcher) {
848
+ try {
849
+ _configWatcher.close();
850
+ }
851
+ catch { /* ignore */ }
852
+ _configWatcher = null;
853
+ }
854
+ }
816
855
  // Watch global location so changes are picked up
817
856
  startWatcher(GLOBAL_CONFIG_DIR);
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Weavetab — desktop/discovery.ts
3
+ * Auto-discovery scanner for running Electron desktop applications.
4
+ *
5
+ * Detects:
6
+ * 1. Active Electron instances with open CDP remote debugging ports
7
+ * (Slack, Discord, VS Code, Notion, Obsidian, Linear, Cursor, etc.).
8
+ * 2. Running Electron processes without remote debugging enabled,
9
+ * providing actionable launch flags.
10
+ *
11
+ * Weavetab by fy2ne
12
+ */
13
+ export interface DiscoveredElectronApp {
14
+ name: string;
15
+ port?: number;
16
+ status: "debug_port_open" | "process_running_no_debug_port";
17
+ title?: string;
18
+ webSocketDebuggerUrl?: string;
19
+ browserVersion?: string;
20
+ pid?: number;
21
+ recommendation?: string;
22
+ }
23
+ export declare const COMMON_DEBUG_PORTS: number[];
24
+ /**
25
+ * Fast probe of a potential CDP debug port on 127.0.0.1.
26
+ * Resolves with version/target metadata if open, or null if unreachable.
27
+ */
28
+ export declare function probeDebugPort(port: number, timeoutMs?: number): Promise<{
29
+ versionInfo: any;
30
+ targets: any[];
31
+ } | null>;
32
+ export declare function fetchTargets(port: number, timeoutMs?: number): Promise<any[]>;
33
+ /**
34
+ * Infers an application name from CDP version and target metadata.
35
+ */
36
+ export declare function identifyElectronAppName(versionInfo: any, targets: any[]): string;
37
+ /**
38
+ * Discovers running Electron apps on the host machine.
39
+ * 1. Probes common and provided debug ports for open CDP listeners.
40
+ * 2. Checks OS process list for running Electron apps lacking debug ports.
41
+ */
42
+ export declare function discoverElectronApps(customPorts?: number[]): Promise<DiscoveredElectronApp[]>;
43
+ export declare function isValidElectronExecutablePath(execPath: string): boolean;
44
+ export declare function resolveElectronExecutablePath(appName: string, configuredPath?: string): string | null;
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Weavetab — desktop/discovery.ts
3
+ * Auto-discovery scanner for running Electron desktop applications.
4
+ *
5
+ * Detects:
6
+ * 1. Active Electron instances with open CDP remote debugging ports
7
+ * (Slack, Discord, VS Code, Notion, Obsidian, Linear, Cursor, etc.).
8
+ * 2. Running Electron processes without remote debugging enabled,
9
+ * providing actionable launch flags.
10
+ *
11
+ * Weavetab by fy2ne
12
+ */
13
+ import http from "node:http";
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ import { exec } from "node:child_process";
17
+ import { promisify } from "node:util";
18
+ import { logDebug } from "../audit/logger.js";
19
+ const execAsync = promisify(exec);
20
+ export const COMMON_DEBUG_PORTS = [9222, 9229, 9333, 8315, 9223, 9224];
21
+ const KNOWN_ELECTRON_PROCS = {
22
+ slack: "Slack",
23
+ discord: "Discord",
24
+ code: "VS Code",
25
+ "visual studio code": "VS Code",
26
+ notion: "Notion",
27
+ obsidian: "Obsidian",
28
+ linear: "Linear",
29
+ spotify: "Spotify",
30
+ postman: "Postman",
31
+ cursor: "Cursor",
32
+ figma: "Figma",
33
+ };
34
+ /**
35
+ * Fast probe of a potential CDP debug port on 127.0.0.1.
36
+ * Resolves with version/target metadata if open, or null if unreachable.
37
+ */
38
+ export async function probeDebugPort(port, timeoutMs = 300) {
39
+ return new Promise((resolve) => {
40
+ const req = http.get(`http://127.0.0.1:${port}/json/version`, { timeout: timeoutMs }, (res) => {
41
+ if (res.statusCode !== 200) {
42
+ res.resume();
43
+ return resolve(null);
44
+ }
45
+ let data = "";
46
+ res.on("data", (chunk) => (data += chunk));
47
+ res.on("end", async () => {
48
+ try {
49
+ const versionInfo = JSON.parse(data);
50
+ const targets = await fetchTargets(port, timeoutMs).catch(() => []);
51
+ resolve({ versionInfo, targets });
52
+ }
53
+ catch {
54
+ resolve(null);
55
+ }
56
+ });
57
+ });
58
+ req.on("error", () => resolve(null));
59
+ req.on("timeout", () => {
60
+ req.destroy();
61
+ resolve(null);
62
+ });
63
+ });
64
+ }
65
+ export async function fetchTargets(port, timeoutMs = 300) {
66
+ return new Promise((resolve) => {
67
+ const req = http.get(`http://127.0.0.1:${port}/json/list`, { timeout: timeoutMs }, (res) => {
68
+ if (res.statusCode !== 200) {
69
+ res.resume();
70
+ return resolve([]);
71
+ }
72
+ let data = "";
73
+ res.on("data", (chunk) => (data += chunk));
74
+ res.on("end", () => {
75
+ try {
76
+ resolve(JSON.parse(data));
77
+ }
78
+ catch {
79
+ resolve([]);
80
+ }
81
+ });
82
+ });
83
+ req.on("error", () => resolve([]));
84
+ req.on("timeout", () => {
85
+ req.destroy();
86
+ resolve([]);
87
+ });
88
+ });
89
+ }
90
+ /**
91
+ * Infers an application name from CDP version and target metadata.
92
+ */
93
+ export function identifyElectronAppName(versionInfo, targets) {
94
+ const allText = [
95
+ versionInfo?.Browser || "",
96
+ versionInfo?.["User-Agent"] || "",
97
+ ...targets.map((t) => `${t.title || ""} ${t.url || ""}`),
98
+ ].join(" ").toLowerCase();
99
+ for (const [key, displayName] of Object.entries(KNOWN_ELECTRON_PROCS)) {
100
+ if (allText.includes(key)) {
101
+ return displayName;
102
+ }
103
+ }
104
+ // Fallback to first non-empty page title
105
+ const primaryTarget = targets.find((t) => t.type === "page" && t.title);
106
+ if (primaryTarget?.title) {
107
+ return primaryTarget.title.slice(0, 30);
108
+ }
109
+ return "Electron Application";
110
+ }
111
+ /**
112
+ * Discovers running Electron apps on the host machine.
113
+ * 1. Probes common and provided debug ports for open CDP listeners.
114
+ * 2. Checks OS process list for running Electron apps lacking debug ports.
115
+ */
116
+ export async function discoverElectronApps(customPorts = []) {
117
+ const discovered = [];
118
+ const portsToScan = Array.from(new Set([...COMMON_DEBUG_PORTS, ...customPorts]));
119
+ // 1. Probe all candidate debug ports in parallel
120
+ const probeResults = await Promise.all(portsToScan.map(async (port) => ({
121
+ port,
122
+ res: await probeDebugPort(port),
123
+ })));
124
+ const openAppNames = new Set();
125
+ for (const { port, res } of probeResults) {
126
+ if (res) {
127
+ const name = identifyElectronAppName(res.versionInfo, res.targets);
128
+ openAppNames.add(name.toLowerCase());
129
+ discovered.push({
130
+ name,
131
+ port,
132
+ status: "debug_port_open",
133
+ title: res.targets.find((t) => t.title)?.title || undefined,
134
+ webSocketDebuggerUrl: res.versionInfo.webSocketDebuggerUrl,
135
+ browserVersion: res.versionInfo.Browser,
136
+ });
137
+ }
138
+ }
139
+ // 2. Scan OS process list for known Electron apps without debug ports
140
+ try {
141
+ const runningProcs = await getRunningElectronProcesses();
142
+ for (const proc of runningProcs) {
143
+ if (!openAppNames.has(proc.name.toLowerCase())) {
144
+ discovered.push({
145
+ name: proc.name,
146
+ pid: proc.pid,
147
+ status: "process_running_no_debug_port",
148
+ recommendation: `Launch or restart ${proc.name} with: --remote-debugging-port=9222`,
149
+ });
150
+ }
151
+ }
152
+ }
153
+ catch {
154
+ // Process list scan is best-effort across restricted OS environments
155
+ }
156
+ return discovered;
157
+ }
158
+ async function getRunningElectronProcesses() {
159
+ const platform = process.platform;
160
+ const running = [];
161
+ try {
162
+ if (platform === "win32") {
163
+ const { stdout } = await execAsync('tasklist /FO CSV /NH', { timeout: 1500 });
164
+ const lines = stdout.split("\r\n");
165
+ const seenNames = new Set();
166
+ for (const line of lines) {
167
+ if (!line.trim())
168
+ continue;
169
+ const parts = line.split('","').map((p) => p.replace(/"/g, ""));
170
+ const exeName = (parts[0] || "").toLowerCase();
171
+ const pid = parseInt(parts[1], 10);
172
+ for (const [key, displayName] of Object.entries(KNOWN_ELECTRON_PROCS)) {
173
+ if (exeName.includes(key) && !seenNames.has(displayName)) {
174
+ seenNames.add(displayName);
175
+ running.push({ name: displayName, pid });
176
+ }
177
+ }
178
+ }
179
+ }
180
+ else {
181
+ const { stdout } = await execAsync('ps -eo pid,comm', { timeout: 1500 });
182
+ const lines = stdout.split("\n");
183
+ const seenNames = new Set();
184
+ for (const line of lines) {
185
+ const trimmed = line.trim();
186
+ if (!trimmed)
187
+ continue;
188
+ const [pidStr, ...cmdParts] = trimmed.split(/\s+/);
189
+ const cmd = cmdParts.join(" ").toLowerCase();
190
+ const pid = parseInt(pidStr, 10);
191
+ for (const [key, displayName] of Object.entries(KNOWN_ELECTRON_PROCS)) {
192
+ if (cmd.includes(key) && !seenNames.has(displayName)) {
193
+ seenNames.add(displayName);
194
+ running.push({ name: displayName, pid });
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ catch {
201
+ // ignore
202
+ }
203
+ return running;
204
+ }
205
+ export function isValidElectronExecutablePath(execPath) {
206
+ if (!execPath || typeof execPath !== "string")
207
+ return false;
208
+ const normalizedPath = execPath.toLowerCase().trim();
209
+ // Check for suspicious patterns that might indicate command injection or shell escapes
210
+ const suspiciousPatterns = [
211
+ '%', // URL encoding
212
+ 'cmd.exe', 'powershell.exe', 'bash', 'sh', 'wscript.exe', 'cscript.exe'
213
+ ];
214
+ if (suspiciousPatterns.some(pattern => normalizedPath.includes(pattern))) {
215
+ logDebug(`[SECURITY] Electron path validation failed: suspicious pattern in path: ${execPath}`);
216
+ return false;
217
+ }
218
+ try {
219
+ if (!fs.existsSync(execPath)) {
220
+ return false;
221
+ }
222
+ const stat = fs.statSync(execPath);
223
+ if (!stat.isFile()) {
224
+ return false;
225
+ }
226
+ }
227
+ catch {
228
+ return false;
229
+ }
230
+ return true;
231
+ }
232
+ export function resolveElectronExecutablePath(appName, configuredPath) {
233
+ const normApp = appName.toLowerCase().trim();
234
+ // 1. If configuredPath is provided and valid, return it
235
+ if (configuredPath && isValidElectronExecutablePath(configuredPath)) {
236
+ return path.resolve(configuredPath);
237
+ }
238
+ // 2. If configuredPath was provided, but does not exist, check versioned parent folder (e.g. Squirrel app-*)
239
+ if (configuredPath) {
240
+ try {
241
+ const parsed = path.parse(path.resolve(configuredPath));
242
+ const parentDir = parsed.dir;
243
+ const baseExe = parsed.base || (process.platform === "win32" ? `${appName}.exe` : appName);
244
+ const parentBase = path.basename(parentDir);
245
+ const searchDirs = [];
246
+ if (parentBase.toLowerCase().startsWith("app-") || parentBase.toLowerCase().startsWith("app_")) {
247
+ searchDirs.push(path.dirname(parentDir));
248
+ }
249
+ else {
250
+ searchDirs.push(parentDir);
251
+ }
252
+ for (const sDir of searchDirs) {
253
+ if (fs.existsSync(sDir)) {
254
+ const entries = fs.readdirSync(sDir, { withFileTypes: true });
255
+ const versionDirs = entries
256
+ .filter(e => e.isDirectory() && (e.name.startsWith("app-") || e.name.startsWith("app_")))
257
+ .map(e => e.name)
258
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
259
+ for (const vDir of versionDirs) {
260
+ const candidate = path.join(sDir, vDir, baseExe);
261
+ if (isValidElectronExecutablePath(candidate)) {
262
+ return candidate;
263
+ }
264
+ }
265
+ const directCandidate = path.join(sDir, baseExe);
266
+ if (isValidElectronExecutablePath(directCandidate)) {
267
+ return directCandidate;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ catch { /* ignore fallback errors */ }
273
+ }
274
+ // 3. Platform-specific auto-discovery for known apps
275
+ const platform = process.platform;
276
+ const candidates = [];
277
+ if (platform === "win32") {
278
+ const localAppData = process.env.LOCALAPPDATA || "";
279
+ const progFiles = process.env.ProgramFiles || "C:\\Program Files";
280
+ const progFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
281
+ if (normApp === "discord") {
282
+ const discordDir = path.join(localAppData, "Discord");
283
+ if (fs.existsSync(discordDir)) {
284
+ try {
285
+ const entries = fs.readdirSync(discordDir, { withFileTypes: true });
286
+ const vDirs = entries
287
+ .filter(e => e.isDirectory() && e.name.startsWith("app-"))
288
+ .map(e => e.name)
289
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
290
+ for (const v of vDirs) {
291
+ candidates.push(path.join(discordDir, v, "Discord.exe"));
292
+ }
293
+ }
294
+ catch { }
295
+ }
296
+ candidates.push(path.join(progFiles, "Discord", "Discord.exe"));
297
+ candidates.push(path.join(progFilesX86, "Discord", "Discord.exe"));
298
+ }
299
+ else if (normApp === "slack") {
300
+ const slackDir = path.join(localAppData, "slack");
301
+ if (fs.existsSync(slackDir)) {
302
+ try {
303
+ const entries = fs.readdirSync(slackDir, { withFileTypes: true });
304
+ const vDirs = entries
305
+ .filter(e => e.isDirectory() && e.name.startsWith("app-"))
306
+ .map(e => e.name)
307
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
308
+ for (const v of vDirs) {
309
+ candidates.push(path.join(slackDir, v, "slack.exe"));
310
+ }
311
+ }
312
+ catch { }
313
+ }
314
+ candidates.push(path.join(progFiles, "Slack", "slack.exe"));
315
+ candidates.push(path.join(progFilesX86, "Slack", "slack.exe"));
316
+ }
317
+ else if (normApp === "antigravity") {
318
+ candidates.push(path.join(localAppData, "Programs", "antigravity", "Antigravity.exe"));
319
+ }
320
+ else if (normApp === "antigravity-ide") {
321
+ candidates.push(path.join(localAppData, "Programs", "Antigravity IDE", "Antigravity IDE.exe"));
322
+ }
323
+ else if (normApp === "code" || normApp === "vscode") {
324
+ candidates.push(path.join(localAppData, "Programs", "Microsoft VS Code", "Code.exe"));
325
+ candidates.push(path.join(progFiles, "Microsoft VS Code", "Code.exe"));
326
+ candidates.push(path.join(progFilesX86, "Microsoft VS Code", "Code.exe"));
327
+ }
328
+ else if (normApp === "linear") {
329
+ candidates.push(path.join(localAppData, "Programs", "Linear", "Linear.exe"));
330
+ }
331
+ else if (normApp === "notion") {
332
+ candidates.push(path.join(localAppData, "Programs", "Notion", "Notion.exe"));
333
+ }
334
+ else if (normApp === "obsidian") {
335
+ candidates.push(path.join(localAppData, "Programs", "obsidian", "Obsidian.exe"));
336
+ }
337
+ }
338
+ else if (platform === "darwin") {
339
+ if (normApp === "discord")
340
+ candidates.push("/Applications/Discord.app/Contents/MacOS/Discord");
341
+ else if (normApp === "slack")
342
+ candidates.push("/Applications/Slack.app/Contents/MacOS/Slack");
343
+ else if (normApp === "code" || normApp === "vscode")
344
+ candidates.push("/Applications/Visual Studio Code.app/Contents/MacOS/Electron");
345
+ else if (normApp === "linear")
346
+ candidates.push("/Applications/Linear.app/Contents/MacOS/Linear");
347
+ else if (normApp === "notion")
348
+ candidates.push("/Applications/Notion.app/Contents/MacOS/Notion");
349
+ else if (normApp === "obsidian")
350
+ candidates.push("/Applications/Obsidian.app/Contents/MacOS/Obsidian");
351
+ }
352
+ else {
353
+ // Linux
354
+ candidates.push(`/usr/bin/${normApp}`);
355
+ candidates.push(`/usr/local/bin/${normApp}`);
356
+ candidates.push(`/usr/share/${normApp}/${normApp}`);
357
+ }
358
+ for (const c of candidates) {
359
+ if (isValidElectronExecutablePath(c)) {
360
+ return c;
361
+ }
362
+ }
363
+ return null;
364
+ }