@weavetab/mcp 2.5.0-beta.1 → 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 (156) hide show
  1. package/AGENTS.md +2 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +1 -1
  4. package/README.md +164 -802
  5. package/SECURITY.md +33 -22
  6. package/TROUBLESHOOTING.md +3 -3
  7. package/dist/cdp/bridge.d.ts +10 -0
  8. package/dist/cdp/bridge.js +285 -78
  9. package/dist/cdp/connector.d.ts +7 -3
  10. package/dist/cdp/connector.js +196 -75
  11. package/dist/cdp/helpers.d.ts +4 -0
  12. package/dist/cdp/helpers.js +181 -0
  13. package/dist/cdp/lock.d.ts +9 -6
  14. package/dist/cdp/lock.js +72 -59
  15. package/dist/cdp/omni_broker.d.ts +2 -1
  16. package/dist/cdp/omni_broker.js +13 -7
  17. package/dist/cdp/profiles.d.ts +10 -0
  18. package/dist/cdp/profiles.js +65 -0
  19. package/dist/cdp/walker.d.ts +25 -1
  20. package/dist/cdp/walker.js +354 -7
  21. package/dist/cli/plugin.d.ts +0 -2
  22. package/dist/cli/plugin.js +51 -696
  23. package/dist/cli/updater.d.ts +1 -1
  24. package/dist/cli/updater.js +27 -7
  25. package/dist/config/loader.d.ts +33 -6
  26. package/dist/config/loader.js +174 -22
  27. package/dist/desktop/discovery.d.ts +44 -0
  28. package/dist/desktop/discovery.js +364 -0
  29. package/dist/index.js +140 -64
  30. package/dist/intelligence/marks.d.ts +36 -0
  31. package/dist/intelligence/marks.js +30 -0
  32. package/dist/intelligence/patternCompiler.d.ts +118 -0
  33. package/dist/intelligence/patternCompiler.js +259 -0
  34. package/dist/intelligence/sessionMemory.d.ts +2 -0
  35. package/dist/intelligence/sessionMemory.js +18 -8
  36. package/dist/overlay/ghost.js +2 -2
  37. package/dist/overlay/injector.js +3 -5
  38. package/dist/plugin/extension_bridge.d.ts +40 -0
  39. package/dist/plugin/extension_bridge.js +158 -0
  40. package/dist/plugin/interface.d.ts +151 -15
  41. package/dist/plugin/loader.d.ts +19 -1
  42. package/dist/plugin/loader.js +331 -63
  43. package/dist/plugin/pipeline.d.ts +75 -0
  44. package/dist/plugin/pipeline.js +183 -0
  45. package/dist/plugin/registry.js +12 -1
  46. package/dist/plugin/storage.d.ts +51 -0
  47. package/dist/plugin/storage.js +164 -0
  48. package/dist/security/secrets.d.ts +1 -0
  49. package/dist/security/secrets.js +13 -0
  50. package/dist/sensors/actionability.js +1 -2
  51. package/dist/sensors/guard.js +4 -10
  52. package/dist/sensors/mutations.js +12 -0
  53. package/dist/sensors/operation_monitor.js +1 -0
  54. package/dist/server.d.ts +1 -1
  55. package/dist/server.js +600 -236
  56. package/dist/state/agent.d.ts +1 -0
  57. package/dist/state/agent.js +3 -0
  58. package/dist/state/delta.d.ts +12 -4
  59. package/dist/state/delta.js +67 -14
  60. package/dist/state/session.d.ts +8 -2
  61. package/dist/state/session.js +60 -13
  62. package/dist/tools/browser_detect.d.ts +16 -2
  63. package/dist/tools/browser_detect.js +54 -6
  64. package/dist/tools/burst.d.ts +17 -7
  65. package/dist/tools/burst.js +240 -83
  66. package/dist/tools/checkpoint.d.ts +38 -0
  67. package/dist/tools/checkpoint.js +271 -0
  68. package/dist/tools/click.d.ts +1 -0
  69. package/dist/tools/click.js +97 -98
  70. package/dist/tools/clipboard.js +31 -24
  71. package/dist/tools/design_extract.js +1 -1
  72. package/dist/tools/eval.js +1 -1
  73. package/dist/tools/fill.d.ts +20 -1
  74. package/dist/tools/fill.js +199 -40
  75. package/dist/tools/find.d.ts +16 -0
  76. package/dist/tools/find.js +67 -44
  77. package/dist/tools/github/index.d.ts +19 -4
  78. package/dist/tools/github/index.js +40 -4
  79. package/dist/tools/highlight.d.ts +6 -2
  80. package/dist/tools/highlight.js +158 -72
  81. package/dist/tools/inspect.js +1 -1
  82. package/dist/tools/key.js +65 -56
  83. package/dist/tools/navigate.d.ts +1 -1
  84. package/dist/tools/navigate.js +57 -44
  85. package/dist/tools/pdf.js +21 -13
  86. package/dist/tools/pointer.d.ts +18 -3
  87. package/dist/tools/pointer.js +89 -1
  88. package/dist/tools/read.d.ts +28 -8
  89. package/dist/tools/read.js +230 -56
  90. package/dist/tools/scrape.js +67 -13
  91. package/dist/tools/screenshot.js +21 -16
  92. package/dist/tools/scroll.js +6 -11
  93. package/dist/tools/select.js +8 -2
  94. package/dist/tools/snapshot.js +3 -34
  95. package/dist/tools/tabs.d.ts +10 -10
  96. package/dist/tools/tabs.js +55 -39
  97. package/dist/tools/thought.d.ts +2 -0
  98. package/dist/tools/thought.js +13 -14
  99. package/dist/tools/type.d.ts +2 -0
  100. package/dist/tools/type.js +109 -60
  101. package/dist/tools/type_secret.js +42 -76
  102. package/dist/tools/upload.js +1 -1
  103. package/dist/tools/video_compiler.d.ts +1 -1
  104. package/dist/tools/video_compiler.js +11 -12
  105. package/dist/version.d.ts +5 -0
  106. package/dist/version.js +5 -0
  107. package/docs/DOCUMENTATION.md +82 -36
  108. package/docs/PLUGINS.md +82 -499
  109. package/docs/TOOLS.md +51 -99
  110. package/docs/architecture/blind-injection.md +83 -0
  111. package/docs/architecture/dual-injection.md +32 -0
  112. package/docs/architecture/overview.md +49 -0
  113. package/docs/architecture/security.md +35 -0
  114. package/docs/architecture/transport.md +37 -0
  115. package/docs/captcha-stealth/captcha-solver.md +75 -0
  116. package/docs/captcha-stealth/stealth-physics.md +60 -0
  117. package/docs/cli/cli-commands.md +44 -0
  118. package/docs/cli/cli-flags.md +20 -0
  119. package/docs/cli/cli-introduction.md +20 -0
  120. package/docs/cli/cli-scripting.md +32 -0
  121. package/docs/cli/g-ide/ide-antigravity.md +24 -0
  122. package/docs/cli/g-ide/ide-claude-code.md +24 -0
  123. package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
  124. package/docs/cli/g-ide/ide-cursor.md +31 -0
  125. package/docs/cli/g-ide/ide-generic.md +34 -0
  126. package/docs/cli/g-ide/ide-opencode.md +24 -0
  127. package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
  128. package/docs/cli/g-ide/ide-windsurf.md +24 -0
  129. package/docs/configuration/config-locations.md +24 -0
  130. package/docs/configuration/configuration.md +73 -0
  131. package/docs/configuration/env-variables.md +19 -0
  132. package/docs/configuration/folders.md +55 -0
  133. package/docs/desktop-automation/electron-apps.md +217 -0
  134. package/docs/get-started/installation.md +47 -0
  135. package/docs/get-started/introduction.md +60 -0
  136. package/docs/get-started/quickstart.md +54 -0
  137. package/docs/get-started/setup-wizard.md +26 -0
  138. package/docs/intelligence/site-profiles.md +151 -0
  139. package/docs/skills-ecosystem/skills-overview.md +35 -0
  140. package/docs/skills-ecosystem/skills-recipes.md +36 -0
  141. package/docs/tools/tools.md +631 -0
  142. package/docs/troubleshooting/trouble-browser.md +27 -0
  143. package/docs/troubleshooting/trouble-config.md +20 -0
  144. package/docs/troubleshooting/trouble-loop.md +31 -0
  145. package/ext-dist/background.js +10 -62
  146. package/ext-dist/content.js +333 -921
  147. package/ext-dist/hud/hud.js +9 -9
  148. package/ext-dist/manifest.json +2 -6
  149. package/ext-dist/styles.css +6 -1
  150. package/mcp.json +108 -72
  151. package/package.json +14 -9
  152. package/server.json +3 -3
  153. package/docs/DEV_MODE.md +0 -68
  154. package/docs/dev_plugin.md +0 -310
  155. package/ext-dist/sidepanel/sidepanel.js +0 -25
  156. package/ext-dist/sidepanel.html +0 -547
@@ -1 +1 @@
1
- export declare function checkForUpdates(): Promise<void>;
1
+ export declare function checkForUpdates(force?: boolean): Promise<void>;
@@ -1,19 +1,33 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import os from "node:os";
3
4
  import { fileURLToPath } from "node:url";
4
5
  const __filename = fileURLToPath(import.meta.url);
5
6
  const __dirname = path.dirname(__filename);
6
- export async function checkForUpdates() {
7
+ const UPDATE_CACHE_FILE = path.join(os.homedir(), ".weavetab-system", "cache", "update_check.json");
8
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
9
+ export async function checkForUpdates(force = false) {
7
10
  try {
11
+ const now = Date.now();
12
+ // Check cache unless forced
13
+ if (!force && fs.existsSync(UPDATE_CACHE_FILE)) {
14
+ try {
15
+ const cached = JSON.parse(fs.readFileSync(UPDATE_CACHE_FILE, "utf-8"));
16
+ if (cached.lastChecked && now - cached.lastChecked < CHECK_INTERVAL_MS) {
17
+ return;
18
+ }
19
+ }
20
+ catch { /* malformed cache */ }
21
+ }
8
22
  // Read local package.json to get current version
9
23
  const packageJsonPath = path.resolve(__dirname, "../../package.json");
10
24
  if (!fs.existsSync(packageJsonPath))
11
25
  return;
12
26
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
13
27
  const currentVersion = pkg.version;
14
- // Fast, non-blocking fetch with 2-second timeout
28
+ // Fast, non-blocking fetch with 1.5-second timeout
15
29
  const controller = new AbortController();
16
- const timeoutId = setTimeout(() => controller.abort(), 2000);
30
+ const timeoutId = setTimeout(() => controller.abort(), 1500);
17
31
  const res = await fetch("https://registry.npmjs.org/@weavetab/mcp/latest", {
18
32
  signal: controller.signal,
19
33
  });
@@ -22,12 +36,18 @@ export async function checkForUpdates() {
22
36
  return;
23
37
  const data = await res.json();
24
38
  const latestVersion = data.version;
25
- // Simple version comparison (assumes semver format like x.y.z)
39
+ // Save timestamp to cache
40
+ try {
41
+ fs.mkdirSync(path.dirname(UPDATE_CACHE_FILE), { recursive: true });
42
+ fs.writeFileSync(UPDATE_CACHE_FILE, JSON.stringify({ lastChecked: now, latestVersion }), "utf-8");
43
+ }
44
+ catch { /* non-fatal */ }
45
+ // Simple version comparison
26
46
  if (isNewerVersion(currentVersion, latestVersion)) {
27
47
  await printUpdateBox(currentVersion, latestVersion);
28
48
  }
29
49
  }
30
- catch (err) {
50
+ catch {
31
51
  // Silently fail if offline or npm is down
32
52
  }
33
53
  }
@@ -52,7 +72,7 @@ async function printUpdateBox(current, latest) {
52
72
  chalk.bold(`Update available! `) + chalk.dim(current) + chalk.reset(` → `) + chalk.green(latest),
53
73
  `Run ${chalk.cyan("npm i -g @weavetab/mcp")} to update.`,
54
74
  ``,
55
- `Changelog: ${chalk.blue(`https://weavetab.dev/changelog/v${latest}`)}`
75
+ `Changelog: ${chalk.blue(`https://github.com/weavetab/mcp/releases/tag/v${latest}`)}`
56
76
  ].join("\n");
57
77
  const box = boxen(msg, {
58
78
  padding: 1,
@@ -67,6 +87,6 @@ async function printUpdateBox(current, latest) {
67
87
  // Fallback if formatting fails
68
88
  console.error(`\n[Weavetab MCP] Update available: ${current} -> ${latest}`);
69
89
  console.error(`Run "npm i -g @weavetab/mcp" to update.`);
70
- console.error(`Changelog: https://weavetab.dev/changelog/v${latest}\n`);
90
+ console.error(`Changelog: https://github.com/weavetab/mcp/releases/tag/v${latest}\n`);
71
91
  }
72
92
  }
@@ -6,6 +6,13 @@ export interface PolicyEntry {
6
6
  pattern: string;
7
7
  mode: PolicyMode;
8
8
  }
9
+ export interface ElectronAppConfig {
10
+ path?: string;
11
+ port?: number;
12
+ url?: string;
13
+ args?: string[];
14
+ profileDir?: string;
15
+ }
9
16
  export interface Config {
10
17
  mode: "session" | "attach";
11
18
  session: string;
@@ -15,13 +22,13 @@ export interface Config {
15
22
  browserPath: string;
16
23
  browserType: BrowserEngine;
17
24
  headless: boolean;
18
- visuals: boolean;
19
- darkMode: boolean;
20
- disableExtensions: boolean;
25
+ extension: boolean;
21
26
  browserFlags: string[];
22
27
  profileDir: string;
23
28
  cookies: boolean;
24
29
  syncDataAcrossBrowsers: boolean;
30
+ apps?: Record<string, ElectronAppConfig>;
31
+ electron?: Record<string, ElectronAppConfig>;
25
32
  role: Role;
26
33
  block: string[];
27
34
  allow: string[];
@@ -48,6 +55,11 @@ export declare const GLOBAL_CONFIG_PATH: string;
48
55
  export declare const DEFAULT_CONFIG: Config;
49
56
  export type OnConfigChange = (newConfig: Config) => void;
50
57
  export declare function onConfigChange(cb: OnConfigChange): void;
58
+ /**
59
+ * Migrates old sectioned config to new simplified flat format.
60
+ */
61
+ export declare function migrateToSimple(sectioned: any): Config;
62
+ export declare function validateConfigInternal(raw: Record<string, unknown>): Config;
51
63
  /**
52
64
  * Priority: Global (user) → Built-in defaults
53
65
  *
@@ -87,9 +99,7 @@ export declare function getConfigTelemetry(config: Config): boolean;
87
99
  export declare function getConfigBrowserPath(config: Config): string;
88
100
  export declare function getConfigBrowserType(config: Config): BrowserEngine;
89
101
  export declare function getConfigHeadless(config: Config): boolean;
90
- export declare function getConfigVisuals(config: Config): boolean;
91
- export declare function getConfigDarkMode(config: Config): boolean;
92
- export declare function getConfigDisableExtensions(config: Config): boolean;
102
+ export declare function getConfigExtension(config: Config): boolean;
93
103
  export declare function getConfigBrowserFlags(config: Config): string[];
94
104
  export declare function getConfigProfileDir(config: Config): string;
95
105
  export declare function getConfigCookies(config: Config): boolean;
@@ -108,13 +118,30 @@ export declare function getConfigActionabilityTimeoutMs(config: Config): number;
108
118
  export declare function getConfigGithubCacheTtl(config: Config): number;
109
119
  export declare function getConfigRefCacheTtl(config: Config): number;
110
120
  export declare function getConfigDevMode(config: Config): boolean;
121
+ export declare function getConfigApps(config: Config): Record<string, ElectronAppConfig> | undefined;
122
+ export declare function getConfigElectron(config: Config): Record<string, ElectronAppConfig> | undefined;
123
+ export declare function getAppConfig(appName: string, config?: Config): ElectronAppConfig | null;
124
+ export declare function getElectronAppConfig(appName: string, config?: Config): ElectronAppConfig | null;
125
+ export declare function getAppNames(config?: Config): string[];
126
+ export declare function getElectronAppNames(config?: Config): string[];
127
+ export declare function getApps(config?: Config): Record<string, ElectronAppConfig>;
128
+ export declare function getElectronApps(config?: Config): Record<string, ElectronAppConfig>;
111
129
  export declare function getConfigNavigationTimeoutMs(config: Config): number;
112
130
  export declare function getConfigGithubCacheTtlMs(config: Config): number;
113
131
  export declare function getConfigRefCacheTtlMs(config: Config): number;
114
132
  export declare function getSystemDir(subdir: string): string;
133
+ export declare function getSystemAppDir(appName: string): string;
115
134
  /**
116
135
  * Returns a directory inside the global ~/.weavetab-system/ folder.
117
136
  * Alias for consistency — identical to getSystemDir.
118
137
  */
119
138
  export declare function getGlobalSystemDir(subdir: string): string;
139
+ /**
140
+ * Returns the canonical directory for persistent site profiles and learned workflow patterns.
141
+ * Lives in ~/.weavetab-system/memory/sites/ — strictly machine-managed, never user-authored.
142
+ * Separated from the legacy selector-memory dir (~/.weavetab-system/memory/) so existing
143
+ * per-domain selector caches are not disrupted.
144
+ */
145
+ export declare function getSystemMemorySitesDir(): string;
120
146
  export declare function saveConfig(config: Config): void;
147
+ export declare function stopConfigWatcher(): void;
@@ -30,13 +30,13 @@ export const DEFAULT_CONFIG = {
30
30
  browserPath: "auto",
31
31
  browserType: "chromium",
32
32
  headless: false,
33
- visuals: true,
34
- darkMode: true,
35
- disableExtensions: false,
33
+ extension: true,
36
34
  browserFlags: [],
37
35
  profileDir: "",
38
36
  cookies: false,
39
37
  syncDataAcrossBrowsers: false,
38
+ apps: undefined,
39
+ electron: undefined,
40
40
  role: "admin",
41
41
  block: ["*.bank.*", "paypal.com"],
42
42
  allow: [],
@@ -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,
@@ -77,7 +77,7 @@ function isSectionedConfig(raw) {
77
77
  /**
78
78
  * Migrates old sectioned config to new simplified flat format.
79
79
  */
80
- function migrateToSimple(sectioned) {
80
+ export function migrateToSimple(sectioned) {
81
81
  return {
82
82
  mode: sectioned.general?.mode || "session",
83
83
  session: sectioned.general?.session || "default",
@@ -87,12 +87,12 @@ function migrateToSimple(sectioned) {
87
87
  browserPath: sectioned.browser?.path || "auto",
88
88
  browserType: sectioned.browser?.type || "chromium",
89
89
  headless: sectioned.browser?.headless ?? false,
90
- visuals: sectioned.browser?.visuals ?? true,
91
- darkMode: sectioned.browser?.darkMode ?? true,
92
- disableExtensions: sectioned.browser?.disableExtensions || false,
90
+ extension: sectioned.browser?.extension ?? sectioned.browser?.visuals ?? true,
93
91
  browserFlags: sectioned.browser?.flags || [],
94
92
  profileDir: sectioned.browser?.profileDir || "",
95
93
  cookies: sectioned.browser?.cookies || false,
94
+ apps: sectioned.apps || sectioned.electron || undefined,
95
+ electron: sectioned.apps || sectioned.electron || undefined,
96
96
  role: sectioned.security?.role || "admin",
97
97
  block: sectioned.security?.block || ["*.bank.*", "paypal.com"],
98
98
  allow: sectioned.security?.allow || [],
@@ -149,7 +149,7 @@ function validateConfig(raw) {
149
149
  // Validate simplified config
150
150
  return validateConfigInternal(raw);
151
151
  }
152
- function validateConfigInternal(raw) {
152
+ export function validateConfigInternal(raw) {
153
153
  const d = DEFAULT_CONFIG;
154
154
  const VALID_MODES = ["session", "attach"];
155
155
  const VALID_ENGINES = ["chromium", "firefox", "webkit"];
@@ -205,6 +205,83 @@ function validateConfigInternal(raw) {
205
205
  logDebug(`[Weavetab Config] [WARN] Proxy contains literal credentials (://user:pass@). This is a security risk. Use an environment variable instead. Ignoring proxy.`);
206
206
  proxy = undefined;
207
207
  }
208
+ // Validate apps / electron apps mapping
209
+ let electron = undefined;
210
+ const rawApps = raw.apps || raw.electron;
211
+ if (rawApps && typeof rawApps === "object" && !Array.isArray(rawApps)) {
212
+ const rawMap = rawApps;
213
+ const validatedMap = {};
214
+ for (const [rawKey, rawVal] of Object.entries(rawMap)) {
215
+ const sanitizedKey = rawKey.trim();
216
+ if (!/^[a-zA-Z0-9_-]+$/.test(sanitizedKey)) {
217
+ logDebug(`[Weavetab Config] [WARN] Invalid app name "${rawKey}". Must be alphanumeric, dashes, or underscores.`);
218
+ continue;
219
+ }
220
+ if (typeof rawVal === "string") {
221
+ const strVal = rawVal.trim();
222
+ if (/^\d+$/.test(strVal)) {
223
+ const portNum = parseInt(strVal, 10);
224
+ if (portNum > 0 && portNum <= 65535) {
225
+ validatedMap[sanitizedKey] = { port: portNum };
226
+ }
227
+ }
228
+ else if (strVal.startsWith("http://") || strVal.startsWith("https://") || strVal.startsWith("ws://") || strVal.startsWith("wss://")) {
229
+ const cfg = { url: strVal };
230
+ try {
231
+ const parsedUrl = new URL(strVal);
232
+ if (parsedUrl.port) {
233
+ const p = parseInt(parsedUrl.port, 10);
234
+ if (p > 0 && p <= 65535)
235
+ cfg.port = p;
236
+ }
237
+ }
238
+ catch { /* ignore */ }
239
+ validatedMap[sanitizedKey] = cfg;
240
+ }
241
+ else {
242
+ const safePath = safePathField(`apps.${sanitizedKey}`, strVal);
243
+ if (safePath) {
244
+ validatedMap[sanitizedKey] = { path: safePath };
245
+ }
246
+ }
247
+ }
248
+ else if (typeof rawVal === "number" && Number.isFinite(rawVal) && rawVal > 0 && rawVal <= 65535) {
249
+ validatedMap[sanitizedKey] = { port: rawVal };
250
+ }
251
+ else if (typeof rawVal === "object" && rawVal !== null && !Array.isArray(rawVal)) {
252
+ const objVal = rawVal;
253
+ const appCfg = {};
254
+ if (typeof objVal.path === "string" && objVal.path.trim()) {
255
+ const safePath = safePathField(`apps.${sanitizedKey}.path`, objVal.path);
256
+ if (safePath)
257
+ appCfg.path = safePath;
258
+ }
259
+ if (typeof objVal.port === "number" && Number.isFinite(objVal.port) && objVal.port > 0 && objVal.port <= 65535) {
260
+ appCfg.port = objVal.port;
261
+ }
262
+ else if (typeof objVal.port === "string" && /^\d+$/.test(objVal.port.trim())) {
263
+ const p = parseInt(objVal.port.trim(), 10);
264
+ if (p > 0 && p <= 65535)
265
+ appCfg.port = p;
266
+ }
267
+ if (typeof objVal.url === "string" && objVal.url.trim()) {
268
+ appCfg.url = objVal.url.trim();
269
+ }
270
+ if (Array.isArray(objVal.args)) {
271
+ appCfg.args = objVal.args.filter(isStr);
272
+ }
273
+ if (typeof objVal.profileDir === "string" && objVal.profileDir.trim()) {
274
+ appCfg.profileDir = safePathField(`apps.${sanitizedKey}.profileDir`, objVal.profileDir);
275
+ }
276
+ if (appCfg.path || appCfg.port || appCfg.url) {
277
+ validatedMap[sanitizedKey] = appCfg;
278
+ }
279
+ }
280
+ }
281
+ if (Object.keys(validatedMap).length > 0) {
282
+ electron = validatedMap;
283
+ }
284
+ }
208
285
  return {
209
286
  mode,
210
287
  session: validateField("session", raw.session, isStr, d.session),
@@ -214,12 +291,12 @@ function validateConfigInternal(raw) {
214
291
  browserPath,
215
292
  browserType: validateField("browserType", raw.browserType, isStr, d.browserType, v => VALID_ENGINES.includes(v)),
216
293
  headless: validateField("headless", raw.headless, isBool, d.headless),
217
- visuals: validateField("visuals", raw.visuals, isBool, d.visuals),
218
- darkMode: validateField("darkMode", raw.darkMode, isBool, d.darkMode),
219
- disableExtensions: validateField("disableExtensions", raw.disableExtensions, isBool, d.disableExtensions),
294
+ extension: validateField("extension", raw.extension, isBool, d.extension),
220
295
  browserFlags: isArr(raw.browserFlags) ? raw.browserFlags.filter(isStr) : d.browserFlags,
221
296
  profileDir,
222
297
  cookies: validateField("cookies", raw.cookies, isBool, d.cookies),
298
+ apps: electron,
299
+ electron,
223
300
  role: validateField("role", raw.role, isStr, d.role, v => VALID_ROLES.includes(v)),
224
301
  block: isArr(raw.block) ? raw.block.filter(isStr) : d.block,
225
302
  allow: isArr(raw.allow) ? raw.allow.filter(isStr) : d.allow,
@@ -345,7 +422,7 @@ function resolveConfigMerge() {
345
422
  logDebug(`[Weavetab Config] [OK] Active directory: ${activeDir}`);
346
423
  }
347
424
  else {
348
- logDebug(`[Weavetab Config] [WARN] No config found — using built-in defaults. Run: wt config`);
425
+ logDebug(`[Weavetab Config] [WARN] No config found — using built-in defaults. Run: weavetab config`);
349
426
  logDebug(`[Weavetab Config] [OK] Active directory: ${activeDir}`);
350
427
  // Write a starter global config so user isn't surprised
351
428
  ensureConfigDir(globalDir);
@@ -551,14 +628,8 @@ export function getConfigBrowserType(config) {
551
628
  export function getConfigHeadless(config) {
552
629
  return config.headless;
553
630
  }
554
- export function getConfigVisuals(config) {
555
- return config.visuals;
556
- }
557
- export function getConfigDarkMode(config) {
558
- return config.darkMode;
559
- }
560
- export function getConfigDisableExtensions(config) {
561
- return config.disableExtensions;
631
+ export function getConfigExtension(config) {
632
+ return config.extension !== false;
562
633
  }
563
634
  export function getConfigBrowserFlags(config) {
564
635
  return config.browserFlags;
@@ -614,6 +685,40 @@ export function getConfigRefCacheTtl(config) {
614
685
  export function getConfigDevMode(config) {
615
686
  return config.devMode;
616
687
  }
688
+ export function getConfigApps(config) {
689
+ return config.apps || config.electron;
690
+ }
691
+ export function getConfigElectron(config) {
692
+ return config.apps || config.electron;
693
+ }
694
+ export function getAppConfig(appName, config) {
695
+ const cfg = config ?? getCachedConfig();
696
+ const map = cfg.apps || cfg.electron;
697
+ if (!map)
698
+ return null;
699
+ const sanitized = appName.trim();
700
+ return map[sanitized] || null;
701
+ }
702
+ export function getElectronAppConfig(appName, config) {
703
+ return getAppConfig(appName, config);
704
+ }
705
+ export function getAppNames(config) {
706
+ const cfg = config ?? getCachedConfig();
707
+ const map = cfg.apps || cfg.electron;
708
+ if (!map)
709
+ return [];
710
+ return Object.keys(map);
711
+ }
712
+ export function getElectronAppNames(config) {
713
+ return getAppNames(config);
714
+ }
715
+ export function getApps(config) {
716
+ const cfg = config ?? getCachedConfig();
717
+ return cfg.apps || cfg.electron || {};
718
+ }
719
+ export function getElectronApps(config) {
720
+ return getApps(config);
721
+ }
617
722
  // ─── Additional Helper Functions for Code Compatibility ─────────────────────────
618
723
  export function getConfigNavigationTimeoutMs(config) {
619
724
  return config.navigationTimeout;
@@ -637,6 +742,14 @@ export function getSystemDir(subdir) {
637
742
  }
638
743
  return dir;
639
744
  }
745
+ export function getSystemAppDir(appName) {
746
+ const sanitized = appName.trim().replace(/[^a-zA-Z0-9_-]/g, "_");
747
+ const dir = path.join(GLOBAL_SYSTEM_DIR, "apps", sanitized);
748
+ if (!fs.existsSync(dir)) {
749
+ fs.mkdirSync(dir, { recursive: true });
750
+ }
751
+ return dir;
752
+ }
640
753
  /**
641
754
  * Returns a directory inside the global ~/.weavetab-system/ folder.
642
755
  * Alias for consistency — identical to getSystemDir.
@@ -648,6 +761,19 @@ export function getGlobalSystemDir(subdir) {
648
761
  }
649
762
  return dir;
650
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
+ }
651
777
  export function saveConfig(config) {
652
778
  const { dir, file } = getActiveConfigPath();
653
779
  ensureConfigDir(dir);
@@ -676,11 +802,18 @@ export function saveConfig(config) {
676
802
  }
677
803
  // ─── Live-reload watcher ───────────────────────────────────────────────────────
678
804
  let watchTimeout = null;
805
+ let _configWatcher = null;
679
806
  function startWatcher(dir) {
680
807
  try {
681
808
  if (!fs.existsSync(dir))
682
809
  return;
683
- 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) => {
684
817
  if (filename === "config.json" && eventType === "change") {
685
818
  if (watchTimeout)
686
819
  clearTimeout(watchTimeout);
@@ -693,12 +826,31 @@ function startWatcher(dir) {
693
826
  logDebug("[Weavetab] Failed to reload config", e);
694
827
  }
695
828
  }, 150);
829
+ if (watchTimeout && typeof watchTimeout.unref === "function") {
830
+ watchTimeout.unref();
831
+ }
696
832
  }
697
833
  });
834
+ if (_configWatcher && typeof _configWatcher.unref === "function") {
835
+ _configWatcher.unref();
836
+ }
698
837
  }
699
838
  catch {
700
839
  // Ignore watch errors on some OS/network mounts
701
840
  }
702
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
+ }
703
855
  // Watch global location so changes are picked up
704
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;