@opentabs-dev/mcp-server 0.0.19
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/dist/audit-disk.d.ts +23 -0
- package/dist/audit-disk.d.ts.map +1 -0
- package/dist/audit-disk.js +74 -0
- package/dist/audit-disk.js.map +1 -0
- package/dist/browser-tools/analyze-site/detect-apis.d.ts +36 -0
- package/dist/browser-tools/analyze-site/detect-apis.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/detect-apis.js +383 -0
- package/dist/browser-tools/analyze-site/detect-apis.js.map +1 -0
- package/dist/browser-tools/analyze-site/detect-auth.d.ts +72 -0
- package/dist/browser-tools/analyze-site/detect-auth.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/detect-auth.js +384 -0
- package/dist/browser-tools/analyze-site/detect-auth.js.map +1 -0
- package/dist/browser-tools/analyze-site/detect-dom.d.ts +65 -0
- package/dist/browser-tools/analyze-site/detect-dom.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/detect-dom.js +45 -0
- package/dist/browser-tools/analyze-site/detect-dom.js.map +1 -0
- package/dist/browser-tools/analyze-site/detect-framework.d.ts +48 -0
- package/dist/browser-tools/analyze-site/detect-framework.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/detect-framework.js +31 -0
- package/dist/browser-tools/analyze-site/detect-framework.js.map +1 -0
- package/dist/browser-tools/analyze-site/detect-globals.d.ts +41 -0
- package/dist/browser-tools/analyze-site/detect-globals.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/detect-globals.js +42 -0
- package/dist/browser-tools/analyze-site/detect-globals.js.map +1 -0
- package/dist/browser-tools/analyze-site/detect-storage.d.ts +39 -0
- package/dist/browser-tools/analyze-site/detect-storage.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/detect-storage.js +34 -0
- package/dist/browser-tools/analyze-site/detect-storage.js.map +1 -0
- package/dist/browser-tools/analyze-site/index.d.ts +52 -0
- package/dist/browser-tools/analyze-site/index.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site/index.js +827 -0
- package/dist/browser-tools/analyze-site/index.js.map +1 -0
- package/dist/browser-tools/analyze-site.d.ts +17 -0
- package/dist/browser-tools/analyze-site.d.ts.map +1 -0
- package/dist/browser-tools/analyze-site.js +41 -0
- package/dist/browser-tools/analyze-site.js.map +1 -0
- package/dist/browser-tools/clear-console-logs.d.ts +9 -0
- package/dist/browser-tools/clear-console-logs.d.ts.map +1 -0
- package/dist/browser-tools/clear-console-logs.js +16 -0
- package/dist/browser-tools/clear-console-logs.js.map +1 -0
- package/dist/browser-tools/click-element.d.ts +10 -0
- package/dist/browser-tools/click-element.d.ts.map +1 -0
- package/dist/browser-tools/click-element.js +22 -0
- package/dist/browser-tools/click-element.js.map +1 -0
- package/dist/browser-tools/close-tab.d.ts +9 -0
- package/dist/browser-tools/close-tab.d.ts.map +1 -0
- package/dist/browser-tools/close-tab.js +16 -0
- package/dist/browser-tools/close-tab.js.map +1 -0
- package/dist/browser-tools/definition.d.ts +26 -0
- package/dist/browser-tools/definition.d.ts.map +1 -0
- package/dist/browser-tools/definition.js +16 -0
- package/dist/browser-tools/definition.js.map +1 -0
- package/dist/browser-tools/delete-cookies.d.ts +10 -0
- package/dist/browser-tools/delete-cookies.d.ts.map +1 -0
- package/dist/browser-tools/delete-cookies.js +19 -0
- package/dist/browser-tools/delete-cookies.js.map +1 -0
- package/dist/browser-tools/disable-network-capture.d.ts +9 -0
- package/dist/browser-tools/disable-network-capture.d.ts.map +1 -0
- package/dist/browser-tools/disable-network-capture.js +16 -0
- package/dist/browser-tools/disable-network-capture.js.map +1 -0
- package/dist/browser-tools/enable-network-capture.d.ts +12 -0
- package/dist/browser-tools/enable-network-capture.d.ts.map +1 -0
- package/dist/browser-tools/enable-network-capture.js +42 -0
- package/dist/browser-tools/enable-network-capture.js.map +1 -0
- package/dist/browser-tools/execute-script.d.ts +19 -0
- package/dist/browser-tools/execute-script.d.ts.map +1 -0
- package/dist/browser-tools/execute-script.js +51 -0
- package/dist/browser-tools/execute-script.js.map +1 -0
- package/dist/browser-tools/extension-check-adapter.d.ts +11 -0
- package/dist/browser-tools/extension-check-adapter.d.ts.map +1 -0
- package/dist/browser-tools/extension-check-adapter.js +22 -0
- package/dist/browser-tools/extension-check-adapter.js.map +1 -0
- package/dist/browser-tools/extension-force-reconnect.d.ts +9 -0
- package/dist/browser-tools/extension-force-reconnect.d.ts.map +1 -0
- package/dist/browser-tools/extension-force-reconnect.js +19 -0
- package/dist/browser-tools/extension-force-reconnect.js.map +1 -0
- package/dist/browser-tools/extension-get-logs.d.ts +24 -0
- package/dist/browser-tools/extension-get-logs.d.ts.map +1 -0
- package/dist/browser-tools/extension-get-logs.js +34 -0
- package/dist/browser-tools/extension-get-logs.js.map +1 -0
- package/dist/browser-tools/extension-get-side-panel.d.ts +8 -0
- package/dist/browser-tools/extension-get-side-panel.d.ts.map +1 -0
- package/dist/browser-tools/extension-get-side-panel.js +17 -0
- package/dist/browser-tools/extension-get-side-panel.js.map +1 -0
- package/dist/browser-tools/extension-get-state.d.ts +9 -0
- package/dist/browser-tools/extension-get-state.d.ts.map +1 -0
- package/dist/browser-tools/extension-get-state.js +19 -0
- package/dist/browser-tools/extension-get-state.js.map +1 -0
- package/dist/browser-tools/focus-tab.d.ts +9 -0
- package/dist/browser-tools/focus-tab.d.ts.map +1 -0
- package/dist/browser-tools/focus-tab.js +17 -0
- package/dist/browser-tools/focus-tab.js.map +1 -0
- package/dist/browser-tools/get-console-logs.d.ts +18 -0
- package/dist/browser-tools/get-console-logs.d.ts.map +1 -0
- package/dist/browser-tools/get-console-logs.js +30 -0
- package/dist/browser-tools/get-console-logs.js.map +1 -0
- package/dist/browser-tools/get-cookies.d.ts +10 -0
- package/dist/browser-tools/get-cookies.d.ts.map +1 -0
- package/dist/browser-tools/get-cookies.js +23 -0
- package/dist/browser-tools/get-cookies.js.map +1 -0
- package/dist/browser-tools/get-network-requests.d.ts +10 -0
- package/dist/browser-tools/get-network-requests.d.ts.map +1 -0
- package/dist/browser-tools/get-network-requests.js +27 -0
- package/dist/browser-tools/get-network-requests.js.map +1 -0
- package/dist/browser-tools/get-page-html.d.ts +11 -0
- package/dist/browser-tools/get-page-html.d.ts.map +1 -0
- package/dist/browser-tools/get-page-html.js +32 -0
- package/dist/browser-tools/get-page-html.js.map +1 -0
- package/dist/browser-tools/get-resource-content.d.ts +11 -0
- package/dist/browser-tools/get-resource-content.d.ts.map +1 -0
- package/dist/browser-tools/get-resource-content.js +31 -0
- package/dist/browser-tools/get-resource-content.js.map +1 -0
- package/dist/browser-tools/get-storage.d.ts +14 -0
- package/dist/browser-tools/get-storage.d.ts.map +1 -0
- package/dist/browser-tools/get-storage.js +28 -0
- package/dist/browser-tools/get-storage.js.map +1 -0
- package/dist/browser-tools/get-tab-content.d.ts +11 -0
- package/dist/browser-tools/get-tab-content.d.ts.map +1 -0
- package/dist/browser-tools/get-tab-content.js +29 -0
- package/dist/browser-tools/get-tab-content.js.map +1 -0
- package/dist/browser-tools/get-tab-info.d.ts +9 -0
- package/dist/browser-tools/get-tab-info.d.ts.map +1 -0
- package/dist/browser-tools/get-tab-info.js +17 -0
- package/dist/browser-tools/get-tab-info.js.map +1 -0
- package/dist/browser-tools/handle-dialog.d.ts +14 -0
- package/dist/browser-tools/handle-dialog.d.ts.map +1 -0
- package/dist/browser-tools/handle-dialog.js +30 -0
- package/dist/browser-tools/handle-dialog.js.map +1 -0
- package/dist/browser-tools/hover-element.d.ts +11 -0
- package/dist/browser-tools/hover-element.d.ts.map +1 -0
- package/dist/browser-tools/hover-element.js +24 -0
- package/dist/browser-tools/hover-element.js.map +1 -0
- package/dist/browser-tools/index.d.ts +7 -0
- package/dist/browser-tools/index.d.ts.map +1 -0
- package/dist/browser-tools/index.js +81 -0
- package/dist/browser-tools/index.js.map +1 -0
- package/dist/browser-tools/list-resources.d.ts +10 -0
- package/dist/browser-tools/list-resources.d.ts.map +1 -0
- package/dist/browser-tools/list-resources.js +29 -0
- package/dist/browser-tools/list-resources.js.map +1 -0
- package/dist/browser-tools/list-tabs.d.ts +7 -0
- package/dist/browser-tools/list-tabs.d.ts.map +1 -0
- package/dist/browser-tools/list-tabs.js +16 -0
- package/dist/browser-tools/list-tabs.js.map +1 -0
- package/dist/browser-tools/navigate-tab.d.ts +10 -0
- package/dist/browser-tools/navigate-tab.d.ts.map +1 -0
- package/dist/browser-tools/navigate-tab.js +18 -0
- package/dist/browser-tools/navigate-tab.js.map +1 -0
- package/dist/browser-tools/open-tab.d.ts +9 -0
- package/dist/browser-tools/open-tab.d.ts.map +1 -0
- package/dist/browser-tools/open-tab.js +18 -0
- package/dist/browser-tools/open-tab.js.map +1 -0
- package/dist/browser-tools/press-key.d.ts +17 -0
- package/dist/browser-tools/press-key.d.ts.map +1 -0
- package/dist/browser-tools/press-key.js +43 -0
- package/dist/browser-tools/press-key.js.map +1 -0
- package/dist/browser-tools/query-elements.d.ts +12 -0
- package/dist/browser-tools/query-elements.d.ts.map +1 -0
- package/dist/browser-tools/query-elements.js +30 -0
- package/dist/browser-tools/query-elements.js.map +1 -0
- package/dist/browser-tools/reload-extension.d.ts +13 -0
- package/dist/browser-tools/reload-extension.d.ts.map +1 -0
- package/dist/browser-tools/reload-extension.js +35 -0
- package/dist/browser-tools/reload-extension.js.map +1 -0
- package/dist/browser-tools/screenshot-tab.d.ts +9 -0
- package/dist/browser-tools/screenshot-tab.d.ts.map +1 -0
- package/dist/browser-tools/screenshot-tab.js +22 -0
- package/dist/browser-tools/screenshot-tab.js.map +1 -0
- package/dist/browser-tools/scroll.d.ts +23 -0
- package/dist/browser-tools/scroll.d.ts.map +1 -0
- package/dist/browser-tools/scroll.js +56 -0
- package/dist/browser-tools/scroll.js.map +1 -0
- package/dist/browser-tools/select-option.d.ts +12 -0
- package/dist/browser-tools/select-option.d.ts.map +1 -0
- package/dist/browser-tools/select-option.js +25 -0
- package/dist/browser-tools/select-option.js.map +1 -0
- package/dist/browser-tools/set-cookie.d.ts +16 -0
- package/dist/browser-tools/set-cookie.d.ts.map +1 -0
- package/dist/browser-tools/set-cookie.js +42 -0
- package/dist/browser-tools/set-cookie.js.map +1 -0
- package/dist/browser-tools/type-text.d.ts +12 -0
- package/dist/browser-tools/type-text.d.ts.map +1 -0
- package/dist/browser-tools/type-text.js +25 -0
- package/dist/browser-tools/type-text.js.map +1 -0
- package/dist/browser-tools/url-validation.d.ts +13 -0
- package/dist/browser-tools/url-validation.d.ts.map +1 -0
- package/dist/browser-tools/url-validation.js +23 -0
- package/dist/browser-tools/url-validation.js.map +1 -0
- package/dist/browser-tools/wait-for-element.d.ts +12 -0
- package/dist/browser-tools/wait-for-element.d.ts.map +1 -0
- package/dist/browser-tools/wait-for-element.js +29 -0
- package/dist/browser-tools/wait-for-element.js.map +1 -0
- package/dist/config.d.ts +99 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +344 -0
- package/dist/config.js.map +1 -0
- package/dist/dev-mode.d.ts +14 -0
- package/dist/dev-mode.d.ts.map +1 -0
- package/dist/dev-mode.js +15 -0
- package/dist/dev-mode.js.map +1 -0
- package/dist/discovery-legacy.d.ts +32 -0
- package/dist/discovery-legacy.d.ts.map +1 -0
- package/dist/discovery-legacy.js +415 -0
- package/dist/discovery-legacy.js.map +1 -0
- package/dist/discovery.d.ts +28 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +97 -0
- package/dist/discovery.js.map +1 -0
- package/dist/extension-install.d.ts +27 -0
- package/dist/extension-install.d.ts.map +1 -0
- package/dist/extension-install.js +75 -0
- package/dist/extension-install.js.map +1 -0
- package/dist/extension-protocol.d.ts +130 -0
- package/dist/extension-protocol.d.ts.map +1 -0
- package/dist/extension-protocol.js +869 -0
- package/dist/extension-protocol.js.map +1 -0
- package/dist/file-watcher.d.ts +75 -0
- package/dist/file-watcher.d.ts.map +1 -0
- package/dist/file-watcher.js +616 -0
- package/dist/file-watcher.js.map +1 -0
- package/dist/http-routes.d.ts +88 -0
- package/dist/http-routes.d.ts.map +1 -0
- package/dist/http-routes.js +545 -0
- package/dist/http-routes.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +187 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +100 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +402 -0
- package/dist/loader.js.map +1 -0
- package/dist/log-buffer.d.ts +33 -0
- package/dist/log-buffer.d.ts.map +1 -0
- package/dist/log-buffer.js +64 -0
- package/dist/log-buffer.js.map +1 -0
- package/dist/logger.d.ts +34 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +81 -0
- package/dist/logger.js.map +1 -0
- package/dist/manifest-schema.d.ts +14 -0
- package/dist/manifest-schema.d.ts.map +1 -0
- package/dist/manifest-schema.js +51 -0
- package/dist/manifest-schema.js.map +1 -0
- package/dist/mcp-setup.d.ts +131 -0
- package/dist/mcp-setup.d.ts.map +1 -0
- package/dist/mcp-setup.js +673 -0
- package/dist/mcp-setup.js.map +1 -0
- package/dist/permissions.d.ts +59 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +141 -0
- package/dist/permissions.js.map +1 -0
- package/dist/registry.d.ts +78 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +187 -0
- package/dist/registry.js.map +1 -0
- package/dist/reload.d.ts +52 -0
- package/dist/reload.d.ts.map +1 -0
- package/dist/reload.js +326 -0
- package/dist/reload.js.map +1 -0
- package/dist/resolver.d.ts +53 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +272 -0
- package/dist/resolver.js.map +1 -0
- package/dist/sanitize-error.d.ts +8 -0
- package/dist/sanitize-error.d.ts.map +1 -0
- package/dist/sanitize-error.js +25 -0
- package/dist/sanitize-error.js.map +1 -0
- package/dist/sanitize-tool-output.d.ts +20 -0
- package/dist/sanitize-tool-output.d.ts.map +1 -0
- package/dist/sanitize-tool-output.js +52 -0
- package/dist/sanitize-tool-output.js.map +1 -0
- package/dist/sdk-version.d.ts +11 -0
- package/dist/sdk-version.d.ts.map +1 -0
- package/dist/sdk-version.js +23 -0
- package/dist/sdk-version.js.map +1 -0
- package/dist/shutdown.d.ts +28 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +68 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/skip-confirmation.d.ts +15 -0
- package/dist/skip-confirmation.d.ts.map +1 -0
- package/dist/skip-confirmation.js +16 -0
- package/dist/skip-confirmation.js.map +1 -0
- package/dist/skip-sanitization.d.ts +17 -0
- package/dist/skip-sanitization.d.ts.map +1 -0
- package/dist/skip-sanitization.js +18 -0
- package/dist/skip-sanitization.js.map +1 -0
- package/dist/skip-verification.d.ts +11 -0
- package/dist/skip-verification.d.ts.map +1 -0
- package/dist/skip-verification.js +12 -0
- package/dist/skip-verification.js.map +1 -0
- package/dist/state.d.ts +290 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +111 -0
- package/dist/state.js.map +1 -0
- package/dist/verify-plugin.d.ts +53 -0
- package/dist/verify-plugin.d.ts.map +1 -0
- package/dist/verify-plugin.js +123 -0
- package/dist/verify-plugin.js.map +1 -0
- package/dist/version-check.d.ts +35 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +111 -0
- package/dist/version-check.js.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +22 -0
- package/dist/version.js.map +1 -0
- package/package.json +28 -0
package/dist/reload.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hot reload orchestration module.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates the entire reload sequence: config loading, plugin discovery,
|
|
5
|
+
* state swap, stale entry pruning, MCP handler re-registration, file watcher
|
|
6
|
+
* restart, extension re-sync, client notification, and version check.
|
|
7
|
+
*
|
|
8
|
+
* Called on every module evaluation (both first load and hot reload). This
|
|
9
|
+
* separation keeps index.ts as a thin frozen shell (Bun.serve() delegate,
|
|
10
|
+
* HotState management, handler definitions) that rarely needs to change,
|
|
11
|
+
* while all reload logic lives here and is freely editable.
|
|
12
|
+
*/
|
|
13
|
+
import { browserTools } from './browser-tools/index.js';
|
|
14
|
+
import { loadConfig, getConfigDir } from './config.js';
|
|
15
|
+
import { isDev } from './dev-mode.js';
|
|
16
|
+
import { discoverPlugins } from './discovery.js';
|
|
17
|
+
import { ensureExtensionInstalled } from './extension-install.js';
|
|
18
|
+
import { sendSyncFull, sendPluginUpdate, sendExtensionReload, cleanupStaleExecFiles } from './extension-protocol.js';
|
|
19
|
+
import { startConfigWatching, startFileWatching, stopFileWatching } from './file-watcher.js';
|
|
20
|
+
import { sweepStaleSessions } from './http-routes.js';
|
|
21
|
+
import { log } from './logger.js';
|
|
22
|
+
import { registerMcpHandlers, rebuildCachedBrowserTools, notifyToolListChanged, notifyResourceListChanged, notifyPromptListChanged, } from './mcp-setup.js';
|
|
23
|
+
import { buildRegistry } from './registry.js';
|
|
24
|
+
import { isCliSkipConfirmation } from './skip-confirmation.js';
|
|
25
|
+
import { prefixedToolName } from './state.js';
|
|
26
|
+
import { checkForUpdates } from './version-check.js';
|
|
27
|
+
/**
|
|
28
|
+
* globalThis key for the concurrent reload guard promise.
|
|
29
|
+
* Stored on globalThis so it survives across bun --hot re-evaluations.
|
|
30
|
+
* If a reload is in progress when bun --hot triggers, the new module
|
|
31
|
+
* evaluation waits for the previous reload to finish before starting.
|
|
32
|
+
*/
|
|
33
|
+
const RELOAD_GUARD_KEY = '__opentabs_reload_guard__';
|
|
34
|
+
const getReloadGuard = () => globalThis[RELOAD_GUARD_KEY];
|
|
35
|
+
const setReloadGuard = (promise) => {
|
|
36
|
+
globalThis[RELOAD_GUARD_KEY] = promise;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Remove stale entries from state maps after a registry swap.
|
|
40
|
+
* Prunes tabMapping, activeDispatches, toolConfig, and outdatedPlugins
|
|
41
|
+
* for plugins/tools that no longer exist in the current registry.
|
|
42
|
+
*/
|
|
43
|
+
const pruneStaleState = (state) => {
|
|
44
|
+
for (const pluginName of state.tabMapping.keys()) {
|
|
45
|
+
if (!state.registry.plugins.has(pluginName)) {
|
|
46
|
+
state.tabMapping.delete(pluginName);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const pluginName of state.activeDispatches.keys()) {
|
|
50
|
+
if (!state.registry.plugins.has(pluginName)) {
|
|
51
|
+
state.activeDispatches.delete(pluginName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Prune stale toolConfig entries for removed plugins/tools
|
|
55
|
+
const validToolNames = new Set();
|
|
56
|
+
for (const plugin of state.registry.plugins.values()) {
|
|
57
|
+
for (const tool of plugin.tools) {
|
|
58
|
+
validToolNames.add(prefixedToolName(plugin.name, tool.name));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let prunedToolConfigCount = 0;
|
|
62
|
+
for (const key of Object.keys(state.toolConfig)) {
|
|
63
|
+
if (!validToolNames.has(key)) {
|
|
64
|
+
Reflect.deleteProperty(state.toolConfig, key);
|
|
65
|
+
prunedToolConfigCount++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (prunedToolConfigCount > 0) {
|
|
69
|
+
log.info(`Pruned ${prunedToolConfigCount} stale tool config entry/entries`);
|
|
70
|
+
}
|
|
71
|
+
// Keep only outdatedPlugins entries for still-present plugins
|
|
72
|
+
state.outdatedPlugins = state.outdatedPlugins.filter(o => Array.from(state.registry.plugins.values()).some(p => p.npmPackageName === o.name));
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Build file watcher callbacks that close over the current state and sessions.
|
|
76
|
+
* Extracted from reloadCore so the core function stays focused on the
|
|
77
|
+
* config → discover → swap → prune pipeline.
|
|
78
|
+
*/
|
|
79
|
+
const createFileWatcherCallbacks = (state, sessionServers, transports) => {
|
|
80
|
+
const notifyAllClients = () => {
|
|
81
|
+
for (const srv of sessionServers) {
|
|
82
|
+
notifyToolListChanged(srv);
|
|
83
|
+
notifyResourceListChanged(srv);
|
|
84
|
+
notifyPromptListChanged(srv);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
onManifestChanged: (pluginName) => {
|
|
89
|
+
state.registry = buildRegistry(Array.from(state.registry.plugins.values()), [...state.registry.failures]);
|
|
90
|
+
notifyAllClients();
|
|
91
|
+
const plugin = state.registry.plugins.get(pluginName);
|
|
92
|
+
if (plugin) {
|
|
93
|
+
void sendPluginUpdate(state, pluginName, plugin.iife, plugin.iifeSourceMap).catch((err) => {
|
|
94
|
+
log.error(`Failed to write adapter file for ${pluginName}:`, err);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
onIifeChanged: (pluginName, iife, sourceMap) => {
|
|
99
|
+
void sendPluginUpdate(state, pluginName, iife, sourceMap).catch((err) => {
|
|
100
|
+
log.error(`Failed to write adapter file for ${pluginName}:`, err);
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
onConfigChanged: () => {
|
|
104
|
+
void performConfigReload(state, sessionServers, transports).catch((err) => {
|
|
105
|
+
log.error('Config watcher reload failed:', err);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
onPluginDiscovered: (pluginName) => {
|
|
109
|
+
log.info(`Plugin "${pluginName}" discovered by file watcher — rebuilding tools and syncing extension`);
|
|
110
|
+
state.registry = buildRegistry(Array.from(state.registry.plugins.values()), [...state.registry.failures]);
|
|
111
|
+
notifyAllClients();
|
|
112
|
+
if (state.extensionWs) {
|
|
113
|
+
void sendSyncFull(state).catch((err) => {
|
|
114
|
+
log.error('Failed to sync extension after plugin discovery:', err);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Shared reload core: discovery, state swap, pruning, and extension sync.
|
|
122
|
+
* Callers notify MCP clients of tool list changes after this returns.
|
|
123
|
+
* In dev mode, file watchers and config watching are started after discovery.
|
|
124
|
+
* In production mode, no watchers are created — restart to reload.
|
|
125
|
+
*/
|
|
126
|
+
const reloadCore = async ({ state, sessionServers, transports }) => {
|
|
127
|
+
// Always stop existing watchers (cleans up handles from previous hot reload iteration)
|
|
128
|
+
stopFileWatching(state);
|
|
129
|
+
sweepStaleSessions(state, transports, sessionServers);
|
|
130
|
+
try {
|
|
131
|
+
const config = await loadConfig();
|
|
132
|
+
const configDir = getConfigDir();
|
|
133
|
+
const { registry, errors } = await discoverPlugins(config.localPlugins, configDir);
|
|
134
|
+
state.registry = registry;
|
|
135
|
+
state.toolConfig = { ...config.tools };
|
|
136
|
+
state.browserToolPolicy = { ...config.browserToolPolicy };
|
|
137
|
+
state.pluginPaths = [...config.localPlugins];
|
|
138
|
+
state.wsSecret = config.secret ?? null;
|
|
139
|
+
state.discoveryErrors = errors;
|
|
140
|
+
state.permissions = config.permissions;
|
|
141
|
+
state.skipConfirmation = isCliSkipConfirmation() || config.skipConfirmation === true;
|
|
142
|
+
if (errors.length > 0) {
|
|
143
|
+
log.warn(`${errors.length} plugin(s) failed to load:`);
|
|
144
|
+
for (const e of errors) {
|
|
145
|
+
log.warn(` "${e.specifier}": ${e.error}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
log.info(`Config loaded: ${config.localPlugins.length} local plugin path(s), ${Object.keys(config.tools).length} tool setting(s)`);
|
|
149
|
+
rebuildCachedBrowserTools(state);
|
|
150
|
+
pruneStaleState(state);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
log.error('Reload failed, keeping previous state:', err);
|
|
154
|
+
}
|
|
155
|
+
// File watchers, config watching, and mtime polling are dev-only features.
|
|
156
|
+
// Production mode discovers plugins once at startup; restart to reload.
|
|
157
|
+
if (isDev()) {
|
|
158
|
+
const callbacks = createFileWatcherCallbacks(state, sessionServers, transports);
|
|
159
|
+
const failedPaths = state.registry.failures.map(f => f.path);
|
|
160
|
+
startFileWatching(state, callbacks, failedPaths);
|
|
161
|
+
startConfigWatching(state, callbacks);
|
|
162
|
+
}
|
|
163
|
+
if (state.extensionWs) {
|
|
164
|
+
await sendSyncFull(state);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Clear and restart the periodic sweep timer for stale MCP sessions.
|
|
169
|
+
* Both reload paths call this to ensure the timer is always fresh.
|
|
170
|
+
*/
|
|
171
|
+
const restartSweepTimer = (state, transports, sessionServers) => {
|
|
172
|
+
if (state.sweepTimerId !== null) {
|
|
173
|
+
clearInterval(state.sweepTimerId);
|
|
174
|
+
state.sweepTimerId = null;
|
|
175
|
+
}
|
|
176
|
+
state.sweepTimerId = setInterval(() => {
|
|
177
|
+
sweepStaleSessions(state, transports, sessionServers);
|
|
178
|
+
}, 60_000);
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Run the full reload sequence.
|
|
182
|
+
*
|
|
183
|
+
* On first load: discovers plugins, kicks off version check. In dev mode,
|
|
184
|
+
* also starts file watchers for local plugins and config.json.
|
|
185
|
+
* On hot reload: additionally re-registers MCP handlers on existing sessions,
|
|
186
|
+
* refreshes browser tools, installs/updates the managed extension, and notifies
|
|
187
|
+
* all MCP clients.
|
|
188
|
+
*
|
|
189
|
+
* If discovery fails, the server continues with whatever plugins were in state
|
|
190
|
+
* before the reload attempt.
|
|
191
|
+
*
|
|
192
|
+
* A globalThis-based guard prevents concurrent reloads: if a previous reload
|
|
193
|
+
* is still running (e.g., bun --hot fires twice in quick succession), this
|
|
194
|
+
* call waits for it to finish before proceeding.
|
|
195
|
+
*/
|
|
196
|
+
const performReload = async (state, sessionServers, transports, isHotReload) => {
|
|
197
|
+
// Wait for any in-flight reload to complete before starting a new one
|
|
198
|
+
const existingGuard = getReloadGuard();
|
|
199
|
+
if (existingGuard) {
|
|
200
|
+
log.info('Waiting for in-flight reload to complete before starting new reload...');
|
|
201
|
+
await existingGuard;
|
|
202
|
+
}
|
|
203
|
+
// Create a deferred promise that other callers can await
|
|
204
|
+
let resolveGuard;
|
|
205
|
+
const guard = new Promise(resolve => {
|
|
206
|
+
resolveGuard = resolve;
|
|
207
|
+
});
|
|
208
|
+
setReloadGuard(guard);
|
|
209
|
+
const startTs = Date.now();
|
|
210
|
+
try {
|
|
211
|
+
// Clear the previous periodic sweep timer — it closes over stale references
|
|
212
|
+
// from the previous module evaluation. A fresh timer is started below.
|
|
213
|
+
restartSweepTimer(state, transports, sessionServers);
|
|
214
|
+
// Ensure the managed extension in ~/.opentabs/extension/ is up to date.
|
|
215
|
+
// Isolated from the rest of reload so a transient filesystem error
|
|
216
|
+
// (cpSync, mkdirSync, Bun.write) does not block plugin discovery.
|
|
217
|
+
try {
|
|
218
|
+
const installResult = await ensureExtensionInstalled();
|
|
219
|
+
if (installResult.versionChanged) {
|
|
220
|
+
if (state.extensionWs) {
|
|
221
|
+
// Extension is connected — send reload after a short delay to let
|
|
222
|
+
// sync.full flush first (the extension handles extension.reload by
|
|
223
|
+
// calling chrome.runtime.reload() after its own flush delay).
|
|
224
|
+
log.info('Extension version changed — sending reload signal to connected extension');
|
|
225
|
+
setTimeout(() => sendExtensionReload(state), 500);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// Extension not connected — flag for reload on next connect
|
|
229
|
+
log.info('Extension version changed — reload will be sent on next extension connect');
|
|
230
|
+
state.pendingExtensionReload = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
log.warn('Extension install failed (continuing with plugin discovery):', err);
|
|
236
|
+
}
|
|
237
|
+
// Remove leftover __exec-*.js files from previous sessions/crashes
|
|
238
|
+
try {
|
|
239
|
+
await cleanupStaleExecFiles();
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
log.warn('Exec file cleanup failed:', err);
|
|
243
|
+
}
|
|
244
|
+
// Update browser tools from the fresh module import (bun --hot re-evaluates
|
|
245
|
+
// the import chain so browserTools contains the latest definitions)
|
|
246
|
+
state.browserTools = browserTools;
|
|
247
|
+
await reloadCore({ state, sessionServers, transports });
|
|
248
|
+
// Re-register MCP handlers on ALL existing sessions so they invoke the
|
|
249
|
+
// latest handler logic (dispatchToExtension, browser tools, etc.).
|
|
250
|
+
// Each session is wrapped individually so one failing session doesn't
|
|
251
|
+
// block the rest. On first load, sessionServers is empty so this is skipped.
|
|
252
|
+
if (isHotReload) {
|
|
253
|
+
let reregistered = 0;
|
|
254
|
+
for (const srv of sessionServers) {
|
|
255
|
+
try {
|
|
256
|
+
registerMcpHandlers(srv, state);
|
|
257
|
+
reregistered++;
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
log.warn('Failed to re-register handlers on a session:', err);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
log.info(`Hot reload: re-registered ${reregistered}/${sessionServers.length} session(s), notifying of list changes`);
|
|
264
|
+
for (const srv of sessionServers) {
|
|
265
|
+
notifyToolListChanged(srv);
|
|
266
|
+
notifyResourceListChanged(srv);
|
|
267
|
+
notifyPromptListChanged(srv);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Version check: non-blocking, best-effort on every reload
|
|
271
|
+
void checkForUpdates(state).catch(() => {
|
|
272
|
+
// Update check is best-effort — failures are not actionable
|
|
273
|
+
});
|
|
274
|
+
const durationMs = Date.now() - startTs;
|
|
275
|
+
return {
|
|
276
|
+
lastReloadTimestamp: Date.now(),
|
|
277
|
+
lastReloadDurationMs: durationMs,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
// Clear the guard so subsequent reloads can proceed immediately
|
|
282
|
+
resolveGuard();
|
|
283
|
+
setReloadGuard(undefined);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Reload config and rediscover plugins at runtime without bun --hot.
|
|
288
|
+
* Called from the POST /reload HTTP endpoint. Performs the same config/plugin
|
|
289
|
+
* rediscovery as performReload but without the bun --hot-specific module
|
|
290
|
+
* re-evaluation aspects (browser tools refresh, session handler re-registration).
|
|
291
|
+
*
|
|
292
|
+
* Returns the number of plugins discovered and the duration in milliseconds.
|
|
293
|
+
*/
|
|
294
|
+
const performConfigReload = async (state, sessionServers, transports) => {
|
|
295
|
+
const existingGuard = getReloadGuard();
|
|
296
|
+
if (existingGuard) {
|
|
297
|
+
await existingGuard;
|
|
298
|
+
}
|
|
299
|
+
let resolveGuard;
|
|
300
|
+
const guard = new Promise(resolve => {
|
|
301
|
+
resolveGuard = resolve;
|
|
302
|
+
});
|
|
303
|
+
setReloadGuard(guard);
|
|
304
|
+
const startTs = Date.now();
|
|
305
|
+
try {
|
|
306
|
+
// Clear and restart the sweep timer so it uses fresh references
|
|
307
|
+
restartSweepTimer(state, transports, sessionServers);
|
|
308
|
+
await reloadCore({ state, sessionServers, transports });
|
|
309
|
+
// Notify all MCP clients that tool/resource/prompt lists changed after config reload.
|
|
310
|
+
// (performReload handles its own notification after handler re-registration,
|
|
311
|
+
// so reloadCore itself does not notify — each caller is responsible.)
|
|
312
|
+
for (const srv of sessionServers) {
|
|
313
|
+
notifyToolListChanged(srv);
|
|
314
|
+
notifyResourceListChanged(srv);
|
|
315
|
+
notifyPromptListChanged(srv);
|
|
316
|
+
}
|
|
317
|
+
log.info(`Config reload complete: ${state.registry.plugins.size} plugin(s) in ${Date.now() - startTs}ms`);
|
|
318
|
+
return { plugins: state.registry.plugins.size, durationMs: Date.now() - startTs };
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
resolveGuard();
|
|
322
|
+
setReloadGuard(undefined);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
export { performReload, performConfigReload };
|
|
326
|
+
//# sourceMappingURL=reload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reload.js","sourceRoot":"","sources":["../src/reload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin resolver module.
|
|
3
|
+
*
|
|
4
|
+
* Resolves plugin specifiers (npm package names or filesystem paths) into
|
|
5
|
+
* absolute directory paths containing a plugin's package.json. Decouples
|
|
6
|
+
* path resolution from plugin loading so each phase can be tested independently.
|
|
7
|
+
*
|
|
8
|
+
* Also provides global npm plugin auto-discovery: scans global node_modules
|
|
9
|
+
* directories (from both npm and bun) for packages matching the opentabs-plugin-*
|
|
10
|
+
* naming convention.
|
|
11
|
+
*/
|
|
12
|
+
import type { Result } from '@opentabs-dev/shared';
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a resolved plugin path is under an allowed root directory.
|
|
15
|
+
* Uses realpath on both the plugin path and the allowed roots to resolve
|
|
16
|
+
* symlinks (e.g., macOS /var → /private/var), preventing traversal attacks.
|
|
17
|
+
* Checks against both raw and resolved roots to handle non-existent paths
|
|
18
|
+
* where realpath falls back to the unresolved input.
|
|
19
|
+
*/
|
|
20
|
+
declare const isAllowedPluginPath: (resolvedPath: string) => Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a plugin specifier to an absolute directory path.
|
|
23
|
+
*
|
|
24
|
+
* Specifiers can be:
|
|
25
|
+
* - Local paths: './my-plugin', '../plugins/foo', '/absolute/path', '~/plugins/foo'
|
|
26
|
+
* - npm package names: 'opentabs-plugin-slack', '@org/opentabs-plugin-foo'
|
|
27
|
+
*
|
|
28
|
+
* For local paths, resolves relative to configDir and validates the path is
|
|
29
|
+
* under an allowed root directory (homedir or tmpdir).
|
|
30
|
+
*
|
|
31
|
+
* For npm packages, uses Bun.resolveSync to locate the package directory.
|
|
32
|
+
*
|
|
33
|
+
* Returns the directory path containing the plugin's package.json.
|
|
34
|
+
*/
|
|
35
|
+
declare const resolvePluginPath: (specifier: string, configDir: string) => Promise<Result<string, string>>;
|
|
36
|
+
/**
|
|
37
|
+
* Discover globally installed npm plugin packages.
|
|
38
|
+
*
|
|
39
|
+
* Scans global node_modules directories (from both npm and bun) for packages
|
|
40
|
+
* matching the opentabs-plugin-* naming convention. Each match is validated
|
|
41
|
+
* by checking for a package.json with an `opentabs` field.
|
|
42
|
+
*
|
|
43
|
+
* Returns an array of absolute directory paths for discovered plugins,
|
|
44
|
+
* plus any non-fatal errors encountered during scanning.
|
|
45
|
+
*/
|
|
46
|
+
declare const discoverGlobalNpmPlugins: () => Promise<{
|
|
47
|
+
dirs: string[];
|
|
48
|
+
errors: string[];
|
|
49
|
+
}>;
|
|
50
|
+
/** Reset the cached global paths (for testing). */
|
|
51
|
+
declare const resetGlobalPathsCache: () => void;
|
|
52
|
+
export { discoverGlobalNpmPlugins, isAllowedPluginPath, resetGlobalPathsCache, resolvePluginPath };
|
|
53
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAqBnD;;;;;;GAMG;AACH,QAAA,MAAM,mBAAmB,GAAU,cAAc,MAAM,KAAG,OAAO,CAAC,OAAO,CAQxE,CAAC;AA4CF;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,iBAAiB,GAAU,WAAW,MAAM,EAAE,WAAW,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBrG,CAAC;AAmHF;;;;;;;;;GASG;AACH,QAAA,MAAM,wBAAwB,QAAa,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCtF,CAAC;AAEF,mDAAmD;AACnD,QAAA,MAAM,qBAAqB,QAAO,IAEjC,CAAC;AAEF,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,CAAC"}
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin resolver module.
|
|
3
|
+
*
|
|
4
|
+
* Resolves plugin specifiers (npm package names or filesystem paths) into
|
|
5
|
+
* absolute directory paths containing a plugin's package.json. Decouples
|
|
6
|
+
* path resolution from plugin loading so each phase can be tested independently.
|
|
7
|
+
*
|
|
8
|
+
* Also provides global npm plugin auto-discovery: scans global node_modules
|
|
9
|
+
* directories (from both npm and bun) for packages matching the opentabs-plugin-*
|
|
10
|
+
* naming convention.
|
|
11
|
+
*/
|
|
12
|
+
import { log } from './logger.js';
|
|
13
|
+
import { ok, err } from '@opentabs-dev/shared';
|
|
14
|
+
import { readdir, realpath, stat } from 'node:fs/promises';
|
|
15
|
+
import { homedir, tmpdir } from 'node:os';
|
|
16
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
/**
|
|
18
|
+
* Allowed root directories for local plugin paths.
|
|
19
|
+
* Plugins must reside under the user's home directory or the system temp directory.
|
|
20
|
+
* The temp directory allowance supports E2E tests and development workflows.
|
|
21
|
+
*/
|
|
22
|
+
const getAllowedRoots = () => [homedir(), tmpdir()];
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a path to its canonical form, following symlinks.
|
|
25
|
+
* Falls back to the input path if realpath fails (e.g., non-existent target).
|
|
26
|
+
*/
|
|
27
|
+
const safeRealpath = async (path) => {
|
|
28
|
+
try {
|
|
29
|
+
return await realpath(path);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Validate that a resolved plugin path is under an allowed root directory.
|
|
37
|
+
* Uses realpath on both the plugin path and the allowed roots to resolve
|
|
38
|
+
* symlinks (e.g., macOS /var → /private/var), preventing traversal attacks.
|
|
39
|
+
* Checks against both raw and resolved roots to handle non-existent paths
|
|
40
|
+
* where realpath falls back to the unresolved input.
|
|
41
|
+
*/
|
|
42
|
+
const isAllowedPluginPath = async (resolvedPath) => {
|
|
43
|
+
const realPath = await safeRealpath(resolvedPath);
|
|
44
|
+
const rawRoots = getAllowedRoots();
|
|
45
|
+
const realRoots = await Promise.all(rawRoots.map(safeRealpath));
|
|
46
|
+
// Deduplicate: on macOS, raw /var/... and resolved /private/var/... differ
|
|
47
|
+
const allRoots = [...new Set([...rawRoots, ...realRoots])];
|
|
48
|
+
return allRoots.some(root => realPath.startsWith(root + '/') || realPath === root);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Check if a specifier is a local filesystem path.
|
|
52
|
+
* Local paths start with './', '.\\', '../', '..\\', '/', '~/', '~\\',
|
|
53
|
+
* or a Windows drive letter (e.g., 'C:\') — everything else is treated
|
|
54
|
+
* as an npm package name.
|
|
55
|
+
*/
|
|
56
|
+
const isLocalPath = (specifier) => specifier.startsWith('./') ||
|
|
57
|
+
specifier.startsWith('.\\') ||
|
|
58
|
+
specifier.startsWith('../') ||
|
|
59
|
+
specifier.startsWith('..\\') ||
|
|
60
|
+
specifier.startsWith('/') ||
|
|
61
|
+
specifier.startsWith('~/') ||
|
|
62
|
+
specifier.startsWith('~\\') ||
|
|
63
|
+
/^[A-Za-z]:[/\\]/.test(specifier);
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a local filesystem path specifier to an absolute directory path.
|
|
66
|
+
* Paths starting with '~/' or '~\' are expanded to the user's home directory.
|
|
67
|
+
* Other relative paths are resolved against configDir.
|
|
68
|
+
*/
|
|
69
|
+
const resolveLocalPath = (specifier, configDir) => {
|
|
70
|
+
if (specifier.startsWith('~/') || specifier.startsWith('~\\')) {
|
|
71
|
+
return resolve(homedir(), specifier.slice(2));
|
|
72
|
+
}
|
|
73
|
+
return resolve(configDir, specifier);
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Resolve an npm package specifier to the directory containing its package.json.
|
|
77
|
+
* Uses Bun.resolveSync to locate the package's package.json, then returns
|
|
78
|
+
* the containing directory.
|
|
79
|
+
*/
|
|
80
|
+
const resolveNpmPackage = (specifier) => {
|
|
81
|
+
try {
|
|
82
|
+
const resolved = Bun.resolveSync(`${specifier}/package.json`, process.cwd());
|
|
83
|
+
return ok(dirname(resolved));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return err(`Package not found: ${specifier}`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Resolve a plugin specifier to an absolute directory path.
|
|
91
|
+
*
|
|
92
|
+
* Specifiers can be:
|
|
93
|
+
* - Local paths: './my-plugin', '../plugins/foo', '/absolute/path', '~/plugins/foo'
|
|
94
|
+
* - npm package names: 'opentabs-plugin-slack', '@org/opentabs-plugin-foo'
|
|
95
|
+
*
|
|
96
|
+
* For local paths, resolves relative to configDir and validates the path is
|
|
97
|
+
* under an allowed root directory (homedir or tmpdir).
|
|
98
|
+
*
|
|
99
|
+
* For npm packages, uses Bun.resolveSync to locate the package directory.
|
|
100
|
+
*
|
|
101
|
+
* Returns the directory path containing the plugin's package.json.
|
|
102
|
+
*/
|
|
103
|
+
const resolvePluginPath = async (specifier, configDir) => {
|
|
104
|
+
if (isLocalPath(specifier)) {
|
|
105
|
+
const resolvedPath = resolveLocalPath(specifier, configDir);
|
|
106
|
+
if (!(await isAllowedPluginPath(resolvedPath))) {
|
|
107
|
+
return err(`Path outside allowed directories: ${resolvedPath}`);
|
|
108
|
+
}
|
|
109
|
+
// Verify the directory exists before passing to the loader
|
|
110
|
+
try {
|
|
111
|
+
const stats = await stat(resolvedPath);
|
|
112
|
+
if (!stats.isDirectory()) {
|
|
113
|
+
return err(`Path is not a directory: ${resolvedPath}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return err(`Path not found: ${resolvedPath} — check that the directory exists in your config`);
|
|
118
|
+
}
|
|
119
|
+
return ok(resolvedPath);
|
|
120
|
+
}
|
|
121
|
+
return resolveNpmPackage(specifier);
|
|
122
|
+
};
|
|
123
|
+
/** Cached global node_modules paths (computed once at startup). */
|
|
124
|
+
let cachedGlobalPaths = null;
|
|
125
|
+
/**
|
|
126
|
+
* Get global node_modules directories from both npm and bun.
|
|
127
|
+
* Results are cached in a module-level variable so the shell commands
|
|
128
|
+
* run at most once per process lifetime.
|
|
129
|
+
*/
|
|
130
|
+
const getGlobalNodeModulesPaths = () => {
|
|
131
|
+
if (cachedGlobalPaths !== null)
|
|
132
|
+
return cachedGlobalPaths;
|
|
133
|
+
const paths = [];
|
|
134
|
+
// npm global node_modules
|
|
135
|
+
try {
|
|
136
|
+
const result = Bun.spawnSync(['npm', 'root', '-g']);
|
|
137
|
+
if (result.exitCode === 0) {
|
|
138
|
+
const npmPath = result.stdout.toString().trim();
|
|
139
|
+
if (npmPath.length > 0)
|
|
140
|
+
paths.push(npmPath);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// npm not installed or not in PATH
|
|
145
|
+
}
|
|
146
|
+
// bun global node_modules (derive from bin path: .../bin → .../node_modules)
|
|
147
|
+
try {
|
|
148
|
+
const result = Bun.spawnSync(['bun', 'pm', '-g', 'bin']);
|
|
149
|
+
if (result.exitCode === 0) {
|
|
150
|
+
const bunBinPath = result.stdout.toString().trim();
|
|
151
|
+
if (bunBinPath.length > 0) {
|
|
152
|
+
const bunNodeModules = join(dirname(bunBinPath), 'node_modules');
|
|
153
|
+
// Avoid duplicates if npm and bun share the same global directory
|
|
154
|
+
if (!paths.includes(bunNodeModules))
|
|
155
|
+
paths.push(bunNodeModules);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// bun not installed or not in PATH
|
|
161
|
+
}
|
|
162
|
+
cachedGlobalPaths = paths;
|
|
163
|
+
return paths;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Check if a directory contains a valid opentabs plugin package.json
|
|
167
|
+
* (has an `opentabs` field that is an object).
|
|
168
|
+
*/
|
|
169
|
+
const hasOpentabsField = async (dir) => {
|
|
170
|
+
try {
|
|
171
|
+
const raw = (await Bun.file(join(dir, 'package.json')).json());
|
|
172
|
+
return typeof raw.opentabs === 'object' && raw.opentabs !== null && !Array.isArray(raw.opentabs);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Scan a single global node_modules directory for opentabs plugin packages.
|
|
180
|
+
* Matches unscoped opentabs-plugin-* and scoped @scope/opentabs-plugin-* entries.
|
|
181
|
+
* Returns absolute paths of directories that contain a valid plugin package.json.
|
|
182
|
+
*/
|
|
183
|
+
const scanGlobalDir = async (globalDir) => {
|
|
184
|
+
const found = [];
|
|
185
|
+
let entries;
|
|
186
|
+
try {
|
|
187
|
+
entries = await readdir(globalDir);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return found;
|
|
191
|
+
}
|
|
192
|
+
// Collect unscoped matches and scoped directories to inspect
|
|
193
|
+
const unscopedChecks = [];
|
|
194
|
+
const scopeDirScans = [];
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (entry.startsWith('opentabs-plugin-')) {
|
|
197
|
+
const fullPath = join(globalDir, entry);
|
|
198
|
+
unscopedChecks.push(hasOpentabsField(fullPath).then(valid => {
|
|
199
|
+
if (valid)
|
|
200
|
+
found.push(fullPath);
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
else if (entry.startsWith('@')) {
|
|
204
|
+
// Scoped package directory — scan for opentabs-plugin-* within it
|
|
205
|
+
const scopeDir = join(globalDir, entry);
|
|
206
|
+
scopeDirScans.push(readdir(scopeDir)
|
|
207
|
+
.then(async (scopeEntries) => {
|
|
208
|
+
const scopeChecks = [];
|
|
209
|
+
for (const scopeEntry of scopeEntries) {
|
|
210
|
+
if (scopeEntry.startsWith('opentabs-plugin-')) {
|
|
211
|
+
const fullPath = join(scopeDir, scopeEntry);
|
|
212
|
+
scopeChecks.push(hasOpentabsField(fullPath).then(valid => {
|
|
213
|
+
if (valid)
|
|
214
|
+
found.push(fullPath);
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
await Promise.all(scopeChecks);
|
|
219
|
+
})
|
|
220
|
+
.catch(() => {
|
|
221
|
+
// Scope directory unreadable — skip it
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
await Promise.all([...unscopedChecks, ...scopeDirScans]);
|
|
226
|
+
return found;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Discover globally installed npm plugin packages.
|
|
230
|
+
*
|
|
231
|
+
* Scans global node_modules directories (from both npm and bun) for packages
|
|
232
|
+
* matching the opentabs-plugin-* naming convention. Each match is validated
|
|
233
|
+
* by checking for a package.json with an `opentabs` field.
|
|
234
|
+
*
|
|
235
|
+
* Returns an array of absolute directory paths for discovered plugins,
|
|
236
|
+
* plus any non-fatal errors encountered during scanning.
|
|
237
|
+
*/
|
|
238
|
+
const discoverGlobalNpmPlugins = async () => {
|
|
239
|
+
const globalPaths = getGlobalNodeModulesPaths();
|
|
240
|
+
const errors = [];
|
|
241
|
+
if (globalPaths.length === 0) {
|
|
242
|
+
log.info('No global node_modules paths found — skipping npm auto-discovery');
|
|
243
|
+
return { dirs: [], errors };
|
|
244
|
+
}
|
|
245
|
+
log.info(`Scanning global node_modules for plugins: ${globalPaths.join(', ')}`);
|
|
246
|
+
const allDirs = [];
|
|
247
|
+
const seen = new Set();
|
|
248
|
+
for (const globalDir of globalPaths) {
|
|
249
|
+
try {
|
|
250
|
+
const dirs = await scanGlobalDir(globalDir);
|
|
251
|
+
for (const dir of dirs) {
|
|
252
|
+
// Deduplicate across npm/bun global paths (same package may appear in both)
|
|
253
|
+
if (!seen.has(dir)) {
|
|
254
|
+
seen.add(dir);
|
|
255
|
+
allDirs.push(dir);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
261
|
+
errors.push(`Error scanning ${globalDir}: ${msg}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
log.info(`Auto-discovered ${allDirs.length} npm plugin(s) globally`);
|
|
265
|
+
return { dirs: allDirs, errors };
|
|
266
|
+
};
|
|
267
|
+
/** Reset the cached global paths (for testing). */
|
|
268
|
+
const resetGlobalPathsCache = () => {
|
|
269
|
+
cachedGlobalPaths = null;
|
|
270
|
+
};
|
|
271
|
+
export { discoverGlobalNpmPlugins, isAllowedPluginPath, resetGlobalPathsCache, resolvePluginPath };
|
|
272
|
+
//# sourceMappingURL=resolver.js.map
|