promethios-bridge 1.8.0 → 2.0.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/package.json +4 -2
- package/src/bridge.js +49 -1
- package/src/mcp-server.js +348 -0
- package/src/overlay/launcher.js +92 -60
- package/src/overlay/main.js +864 -113
- package/src/overlay/preload-browser.js +20 -0
- package/src/overlay/preload.js +57 -10
- package/src/overlay/renderer/index.html +1430 -208
- package/src/overlay/renderer/manus-browser.html +768 -0
- package/src/postinstall.js +122 -30
- package/src/tools/accessibility.js +408 -0
- package/src/tools/android.js +246 -0
- package/src/tools/browser-dom.js +475 -0
- package/src/tools/desktop.js +514 -0
- package/src/tools/interact.js +467 -0
- package/src/tools/stream.js +425 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promethios-bridge",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Run Promethios agent frameworks locally on your computer with full file, terminal, browser access, ambient context capture, and the always-on-top floating chat overlay. Native Framework Mode supports OpenClaw and other frameworks via the bridge.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
},
|
|
54
54
|
"optionalDependencies": {
|
|
55
55
|
"playwright": "^1.42.0",
|
|
56
|
-
"electron": "^29.0.0"
|
|
56
|
+
"electron": "^29.0.0",
|
|
57
|
+
"screenshot-desktop": "^1.12.7",
|
|
58
|
+
"sharp": "^0.33.0"
|
|
57
59
|
},
|
|
58
60
|
"engines": {
|
|
59
61
|
"node": ">=18.0.0"
|
package/src/bridge.js
CHANGED
|
@@ -21,6 +21,20 @@ const ora = require('ora');
|
|
|
21
21
|
const fetch = require('node-fetch');
|
|
22
22
|
const { executeLocalTool } = require('./executor');
|
|
23
23
|
const { captureContext } = require('./contextCapture');
|
|
24
|
+
const { startMcpServer } = require('./mcp-server');
|
|
25
|
+
const { setPinnedRegion, setPinnedApps, registerBrowserPageAccessor } = require('./tools/desktop');
|
|
26
|
+
|
|
27
|
+
// Wire the browser-dom tools to the shared Playwright context.
|
|
28
|
+
// The context is created lazily in executor.js when browser_control is first used.
|
|
29
|
+
// We expose a getter so browser-dom tools can access the live page.
|
|
30
|
+
registerBrowserPageAccessor(async () => {
|
|
31
|
+
if (!global.__playwrightContext) {
|
|
32
|
+
throw new Error('Browser not open. Use the browser_control tool to navigate to a page first, then retry.');
|
|
33
|
+
}
|
|
34
|
+
const pages = global.__playwrightContext.pages();
|
|
35
|
+
return pages.length > 0 ? pages[pages.length - 1] : await global.__playwrightContext.newPage();
|
|
36
|
+
});
|
|
37
|
+
const { initAndroidTools } = require('./tools/android');
|
|
24
38
|
|
|
25
39
|
// Optional: Electron overlay window (bundled in src/overlay — gracefully skipped if Electron not available)
|
|
26
40
|
let launchOverlay = null;
|
|
@@ -196,6 +210,22 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
|
|
|
196
210
|
// Health check
|
|
197
211
|
app.get('/health', (req, res) => res.json({ ok: true, version: require('../package.json').version }));
|
|
198
212
|
|
|
213
|
+
// ── /status: used by the Electron overlay to auto-connect without manual token entry ──
|
|
214
|
+
// Only accessible from localhost (127.0.0.1 or ::1) for security.
|
|
215
|
+
let bridgeUsername = null; // set after registerBridge resolves
|
|
216
|
+
app.get('/status', (req, res) => {
|
|
217
|
+
const ip = req.socket.remoteAddress || '';
|
|
218
|
+
const isLocal = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
|
|
219
|
+
if (!isLocal) return res.status(403).json({ error: 'localhost only' });
|
|
220
|
+
res.json({
|
|
221
|
+
ok: true,
|
|
222
|
+
token: overlayAuthToken || null,
|
|
223
|
+
username: bridgeUsername || require('os').userInfo().username,
|
|
224
|
+
version: require('../package.json').version,
|
|
225
|
+
port,
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
199
229
|
// Legacy direct tool-call endpoint (kept for backward compatibility with
|
|
200
230
|
// older backend versions that call callbackUrl/tool-call directly)
|
|
201
231
|
app.post('/tool-call', async (req, res) => {
|
|
@@ -303,8 +333,11 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
|
|
|
303
333
|
'Authorization': `Bearer ${token}`,
|
|
304
334
|
},
|
|
305
335
|
body: JSON.stringify({
|
|
306
|
-
message:
|
|
336
|
+
message: req.body.message,
|
|
307
337
|
conversationHistory: req.body.conversationHistory || [],
|
|
338
|
+
model: req.body.model || undefined,
|
|
339
|
+
provider: req.body.provider || undefined,
|
|
340
|
+
context: req.body.context || undefined,
|
|
308
341
|
}),
|
|
309
342
|
});
|
|
310
343
|
const data = await upstream.json();
|
|
@@ -761,6 +794,20 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
|
|
|
761
794
|
const callbackUrl = `http://127.0.0.1:${port}`;
|
|
762
795
|
console.log(chalk.green(` ✓ Local server listening on port ${port}`));
|
|
763
796
|
|
|
797
|
+
// ── MCP server (port 7824) — universal tool layer for all frameworks ────────
|
|
798
|
+
const mcpPort = port + 1; // 7824 when main bridge is on 7823
|
|
799
|
+
|
|
800
|
+
// Init Android tools with auth context
|
|
801
|
+
initAndroidTools({ authToken, apiBase });
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
await startMcpServer({ port: mcpPort, authToken, log });
|
|
805
|
+
console.log(chalk.cyan(` ✓ MCP server ready on port ${mcpPort} (Claude, OpenClaw, ChatGPT, Cursor)`));
|
|
806
|
+
console.log(chalk.cyan(` ✓ Android bridge tools registered (SMS, contacts, calls, notifications)`));
|
|
807
|
+
} catch (err) {
|
|
808
|
+
log('MCP server failed to start (non-critical):', err.message);
|
|
809
|
+
}
|
|
810
|
+
|
|
764
811
|
// ── Step 3: Register with Promethios API ─────────────────────────────────
|
|
765
812
|
const regSpinner = ora(' Registering bridge with Promethios...').start();
|
|
766
813
|
try {
|
|
@@ -788,6 +835,7 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
|
|
|
788
835
|
// ── Step 4c: Launch overlay ──────────────────────────────────────────────
|
|
789
836
|
// Set the auth token so the /overlay route can embed it in the HTML.
|
|
790
837
|
overlayAuthToken = authToken;
|
|
838
|
+
bridgeUsername = require('os').userInfo().username;
|
|
791
839
|
|
|
792
840
|
// Try Electron first (available when installed globally or via postinstall).
|
|
793
841
|
// Fall back to opening the browser-based overlay at http://localhost:<port>/overlay.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-server.js — Promethios MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Implements the Model Context Protocol (MCP) so that any MCP-compatible
|
|
5
|
+
* AI framework can discover and call Promethios desktop tools.
|
|
6
|
+
*
|
|
7
|
+
* Supported frameworks:
|
|
8
|
+
* - Claude Desktop (Anthropic's MCP client)
|
|
9
|
+
* - Cursor / Windsurf
|
|
10
|
+
* - Any framework implementing MCP spec
|
|
11
|
+
*
|
|
12
|
+
* Also exposes a REST API for non-MCP frameworks:
|
|
13
|
+
* GET /tools → tool manifest (OpenAPI-compatible)
|
|
14
|
+
* POST /tools/call → execute a tool
|
|
15
|
+
* GET /tools/context → get current context block (for context injection)
|
|
16
|
+
*
|
|
17
|
+
* Authentication: Bearer token (same Promethios token as the main bridge)
|
|
18
|
+
*
|
|
19
|
+
* Runs on port 7824 alongside the main bridge on 7823.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const express = require('express');
|
|
25
|
+
const { TOOL_MANIFEST, executeDesktopTool, buildContextBlock } = require('./tools/desktop');
|
|
26
|
+
const { ANDROID_TOOL_SCHEMAS, executeAndroidTool, isAndroidTool } = require('./tools/android');
|
|
27
|
+
|
|
28
|
+
// Also include the core executor tools in the manifest for external frameworks
|
|
29
|
+
const { executeLocalTool } = require('./executor');
|
|
30
|
+
|
|
31
|
+
// ── Extended manifest — adds executor tools for external frameworks ───────────
|
|
32
|
+
const EXECUTOR_TOOL_MANIFEST = [
|
|
33
|
+
{
|
|
34
|
+
name: 'read_file',
|
|
35
|
+
description: 'Read the contents of a file on the user\'s desktop computer.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
required: ['path'],
|
|
39
|
+
properties: {
|
|
40
|
+
path: { type: 'string', description: 'Absolute or ~ relative path to the file' },
|
|
41
|
+
encoding: { type: 'string', description: 'File encoding (default: utf8)', default: 'utf8' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
handler: async (args) => executeLocalTool({ toolName: 'read_file', args }),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'write_file',
|
|
48
|
+
description: 'Write or create a file on the user\'s desktop computer.',
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
required: ['path', 'content'],
|
|
52
|
+
properties: {
|
|
53
|
+
path: { type: 'string', description: 'Absolute or ~ relative path to the file' },
|
|
54
|
+
content: { type: 'string', description: 'Content to write' },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
handler: async (args) => executeLocalTool({ toolName: 'write_file', args }),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'list_directory',
|
|
61
|
+
description: 'List files and folders in a directory on the user\'s desktop computer.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
path: { type: 'string', description: 'Directory path (default: home directory)', default: '~' },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
handler: async (args) => executeLocalTool({ toolName: 'list_directory', args }),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'run_command',
|
|
72
|
+
description: 'Execute a shell command on the user\'s desktop computer and return the output.',
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
required: ['command'],
|
|
76
|
+
properties: {
|
|
77
|
+
command: { type: 'string', description: 'Shell command to execute' },
|
|
78
|
+
cwd: { type: 'string', description: 'Working directory (optional)' },
|
|
79
|
+
timeout: { type: 'number', description: 'Timeout in milliseconds (default: 30000)' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
handler: async (args) => executeLocalTool({ toolName: 'run_command', args }),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'open_browser',
|
|
86
|
+
description: 'Open a URL in the user\'s default browser.',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
required: ['url'],
|
|
90
|
+
properties: {
|
|
91
|
+
url: { type: 'string', description: 'URL to open' },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
handler: async (args) => executeLocalTool({ toolName: 'open_browser', args }),
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
// Android tools — add handlers to the schemas
|
|
99
|
+
const ANDROID_TOOL_MANIFEST = ANDROID_TOOL_SCHEMAS.map(schema => ({
|
|
100
|
+
...schema,
|
|
101
|
+
handler: async (args) => executeAndroidTool(schema.name, args),
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// Full combined manifest — desktop tools + executor tools + android tools
|
|
105
|
+
const FULL_MANIFEST = [...TOOL_MANIFEST, ...EXECUTOR_TOOL_MANIFEST, ...ANDROID_TOOL_MANIFEST];
|
|
106
|
+
|
|
107
|
+
// ── MCP Server ────────────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
function createMcpServer(authToken) {
|
|
110
|
+
const app = express();
|
|
111
|
+
app.use(express.json({ limit: '10mb' }));
|
|
112
|
+
|
|
113
|
+
// CORS — allow all origins so web-based AI tools can connect
|
|
114
|
+
app.use((req, res, next) => {
|
|
115
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
116
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
117
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
118
|
+
if (req.method === 'OPTIONS') return res.sendStatus(200);
|
|
119
|
+
next();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ── Auth middleware ───────────────────────────────────────────────────────
|
|
123
|
+
function requireAuth(req, res, next) {
|
|
124
|
+
if (!authToken) return next(); // No token configured — open access (dev mode)
|
|
125
|
+
const header = req.headers['authorization'] || '';
|
|
126
|
+
const token = header.replace(/^Bearer\s+/i, '').trim();
|
|
127
|
+
if (token !== authToken) {
|
|
128
|
+
return res.status(401).json({ error: 'Invalid or missing Promethios token' });
|
|
129
|
+
}
|
|
130
|
+
next();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Health check ─────────────────────────────────────────────────────────
|
|
134
|
+
app.get('/health', (req, res) => {
|
|
135
|
+
res.json({
|
|
136
|
+
status: 'ok',
|
|
137
|
+
service: 'promethios-mcp',
|
|
138
|
+
version: require('../package.json').version,
|
|
139
|
+
tools: FULL_MANIFEST.length,
|
|
140
|
+
platform: process.platform,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ── Tool discovery ────────────────────────────────────────────────────────
|
|
145
|
+
// GET /tools — returns the full tool manifest
|
|
146
|
+
// Compatible with MCP tool discovery and OpenAPI Custom Actions
|
|
147
|
+
app.get('/tools', requireAuth, (req, res) => {
|
|
148
|
+
res.json({
|
|
149
|
+
tools: FULL_MANIFEST.map(t => ({
|
|
150
|
+
name: t.name,
|
|
151
|
+
description: t.description,
|
|
152
|
+
inputSchema: t.inputSchema,
|
|
153
|
+
})),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ── MCP protocol endpoint ─────────────────────────────────────────────────
|
|
158
|
+
// POST /mcp — handles MCP JSON-RPC messages
|
|
159
|
+
// Claude Desktop and other MCP clients send requests here
|
|
160
|
+
app.post('/mcp', requireAuth, async (req, res) => {
|
|
161
|
+
const { method, params, id } = req.body;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
if (method === 'tools/list') {
|
|
165
|
+
return res.json({
|
|
166
|
+
jsonrpc: '2.0',
|
|
167
|
+
id,
|
|
168
|
+
result: {
|
|
169
|
+
tools: FULL_MANIFEST.map(t => ({
|
|
170
|
+
name: t.name,
|
|
171
|
+
description: t.description,
|
|
172
|
+
inputSchema: t.inputSchema,
|
|
173
|
+
})),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (method === 'tools/call') {
|
|
179
|
+
const { name, arguments: args } = params;
|
|
180
|
+
const tool = FULL_MANIFEST.find(t => t.name === name);
|
|
181
|
+
|
|
182
|
+
if (!tool) {
|
|
183
|
+
return res.json({
|
|
184
|
+
jsonrpc: '2.0',
|
|
185
|
+
id,
|
|
186
|
+
error: { code: -32601, message: `Tool not found: ${name}` },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = await tool.handler(args || {});
|
|
191
|
+
return res.json({
|
|
192
|
+
jsonrpc: '2.0',
|
|
193
|
+
id,
|
|
194
|
+
result: {
|
|
195
|
+
content: [
|
|
196
|
+
{
|
|
197
|
+
type: result.screenshot || result.fresh_screenshot ? 'image' : 'text',
|
|
198
|
+
text: result.screenshot || result.fresh_screenshot
|
|
199
|
+
? undefined
|
|
200
|
+
: JSON.stringify(result, null, 2),
|
|
201
|
+
data: result.screenshot || result.fresh_screenshot || undefined,
|
|
202
|
+
mimeType: result.screenshot || result.fresh_screenshot ? 'image/png' : undefined,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Unknown method
|
|
210
|
+
return res.json({
|
|
211
|
+
jsonrpc: '2.0',
|
|
212
|
+
id,
|
|
213
|
+
error: { code: -32601, message: `Unknown method: ${method}` },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return res.json({
|
|
218
|
+
jsonrpc: '2.0',
|
|
219
|
+
id,
|
|
220
|
+
error: { code: -32603, message: err.message },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ── REST tool execution ───────────────────────────────────────────────────
|
|
226
|
+
// POST /tools/call — execute any tool by name (REST alternative to MCP)
|
|
227
|
+
// Used by ChatGPT Custom Actions, web apps, and non-MCP frameworks
|
|
228
|
+
app.post('/tools/call', requireAuth, async (req, res) => {
|
|
229
|
+
const { tool, args } = req.body;
|
|
230
|
+
if (!tool) return res.status(400).json({ error: 'tool name is required' });
|
|
231
|
+
|
|
232
|
+
const toolDef = FULL_MANIFEST.find(t => t.name === tool);
|
|
233
|
+
if (!toolDef) {
|
|
234
|
+
return res.status(404).json({
|
|
235
|
+
error: `Tool not found: ${tool}`,
|
|
236
|
+
available: FULL_MANIFEST.map(t => t.name),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const result = await toolDef.handler(args || {});
|
|
242
|
+
res.json({ ok: true, tool, result });
|
|
243
|
+
} catch (err) {
|
|
244
|
+
res.status(500).json({ ok: false, tool, error: err.message });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ── Context endpoint ──────────────────────────────────────────────────────
|
|
249
|
+
// GET /tools/context — returns the current context block
|
|
250
|
+
// Used by the Manus floater for context injection
|
|
251
|
+
app.get('/tools/context', requireAuth, async (req, res) => {
|
|
252
|
+
try {
|
|
253
|
+
const includeScreenshot = req.query.screenshot === 'true';
|
|
254
|
+
const block = await buildContextBlock({ include_screenshot: includeScreenshot });
|
|
255
|
+
res.json({ ok: true, ...block });
|
|
256
|
+
} catch (err) {
|
|
257
|
+
res.status(500).json({ ok: false, error: err.message });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ── OpenAPI spec ──────────────────────────────────────────────────────────
|
|
262
|
+
// GET /openapi.json — OpenAPI 3.0 spec for ChatGPT Custom Actions
|
|
263
|
+
app.get('/openapi.json', (req, res) => {
|
|
264
|
+
const host = req.headers.host || 'bridge.promethios.ai';
|
|
265
|
+
res.json({
|
|
266
|
+
openapi: '3.0.0',
|
|
267
|
+
info: {
|
|
268
|
+
title: 'Promethios Desktop Bridge',
|
|
269
|
+
description: 'Give any AI access to the user\'s desktop context, files, and applications.',
|
|
270
|
+
version: require('../package.json').version,
|
|
271
|
+
},
|
|
272
|
+
servers: [{ url: `https://${host}` }],
|
|
273
|
+
paths: {
|
|
274
|
+
'/tools': {
|
|
275
|
+
get: {
|
|
276
|
+
operationId: 'listTools',
|
|
277
|
+
summary: 'List all available desktop tools',
|
|
278
|
+
responses: { '200': { description: 'Tool manifest' } },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
'/tools/call': {
|
|
282
|
+
post: {
|
|
283
|
+
operationId: 'callTool',
|
|
284
|
+
summary: 'Execute a desktop tool',
|
|
285
|
+
requestBody: {
|
|
286
|
+
required: true,
|
|
287
|
+
content: {
|
|
288
|
+
'application/json': {
|
|
289
|
+
schema: {
|
|
290
|
+
type: 'object',
|
|
291
|
+
required: ['tool'],
|
|
292
|
+
properties: {
|
|
293
|
+
tool: { type: 'string', description: 'Tool name' },
|
|
294
|
+
args: { type: 'object', description: 'Tool arguments' },
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
responses: { '200': { description: 'Tool result' } },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
'/tools/context': {
|
|
304
|
+
get: {
|
|
305
|
+
operationId: 'getContext',
|
|
306
|
+
summary: 'Get current desktop context (active app, window, clipboard)',
|
|
307
|
+
parameters: [
|
|
308
|
+
{
|
|
309
|
+
name: 'screenshot', in: 'query', schema: { type: 'boolean' },
|
|
310
|
+
description: 'Include screenshot of pinned region',
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
responses: { '200': { description: 'Context block' } },
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
components: {
|
|
318
|
+
securitySchemes: {
|
|
319
|
+
BearerAuth: { type: 'http', scheme: 'bearer' },
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
security: [{ BearerAuth: [] }],
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return app;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Start the MCP server on the given port.
|
|
331
|
+
* Called from bridge.js during startup.
|
|
332
|
+
*/
|
|
333
|
+
function startMcpServer({ port = 7824, authToken, log = console.log } = {}) {
|
|
334
|
+
const app = createMcpServer(authToken);
|
|
335
|
+
|
|
336
|
+
return new Promise((resolve, reject) => {
|
|
337
|
+
const server = app.listen(port, '127.0.0.1', () => {
|
|
338
|
+
log(`[mcp] Promethios MCP server listening on port ${port}`);
|
|
339
|
+
log(`[mcp] Tool discovery: http://127.0.0.1:${port}/tools`);
|
|
340
|
+
log(`[mcp] MCP endpoint: http://127.0.0.1:${port}/mcp`);
|
|
341
|
+
log(`[mcp] OpenAPI spec: http://127.0.0.1:${port}/openapi.json`);
|
|
342
|
+
resolve(server);
|
|
343
|
+
});
|
|
344
|
+
server.on('error', reject);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = { startMcpServer, createMcpServer, FULL_MANIFEST, ANDROID_TOOL_MANIFEST };
|
package/src/overlay/launcher.js
CHANGED
|
@@ -1,101 +1,133 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* launcher.js
|
|
2
|
+
* launcher.js — Promethios Bridge
|
|
3
3
|
*
|
|
4
|
-
* Called by
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* dependencies are not installed in the npx cache).
|
|
4
|
+
* Called by bridge.js after successful authentication.
|
|
5
|
+
* Finds the pre-built Promethios Desktop binary (downloaded by postinstall.js
|
|
6
|
+
* into ~/.promethios/desktop/) and spawns it with auth credentials via env vars.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Binary search order:
|
|
9
|
+
* 1. PROMETHIOS_DESKTOP_BIN environment variable (dev override)
|
|
10
|
+
* 2. ~/.promethios/desktop/ — where postinstall.js places the binary
|
|
11
|
+
* 3. Electron source-mode fallback (dev only, requires electron in node_modules)
|
|
12
|
+
*
|
|
13
|
+
* The pre-built binary reads its config from env vars:
|
|
14
|
+
* PROMETHIOS_TOKEN, PROMETHIOS_API_BASE, PROMETHIOS_THREAD_ID, PROMETHIOS_DEV
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
'use strict';
|
|
18
18
|
|
|
19
19
|
const { spawn } = require('child_process');
|
|
20
|
-
const path
|
|
21
|
-
const fs
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const INSTALL_DIR = path.join(os.homedir(), '.promethios', 'desktop');
|
|
25
|
+
|
|
26
|
+
// ── Find the pre-built binary ─────────────────────────────────────────────────
|
|
27
|
+
function findDesktopBinary() {
|
|
28
|
+
// 1. Explicit env override (useful for local dev / CI)
|
|
29
|
+
if (process.env.PROMETHIOS_DESKTOP_BIN && fs.existsSync(process.env.PROMETHIOS_DESKTOP_BIN)) {
|
|
30
|
+
return process.env.PROMETHIOS_DESKTOP_BIN;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 2. Scan ~/.promethios/desktop/ for a matching binary
|
|
34
|
+
if (fs.existsSync(INSTALL_DIR)) {
|
|
35
|
+
const files = fs.readdirSync(INSTALL_DIR);
|
|
36
|
+
const exts = process.platform === 'win32' ? ['.exe']
|
|
37
|
+
: process.platform === 'darwin' ? ['.dmg', '.app']
|
|
38
|
+
: ['.AppImage'];
|
|
39
|
+
|
|
40
|
+
for (const ext of exts) {
|
|
41
|
+
const match = files.find(f => f.endsWith(ext));
|
|
42
|
+
if (match) {
|
|
43
|
+
const full = path.join(INSTALL_DIR, match);
|
|
44
|
+
// Ensure executable bit is set on Unix
|
|
45
|
+
try {
|
|
46
|
+
if (process.platform !== 'win32') fs.chmodSync(full, 0o755);
|
|
47
|
+
} catch {}
|
|
48
|
+
return full;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Find Electron binary for source-mode fallback (dev / CI) ─────────────────
|
|
27
57
|
function findElectronBinary() {
|
|
28
58
|
const isWin = process.platform === 'win32';
|
|
29
|
-
|
|
30
|
-
// Candidate paths for the real Electron binary (not just the npm package stub)
|
|
59
|
+
const isMac = process.platform === 'darwin';
|
|
31
60
|
const candidates = [
|
|
32
|
-
// 1. Installed inside promethios-bridge's own node_modules (full npm install, not npx)
|
|
33
61
|
path.join(__dirname, '..', '..', 'node_modules', 'electron', 'dist',
|
|
34
|
-
isWin ? 'electron.exe' :
|
|
35
|
-
// 2. .bin shim (only works if the real binary was downloaded by postinstall)
|
|
62
|
+
isWin ? 'electron.exe' : isMac ? 'Electron.app/Contents/MacOS/Electron' : 'electron'),
|
|
36
63
|
path.join(__dirname, '..', '..', 'node_modules', '.bin', isWin ? 'electron.cmd' : 'electron'),
|
|
37
|
-
// 3. Global electron install
|
|
38
|
-
path.join(require('os').homedir(), '.npm', '_npx', '**', 'node_modules', 'electron', 'dist', isWin ? 'electron.exe' : 'electron'),
|
|
39
64
|
];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Extra check: the .bin shim always exists even without the real binary.
|
|
47
|
-
// For the .bin shim, also verify the actual dist binary exists.
|
|
48
|
-
if (candidate.includes('.bin')) {
|
|
49
|
-
const distBin = path.join(
|
|
50
|
-
path.dirname(candidate), '..', 'electron', 'dist',
|
|
51
|
-
isWin ? 'electron.exe' : process.platform === 'darwin'
|
|
52
|
-
? 'Electron.app/Contents/MacOS/Electron' : 'electron'
|
|
53
|
-
);
|
|
54
|
-
if (!fs.existsSync(distBin)) continue;
|
|
55
|
-
}
|
|
56
|
-
return candidate;
|
|
65
|
+
for (const c of candidates) {
|
|
66
|
+
if (!c.includes('**') && fs.existsSync(c)) {
|
|
67
|
+
if (c.includes('.bin')) {
|
|
68
|
+
const dist = path.join(path.dirname(c), '..', 'electron', 'dist',
|
|
69
|
+
isWin ? 'electron.exe' : isMac ? 'Electron.app/Contents/MacOS/Electron' : 'electron');
|
|
70
|
+
if (!fs.existsSync(dist)) continue;
|
|
57
71
|
}
|
|
58
|
-
|
|
72
|
+
return c;
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
|
-
|
|
61
75
|
return null;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
* Launch the Promethios overlay Electron window.
|
|
66
|
-
* Returns the child process if Electron was found and launched, or null otherwise.
|
|
67
|
-
*/
|
|
78
|
+
// ── Launch ────────────────────────────────────────────────────────────────────
|
|
68
79
|
function launchOverlay({ authToken, apiBase = 'https://api.promethios.ai', threadId = '', dev = false } = {}) {
|
|
69
|
-
const
|
|
80
|
+
const desktopBin = findDesktopBinary();
|
|
81
|
+
|
|
82
|
+
if (desktopBin) {
|
|
83
|
+
// ── Mode A: pre-built binary ──────────────────────────────────────────────
|
|
84
|
+
// Pass credentials via env vars — AppImage/exe wrappers don't always
|
|
85
|
+
// forward CLI args to the inner Electron process reliably.
|
|
86
|
+
const env = {
|
|
87
|
+
...process.env,
|
|
88
|
+
PROMETHIOS_TOKEN: authToken || '',
|
|
89
|
+
PROMETHIOS_API_BASE: apiBase || '',
|
|
90
|
+
PROMETHIOS_THREAD_ID: threadId || '',
|
|
91
|
+
PROMETHIOS_DEV: dev ? '1' : '',
|
|
92
|
+
ELECTRON_NO_ATTACH_CONSOLE: '1',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (dev) console.log(`[overlay] Launching pre-built binary: ${desktopBin}`);
|
|
96
|
+
|
|
97
|
+
const child = spawn(desktopBin, [], { detached: true, stdio: 'ignore', env });
|
|
98
|
+
child.on('error', (err) => {
|
|
99
|
+
if (dev) console.log(`[overlay] Binary spawn error (non-critical): ${err.message}`);
|
|
100
|
+
});
|
|
101
|
+
child.unref();
|
|
102
|
+
return child;
|
|
103
|
+
}
|
|
70
104
|
|
|
105
|
+
// ── Mode B: source mode via Electron (dev / CI fallback) ─────────────────
|
|
106
|
+
const electronBin = findElectronBinary();
|
|
71
107
|
if (!electronBin) {
|
|
72
|
-
if (dev) console.log('[overlay]
|
|
108
|
+
if (dev) console.log('[overlay] No pre-built binary and no Electron found — skipping overlay.');
|
|
73
109
|
return null;
|
|
74
110
|
}
|
|
75
111
|
|
|
76
112
|
const mainScript = path.join(__dirname, 'main.js');
|
|
77
|
-
|
|
78
113
|
const args = [mainScript];
|
|
79
|
-
if (authToken)
|
|
80
|
-
if (apiBase)
|
|
81
|
-
if (threadId)
|
|
82
|
-
if (dev)
|
|
114
|
+
if (authToken) args.push('--token', authToken);
|
|
115
|
+
if (apiBase) args.push('--api', apiBase);
|
|
116
|
+
if (threadId) args.push('--thread', threadId);
|
|
117
|
+
if (dev) args.push('--dev');
|
|
118
|
+
|
|
119
|
+
if (dev) console.log(`[overlay] Launching Electron source mode: ${electronBin}`);
|
|
83
120
|
|
|
84
121
|
const child = spawn(electronBin, args, {
|
|
85
122
|
detached: true,
|
|
86
123
|
stdio: 'ignore',
|
|
87
124
|
env: { ...process.env, ELECTRON_NO_ATTACH_CONSOLE: '1' },
|
|
88
125
|
});
|
|
89
|
-
|
|
90
|
-
// Attach error handler BEFORE unref() to prevent unhandled 'error' event crashes.
|
|
91
126
|
child.on('error', (err) => {
|
|
92
127
|
if (dev) console.log(`[overlay] Spawn error (non-critical): ${err.message}`);
|
|
93
128
|
});
|
|
94
|
-
|
|
95
129
|
child.unref();
|
|
96
|
-
|
|
97
|
-
if (dev) console.log(`[overlay] Launched Electron (pid ${child.pid})`);
|
|
98
|
-
|
|
130
|
+
if (dev) console.log(`[overlay] Launched Electron source mode (pid ${child.pid})`);
|
|
99
131
|
return child;
|
|
100
132
|
}
|
|
101
133
|
|