friend-assistant 0.1.36 → 0.1.37
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/README.md +2 -1
- package/dist/BUILD.json +2 -2
- package/dist/chrome-control/extension/background.js +20 -6
- package/dist/chrome-control/extension/popup.js +2 -2
- package/dist/friend.mjs +574 -228
- package/dist/web/assets/{index-DMgFiCjT.js → index-B5L9wvda.js} +60 -60
- package/dist/web/assets/index-qwk36PfU.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DEst2el6.css +0 -1
package/README.md
CHANGED
|
@@ -154,6 +154,7 @@ Example: `friend config set voice.speakTyped false` keeps typed messages silent.
|
|
|
154
154
|
| `FRIEND_ANTHROPIC_KEY` | An API key to use when none is saved. |
|
|
155
155
|
| `FRIEND_PYTHON` | The Python program to use for voice, if the usual ones are not the right version. |
|
|
156
156
|
| `FRIEND_WEB_DIR`, `FRIEND_PLUGIN_CATALOG`, `FRIEND_VOICE_DIR`, `FRIEND_CHROME_CONTROL` | Where the web app, the bundled plugins, the Python voice code and the Chrome control program (its `index.js`) are, for unusual installs. |
|
|
157
|
+
| `CHROME_BRIDGE_PORT`, `CHROME_BRIDGE_HTTP_PORT` | Where Chrome control itself listens on this PC (its extension port, default 8765, and its MCP port, default 8766). Friend reaches both for you: other PCs only need Friend's own address. |
|
|
157
158
|
| `FRIEND_DEMO_PORT` | The port `pnpm demo` listens on (default 8788). |
|
|
158
159
|
|
|
159
160
|
### From the source code
|
|
@@ -225,7 +226,7 @@ pnpm friend claude status
|
|
|
225
226
|
|
|
226
227
|
A connector gives Friend the tools of an outside program or service through the MCP standard. Open **Esc, then Connectors** (or type `/connectors`). Ready-made entries:
|
|
227
228
|
|
|
228
|
-
- **Chrome control** is a plugin that comes with Friend, so there is nothing else to install (no npm package, no `claude mcp add`). Open **Plugins**, install **Chrome control** (PIN), and Friend sets it up by itself from the copy inside it: the small helper Chrome needs and the extension folder. The one step only you can do is loading that folder in Chrome (chrome://extensions, Developer mode, Load unpacked; Configure shows the folder with a Copy button) and switching the extension on from its icon. Then switch the plugin on (PIN); Friend connects, and the plugin page shows whether the extension is linked. Every browser action still asks for your approval. Removing the plugin only removes Friend's connection; the helper and the extension stay, because other programs on your PC may use them. See [docs/guides/chrome-control.md](docs/guides/chrome-control.md).
|
|
229
|
+
- **Chrome control** is a plugin that comes with Friend, so there is nothing else to install (no npm package, no `claude mcp add`). Open **Plugins**, install **Chrome control** (PIN), and Friend sets it up by itself from the copy inside it: the small helper Chrome needs and the extension folder. The one step only you can do is loading that folder in Chrome (chrome://extensions, Developer mode, Load unpacked; Configure shows the folder with a Copy button) and switching the extension on from its icon. Then switch the plugin on (PIN); Friend connects, and the plugin page shows whether the extension is linked. Every browser action still asks for your approval. Removing the plugin only removes Friend's connection; the helper and the extension stay, because other programs on your PC may use them. See [docs/guides/chrome-control.md](docs/guides/chrome-control.md). **From another PC** it needs no extra port: switch on *Allow other PCs* in Configure and Friend serves Chrome control on its own address (`/chrome/mcp` for apps, `/chrome/ws` for the extension), with a download link for the extension and the key shown only after your PIN.
|
|
229
230
|
- **Gmail**: a community Gmail MCP server (`@gongrzhe/server-gmail-autoauth-mcp`). You create the Google OAuth credentials and sign in on Google's own page; **Friend never sees your Google password**. More mail and calendar connectors can be added the same way ("Something else": any program or any server by address).
|
|
230
231
|
|
|
231
232
|
How it is kept safe:
|
package/dist/BUILD.json
CHANGED
|
@@ -32,19 +32,33 @@ function parseTarget(c) {
|
|
|
32
32
|
scheme = m[1].toLowerCase();
|
|
33
33
|
raw = raw.slice(m[0].length);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
// An address with a path (Friend's own, wss://name/chrome/ws) is used as it is: its port is the one in the address, not the port box.
|
|
36
|
+
let path = "";
|
|
37
|
+
const slash = raw.indexOf("/");
|
|
38
|
+
if (slash >= 0) {
|
|
39
|
+
path = raw.slice(slash).replace(/\/{2,}/g, "/").replace(/\/+$/, "");
|
|
40
|
+
raw = raw.slice(0, slash);
|
|
41
|
+
if (path && !/^\/[A-Za-z0-9._~/-]*$/.test(path)) return null;
|
|
42
|
+
}
|
|
36
43
|
if (!raw) raw = "127.0.0.1";
|
|
44
|
+
let own = null;
|
|
45
|
+
const hp = /^(\[[0-9a-f:.]+\]|[^:[\]]+):(\d{1,5})$/i.exec(raw);
|
|
46
|
+
if (hp) {
|
|
47
|
+
raw = hp[1];
|
|
48
|
+
own = Number(hp[2]);
|
|
49
|
+
}
|
|
37
50
|
if (/^[0-9a-f:]*:[0-9a-f:]*$/i.test(raw)) raw = `[${raw}]`; // bare IPv6
|
|
38
51
|
const valid = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/i.test(raw) || /^\[[0-9a-f:.]+\]$/i.test(raw);
|
|
39
52
|
if (!valid) return null;
|
|
40
|
-
const port = Number(c.port) || 8765;
|
|
41
|
-
const
|
|
53
|
+
const port = own ?? (path ? null : Number(c.port) || 8765);
|
|
54
|
+
const where = `${raw}${port ? ":" + port : ""}${path}`;
|
|
55
|
+
const base = `${scheme}://${where}${path ? "" : "/"}`;
|
|
42
56
|
try {
|
|
43
|
-
new URL(
|
|
57
|
+
new URL(base);
|
|
44
58
|
} catch {
|
|
45
59
|
return null;
|
|
46
60
|
}
|
|
47
|
-
return {
|
|
61
|
+
return { base, label: where, local: LOCAL_HOSTS.has(raw.toLowerCase()) && !path };
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
let ws = null;
|
|
@@ -290,7 +304,7 @@ async function doConnect() {
|
|
|
290
304
|
if (!c.token) return setStatus("no token");
|
|
291
305
|
if (nameTaken) return setStatus("name in use");
|
|
292
306
|
setStatus("connecting");
|
|
293
|
-
const sock = new WebSocket(`${target.
|
|
307
|
+
const sock = new WebSocket(`${target.base}?token=${encodeURIComponent(c.token)}&name=${encodeURIComponent(c.name)}`);
|
|
294
308
|
ws = sock;
|
|
295
309
|
sock.onopen = () => {
|
|
296
310
|
stats = { attached: attached.size, commands: 0, last: "", lastAt: 0 };
|
|
@@ -31,8 +31,8 @@ function look(s) {
|
|
|
31
31
|
return { cls: "state-nokey", word: "NO KEY", sub: isRemote(s.host) ? "PASTE THE SERVER'S ACCESS KEY" : "RUN: NPX CHROME-CONTROL-MCP SETUP" };
|
|
32
32
|
}
|
|
33
33
|
if (s.status === "name in use") return { cls: "state-nokey", word: "NAME TAKEN", sub: "ANOTHER CHROME USES THIS NAME. RENAME THIS ONE" };
|
|
34
|
-
if (s.status === "bad host") return { cls: "state-nokey", word: "BAD HOST", sub: "USE A DOMAIN
|
|
35
|
-
const where = isRemote(s.host) ? `${hostName(s.host).toUpperCase()}:${port}` : `PORT ${port}`;
|
|
34
|
+
if (s.status === "bad host") return { cls: "state-nokey", word: "BAD HOST", sub: "USE A DOMAIN, AN IP, OR WSS://NAME/CHROME/WS" };
|
|
35
|
+
const where = isRemote(s.host) ? (hostName(s.host).includes("/") ? hostName(s.host).toUpperCase() : `${hostName(s.host).toUpperCase()}:${port}`) : `PORT ${port}`;
|
|
36
36
|
return { cls: "state-nosig", word: "NO SIGNAL", sub: `MCP SERVER NOT FOUND ON ${where}` };
|
|
37
37
|
}
|
|
38
38
|
|