@nopeek/agent-bridge 0.2.1 → 0.3.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/dist/bridge.d.ts +11 -1
- package/dist/bridge.js +58 -1
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +11 -1
- package/dist/config.js +14 -0
- package/dist/service.d.ts +1 -1
- package/dist/service.js +14 -2
- package/package.json +1 -1
package/dist/bridge.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BridgeConfig, BrainSpec } from "./config.js";
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.3.0";
|
|
3
3
|
export interface PairRequest {
|
|
4
4
|
pairingSecret: string;
|
|
5
5
|
appId: string;
|
|
@@ -10,6 +10,8 @@ export interface BrainsPatch {
|
|
|
10
10
|
brainCmd?: string | null;
|
|
11
11
|
/** Global webhook brain; null clears it. */
|
|
12
12
|
brainUrl?: string | null;
|
|
13
|
+
/** Auto-provisioner command for newly adopted bots; null clears it. */
|
|
14
|
+
brainProvisionCmd?: string | null;
|
|
13
15
|
/** Per-handle entries; a null value removes that bot's override. */
|
|
14
16
|
map?: Record<string, BrainSpec | null>;
|
|
15
17
|
}
|
|
@@ -43,6 +45,14 @@ export declare class BridgeApp {
|
|
|
43
45
|
statusMinimal(): Record<string, unknown>;
|
|
44
46
|
statusFull(): Record<string, unknown>;
|
|
45
47
|
private startBot;
|
|
48
|
+
private provisioning;
|
|
49
|
+
/**
|
|
50
|
+
* Auto-provision a brain for a newly adopted bot: run BRAIN_PROVISION_CMD
|
|
51
|
+
* (e.g. "create a Hermes profile with its own soul + memory for this handle")
|
|
52
|
+
* and store its stdout as the bot's brain command. Best-effort — on any
|
|
53
|
+
* failure the bot simply keeps the default brain.
|
|
54
|
+
*/
|
|
55
|
+
private provisionBrain;
|
|
46
56
|
/** Fetch the authoritative bot list and start anything we're missing. */
|
|
47
57
|
private syncBots;
|
|
48
58
|
private startCore;
|
package/dist/bridge.js
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
// Pairing, unpairing and brain config all happen at runtime (from the app) and
|
|
7
7
|
// persist to <home>/settings.json — no restart, no terminal.
|
|
8
8
|
import { hostname } from "node:os";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
9
10
|
import { saveSettings } from "./config.js";
|
|
10
11
|
import { BotRunner } from "./bot.js";
|
|
11
12
|
import { ControlSocket } from "./control.js";
|
|
12
13
|
import { resolveBrain } from "./brain.js";
|
|
13
|
-
export const VERSION = "0.
|
|
14
|
+
export const VERSION = "0.3.0";
|
|
14
15
|
export class PairError extends Error {
|
|
15
16
|
code;
|
|
16
17
|
constructor(code, message) {
|
|
@@ -99,6 +100,8 @@ export class BridgeApp {
|
|
|
99
100
|
this.cfg.brainCmd = patch.brainCmd || null;
|
|
100
101
|
if (patch.brainUrl !== undefined)
|
|
101
102
|
this.cfg.brainUrl = patch.brainUrl || null;
|
|
103
|
+
if (patch.brainProvisionCmd !== undefined)
|
|
104
|
+
this.cfg.brainProvisionCmd = patch.brainProvisionCmd || null;
|
|
102
105
|
if (patch.map) {
|
|
103
106
|
for (const [rawHandle, spec] of Object.entries(patch.map)) {
|
|
104
107
|
const handle = rawHandle.replace(/^@/, "");
|
|
@@ -146,6 +149,7 @@ export class BridgeApp {
|
|
|
146
149
|
? { url: this.cfg.brainUrl }
|
|
147
150
|
: { echo: true },
|
|
148
151
|
map: this.cfg.brainMap,
|
|
152
|
+
provisionCmd: this.cfg.brainProvisionCmd,
|
|
149
153
|
},
|
|
150
154
|
bots: [...this.bots.values()].map((b) => ({
|
|
151
155
|
handle: b.info.handle,
|
|
@@ -163,6 +167,59 @@ export class BridgeApp {
|
|
|
163
167
|
const runner = new BotRunner(info, this.cfg);
|
|
164
168
|
this.bots.set(info.userId, runner);
|
|
165
169
|
runner.start(); // background; failures are isolated inside the runner
|
|
170
|
+
void this.provisionBrain(info); // background; bot echoes until it lands
|
|
171
|
+
}
|
|
172
|
+
// A handle is provisioned at most once per process; the persisted BRAIN_MAP
|
|
173
|
+
// entry prevents re-provisioning across restarts.
|
|
174
|
+
provisioning = new Set();
|
|
175
|
+
/**
|
|
176
|
+
* Auto-provision a brain for a newly adopted bot: run BRAIN_PROVISION_CMD
|
|
177
|
+
* (e.g. "create a Hermes profile with its own soul + memory for this handle")
|
|
178
|
+
* and store its stdout as the bot's brain command. Best-effort — on any
|
|
179
|
+
* failure the bot simply keeps the default brain.
|
|
180
|
+
*/
|
|
181
|
+
async provisionBrain(info) {
|
|
182
|
+
const cmd = this.cfg.brainProvisionCmd;
|
|
183
|
+
const handle = info.handle.replace(/^@/, "");
|
|
184
|
+
if (!cmd || this.cfg.brainMap[handle] || this.provisioning.has(handle))
|
|
185
|
+
return;
|
|
186
|
+
this.provisioning.add(handle);
|
|
187
|
+
console.log(`[provision:@${handle}] running brain provisioner`);
|
|
188
|
+
const out = await new Promise((resolvePromise) => {
|
|
189
|
+
const child = spawn("bash", ["-c", cmd], {
|
|
190
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
191
|
+
env: { ...process.env, NOPEEK_BOT_HANDLE: handle, NOPEEK_BOT_USER_ID: info.userId },
|
|
192
|
+
});
|
|
193
|
+
let stdout = "";
|
|
194
|
+
let stderr = "";
|
|
195
|
+
const timer = setTimeout(() => {
|
|
196
|
+
child.kill("SIGKILL");
|
|
197
|
+
resolvePromise(null);
|
|
198
|
+
}, 120_000);
|
|
199
|
+
child.stdout.on("data", (d) => (stdout += d.toString()));
|
|
200
|
+
child.stderr.on("data", (d) => (stderr += d.toString()));
|
|
201
|
+
child.on("error", () => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
resolvePromise(null);
|
|
204
|
+
});
|
|
205
|
+
child.on("close", (code) => {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
if (code !== 0) {
|
|
208
|
+
console.error(`[provision:@${handle}] exit ${code}. stderr: ${stderr.slice(0, 1000)}`);
|
|
209
|
+
resolvePromise(null);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
resolvePromise(stdout.trim());
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
if (!out) {
|
|
216
|
+
console.error(`[provision:@${handle}] provisioner produced no brain command — bot keeps the default brain`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Use the LAST non-empty stdout line: provisioners may log progress above it.
|
|
220
|
+
const brainCmd = out.split("\n").map((l) => l.trim()).filter(Boolean).pop();
|
|
221
|
+
this.setBrains({ map: { [handle]: { cmd: brainCmd } } });
|
|
222
|
+
console.log(`[provision:@${handle}] brain provisioned`);
|
|
166
223
|
}
|
|
167
224
|
/** Fetch the authoritative bot list and start anything we're missing. */
|
|
168
225
|
async syncBots() {
|
package/dist/cli.js
CHANGED
package/dist/config.d.ts
CHANGED
|
@@ -18,7 +18,16 @@ export interface BridgeConfig {
|
|
|
18
18
|
brainUrl: string | null;
|
|
19
19
|
/** Per-handle overrides: { "<handle>": {"cmd": "…"} | {"url": "…"} }. */
|
|
20
20
|
brainMap: Record<string, BrainSpec>;
|
|
21
|
+
/**
|
|
22
|
+
* Auto-provisioner: run ONCE for each adopted bot that has no BRAIN_MAP
|
|
23
|
+
* entry (env: NOPEEK_BOT_HANDLE, NOPEEK_BOT_USER_ID). Its trimmed stdout
|
|
24
|
+
* becomes that bot's brain command — e.g. a script that creates a fresh
|
|
25
|
+
* Hermes profile (own soul + memory) per bot and prints how to invoke it.
|
|
26
|
+
*/
|
|
27
|
+
brainProvisionCmd: string | null;
|
|
21
28
|
brainTimeoutMs: number;
|
|
29
|
+
/** Where `install` sends the user to finish setup (opened in the browser). */
|
|
30
|
+
appUrl: string;
|
|
22
31
|
/** Local control API port (status + pairing + brain config, loopback only). */
|
|
23
32
|
port: number;
|
|
24
33
|
/** Where per-bot device identity/key stores live. */
|
|
@@ -30,10 +39,11 @@ export interface BridgeConfig {
|
|
|
30
39
|
declineMessage: string | null;
|
|
31
40
|
}
|
|
32
41
|
export declare const DEFAULT_API_URL = "https://d3qweh72vesa98.cloudfront.net";
|
|
42
|
+
export declare const DEFAULT_APP_URL = "https://d32w3to73s0pu5.cloudfront.net/messenger/";
|
|
33
43
|
export declare const DEFAULT_PORT = 8790;
|
|
34
44
|
export declare const DEFAULT_BRAIN_TIMEOUT_MS = 180000;
|
|
35
45
|
export declare function defaultHomeDir(): string;
|
|
36
|
-
export declare const HELP = "nopeek-agent-bridge \u2014 run your agents as E2EE NoPeek bots\n\nUsage:\n nopeek-agent-bridge install Install as a background service (launchd/systemd),\n then finish setup from the NoPeek app:\n Bots -> Connect this computer.\n nopeek-agent-bridge uninstall Remove the background service (keeps data/settings).\n nopeek-agent-bridge status Show the running bridge's status.\n nopeek-agent-bridge [run] Run in the foreground. Unpaired bridges wait to be\n paired from the NoPeek app; --pair still works:\n npx @nopeek/agent-bridge --pair npr_\u2026 --app-id app_\u2026\n\nOptions:\n --pair <code> Pairing code from the NoPeek app (env NOPEEK_PAIRING_CODE)\n --app-id <id> NoPeek app id (env NOPEEK_APP_ID)\n --api-url <url> API base, default https://d3qweh72vesa98.cloudfront.net (env NOPEEK_API_URL)\n --brain-cmd <cmd> Shell brain: message on stdin -> reply on stdout (env BRAIN_CMD)\n --brain-url <url> Webhook brain: POST {text,...} -> {text|reply} (env BRAIN_URL)\n --brain-map <json> Per-bot overrides {\"<handle>\":{\"cmd\":\"\u2026\"}|{\"url\":\"\u2026\"}} (env BRAIN_MAP)\n --brain-timeout-ms <ms> Brain timeout, default 180000 (env BRAIN_TIMEOUT_MS)\n --port <port> Local control API port, default 8790 (env NOPEEK_BRIDGE_PORT)\n --data-dir <dir> Device-key store dir (env NOPEEK_BRIDGE_DATA_DIR)\n --home <dir> Bridge home, default ~/.nopeek-bridge (env NOPEEK_BRIDGE_HOME)\n --config <path> Config file, default ./nopeek-bridge.config.json\n -h, --help Show this help\n\nWith no brain configured, bots run in echo mode (\"You said: \u2026\") \u2014 a zero-config smoke test.\nPairing and brains can be managed entirely from the NoPeek app once the service is running.";
|
|
46
|
+
export declare const HELP = "nopeek-agent-bridge \u2014 run your agents as E2EE NoPeek bots\n\nUsage:\n nopeek-agent-bridge install Install as a background service (launchd/systemd),\n then finish setup from the NoPeek app:\n Bots -> Connect this computer.\n nopeek-agent-bridge uninstall Remove the background service (keeps data/settings).\n nopeek-agent-bridge status Show the running bridge's status.\n nopeek-agent-bridge [run] Run in the foreground. Unpaired bridges wait to be\n paired from the NoPeek app; --pair still works:\n npx @nopeek/agent-bridge --pair npr_\u2026 --app-id app_\u2026\n\nOptions:\n --pair <code> Pairing code from the NoPeek app (env NOPEEK_PAIRING_CODE)\n --app-id <id> NoPeek app id (env NOPEEK_APP_ID)\n --api-url <url> API base, default https://d3qweh72vesa98.cloudfront.net (env NOPEEK_API_URL)\n --brain-cmd <cmd> Shell brain: message on stdin -> reply on stdout (env BRAIN_CMD)\n --brain-url <url> Webhook brain: POST {text,...} -> {text|reply} (env BRAIN_URL)\n --brain-map <json> Per-bot overrides {\"<handle>\":{\"cmd\":\"\u2026\"}|{\"url\":\"\u2026\"}} (env BRAIN_MAP)\n --brain-provision-cmd <cmd> Run once per new bot; stdout becomes its brain command\n (env BRAIN_PROVISION_CMD \u2014 e.g. a script that creates a\n fresh Hermes profile with its own soul + memory)\n --app-url <url> App opened after install (env NOPEEK_APP_URL)\n --no-open install: don't open the app in the browser\n --brain-timeout-ms <ms> Brain timeout, default 180000 (env BRAIN_TIMEOUT_MS)\n --port <port> Local control API port, default 8790 (env NOPEEK_BRIDGE_PORT)\n --data-dir <dir> Device-key store dir (env NOPEEK_BRIDGE_DATA_DIR)\n --home <dir> Bridge home, default ~/.nopeek-bridge (env NOPEEK_BRIDGE_HOME)\n --config <path> Config file, default ./nopeek-bridge.config.json\n -h, --help Show this help\n\nWith no brain configured, bots run in echo mode (\"You said: \u2026\") \u2014 a zero-config smoke test.\nPairing and brains can be managed entirely from the NoPeek app once the service is running.";
|
|
37
47
|
/** Load config from argv + env + cwd config file + home settings. Never
|
|
38
48
|
* requires pairing — an unpaired bridge waits for the app to pair it. */
|
|
39
49
|
export declare function loadConfig(argv?: string[]): BridgeConfig;
|
package/dist/config.js
CHANGED
|
@@ -10,6 +10,7 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { join, resolve } from "node:path";
|
|
11
11
|
import { parseArgs } from "node:util";
|
|
12
12
|
export const DEFAULT_API_URL = "https://d3qweh72vesa98.cloudfront.net";
|
|
13
|
+
export const DEFAULT_APP_URL = "https://d32w3to73s0pu5.cloudfront.net/messenger/";
|
|
13
14
|
export const DEFAULT_PORT = 8790;
|
|
14
15
|
export const DEFAULT_BRAIN_TIMEOUT_MS = 180_000;
|
|
15
16
|
export function defaultHomeDir() {
|
|
@@ -22,8 +23,11 @@ const CLI_OPTIONS = {
|
|
|
22
23
|
"brain-cmd": { type: "string" },
|
|
23
24
|
"brain-url": { type: "string" },
|
|
24
25
|
"brain-map": { type: "string" },
|
|
26
|
+
"brain-provision-cmd": { type: "string" },
|
|
25
27
|
"brain-timeout-ms": { type: "string" },
|
|
26
28
|
"decline-message": { type: "string" },
|
|
29
|
+
"app-url": { type: "string" },
|
|
30
|
+
"no-open": { type: "boolean" },
|
|
27
31
|
port: { type: "string" },
|
|
28
32
|
"data-dir": { type: "string" },
|
|
29
33
|
home: { type: "string" },
|
|
@@ -38,8 +42,10 @@ const FLAG_TO_KEY = {
|
|
|
38
42
|
"brain-cmd": "BRAIN_CMD",
|
|
39
43
|
"brain-url": "BRAIN_URL",
|
|
40
44
|
"brain-map": "BRAIN_MAP",
|
|
45
|
+
"brain-provision-cmd": "BRAIN_PROVISION_CMD",
|
|
41
46
|
"brain-timeout-ms": "BRAIN_TIMEOUT_MS",
|
|
42
47
|
"decline-message": "NOPEEK_DECLINE_MESSAGE",
|
|
48
|
+
"app-url": "NOPEEK_APP_URL",
|
|
43
49
|
port: "NOPEEK_BRIDGE_PORT",
|
|
44
50
|
"data-dir": "NOPEEK_BRIDGE_DATA_DIR",
|
|
45
51
|
home: "NOPEEK_BRIDGE_HOME",
|
|
@@ -63,6 +69,11 @@ Options:
|
|
|
63
69
|
--brain-cmd <cmd> Shell brain: message on stdin -> reply on stdout (env BRAIN_CMD)
|
|
64
70
|
--brain-url <url> Webhook brain: POST {text,...} -> {text|reply} (env BRAIN_URL)
|
|
65
71
|
--brain-map <json> Per-bot overrides {"<handle>":{"cmd":"…"}|{"url":"…"}} (env BRAIN_MAP)
|
|
72
|
+
--brain-provision-cmd <cmd> Run once per new bot; stdout becomes its brain command
|
|
73
|
+
(env BRAIN_PROVISION_CMD — e.g. a script that creates a
|
|
74
|
+
fresh Hermes profile with its own soul + memory)
|
|
75
|
+
--app-url <url> App opened after install (env NOPEEK_APP_URL)
|
|
76
|
+
--no-open install: don't open the app in the browser
|
|
66
77
|
--brain-timeout-ms <ms> Brain timeout, default ${DEFAULT_BRAIN_TIMEOUT_MS} (env BRAIN_TIMEOUT_MS)
|
|
67
78
|
--port <port> Local control API port, default ${DEFAULT_PORT} (env NOPEEK_BRIDGE_PORT)
|
|
68
79
|
--data-dir <dir> Device-key store dir (env NOPEEK_BRIDGE_DATA_DIR)
|
|
@@ -179,11 +190,13 @@ export function loadConfig(argv = process.argv.slice(2)) {
|
|
|
179
190
|
brainCmd: get("brain-cmd") ?? null,
|
|
180
191
|
brainUrl: get("brain-url") ?? null,
|
|
181
192
|
brainMap: parseBrainMap(get("brain-map")),
|
|
193
|
+
brainProvisionCmd: get("brain-provision-cmd") ?? null,
|
|
182
194
|
brainTimeoutMs,
|
|
183
195
|
port,
|
|
184
196
|
dataDir,
|
|
185
197
|
homeDir,
|
|
186
198
|
declineMessage: get("decline-message") ?? null,
|
|
199
|
+
appUrl: get("app-url") ?? DEFAULT_APP_URL,
|
|
187
200
|
};
|
|
188
201
|
}
|
|
189
202
|
/**
|
|
@@ -200,6 +213,7 @@ export function saveSettings(cfg) {
|
|
|
200
213
|
...(cfg.brainCmd ? { BRAIN_CMD: cfg.brainCmd } : {}),
|
|
201
214
|
...(cfg.brainUrl ? { BRAIN_URL: cfg.brainUrl } : {}),
|
|
202
215
|
...(Object.keys(cfg.brainMap).length ? { BRAIN_MAP: JSON.stringify(cfg.brainMap) } : {}),
|
|
216
|
+
...(cfg.brainProvisionCmd ? { BRAIN_PROVISION_CMD: cfg.brainProvisionCmd } : {}),
|
|
203
217
|
...(cfg.declineMessage ? { NOPEEK_DECLINE_MESSAGE: cfg.declineMessage } : {}),
|
|
204
218
|
};
|
|
205
219
|
const path = settingsPath(cfg.homeDir);
|
package/dist/service.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { BridgeConfig } from "./config.js";
|
|
2
|
-
export declare function installService(cfg: BridgeConfig): Promise<void>;
|
|
2
|
+
export declare function installService(cfg: BridgeConfig, noOpen?: boolean): Promise<void>;
|
|
3
3
|
export declare function uninstallService(): void;
|
|
4
4
|
export declare function printStatus(port: number): Promise<void>;
|
package/dist/service.js
CHANGED
|
@@ -41,7 +41,7 @@ function launchctl(args, ignoreFailure = false) {
|
|
|
41
41
|
throw err;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
export async function installService(cfg) {
|
|
44
|
+
export async function installService(cfg, noOpen = false) {
|
|
45
45
|
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
46
46
|
throw new Error(`automatic service install supports macOS and Linux. On this platform, run the bridge with any process manager:\n nopeek-agent-bridge run`);
|
|
47
47
|
}
|
|
@@ -122,9 +122,21 @@ WantedBy=default.target
|
|
|
122
122
|
}
|
|
123
123
|
console.log(`
|
|
124
124
|
Done. Next step — in the NoPeek app on THIS computer:
|
|
125
|
-
Contacts -> My Bots ->
|
|
125
|
+
Contacts -> My Bots -> "Run your bots on this computer" -> Connect
|
|
126
126
|
Pairing, choosing your agent (Hermes, …) and everything else happens in the app.
|
|
127
127
|
Logs: ${logFile}`);
|
|
128
|
+
// Take the user straight to the app: sign in once, tap Connect, done.
|
|
129
|
+
// (Sessions persist, so next time this is automatic.)
|
|
130
|
+
if (!noOpen) {
|
|
131
|
+
const opener = process.platform === "darwin" ? "open" : "xdg-open";
|
|
132
|
+
try {
|
|
133
|
+
execFileSync(opener, [cfg.appUrl], { stdio: "ignore" });
|
|
134
|
+
console.log(`[install] opened ${cfg.appUrl}`);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
console.log(`[install] open ${cfg.appUrl} in your browser to finish setup`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
128
140
|
}
|
|
129
141
|
export function uninstallService() {
|
|
130
142
|
if (process.platform === "darwin") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nopeek/agent-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Run your own agents as E2EE NoPeek bots. Pairs with a one-time code, runs every bot you own, and pipes messages to any command or webhook.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|