bridgerator 1.1.5 → 1.1.7
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/LICENSE +0 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/bridge.mjs +10 -9
- package/src/cli.mjs +3 -3
- package/src/dc-config-hooks.mjs +1 -1
- package/src/dc-isolate.mjs +1 -1
- package/src/policy.mjs +14 -5
- package/ui/app.js +1 -1
package/LICENSE
CHANGED
|
@@ -19,6 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
Bundled dependency: Desktop Commander (https://github.com/wonderwhy-er/DesktopCommanderMCP),
|
|
24
|
-
MIT License, Copyright (c) 2024-2025 Eduard Ruzga and Desktop Commander Contributors.
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bridgerator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "bridgerator",
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.7",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bridgerator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Connect ChatGPT to your Windows PC: files, search and terminal through an outbound-only bridge with folder limits. No inbound ports, no admin rights.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/bridge.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Bridgerator bridge: runs on the user's PC.
|
|
2
|
-
// - starts the bundled
|
|
2
|
+
// - starts the bundled MCP engine as a child over stdio;
|
|
3
3
|
// - pushes this PC's limits (allowedDirectories, blockedCommands) into it at startup;
|
|
4
4
|
// - opens ONE outbound WebSocket to the relay (no inbound port is opened on this PC);
|
|
5
5
|
// - forwards each relay `call` through the policy (excluded / read-only folders, disabled tools)
|
|
@@ -9,7 +9,7 @@ import { createRequire } from 'node:module';
|
|
|
9
9
|
import WebSocket from 'ws';
|
|
10
10
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
11
11
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
12
|
-
import { createPolicy, debrand, debrandResult, TERMINAL_TOOLS, ALWAYS_HIDDEN } from './policy.mjs';
|
|
12
|
+
import { createPolicy, debrand, debrandResult, ourUi, engineUi, TERMINAL_TOOLS, ALWAYS_HIDDEN } from './policy.mjs';
|
|
13
13
|
import { showNotice } from './notice.mjs';
|
|
14
14
|
import { usage } from './telemetry.mjs';
|
|
15
15
|
import { createGate, askOnPc, CONFIRM_TOOL_DEFS } from './confirm.mjs';
|
|
@@ -78,7 +78,7 @@ export async function localDrivesAsync() {
|
|
|
78
78
|
}
|
|
79
79
|
const DRIVES_MS = 60000;
|
|
80
80
|
|
|
81
|
-
export async function
|
|
81
|
+
export async function startEngine(cfg, log = console.log, { ask: askArg, recycle: recycleArg, drives: drivesArg, delegate: delegateOpts } = {}) {
|
|
82
82
|
const ask0 = askArg || askOnPc;
|
|
83
83
|
const recycle = recycleArg || recycleOnPc;
|
|
84
84
|
// Drive list: refreshed in the background once a minute (new / removed drives are seen); only the
|
|
@@ -115,7 +115,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
115
115
|
const absPath = (p) => path.resolve(engineDir, String(p).replace(/^~(?=[\\/]|$)/, os.homedir()));
|
|
116
116
|
const transport = new StdioClientTransport({
|
|
117
117
|
command: process.execPath,
|
|
118
|
-
//
|
|
118
|
+
// The engine's own config folder (dc-isolate.mjs): a copy of the engine the user runs elsewhere keeps its limits.
|
|
119
119
|
args: ['--import', ISOLATE, DC_ENTRY],
|
|
120
120
|
// No calls home: telemetry kill-switch, and remote feature flags (onboarding page, promo text
|
|
121
121
|
// injected into tool results, A/B tests) replaced by an empty local set.
|
|
@@ -133,7 +133,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
133
133
|
// Engine gone (crash, killed): nothing works without it — exit, the autostart starts us again.
|
|
134
134
|
let closing = false;
|
|
135
135
|
client.onclose = () => { if (closing) return; log(`[bridgerator] engine stopped — exiting (autostart restarts the bridge)${engineErr()}`); process.exit(1); };
|
|
136
|
-
// The PC owner's limits go into
|
|
136
|
+
// The PC owner's limits go into the engine's own config; the model can't change them
|
|
137
137
|
// (set_config_value is never exposed to the relay, see policy.ALWAYS_HIDDEN).
|
|
138
138
|
const set = (key, value) => client.callTool({ name: 'set_config_value', arguments: { key, value } });
|
|
139
139
|
await set('allowedDirectories', cfg.allowedDirectories || []);
|
|
@@ -152,7 +152,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
152
152
|
// Snapshots + Undo (on by default) in %LOCALAPPDATA%\Bridgerator\snapshots (given by the CLI).
|
|
153
153
|
const snapCfg = () => ({ ...SNAP_DEFAULTS, ...(cfg.snapshots || {}) });
|
|
154
154
|
const snapshots = cfg.snapshotsDir ? createSnapshots(cfg.snapshotsDir, { limits: snapCfg, log }) : null;
|
|
155
|
-
const visible = [...tools.filter(listed).map(debrand), ...CONFIRM_TOOL_DEFS, SETUP_TOOL_DEF, ...(cfg.configFile ? [SETTINGS_TOOL_DEF] : []), ...(cfg.configDir ? QUEUE_TOOL_DEFS : []), ...(snapshots ? [UNDO_TOOL_DEF] : []), ...extra.defs.filter(policy.visible)].map(shorten);
|
|
155
|
+
const visible = [...tools.filter(listed).map(debrand), ...CONFIRM_TOOL_DEFS, SETUP_TOOL_DEF, ...(cfg.configFile ? [SETTINGS_TOOL_DEF] : []), ...(cfg.configDir ? QUEUE_TOOL_DEFS : []), ...(snapshots ? [UNDO_TOOL_DEF] : []), ...extra.defs.filter(policy.visible)].map(shorten).map(ourUi);
|
|
156
156
|
const virtual = new Set(CONFIRM_TOOL_DEFS.map((t) => t.name));
|
|
157
157
|
// namePending: a computer name given on this PC not yet accepted by the relay — kept in config.json too,
|
|
158
158
|
// so a restart before the next link does not lose it; cleared by nameSent() (welcome / live mode message).
|
|
@@ -197,7 +197,7 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
197
197
|
return gate.accept(s ? { enabled: s.enabled } : {});
|
|
198
198
|
};
|
|
199
199
|
// MCP Apps widgets (file preview, config view) — served to ChatGPT through the relay as-is.
|
|
200
|
-
const resources = ((await client.listResources().catch(() => ({ resources: [] }))).resources || []).map(debrand);
|
|
200
|
+
const resources = ourUi(((await client.listResources().catch(() => ({ resources: [] }))).resources || []).map(debrand));
|
|
201
201
|
const resourceUris = new Set(resources.map((r) => r.uri));
|
|
202
202
|
log(`[bridgerator] engine up: ${visible.length} tools; allowed=${JSON.stringify(cfg.allowedDirectories || [])} excluded=${JSON.stringify(cfg.deniedDirectories || [])} read-only=${JSON.stringify(cfg.readOnlyDirectories || [])} confirm=${gate.mode()}`);
|
|
203
203
|
if (!(cfg.allowedDirectories || []).length) log('[bridgerator] folders: all (no list), except the always-blocked ones');
|
|
@@ -492,8 +492,9 @@ export async function startDesktopCommander(cfg, log = console.log, { ask: askAr
|
|
|
492
492
|
return { ...rest, confirm: { enabled: confirm.enabled, ...confirm.ask, timeoutSeconds: confirm.timeoutSec }, telemetryEnabled: telemetry };
|
|
493
493
|
};
|
|
494
494
|
async function readResource(uri) {
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
// The old widget URI still works: ChatGPT and the relay's offline list may have cached it.
|
|
496
|
+
if (!resourceUris.has(ourUi(String(uri)))) return { error: 'unknown resource' };
|
|
497
|
+
try { return { result: ourUi(debrand(await client.readResource({ uri: engineUi(uri) }))) }; } catch (e) { return { error: String(e?.message || e) }; }
|
|
497
498
|
}
|
|
498
499
|
// Requests of the Bridgerator window (tray helper). Made by the person at the PC: no extra prompt.
|
|
499
500
|
// Theme for our pop-ups = the window's theme (Settings → Appearance); sound / style from Notifications.
|
package/src/cli.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import crypto from 'node:crypto';
|
|
|
10
10
|
import { spawn, execFileSync } from 'node:child_process';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { isSea } from 'node:sea';
|
|
13
|
-
import {
|
|
13
|
+
import { startEngine, connect, checkRelay } from './bridge.mjs';
|
|
14
14
|
import { pair, siteOf, checkKey, machineFp } from './pair.mjs';
|
|
15
15
|
import { ACCOUNT_URL } from './notice.mjs';
|
|
16
16
|
import { send, VERSION } from './telemetry.mjs';
|
|
@@ -393,7 +393,7 @@ async function main() {
|
|
|
393
393
|
protectSensitive: prev.protectSensitive === true,
|
|
394
394
|
// Readable, never changed by the file tools (the terminal is not bound by folders).
|
|
395
395
|
readOnlyDirectories: o['read-only'] ? abs(o['read-only']) : prev.readOnlyDirectories || [],
|
|
396
|
-
// blockedCommands: absent =
|
|
396
|
+
// blockedCommands: absent = the engine's own default list (format, diskpart, reg, net, sc, runas, …).
|
|
397
397
|
...(prev.blockedCommands ? { blockedCommands: prev.blockedCommands } : {}),
|
|
398
398
|
disabledTools: prev.disabledTools || [],
|
|
399
399
|
installId: prev.installId || crypto.randomUUID(),
|
|
@@ -471,7 +471,7 @@ async function main() {
|
|
|
471
471
|
plan: b.kind === 'none' ? null : { label: b.action === 'renew' ? 'Renew Pro…' : 'Upgrade to Pro…', url: b.url, badge: b.tone },
|
|
472
472
|
});
|
|
473
473
|
};
|
|
474
|
-
dc = await
|
|
474
|
+
dc = await startEngine({ ...cfg, dcConfigDir: path.join(DIR, 'dc'), configDir: DIR, confirmIni: INI, configFile: CONFIG, snapshotsDir: SNAP_DIR, scriptsDir: SCRIPTS_DIR }, log, { ask: tray ? (q) => tray.ask(q) : undefined });
|
|
475
475
|
dc.cfgName = () => readConfig()?.deviceName;
|
|
476
476
|
dc.openWindow = (page) => tray?.open(page);
|
|
477
477
|
dc.onPlan = pushTray;
|
package/src/dc-config-hooks.mjs
CHANGED
|
@@ -25,7 +25,7 @@ const PATCHES = [
|
|
|
25
25
|
{ file: /[\\/]dist[\\/]terminal-manager\.js$/, from: "args: ['-Command', command],", to: "args: ['-Command', '[Console]::OutputEncoding=[Text.Encoding]::UTF8;' + command]," },
|
|
26
26
|
{ file: /[\\/]dist[\\/]index\.js$/, from: ' ensureChromeAvailable();\n', to: ' // bridgerator: no Chrome pre-download\n' },
|
|
27
27
|
];
|
|
28
|
-
const ENGINE = /[\\/]bridgerator-engine[\\/]dist[\\/]/; // the bundled engine
|
|
28
|
+
const ENGINE = /[\\/]bridgerator-engine[\\/]dist[\\/]/; // the bundled engine, installed under our own name (owner 26.10)
|
|
29
29
|
|
|
30
30
|
export async function load(url, context, nextLoad) {
|
|
31
31
|
const r = await nextLoad(url, context);
|
package/src/dc-isolate.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Preload for the
|
|
1
|
+
// Preload for the engine child: registers dc-config-hooks.mjs (see there).
|
|
2
2
|
import { register } from 'node:module';
|
|
3
3
|
|
|
4
4
|
if (!process.env.BRIDGERATOR_DC_CONFIG_DIR) throw new Error('bridgerator: BRIDGERATOR_DC_CONFIG_DIR not set');
|
package/src/policy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Bridge-side access policy, applied BEFORE a call reaches
|
|
2
|
-
//
|
|
1
|
+
// Bridge-side access policy, applied BEFORE a call reaches the engine and to its results.
|
|
2
|
+
// The engine itself only knows allowedDirectories; it has no "except these folders" rule,
|
|
3
3
|
// so deniedDirectories is enforced here: input paths are resolved (symlinks/junctions included)
|
|
4
4
|
// and refused when they fall inside a denied folder; result lines that mention a denied path are
|
|
5
5
|
// dropped (search hits, recursive listings).
|
|
@@ -53,7 +53,7 @@ const expandHome = (p) => (p === '~' || p.startsWith('~/') || p.startsWith('~\\'
|
|
|
53
53
|
// base: the folder a relative path is resolved against (default: this process's cwd).
|
|
54
54
|
export const norm = (p, base) => path.resolve(base || '.', expandHome(String(p))).replace(/[\\/]+$/, '').toLowerCase();
|
|
55
55
|
|
|
56
|
-
// Resolve symlinks/junctions on the deepest existing ancestor, like
|
|
56
|
+
// Resolve symlinks/junctions on the deepest existing ancestor, like the engine does.
|
|
57
57
|
export function realish(p, base) {
|
|
58
58
|
const start = path.resolve(base || '.', expandHome(String(p)));
|
|
59
59
|
let cur = start;
|
|
@@ -157,6 +157,8 @@ const SELF_CHANGE = [
|
|
|
157
157
|
/\b(?:taskkill|tskill|pskill|kill|stop-process|wmic|schtasks|(?:start|stop|disable|enable|unregister|set)-scheduledtask)\b[^|;&\n]*\bbridgerator/i,
|
|
158
158
|
/\bbridgerator[^;&\n]*\|\s*(?:stop-process|kill|(?:stop|disable|unregister|set)-scheduledtask)\b/i,
|
|
159
159
|
];
|
|
160
|
+
// Tools whose result is the text of files that passed the folder check (shown whole, never filtered).
|
|
161
|
+
const FILE_TEXT = new Set(['read_file', 'read_multiple_files']);
|
|
160
162
|
export function createPolicy(cfg = {}) {
|
|
161
163
|
const base = cfg.cwd || process.cwd();
|
|
162
164
|
// protectSensitive absent = on (callers that don't say); the bridge passes the person's setting.
|
|
@@ -249,8 +251,10 @@ export function createPolicy(cfg = {}) {
|
|
|
249
251
|
},
|
|
250
252
|
// Drop result lines that reveal anything inside a denied folder. `args` lets a listing of a
|
|
251
253
|
// parent folder hide the excluded child, whose entries are printed relative to that parent.
|
|
254
|
+
// Not for the text of a file that was allowed to be read: a line that merely NAMES an excluded folder is the
|
|
255
|
+
// person's own content (E2E 26.10: .md notes lost lines, a later full rewrite would delete them from disk).
|
|
252
256
|
filter(result, name, args) {
|
|
253
|
-
if (!denied.length || !result || !Array.isArray(result.content)) return result;
|
|
257
|
+
if (!denied.length || !result || !Array.isArray(result.content) || FILE_TEXT.has(name)) return result;
|
|
254
258
|
const rel = [];
|
|
255
259
|
if (name === 'list_directory' && typeof args?.path === 'string') {
|
|
256
260
|
const at = norm(realish(args.path, base));
|
|
@@ -274,6 +278,11 @@ export function createPolicy(cfg = {}) {
|
|
|
274
278
|
// The bundled engine's own name and links are not shown to the user: tool titles/descriptions and
|
|
275
279
|
// widget HTML say Bridgerator. Tool RESULTS are never rewritten (they are the user's own data).
|
|
276
280
|
const BRAND = [[/"DC: \.\.\." or /g, ''], [/Desktop ?Commander/g, 'Bridgerator'], [/https?:\/\/(www\.)?desktopcommander\.app[^\s"'\)<]*/g, 'https://bridgerator.recondeck.com']];
|
|
281
|
+
// The engine's widget URIs and their in-page state key carry its name: ChatGPT sees them in tool _meta and resources.
|
|
282
|
+
// Shown as ui://bridgerator/…, mapped back when a resource is read (owner 26.10: "как будто наш код").
|
|
283
|
+
const UI_ENGINE = 'ui://desktop-commander/', UI_OURS = 'ui://bridgerator/';
|
|
284
|
+
export const ourUi = (v) => JSON.parse(JSON.stringify(v).replaceAll(UI_ENGINE, UI_OURS).replaceAll('desktop-commander:widget-state', 'bridgerator:widget-state'));
|
|
285
|
+
export const engineUi = (uri) => String(uri).replace(UI_OURS, UI_ENGINE);
|
|
277
286
|
export function debrand(v) {
|
|
278
287
|
if (typeof v === 'string') return BRAND.reduce((s, [re, to]) => s.replace(re, to), v);
|
|
279
288
|
if (Array.isArray(v)) return v.map(debrand);
|
|
@@ -287,7 +296,7 @@ export function debrand(v) {
|
|
|
287
296
|
}
|
|
288
297
|
|
|
289
298
|
// Two results are the engine's own text, not user data: the prompt catalog (get_prompts says
|
|
290
|
-
//
|
|
299
|
+
// its own brand in the heading) and get_config, whose `version` is the engine's and is
|
|
291
300
|
// shown as "Bridgerator version" — replaced by the bridge's own version.
|
|
292
301
|
export function debrandResult(name, result, version) {
|
|
293
302
|
if (!result || typeof result !== 'object') return result;
|
package/ui/app.js
CHANGED
|
@@ -172,7 +172,7 @@ function awakeLine() {
|
|
|
172
172
|
return `<div class="awake-line ${a.on ? 'on' : ''}"><span class="dot ${a.on ? 'online' : 'offline'}"></span>${esc(t(k))}</div>`;
|
|
173
173
|
}
|
|
174
174
|
// The Bridgerator app's "Permission" page in ChatGPT (App ID of the store app). Owner 26.10: on first use ChatGPT
|
|
175
|
-
// asks before most actions ("Allow low-risk"); one switch to "Allow all" stops that
|
|
175
|
+
// asks before most actions ("Allow low-risk"); one switch to "Allow all" stops that.
|
|
176
176
|
const CHATGPT_PERMISSIONS = 'https://chatgpt.com/settings/plugins-settings/plugin_asdk_app_6ad78d4a3c408191a9215fd304394c4a/permissions';
|
|
177
177
|
P.home = () => {
|
|
178
178
|
const st = state.state || 'offline';
|