@weavetab/mcp 2.5.0-beta.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.
- package/AGENTS.md +48 -0
- package/ARCHITECTURE.md +590 -0
- package/LICENSE +631 -0
- package/README.md +909 -0
- package/SECURITY.md +196 -0
- package/TROUBLESHOOTING.md +365 -0
- package/dist/anticaptcha/detector.d.ts +49 -0
- package/dist/anticaptcha/detector.js +465 -0
- package/dist/anticaptcha/integration.d.ts +36 -0
- package/dist/anticaptcha/integration.js +123 -0
- package/dist/anticaptcha/solver.d.ts +27 -0
- package/dist/anticaptcha/solver.js +552 -0
- package/dist/audit/logger.d.ts +13 -0
- package/dist/audit/logger.js +177 -0
- package/dist/audit/telemetry.d.ts +36 -0
- package/dist/audit/telemetry.js +162 -0
- package/dist/cdp/bridge.d.ts +16 -0
- package/dist/cdp/bridge.js +826 -0
- package/dist/cdp/confirm.d.ts +26 -0
- package/dist/cdp/confirm.js +79 -0
- package/dist/cdp/connector.d.ts +37 -0
- package/dist/cdp/connector.js +661 -0
- package/dist/cdp/extension.d.ts +36 -0
- package/dist/cdp/extension.js +118 -0
- package/dist/cdp/finder.d.ts +1 -0
- package/dist/cdp/finder.js +2 -0
- package/dist/cdp/helpers.d.ts +11 -0
- package/dist/cdp/helpers.js +195 -0
- package/dist/cdp/label_cache.d.ts +12 -0
- package/dist/cdp/label_cache.js +51 -0
- package/dist/cdp/lock.d.ts +33 -0
- package/dist/cdp/lock.js +206 -0
- package/dist/cdp/omni_broker.d.ts +17 -0
- package/dist/cdp/omni_broker.js +119 -0
- package/dist/cdp/profiles.d.ts +1 -0
- package/dist/cdp/profiles.js +87 -0
- package/dist/cdp/pruner.d.ts +41 -0
- package/dist/cdp/pruner.js +263 -0
- package/dist/cdp/ref_cache.d.ts +101 -0
- package/dist/cdp/ref_cache.js +234 -0
- package/dist/cdp/retry.d.ts +5 -0
- package/dist/cdp/retry.js +39 -0
- package/dist/cdp/retry_enhanced.d.ts +44 -0
- package/dist/cdp/retry_enhanced.js +102 -0
- package/dist/cdp/security.d.ts +50 -0
- package/dist/cdp/security.js +237 -0
- package/dist/cdp/verify.d.ts +39 -0
- package/dist/cdp/verify.js +143 -0
- package/dist/cdp/walker.d.ts +91 -0
- package/dist/cdp/walker.js +896 -0
- package/dist/cdp/walker.test.d.ts +2 -0
- package/dist/cdp/walker.test.js +172 -0
- package/dist/cdp/websocket_transport.d.ts +15 -0
- package/dist/cdp/websocket_transport.js +118 -0
- package/dist/cli/plugin.d.ts +9 -0
- package/dist/cli/plugin.js +1134 -0
- package/dist/cli/updater.d.ts +1 -0
- package/dist/cli/updater.js +72 -0
- package/dist/config/loader.d.ts +120 -0
- package/dist/config/loader.js +704 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1248 -0
- package/dist/intelligence/hints.d.ts +11 -0
- package/dist/intelligence/hints.js +39 -0
- package/dist/intelligence/intent.d.ts +33 -0
- package/dist/intelligence/intent.js +171 -0
- package/dist/intelligence/sessionMemory.d.ts +66 -0
- package/dist/intelligence/sessionMemory.js +284 -0
- package/dist/intelligence/sessionMemory.test.d.ts +2 -0
- package/dist/intelligence/sessionMemory.test.js +62 -0
- package/dist/intelligence/trail.d.ts +64 -0
- package/dist/intelligence/trail.js +233 -0
- package/dist/intelligence/trail.test.d.ts +2 -0
- package/dist/intelligence/trail.test.js +110 -0
- package/dist/intelligence/vision.d.ts +24 -0
- package/dist/intelligence/vision.js +151 -0
- package/dist/lifecycle/mission_manager.d.ts +16 -0
- package/dist/lifecycle/mission_manager.js +44 -0
- package/dist/overlay/bubble.d.ts +16 -0
- package/dist/overlay/bubble.js +37 -0
- package/dist/overlay/ghost.d.ts +35 -0
- package/dist/overlay/ghost.js +508 -0
- package/dist/overlay/injector.d.ts +22 -0
- package/dist/overlay/injector.js +198 -0
- package/dist/plugin/interface.d.ts +134 -0
- package/dist/plugin/interface.js +1 -0
- package/dist/plugin/loader.d.ts +18 -0
- package/dist/plugin/loader.js +155 -0
- package/dist/plugin/registry.d.ts +50 -0
- package/dist/plugin/registry.js +234 -0
- package/dist/security/blacklist.d.ts +24 -0
- package/dist/security/blacklist.js +80 -0
- package/dist/security/filter.d.ts +2 -0
- package/dist/security/filter.js +20 -0
- package/dist/security/guard.d.ts +8 -0
- package/dist/security/guard.js +35 -0
- package/dist/security/ratelimit.d.ts +11 -0
- package/dist/security/ratelimit.js +57 -0
- package/dist/security/secrets.d.ts +75 -0
- package/dist/security/secrets.js +365 -0
- package/dist/sensors/actionability.d.ts +81 -0
- package/dist/sensors/actionability.js +303 -0
- package/dist/sensors/guard.d.ts +25 -0
- package/dist/sensors/guard.js +168 -0
- package/dist/sensors/mutations.d.ts +23 -0
- package/dist/sensors/mutations.js +93 -0
- package/dist/sensors/network.d.ts +67 -0
- package/dist/sensors/network.js +193 -0
- package/dist/sensors/operation_monitor.d.ts +80 -0
- package/dist/sensors/operation_monitor.js +203 -0
- package/dist/sensors/pacing.d.ts +44 -0
- package/dist/sensors/pacing.js +111 -0
- package/dist/sensors/thoughts.d.ts +2 -0
- package/dist/sensors/thoughts.js +59 -0
- package/dist/sensors/wait.d.ts +12 -0
- package/dist/sensors/wait.js +44 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +1687 -0
- package/dist/state/agent.d.ts +36 -0
- package/dist/state/agent.js +101 -0
- package/dist/state/cache.d.ts +16 -0
- package/dist/state/cache.js +92 -0
- package/dist/state/cache.test.d.ts +2 -0
- package/dist/state/cache.test.js +76 -0
- package/dist/state/delta.d.ts +71 -0
- package/dist/state/delta.js +234 -0
- package/dist/state/delta.test.d.ts +2 -0
- package/dist/state/delta.test.js +279 -0
- package/dist/state/groups.d.ts +15 -0
- package/dist/state/groups.js +80 -0
- package/dist/state/session.d.ts +30 -0
- package/dist/state/session.js +155 -0
- package/dist/tools/accessibility.d.ts +14 -0
- package/dist/tools/accessibility.js +32 -0
- package/dist/tools/ask.d.ts +14 -0
- package/dist/tools/ask.js +49 -0
- package/dist/tools/automation.d.ts +36 -0
- package/dist/tools/automation.js +218 -0
- package/dist/tools/browser_detect.d.ts +13 -0
- package/dist/tools/browser_detect.js +73 -0
- package/dist/tools/burst.d.ts +35 -0
- package/dist/tools/burst.js +344 -0
- package/dist/tools/canvas.d.ts +25 -0
- package/dist/tools/canvas.js +132 -0
- package/dist/tools/captcha.d.ts +38 -0
- package/dist/tools/captcha.js +209 -0
- package/dist/tools/click.d.ts +36 -0
- package/dist/tools/click.js +561 -0
- package/dist/tools/click_and_wait.d.ts +33 -0
- package/dist/tools/click_and_wait.js +149 -0
- package/dist/tools/clipboard.d.ts +29 -0
- package/dist/tools/clipboard.js +279 -0
- package/dist/tools/console.d.ts +21 -0
- package/dist/tools/console.js +107 -0
- package/dist/tools/cookies.d.ts +67 -0
- package/dist/tools/cookies.js +121 -0
- package/dist/tools/design_extract.d.ts +109 -0
- package/dist/tools/design_extract.js +641 -0
- package/dist/tools/dialog.d.ts +24 -0
- package/dist/tools/dialog.js +109 -0
- package/dist/tools/drag.d.ts +15 -0
- package/dist/tools/drag.js +29 -0
- package/dist/tools/emulation.d.ts +33 -0
- package/dist/tools/emulation.js +69 -0
- package/dist/tools/eval.d.ts +20 -0
- package/dist/tools/eval.js +92 -0
- package/dist/tools/fill.d.ts +41 -0
- package/dist/tools/fill.js +667 -0
- package/dist/tools/find.d.ts +30 -0
- package/dist/tools/find.js +60 -0
- package/dist/tools/geo.d.ts +19 -0
- package/dist/tools/geo.js +29 -0
- package/dist/tools/github/analyze.d.ts +4 -0
- package/dist/tools/github/analyze.js +113 -0
- package/dist/tools/github/cache.d.ts +21 -0
- package/dist/tools/github/cache.js +28 -0
- package/dist/tools/github/index.d.ts +4 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/issues.d.ts +9 -0
- package/dist/tools/github/issues.js +88 -0
- package/dist/tools/github/pr.d.ts +5 -0
- package/dist/tools/github/pr.js +88 -0
- package/dist/tools/github/rateLimiter.d.ts +4 -0
- package/dist/tools/github/rateLimiter.js +46 -0
- package/dist/tools/github/read.d.ts +34 -0
- package/dist/tools/github/read.js +74 -0
- package/dist/tools/github/repoContext.d.ts +20 -0
- package/dist/tools/github/repoContext.js +129 -0
- package/dist/tools/highlight.d.ts +20 -0
- package/dist/tools/highlight.js +140 -0
- package/dist/tools/hover.d.ts +19 -0
- package/dist/tools/hover.js +57 -0
- package/dist/tools/inspect.d.ts +17 -0
- package/dist/tools/inspect.js +125 -0
- package/dist/tools/integration.test.d.ts +2 -0
- package/dist/tools/integration.test.js +96 -0
- package/dist/tools/key.d.ts +25 -0
- package/dist/tools/key.js +173 -0
- package/dist/tools/macro_compiler.d.ts +7 -0
- package/dist/tools/macro_compiler.js +82 -0
- package/dist/tools/mouse.d.ts +21 -0
- package/dist/tools/mouse.js +62 -0
- package/dist/tools/navigate.d.ts +16 -0
- package/dist/tools/navigate.js +188 -0
- package/dist/tools/network_intercept.d.ts +17 -0
- package/dist/tools/network_intercept.js +151 -0
- package/dist/tools/office/index.d.ts +9 -0
- package/dist/tools/office/index.js +7 -0
- package/dist/tools/office/list.d.ts +15 -0
- package/dist/tools/office/list.js +41 -0
- package/dist/tools/office/parse.d.ts +20 -0
- package/dist/tools/office/parse.js +172 -0
- package/dist/tools/pdf.d.ts +22 -0
- package/dist/tools/pdf.js +42 -0
- package/dist/tools/performance.d.ts +12 -0
- package/dist/tools/performance.js +47 -0
- package/dist/tools/performance_test.d.ts +70 -0
- package/dist/tools/performance_test.js +128 -0
- package/dist/tools/plan.d.ts +39 -0
- package/dist/tools/plan.js +161 -0
- package/dist/tools/pointer.d.ts +35 -0
- package/dist/tools/pointer.js +522 -0
- package/dist/tools/read.d.ts +30 -0
- package/dist/tools/read.js +488 -0
- package/dist/tools/recording.d.ts +45 -0
- package/dist/tools/recording.js +124 -0
- package/dist/tools/response.d.ts +129 -0
- package/dist/tools/response.js +5 -0
- package/dist/tools/scrape.d.ts +24 -0
- package/dist/tools/scrape.js +140 -0
- package/dist/tools/screenshot.d.ts +40 -0
- package/dist/tools/screenshot.js +79 -0
- package/dist/tools/scroll.d.ts +23 -0
- package/dist/tools/scroll.js +196 -0
- package/dist/tools/select.d.ts +13 -0
- package/dist/tools/select.js +217 -0
- package/dist/tools/snapshot.d.ts +27 -0
- package/dist/tools/snapshot.js +99 -0
- package/dist/tools/storage.d.ts +18 -0
- package/dist/tools/storage.js +29 -0
- package/dist/tools/tabs.d.ts +77 -0
- package/dist/tools/tabs.js +342 -0
- package/dist/tools/thought.d.ts +16 -0
- package/dist/tools/thought.js +79 -0
- package/dist/tools/type.d.ts +65 -0
- package/dist/tools/type.js +1061 -0
- package/dist/tools/type_secret.d.ts +23 -0
- package/dist/tools/type_secret.js +229 -0
- package/dist/tools/upload.d.ts +19 -0
- package/dist/tools/upload.js +103 -0
- package/dist/tools/video_compiler.d.ts +24 -0
- package/dist/tools/video_compiler.js +199 -0
- package/dist/tools/viewport.d.ts +44 -0
- package/dist/tools/viewport.js +230 -0
- package/dist/tools/wait.d.ts +19 -0
- package/dist/tools/wait.js +202 -0
- package/dist/tools/wait_for.d.ts +23 -0
- package/dist/tools/wait_for.js +30 -0
- package/dist/tools/windows.d.ts +22 -0
- package/dist/tools/windows.js +57 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +12 -0
- package/docs/DEV_MODE.md +68 -0
- package/docs/DOCUMENTATION.md +532 -0
- package/docs/PLUGINS.md +538 -0
- package/docs/TOOLS.md +104 -0
- package/docs/dev_plugin.md +310 -0
- package/ext-dist/background.js +467 -0
- package/ext-dist/content.js +3089 -0
- package/ext-dist/dashboard.html +583 -0
- package/ext-dist/hud/hud.js +73 -0
- package/ext-dist/hud/index.html +20 -0
- package/ext-dist/manifest.json +77 -0
- package/ext-dist/sidepanel/sidepanel.js +25 -0
- package/ext-dist/sidepanel.html +547 -0
- package/ext-dist/styles.css +218 -0
- package/icon.png +0 -0
- package/mcp.json +796 -0
- package/package.json +113 -0
- package/server.json +23 -0
- package/smithery.yaml +11 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { logDebug } from "../audit/logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Weavetab — config/loader.ts
|
|
4
|
+
* Enterprise-grade config resolution with multi-source merge, per-field validation,
|
|
5
|
+
* broken config recovery, and real-browser isolation checks.
|
|
6
|
+
* Weavetab by fy2ne
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as crypto from "node:crypto";
|
|
12
|
+
// ─── Main Config Interface ─────────────────────────────────────────────────────────
|
|
13
|
+
// ─── Directory constants ───────────────────────────────────────────────────────
|
|
14
|
+
// Platform detection (used throughout the file)
|
|
15
|
+
export const PLATFORM = os.platform();
|
|
16
|
+
const HOME = os.homedir();
|
|
17
|
+
const lowerGlobal = path.join(HOME, ".weavetab");
|
|
18
|
+
const upperGlobal = path.join(HOME, ".Weavetab");
|
|
19
|
+
export const GLOBAL_CONFIG_DIR = fs.existsSync(lowerGlobal) ? lowerGlobal : (fs.existsSync(upperGlobal) ? upperGlobal : lowerGlobal);
|
|
20
|
+
export const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, "config.json");
|
|
21
|
+
const OLD_SYSTEM_PATH = path.join(GLOBAL_CONFIG_DIR, "system.json");
|
|
22
|
+
const OLD_UI_PATH = path.join(GLOBAL_CONFIG_DIR, "ui_manifest.json");
|
|
23
|
+
// ─── Defaults ─────────────────────────────────────────────────────────────────
|
|
24
|
+
export const DEFAULT_CONFIG = {
|
|
25
|
+
mode: "session",
|
|
26
|
+
session: "default",
|
|
27
|
+
attachPort: 0,
|
|
28
|
+
agentId: process.env.WT_AGENT_ID || crypto.randomUUID(),
|
|
29
|
+
telemetry: false,
|
|
30
|
+
browserPath: "auto",
|
|
31
|
+
browserType: "chromium",
|
|
32
|
+
headless: false,
|
|
33
|
+
visuals: true,
|
|
34
|
+
darkMode: true,
|
|
35
|
+
disableExtensions: false,
|
|
36
|
+
browserFlags: [],
|
|
37
|
+
profileDir: "",
|
|
38
|
+
cookies: false,
|
|
39
|
+
syncDataAcrossBrowsers: false,
|
|
40
|
+
role: "admin",
|
|
41
|
+
block: ["*.bank.*", "paypal.com"],
|
|
42
|
+
allow: [],
|
|
43
|
+
policies: [],
|
|
44
|
+
sessionTimeout: 3600,
|
|
45
|
+
navigationTimeout: 30000,
|
|
46
|
+
idleTimeout: 60000,
|
|
47
|
+
actionabilityTimeoutMs: 15000,
|
|
48
|
+
maxTabs: 10,
|
|
49
|
+
maxActionsPerMinute: 60,
|
|
50
|
+
githubCacheTtlMs: 30 * 60 * 1000,
|
|
51
|
+
refCacheTtlMs: 30_000,
|
|
52
|
+
downloadDir: "",
|
|
53
|
+
pdfDir: "",
|
|
54
|
+
snapshotDir: "",
|
|
55
|
+
recordingDir: "",
|
|
56
|
+
designDir: "",
|
|
57
|
+
devMode: false,
|
|
58
|
+
proxy: undefined,
|
|
59
|
+
};
|
|
60
|
+
const listeners = [];
|
|
61
|
+
export function onConfigChange(cb) {
|
|
62
|
+
listeners.push(cb);
|
|
63
|
+
}
|
|
64
|
+
// ─── Directory helpers ─────────────────────────────────────────────────────────
|
|
65
|
+
function ensureConfigDir(dir) {
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// ─── Config Migration ─────────────────────────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Detects if a config object is in the old sectioned format.
|
|
73
|
+
*/
|
|
74
|
+
function isSectionedConfig(raw) {
|
|
75
|
+
return "general" in raw || "browser" in raw || "security" in raw || "performance" in raw;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Migrates old sectioned config to new simplified flat format.
|
|
79
|
+
*/
|
|
80
|
+
function migrateToSimple(sectioned) {
|
|
81
|
+
return {
|
|
82
|
+
mode: sectioned.general?.mode || "session",
|
|
83
|
+
session: sectioned.general?.session || "default",
|
|
84
|
+
attachPort: sectioned.general?.attachPort || 0,
|
|
85
|
+
agentId: sectioned.general?.agentId || crypto.randomUUID(),
|
|
86
|
+
telemetry: sectioned.general?.telemetry || false,
|
|
87
|
+
browserPath: sectioned.browser?.path || "auto",
|
|
88
|
+
browserType: sectioned.browser?.type || "chromium",
|
|
89
|
+
headless: sectioned.browser?.headless ?? false,
|
|
90
|
+
visuals: sectioned.browser?.visuals ?? true,
|
|
91
|
+
darkMode: sectioned.browser?.darkMode ?? true,
|
|
92
|
+
disableExtensions: sectioned.browser?.disableExtensions || false,
|
|
93
|
+
browserFlags: sectioned.browser?.flags || [],
|
|
94
|
+
profileDir: sectioned.browser?.profileDir || "",
|
|
95
|
+
cookies: sectioned.browser?.cookies || false,
|
|
96
|
+
role: sectioned.security?.role || "admin",
|
|
97
|
+
block: sectioned.security?.block || ["*.bank.*", "paypal.com"],
|
|
98
|
+
allow: sectioned.security?.allow || [],
|
|
99
|
+
policies: sectioned.security?.policies || [],
|
|
100
|
+
sessionTimeout: sectioned.performance?.timeouts?.session || 3600,
|
|
101
|
+
navigationTimeout: sectioned.performance?.timeouts?.navigation || 30000,
|
|
102
|
+
idleTimeout: sectioned.performance?.timeouts?.idle || 60000,
|
|
103
|
+
actionabilityTimeoutMs: sectioned.performance?.timeouts?.actionability || 500,
|
|
104
|
+
maxTabs: sectioned.performance?.limits?.maxTabs || 10,
|
|
105
|
+
maxActionsPerMinute: sectioned.performance?.limits?.maxActionsPerMinute || 60,
|
|
106
|
+
githubCacheTtlMs: sectioned.performance?.cache?.githubTtlMs || 30 * 60 * 1000,
|
|
107
|
+
refCacheTtlMs: sectioned.performance?.cache?.refCacheTtlMs || 30_000,
|
|
108
|
+
downloadDir: sectioned.paths?.downloads || "",
|
|
109
|
+
pdfDir: sectioned.paths?.pdfs || "",
|
|
110
|
+
snapshotDir: sectioned.paths?.snapshots || "",
|
|
111
|
+
recordingDir: sectioned.paths?.recordings || "",
|
|
112
|
+
designDir: sectioned.paths?.designs || "",
|
|
113
|
+
devMode: typeof sectioned.devMode === "boolean" ? sectioned.devMode : sectioned.devMode?.enabled || false,
|
|
114
|
+
syncDataAcrossBrowsers: sectioned.general?.syncDataAcrossBrowsers || false,
|
|
115
|
+
proxy: sectioned.network?.proxy,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// ─── Per-field validation ──────────────────────────────────────────────────────
|
|
119
|
+
function validateField(fieldName, value, check, defaultValue, extra) {
|
|
120
|
+
if (!check(value)) {
|
|
121
|
+
logDebug(`[Weavetab Config] [WARN] Invalid type for "${fieldName}" (got ${typeof value}). Resetting to default.`);
|
|
122
|
+
return defaultValue;
|
|
123
|
+
}
|
|
124
|
+
if (extra && !extra(value)) {
|
|
125
|
+
logDebug(`[Weavetab Config] [WARN] Out-of-range value for "${fieldName}" (got ${JSON.stringify(value)}). Resetting to default.`);
|
|
126
|
+
return defaultValue;
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
const isStr = (v) => typeof v === "string";
|
|
131
|
+
const isBool = (v) => typeof v === "boolean";
|
|
132
|
+
const isNum = (v) => typeof v === "number" && isFinite(v);
|
|
133
|
+
const isArr = (v) => Array.isArray(v);
|
|
134
|
+
function validateConfig(raw) {
|
|
135
|
+
// Detect old sectioned format and migrate to simplified format
|
|
136
|
+
if (isSectionedConfig(raw)) {
|
|
137
|
+
logDebug("[Weavetab Config] [INFO] Detected old sectioned config format, migrating to simplified structure...");
|
|
138
|
+
const simple = migrateToSimple(raw);
|
|
139
|
+
// Save migrated config
|
|
140
|
+
try {
|
|
141
|
+
fs.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(simple, null, 2));
|
|
142
|
+
logDebug(`[Weavetab Config] [OK] Migrated config saved to ${GLOBAL_CONFIG_PATH}`);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
logDebug("[Weavetab Config] [WARN] Failed to save migrated config:", e);
|
|
146
|
+
}
|
|
147
|
+
return simple;
|
|
148
|
+
}
|
|
149
|
+
// Validate simplified config
|
|
150
|
+
return validateConfigInternal(raw);
|
|
151
|
+
}
|
|
152
|
+
function validateConfigInternal(raw) {
|
|
153
|
+
const d = DEFAULT_CONFIG;
|
|
154
|
+
const VALID_MODES = ["session", "attach"];
|
|
155
|
+
const VALID_ENGINES = ["chromium", "firefox", "webkit"];
|
|
156
|
+
const VALID_ROLES = ["admin", "viewer", "automation", "system"];
|
|
157
|
+
const mode = validateField("mode", raw.mode, isStr, d.mode, v => VALID_MODES.includes(v));
|
|
158
|
+
// Path validation helpers
|
|
159
|
+
function safePathField(field, value) {
|
|
160
|
+
if (!isStr(value) || !value.trim())
|
|
161
|
+
return "";
|
|
162
|
+
const raw = value.trim();
|
|
163
|
+
// Path traversal guard — check BEFORE resolving
|
|
164
|
+
if (raw.includes("..") || raw.includes("~")) {
|
|
165
|
+
logDebug(`[Weavetab Config] [WARN] Suspicious path in "${field}": "${raw}". Resetting to default.`);
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
const resolved = path.resolve(raw);
|
|
169
|
+
return resolved;
|
|
170
|
+
}
|
|
171
|
+
// Validate downloadDir — auto-create if missing
|
|
172
|
+
const rawDownloadDir = safePathField("downloadDir", raw.downloadDir);
|
|
173
|
+
let downloadDir = rawDownloadDir;
|
|
174
|
+
if (downloadDir) {
|
|
175
|
+
if (!fs.existsSync(downloadDir)) {
|
|
176
|
+
try {
|
|
177
|
+
fs.mkdirSync(downloadDir, { recursive: true });
|
|
178
|
+
logDebug(`[Weavetab Config] [OK] Created custom download directory: ${downloadDir}`);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
logDebug(`[Weavetab Config] [ERROR] Failed to create downloadDir "${downloadDir}". Resetting to default.`);
|
|
182
|
+
downloadDir = "";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Validate browser path — check file exists if not "auto"
|
|
187
|
+
const rawBrowser = validateField("browserPath", raw.browserPath ?? "auto", isStr, d.browserPath);
|
|
188
|
+
let browserPath = rawBrowser;
|
|
189
|
+
if (browserPath && browserPath !== "auto") {
|
|
190
|
+
// Only verify it exists if it looks like a path (contains slashes).
|
|
191
|
+
// Otherwise it might be a browser name like "edge" or "brave".
|
|
192
|
+
if ((browserPath.includes('/') || browserPath.includes('\\')) && !fs.existsSync(browserPath)) {
|
|
193
|
+
logDebug(`[Weavetab Config] [WARN] browser path "${browserPath}" does not exist. Resetting to "auto".`);
|
|
194
|
+
browserPath = "auto";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Validate profileDir — path safety only (real-browser guard is in profiles.ts)
|
|
198
|
+
const profileDir = safePathField("profileDir", raw.profileDir);
|
|
199
|
+
// Validate proxy — resolve env var and reject plaintext credentials
|
|
200
|
+
let proxy = raw.proxy;
|
|
201
|
+
if (proxy === "$WT_PROXY_URL") {
|
|
202
|
+
proxy = process.env.WT_PROXY_URL;
|
|
203
|
+
}
|
|
204
|
+
if (proxy && /:\/\/[^:]+:[^@]+@/.test(proxy)) {
|
|
205
|
+
logDebug(`[Weavetab Config] [WARN] Proxy contains literal credentials (://user:pass@). This is a security risk. Use an environment variable instead. Ignoring proxy.`);
|
|
206
|
+
proxy = undefined;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
mode,
|
|
210
|
+
session: validateField("session", raw.session, isStr, d.session),
|
|
211
|
+
attachPort: mode !== "attach" ? 0 : validateField("attachPort", raw.attachPort, isNum, d.attachPort, v => v >= 0 && v <= 65535),
|
|
212
|
+
agentId: isStr(raw.agentId) ? raw.agentId : d.agentId,
|
|
213
|
+
telemetry: validateField("telemetry", raw.telemetry, isBool, d.telemetry),
|
|
214
|
+
browserPath,
|
|
215
|
+
browserType: validateField("browserType", raw.browserType, isStr, d.browserType, v => VALID_ENGINES.includes(v)),
|
|
216
|
+
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),
|
|
220
|
+
browserFlags: isArr(raw.browserFlags) ? raw.browserFlags.filter(isStr) : d.browserFlags,
|
|
221
|
+
profileDir,
|
|
222
|
+
cookies: validateField("cookies", raw.cookies, isBool, d.cookies),
|
|
223
|
+
role: validateField("role", raw.role, isStr, d.role, v => VALID_ROLES.includes(v)),
|
|
224
|
+
block: isArr(raw.block) ? raw.block.filter(isStr) : d.block,
|
|
225
|
+
allow: isArr(raw.allow) ? raw.allow.filter(isStr) : d.allow,
|
|
226
|
+
policies: isArr(raw.policies) ? raw.policies : d.policies,
|
|
227
|
+
sessionTimeout: validateField("sessionTimeout", raw.sessionTimeout, isNum, d.sessionTimeout, v => v >= 60 && v <= 86400),
|
|
228
|
+
navigationTimeout: validateField("navigationTimeout", raw.navigationTimeout, isNum, d.navigationTimeout, v => v >= 1000 && v <= 120000),
|
|
229
|
+
idleTimeout: validateField("idleTimeout", raw.idleTimeout, isNum, d.idleTimeout, v => v >= 5000),
|
|
230
|
+
actionabilityTimeoutMs: validateField("actionabilityTimeoutMs", raw.actionabilityTimeoutMs, isNum, d.actionabilityTimeoutMs, v => v >= 50 && v <= 15000),
|
|
231
|
+
maxTabs: validateField("maxTabs", raw.maxTabs, isNum, d.maxTabs, v => v >= 1 && v <= 100),
|
|
232
|
+
maxActionsPerMinute: validateField("maxActionsPerMinute", raw.maxActionsPerMinute, isNum, d.maxActionsPerMinute, v => v >= 1 && v <= 600),
|
|
233
|
+
githubCacheTtlMs: validateField("githubCacheTtlMs", raw.githubCacheTtlMs, isNum, d.githubCacheTtlMs, v => v >= 0),
|
|
234
|
+
refCacheTtlMs: validateField("refCacheTtlMs", raw.refCacheTtlMs, isNum, d.refCacheTtlMs, v => v >= 1000 && v <= 300_000),
|
|
235
|
+
downloadDir,
|
|
236
|
+
pdfDir: safePathField("pdfDir", raw.pdfDir),
|
|
237
|
+
snapshotDir: safePathField("snapshotDir", raw.snapshotDir),
|
|
238
|
+
recordingDir: safePathField("recordingDir", raw.recordingDir),
|
|
239
|
+
designDir: safePathField("designDir", raw.designDir),
|
|
240
|
+
devMode: isBool(raw.devMode) ? raw.devMode : d.devMode,
|
|
241
|
+
syncDataAcrossBrowsers: validateField("syncDataAcrossBrowsers", raw.syncDataAcrossBrowsers, isBool, d.syncDataAcrossBrowsers),
|
|
242
|
+
proxy: isStr(proxy) ? proxy : d.proxy,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function safeReadJson(file) {
|
|
246
|
+
if (!fs.existsSync(file))
|
|
247
|
+
return { status: "missing", file };
|
|
248
|
+
try {
|
|
249
|
+
let raw = fs.readFileSync(file, "utf-8").trim();
|
|
250
|
+
if (!raw)
|
|
251
|
+
return { status: "missing", file }; // empty file = treat as missing (atomic write race)
|
|
252
|
+
// Auto-fix unescaped Windows paths in JSON before parsing
|
|
253
|
+
raw = raw.replace(/"([a-zA-Z]:\\[^"]*)"/g, (match, p1) => {
|
|
254
|
+
const normalized = p1.replace(/\\\\/g, '\\').replace(/\\/g, '\\\\');
|
|
255
|
+
return `"${normalized}"`;
|
|
256
|
+
});
|
|
257
|
+
const data = JSON.parse(raw);
|
|
258
|
+
if (typeof data !== "object" || Array.isArray(data) || data === null) {
|
|
259
|
+
return { status: "broken", file };
|
|
260
|
+
}
|
|
261
|
+
return { status: "ok", data: data, file };
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return { status: "broken", file };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** Renames a broken config file to config.broken.<timestamp>.json for forensics. */
|
|
268
|
+
const _lastQuarantine = new Map();
|
|
269
|
+
const QUARANTINE_COOLDOWN_MS = 5_000; // min 5s between quarantines of same file
|
|
270
|
+
function quarantineBrokenConfig(file) {
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
const last = _lastQuarantine.get(file) || 0;
|
|
273
|
+
if (now - last < QUARANTINE_COOLDOWN_MS)
|
|
274
|
+
return; // too soon, skip
|
|
275
|
+
_lastQuarantine.set(file, now);
|
|
276
|
+
const dir = path.dirname(file);
|
|
277
|
+
const name = `config.broken.${Date.now()}.json`;
|
|
278
|
+
const dest = path.join(dir, name);
|
|
279
|
+
try {
|
|
280
|
+
fs.renameSync(file, dest);
|
|
281
|
+
logDebug(`[Weavetab Config] [WARN] Broken config quarantined → ${dest}`);
|
|
282
|
+
// Limit quarantine folder size (keep last 5 broken files)
|
|
283
|
+
const brokenFiles = fs.readdirSync(dir)
|
|
284
|
+
.filter(f => f.startsWith("config.broken.") && f.endsWith(".json"))
|
|
285
|
+
.map(f => ({ name: f, time: fs.statSync(path.join(dir, f)).mtimeMs }))
|
|
286
|
+
.sort((a, b) => b.time - a.time);
|
|
287
|
+
if (brokenFiles.length > 5) {
|
|
288
|
+
brokenFiles.slice(5).forEach(f => {
|
|
289
|
+
try {
|
|
290
|
+
fs.unlinkSync(path.join(dir, f.name));
|
|
291
|
+
}
|
|
292
|
+
catch { /* ignore */ }
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
logDebug(`[Weavetab Config] [ERROR] Could not quarantine broken config at ${file}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// ─── Multi-source merge pipeline ──────────────────────────────────────────────
|
|
301
|
+
/**
|
|
302
|
+
* Priority: Global (user) → Built-in defaults
|
|
303
|
+
*
|
|
304
|
+
* Each field is individually merged from global config.
|
|
305
|
+
* If a config file is broken JSON it is quarantined and defaults are used.
|
|
306
|
+
*/
|
|
307
|
+
export function findProjectDir(startDir = process.cwd()) {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
function resolveConfigMerge() {
|
|
311
|
+
const globalDir = GLOBAL_CONFIG_DIR;
|
|
312
|
+
const globalFile = GLOBAL_CONFIG_PATH;
|
|
313
|
+
// Ensure global dir always exists
|
|
314
|
+
ensureConfigDir(globalDir);
|
|
315
|
+
const globalResult = safeReadJson(globalFile);
|
|
316
|
+
// Recover from broken files
|
|
317
|
+
if (globalResult.status === "broken") {
|
|
318
|
+
logDebug(`[Weavetab Config] [ERROR] Global config at "${globalFile}" is broken JSON!`);
|
|
319
|
+
quarantineBrokenConfig(globalFile);
|
|
320
|
+
}
|
|
321
|
+
const hasGlobal = globalResult.status === "ok";
|
|
322
|
+
// Check for project config
|
|
323
|
+
const projectDir = findProjectDir();
|
|
324
|
+
let projectFile;
|
|
325
|
+
let projectResult;
|
|
326
|
+
if (projectDir) {
|
|
327
|
+
projectFile = path.join(projectDir, "config.json");
|
|
328
|
+
if (fs.existsSync(projectFile)) {
|
|
329
|
+
projectResult = safeReadJson(projectFile);
|
|
330
|
+
if (projectResult.status === "broken") {
|
|
331
|
+
logDebug(`[Weavetab Config] [ERROR] Project config at "${projectFile}" is broken JSON!`);
|
|
332
|
+
quarantineBrokenConfig(projectFile);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const hasProject = projectResult && projectResult.status === "ok";
|
|
337
|
+
const activeDir = projectDir && hasProject ? projectDir : globalDir;
|
|
338
|
+
// Log the resolution decision
|
|
339
|
+
if (hasProject) {
|
|
340
|
+
logDebug(`[Weavetab Config] [OK] Loaded project config: ${projectFile} (overrides global)`);
|
|
341
|
+
logDebug(`[Weavetab Config] [OK] Active directory: ${activeDir}`);
|
|
342
|
+
}
|
|
343
|
+
else if (hasGlobal) {
|
|
344
|
+
logDebug(`[Weavetab Config] [OK] Using global config only: ${globalFile}`);
|
|
345
|
+
logDebug(`[Weavetab Config] [OK] Active directory: ${activeDir}`);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
logDebug(`[Weavetab Config] [WARN] No config found — using built-in defaults. Run: wt config`);
|
|
349
|
+
logDebug(`[Weavetab Config] [OK] Active directory: ${activeDir}`);
|
|
350
|
+
// Write a starter global config so user isn't surprised
|
|
351
|
+
ensureConfigDir(globalDir);
|
|
352
|
+
if (!fs.existsSync(globalFile)) {
|
|
353
|
+
try {
|
|
354
|
+
fs.writeFileSync(globalFile, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf-8");
|
|
355
|
+
logDebug(`[Weavetab Config] [OK] Created starter config at: ${globalFile}`);
|
|
356
|
+
}
|
|
357
|
+
catch { /* non-fatal */ }
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Field-by-field merge: project > global > default
|
|
361
|
+
const globalData = hasGlobal ? globalResult.data : {};
|
|
362
|
+
const projectData = hasProject ? projectResult.data : {};
|
|
363
|
+
const merged = {
|
|
364
|
+
...DEFAULT_CONFIG,
|
|
365
|
+
...globalData,
|
|
366
|
+
...projectData,
|
|
367
|
+
};
|
|
368
|
+
const config = validateConfig(merged);
|
|
369
|
+
// Fill in default paths for output directories when config has empty strings.
|
|
370
|
+
// This ensures users see the actual paths (not "") when viewing their config.
|
|
371
|
+
const dirDefaults = [
|
|
372
|
+
["downloadDir", "downloads"],
|
|
373
|
+
["pdfDir", "pdfs"],
|
|
374
|
+
["snapshotDir", "snapshots"],
|
|
375
|
+
["recordingDir", "recordings"],
|
|
376
|
+
["designDir", "designs"],
|
|
377
|
+
];
|
|
378
|
+
for (const [field, subdir] of dirDefaults) {
|
|
379
|
+
if (!config[field]) {
|
|
380
|
+
config[field] = path.join(activeDir, subdir);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return { config, activeDir };
|
|
384
|
+
}
|
|
385
|
+
// ─── Backward-compat: legacy config migration ─────────────────────────────────
|
|
386
|
+
function cleanupBrokenConfigs() {
|
|
387
|
+
try {
|
|
388
|
+
const files = fs.readdirSync(GLOBAL_CONFIG_DIR);
|
|
389
|
+
let cleaned = 0;
|
|
390
|
+
for (const f of files) {
|
|
391
|
+
if ((f.startsWith("config.broken.") && f.endsWith(".json")) || f.startsWith("config.tmp.")) {
|
|
392
|
+
try {
|
|
393
|
+
fs.unlinkSync(path.join(GLOBAL_CONFIG_DIR, f));
|
|
394
|
+
cleaned++;
|
|
395
|
+
}
|
|
396
|
+
catch { /* ignore */ }
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (cleaned > 0) {
|
|
400
|
+
logDebug(`[Weavetab Config] [OK] Cleaned up ${cleaned} stale broken/temp config file(s)`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch { /* ignore */ }
|
|
404
|
+
}
|
|
405
|
+
function migrateOldConfigIfNeeded() {
|
|
406
|
+
if (fs.existsSync(GLOBAL_CONFIG_PATH))
|
|
407
|
+
return; // already done
|
|
408
|
+
const hasSystem = fs.existsSync(OLD_SYSTEM_PATH);
|
|
409
|
+
const hasUI = fs.existsSync(OLD_UI_PATH);
|
|
410
|
+
if (!hasSystem && !hasUI)
|
|
411
|
+
return;
|
|
412
|
+
let merged = { ...DEFAULT_CONFIG };
|
|
413
|
+
if (hasSystem) {
|
|
414
|
+
try {
|
|
415
|
+
merged = { ...merged, ...JSON.parse(fs.readFileSync(OLD_SYSTEM_PATH, "utf-8")) };
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
logDebug("[Weavetab] Failed to parse old system.json");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (hasUI) {
|
|
422
|
+
try {
|
|
423
|
+
merged = { ...merged, ...JSON.parse(fs.readFileSync(OLD_UI_PATH, "utf-8")) };
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
logDebug("[Weavetab] Failed to parse old ui_manifest.json");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
merged.mode = "session";
|
|
430
|
+
merged.attachPort = 0;
|
|
431
|
+
ensureConfigDir(GLOBAL_CONFIG_DIR);
|
|
432
|
+
fs.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(merged, null, 2));
|
|
433
|
+
logDebug("[Weavetab] Auto-migrated old config files to config.json.");
|
|
434
|
+
try {
|
|
435
|
+
if (hasSystem)
|
|
436
|
+
fs.unlinkSync(OLD_SYSTEM_PATH);
|
|
437
|
+
if (hasUI)
|
|
438
|
+
fs.unlinkSync(OLD_UI_PATH);
|
|
439
|
+
}
|
|
440
|
+
catch { /* ignore */ }
|
|
441
|
+
}
|
|
442
|
+
// ─── Active config path (for saveConfig + tools that need to know the dir) ────
|
|
443
|
+
/**
|
|
444
|
+
* Returns the "write target" directory for configs.
|
|
445
|
+
* Priority: Project config if it exists, otherwise global.
|
|
446
|
+
*/
|
|
447
|
+
export function getActiveConfigPath() {
|
|
448
|
+
return { dir: GLOBAL_CONFIG_DIR, file: GLOBAL_CONFIG_PATH };
|
|
449
|
+
}
|
|
450
|
+
// ─── In-memory cache + live reload ────────────────────────────────────────────
|
|
451
|
+
let _cachedConfig = null;
|
|
452
|
+
let _cachedActiveDir = null;
|
|
453
|
+
export function loadConfigSync() {
|
|
454
|
+
migrateOldConfigIfNeeded();
|
|
455
|
+
cleanupBrokenConfigs();
|
|
456
|
+
const { config, activeDir } = resolveConfigMerge();
|
|
457
|
+
_cachedConfig = config;
|
|
458
|
+
_cachedActiveDir = activeDir;
|
|
459
|
+
return config;
|
|
460
|
+
}
|
|
461
|
+
export async function loadConfig() {
|
|
462
|
+
_cachedConfig = loadConfigSync();
|
|
463
|
+
return _cachedConfig;
|
|
464
|
+
}
|
|
465
|
+
/** Zero disk I/O after first load. Refreshed by fs.watch. */
|
|
466
|
+
export function getCachedConfig() {
|
|
467
|
+
if (!_cachedConfig)
|
|
468
|
+
_cachedConfig = loadConfigSync();
|
|
469
|
+
return _cachedConfig;
|
|
470
|
+
}
|
|
471
|
+
/** Force re-read from disk on next getCachedConfig/loadConfig call. */
|
|
472
|
+
export function invalidateConfigCache() {
|
|
473
|
+
_cachedConfig = null;
|
|
474
|
+
_cachedActiveDir = null;
|
|
475
|
+
}
|
|
476
|
+
/** Returns the resolved active directory (global). */
|
|
477
|
+
export function getActiveDirCached() {
|
|
478
|
+
if (!_cachedActiveDir)
|
|
479
|
+
loadConfigSync();
|
|
480
|
+
return _cachedActiveDir ?? GLOBAL_CONFIG_DIR;
|
|
481
|
+
}
|
|
482
|
+
// ─── Output directory resolution ──────────────────────────────────────────────
|
|
483
|
+
// Outputs use configurable paths from config, falling back to global Weavetab directory.
|
|
484
|
+
export function getOutputDir(subdir, config) {
|
|
485
|
+
if (!config) {
|
|
486
|
+
// Fall back to default global directory
|
|
487
|
+
const dir = path.join(GLOBAL_CONFIG_DIR, subdir);
|
|
488
|
+
if (!fs.existsSync(dir)) {
|
|
489
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
490
|
+
}
|
|
491
|
+
return dir;
|
|
492
|
+
}
|
|
493
|
+
// Map simplified config fields to subdirs
|
|
494
|
+
const fieldMap = {
|
|
495
|
+
downloads: "downloadDir",
|
|
496
|
+
pdfs: "pdfDir",
|
|
497
|
+
snapshots: "snapshotDir",
|
|
498
|
+
recordings: "recordingDir",
|
|
499
|
+
designs: "designDir"
|
|
500
|
+
};
|
|
501
|
+
const field = fieldMap[subdir];
|
|
502
|
+
const customPath = config[field];
|
|
503
|
+
if (customPath && customPath.trim()) {
|
|
504
|
+
const resolved = customPath.trim();
|
|
505
|
+
if (!fs.existsSync(resolved)) {
|
|
506
|
+
try {
|
|
507
|
+
fs.mkdirSync(resolved, { recursive: true });
|
|
508
|
+
}
|
|
509
|
+
catch { /* ignore */ }
|
|
510
|
+
}
|
|
511
|
+
return resolved;
|
|
512
|
+
}
|
|
513
|
+
// Fall back to default global directory
|
|
514
|
+
const dir = path.join(GLOBAL_CONFIG_DIR, subdir);
|
|
515
|
+
if (!fs.existsSync(dir)) {
|
|
516
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
517
|
+
}
|
|
518
|
+
return dir;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Helper to get download directory from config.
|
|
522
|
+
*/
|
|
523
|
+
export function getDownloadDir(config) {
|
|
524
|
+
return config.downloadDir || "";
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Helper to get design output directory from config.
|
|
528
|
+
*/
|
|
529
|
+
export function getDesignDir(config) {
|
|
530
|
+
return config.designDir || "";
|
|
531
|
+
}
|
|
532
|
+
// ─── Config Property Access Helpers ───────────────────────────────────────────────
|
|
533
|
+
export function getConfigMode(config) {
|
|
534
|
+
return config.mode;
|
|
535
|
+
}
|
|
536
|
+
export function getConfigAttachPort(config) {
|
|
537
|
+
return config.attachPort;
|
|
538
|
+
}
|
|
539
|
+
export function getConfigAgentId(config) {
|
|
540
|
+
return config.agentId;
|
|
541
|
+
}
|
|
542
|
+
export function getConfigTelemetry(config) {
|
|
543
|
+
return config.telemetry;
|
|
544
|
+
}
|
|
545
|
+
export function getConfigBrowserPath(config) {
|
|
546
|
+
return config.browserPath;
|
|
547
|
+
}
|
|
548
|
+
export function getConfigBrowserType(config) {
|
|
549
|
+
return config.browserType;
|
|
550
|
+
}
|
|
551
|
+
export function getConfigHeadless(config) {
|
|
552
|
+
return config.headless;
|
|
553
|
+
}
|
|
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;
|
|
562
|
+
}
|
|
563
|
+
export function getConfigBrowserFlags(config) {
|
|
564
|
+
return config.browserFlags;
|
|
565
|
+
}
|
|
566
|
+
export function getConfigProfileDir(config) {
|
|
567
|
+
return config.profileDir;
|
|
568
|
+
}
|
|
569
|
+
export function getConfigCookies(config) {
|
|
570
|
+
return config.cookies;
|
|
571
|
+
}
|
|
572
|
+
export function getConfigSyncDataAcrossBrowsers(config) {
|
|
573
|
+
return config.syncDataAcrossBrowsers;
|
|
574
|
+
}
|
|
575
|
+
export function getConfigRole(config) {
|
|
576
|
+
return config.role;
|
|
577
|
+
}
|
|
578
|
+
export function getConfigBlock(config) {
|
|
579
|
+
return config.block;
|
|
580
|
+
}
|
|
581
|
+
export function getConfigAllow(config) {
|
|
582
|
+
return config.allow;
|
|
583
|
+
}
|
|
584
|
+
export function getConfigPolicies(config) {
|
|
585
|
+
return config.policies;
|
|
586
|
+
}
|
|
587
|
+
export function getConfigProxy(config) {
|
|
588
|
+
return config.proxy;
|
|
589
|
+
}
|
|
590
|
+
export function getConfigMaxTabs(config) {
|
|
591
|
+
return config.maxTabs;
|
|
592
|
+
}
|
|
593
|
+
export function getConfigMaxActionsPerMinute(config) {
|
|
594
|
+
return config.maxActionsPerMinute;
|
|
595
|
+
}
|
|
596
|
+
export function getConfigSessionTimeout(config) {
|
|
597
|
+
return config.sessionTimeout;
|
|
598
|
+
}
|
|
599
|
+
export function getConfigNavigationTimeout(config) {
|
|
600
|
+
return config.navigationTimeout;
|
|
601
|
+
}
|
|
602
|
+
export function getConfigIdleTimeout(config) {
|
|
603
|
+
return config.idleTimeout;
|
|
604
|
+
}
|
|
605
|
+
export function getConfigActionabilityTimeoutMs(config) {
|
|
606
|
+
return config.actionabilityTimeoutMs;
|
|
607
|
+
}
|
|
608
|
+
export function getConfigGithubCacheTtl(config) {
|
|
609
|
+
return config.githubCacheTtlMs;
|
|
610
|
+
}
|
|
611
|
+
export function getConfigRefCacheTtl(config) {
|
|
612
|
+
return config.refCacheTtlMs;
|
|
613
|
+
}
|
|
614
|
+
export function getConfigDevMode(config) {
|
|
615
|
+
return config.devMode;
|
|
616
|
+
}
|
|
617
|
+
// ─── Additional Helper Functions for Code Compatibility ─────────────────────────
|
|
618
|
+
export function getConfigNavigationTimeoutMs(config) {
|
|
619
|
+
return config.navigationTimeout;
|
|
620
|
+
}
|
|
621
|
+
export function getConfigGithubCacheTtlMs(config) {
|
|
622
|
+
return config.githubCacheTtlMs;
|
|
623
|
+
}
|
|
624
|
+
export function getConfigRefCacheTtlMs(config) {
|
|
625
|
+
return config.refCacheTtlMs;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Returns a directory inside the global Weavetab system folder.
|
|
629
|
+
* User-facing outputs stay in `.weavetab/` — system internals go here.
|
|
630
|
+
* Platform-specific locations for better organization.
|
|
631
|
+
*/
|
|
632
|
+
const GLOBAL_SYSTEM_DIR = path.join(HOME, ".weavetab-system");
|
|
633
|
+
export function getSystemDir(subdir) {
|
|
634
|
+
const dir = path.join(GLOBAL_SYSTEM_DIR, subdir);
|
|
635
|
+
if (!fs.existsSync(dir)) {
|
|
636
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
637
|
+
}
|
|
638
|
+
return dir;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Returns a directory inside the global ~/.weavetab-system/ folder.
|
|
642
|
+
* Alias for consistency — identical to getSystemDir.
|
|
643
|
+
*/
|
|
644
|
+
export function getGlobalSystemDir(subdir) {
|
|
645
|
+
const dir = path.join(GLOBAL_SYSTEM_DIR, subdir);
|
|
646
|
+
if (!fs.existsSync(dir)) {
|
|
647
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
648
|
+
}
|
|
649
|
+
return dir;
|
|
650
|
+
}
|
|
651
|
+
export function saveConfig(config) {
|
|
652
|
+
const { dir, file } = getActiveConfigPath();
|
|
653
|
+
ensureConfigDir(dir);
|
|
654
|
+
const mode = getConfigMode(config);
|
|
655
|
+
if (mode !== "attach") {
|
|
656
|
+
config.attachPort = 0;
|
|
657
|
+
}
|
|
658
|
+
// Atomic write: write to temp file then rename to prevent partial reads
|
|
659
|
+
// from concurrent processes or crashes mid-write.
|
|
660
|
+
const tmpFile = path.join(dir, `config.tmp.${process.pid}.json`);
|
|
661
|
+
try {
|
|
662
|
+
const content = JSON.stringify(config, null, 2);
|
|
663
|
+
fs.writeFileSync(tmpFile, content, "utf-8");
|
|
664
|
+
fs.renameSync(tmpFile, file);
|
|
665
|
+
}
|
|
666
|
+
catch (e) {
|
|
667
|
+
try {
|
|
668
|
+
if (fs.existsSync(tmpFile))
|
|
669
|
+
fs.unlinkSync(tmpFile);
|
|
670
|
+
}
|
|
671
|
+
catch { /* ignore */ }
|
|
672
|
+
throw e;
|
|
673
|
+
}
|
|
674
|
+
_cachedConfig = loadConfigSync();
|
|
675
|
+
listeners.forEach(cb => cb(_cachedConfig));
|
|
676
|
+
}
|
|
677
|
+
// ─── Live-reload watcher ───────────────────────────────────────────────────────
|
|
678
|
+
let watchTimeout = null;
|
|
679
|
+
function startWatcher(dir) {
|
|
680
|
+
try {
|
|
681
|
+
if (!fs.existsSync(dir))
|
|
682
|
+
return;
|
|
683
|
+
fs.watch(dir, (eventType, filename) => {
|
|
684
|
+
if (filename === "config.json" && eventType === "change") {
|
|
685
|
+
if (watchTimeout)
|
|
686
|
+
clearTimeout(watchTimeout);
|
|
687
|
+
watchTimeout = setTimeout(() => {
|
|
688
|
+
try {
|
|
689
|
+
_cachedConfig = loadConfigSync();
|
|
690
|
+
listeners.forEach(cb => cb(_cachedConfig));
|
|
691
|
+
}
|
|
692
|
+
catch (e) {
|
|
693
|
+
logDebug("[Weavetab] Failed to reload config", e);
|
|
694
|
+
}
|
|
695
|
+
}, 150);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
catch {
|
|
700
|
+
// Ignore watch errors on some OS/network mounts
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
// Watch global location so changes are picked up
|
|
704
|
+
startWatcher(GLOBAL_CONFIG_DIR);
|