@agent-beam/beam 0.2.18 → 0.2.21
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 +19 -0
- package/dist/account.js +1 -1
- package/dist/account.js.map +1 -1
- package/dist/agents.d.ts +8 -2
- package/dist/agents.js +7 -7
- package/dist/agents.js.map +1 -1
- package/dist/blocked-targets.d.ts +8 -0
- package/dist/blocked-targets.js +83 -0
- package/dist/blocked-targets.js.map +1 -0
- package/dist/cli.js +208 -42
- package/dist/cli.js.map +1 -1
- package/dist/client.js +67 -11
- package/dist/client.js.map +1 -1
- package/dist/collector-health.d.ts +1 -0
- package/dist/collector-health.js +20 -0
- package/dist/collector-health.js.map +1 -0
- package/dist/connect.js +8 -4
- package/dist/connect.js.map +1 -1
- package/dist/enroll.d.ts +0 -4
- package/dist/enroll.js +2 -60
- package/dist/enroll.js.map +1 -1
- package/dist/forward.d.ts +5 -1
- package/dist/forward.js +23 -7
- package/dist/forward.js.map +1 -1
- package/dist/install.d.ts +2 -0
- package/dist/install.js +58 -6
- package/dist/install.js.map +1 -1
- package/dist/policy.d.ts +1 -0
- package/dist/policy.js +11 -3
- package/dist/policy.js.map +1 -1
- package/dist/serve.js +25 -2
- package/dist/serve.js.map +1 -1
- package/dist/service.d.ts +2 -0
- package/dist/service.js +16 -4
- package/dist/service.js.map +1 -1
- package/dist/setup.js +9 -4
- package/dist/setup.js.map +1 -1
- package/dist/tamper.d.ts +9 -0
- package/dist/tamper.js +116 -0
- package/dist/tamper.js.map +1 -0
- package/dist/trusted-paths.d.ts +5 -0
- package/dist/trusted-paths.js +59 -0
- package/dist/trusted-paths.js.map +1 -0
- package/dist/tty-prompt.d.ts +5 -0
- package/dist/tty-prompt.js +55 -0
- package/dist/tty-prompt.js.map +1 -0
- package/dist/update.d.ts +7 -0
- package/dist/update.js +31 -0
- package/dist/update.js.map +1 -0
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
AgentBeam is a local security layer for AI agents. `beam` is the AgentBeam CLI. It installs hooks for the agents detected on your machine, captures agent and MCP activity, applies your AgentBeam policy locally, and sends approved telemetry to the AgentBeam dashboard.
|
|
4
4
|
|
|
5
|
+
## WipeBench: coding agent safety benchmark
|
|
6
|
+
|
|
7
|
+
[WipeBench](https://github.com/whyashthakker/WipeBench) gives coding agents an
|
|
8
|
+
objective, captures the commands they choose, and evaluates task completion and
|
|
9
|
+
harmful effects in Docker. A benchmark by [AgentBeam](https://agentbeam.com), in
|
|
10
|
+
collaboration with [ExplainX.ai](https://explainx.ai).
|
|
11
|
+
|
|
12
|
+
The [local snapshot](safety-bench/README.md) includes 112 scored scenarios across
|
|
13
|
+
12 categories and a no-key smoke test:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
python3 safety-bench/bench.py selftest --scenarios smoke-hello
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.11+ and a running Docker daemon. Scripted self-tests validate
|
|
20
|
+
the harness; they are not model rankings. The benchmark snapshot uses its own
|
|
21
|
+
[Apache-2.0 license](safety-bench/LICENSE). See the
|
|
22
|
+
[benchmark page](https://agentbeam.com/benchmarks/wipebench) for scope and usage.
|
|
23
|
+
|
|
5
24
|
## Install and setup
|
|
6
25
|
|
|
7
26
|
```bash
|
package/dist/account.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function fetchAccount(identity) {
|
|
|
21
21
|
}
|
|
22
22
|
catch { /* keep the status-only message */ }
|
|
23
23
|
if (response.status === 401)
|
|
24
|
-
message = "This device's credentials were revoked. Run 'beam
|
|
24
|
+
message = "This device's credentials were revoked. Run 'beam setup' again.";
|
|
25
25
|
throw new Error(message);
|
|
26
26
|
}
|
|
27
27
|
return (await response.json());
|
package/dist/account.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAQA,+FAA+F;AAC/F,4FAA4F;AAC5F,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB;IACnD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,OAAO,aAAa,EAAE;YACvD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,QAAQ,CAAC,YAAY,EAAE,EAAE;YAC7D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,oCAAoC,QAAQ,CAAC,MAAM,IAAI,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;YAC3D,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAQA,+FAA+F;AAC/F,4FAA4F;AAC5F,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB;IACnD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,OAAO,aAAa,EAAE;YACvD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,QAAQ,CAAC,YAAY,EAAE,EAAE;YAC7D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,oCAAoC,QAAQ,CAAC,MAAM,IAAI,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;YAC3D,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,GAAG,iEAAiE,CAAC;QACzG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAY,CAAC;AAC5C,CAAC;AAED,+FAA+F;AAC/F,gGAAgG;AAChG,gGAAgG;AAChG,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB;IACnD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,OAAO,YAAY,EAAE;YACtD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,QAAQ,CAAC,YAAY,EAAE,EAAE;YAC7D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,6EAA6E;IAC7E,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO;IAEpC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,iCAAiC,QAAQ,CAAC,MAAM,IAAI,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;YAC3D,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
|
package/dist/agents.d.ts
CHANGED
|
@@ -19,9 +19,15 @@ export interface AgentDefinition {
|
|
|
19
19
|
/**
|
|
20
20
|
* Reverse of mergeHookConfig — strips beam's hook entry back out, leaving any other hooks the
|
|
21
21
|
* agent or the user configured untouched. Must be idempotent — safe to call on a config that
|
|
22
|
-
* never had beam installed (a no-op).
|
|
22
|
+
* never had beam installed (a no-op). Takes a predicate rather than an exact command string:
|
|
23
|
+
* a hook's command embeds the absolute path to whichever beam install wrote it (see
|
|
24
|
+
* resolveHookCommand in install.ts), which differs between a global npm install, a local dev
|
|
25
|
+
* checkout, or the same install after 'beam update' changes its resolved path. An uninstall
|
|
26
|
+
* running from a *different* beam install than the one that wrote the entry must still
|
|
27
|
+
* recognize and remove it -- matching only one exact string silently leaves it in place (and
|
|
28
|
+
* beam fully monitoring/enforcing) instead. See isOwnHookCommand in install.ts.
|
|
23
29
|
*/
|
|
24
|
-
unmergeHookConfig: ((existing: Obj, command: string) => Obj) | null;
|
|
30
|
+
unmergeHookConfig: ((existing: Obj, isOwnCommand: (command: string) => boolean) => Obj) | null;
|
|
25
31
|
/** Payload adapter id used by src/hook-adapters.ts to normalize this agent's stdin JSON. */
|
|
26
32
|
adapter: "passthrough" | "copilot-camel" | "generic" | "gemini";
|
|
27
33
|
/** Whether the AGENT ITSELF (not just this entry's payload verification) is unverified — the
|
package/dist/agents.js
CHANGED
|
@@ -43,33 +43,33 @@ function mergeCopilotHooks(event) {
|
|
|
43
43
|
// Reverse of mergeClaudeStyleHooks: drops beam's command out of each entry's inner hooks array
|
|
44
44
|
// (rather than the whole entry), so a user-added hook sharing the same matcher is left alone.
|
|
45
45
|
function unmergeClaudeStyleHooks(event) {
|
|
46
|
-
return (existing,
|
|
46
|
+
return (existing, isOwnCommand) => {
|
|
47
47
|
const hooks = obj(existing.hooks);
|
|
48
48
|
if (!(event in hooks))
|
|
49
49
|
return existing;
|
|
50
50
|
const list = arr(hooks[event]).map(obj)
|
|
51
|
-
.map(entry => ({ ...entry, hooks: arr(entry.hooks).map(obj).filter(h => h.command
|
|
51
|
+
.map(entry => ({ ...entry, hooks: arr(entry.hooks).map(obj).filter(h => !isOwnCommand(String(h.command ?? ""))) }))
|
|
52
52
|
.filter(entry => arr(entry.hooks).length > 0);
|
|
53
53
|
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
// Reverse of mergeCursorHooks.
|
|
57
57
|
function unmergeCursorHooks(event) {
|
|
58
|
-
return (existing,
|
|
58
|
+
return (existing, isOwnCommand) => {
|
|
59
59
|
const hooks = obj(existing.hooks);
|
|
60
60
|
if (!(event in hooks))
|
|
61
61
|
return existing;
|
|
62
|
-
const list = arr(hooks[event]).map(obj).filter(entry => entry.command
|
|
62
|
+
const list = arr(hooks[event]).map(obj).filter(entry => !isOwnCommand(String(entry.command ?? "")));
|
|
63
63
|
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
// Reverse of mergeCopilotHooks.
|
|
67
67
|
function unmergeCopilotHooks(event) {
|
|
68
|
-
return (existing,
|
|
68
|
+
return (existing, isOwnCommand) => {
|
|
69
69
|
const hooks = obj(existing.hooks);
|
|
70
70
|
if (!(event in hooks))
|
|
71
71
|
return existing;
|
|
72
|
-
const list = arr(hooks[event]).map(obj).filter(entry => entry.bash
|
|
72
|
+
const list = arr(hooks[event]).map(obj).filter(entry => !isOwnCommand(String(entry.bash ?? "")));
|
|
73
73
|
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
74
74
|
};
|
|
75
75
|
}
|
|
@@ -83,7 +83,7 @@ function mergeAll(mergers) {
|
|
|
83
83
|
return (existing, command) => mergers.reduce((acc, merge) => merge(acc, command), existing);
|
|
84
84
|
}
|
|
85
85
|
function unmergeAll(unmergers) {
|
|
86
|
-
return (existing,
|
|
86
|
+
return (existing, isOwnCommand) => unmergers.reduce((acc, unmerge) => unmerge(acc, isOwnCommand), existing);
|
|
87
87
|
}
|
|
88
88
|
export const AGENTS = [
|
|
89
89
|
{
|
package/dist/agents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function blockedTargetsFile(): string;
|
|
2
|
+
export declare function readBlockedTargets(): Promise<string[]>;
|
|
3
|
+
export declare function addBlockedTarget(target: string): Promise<string[]>;
|
|
4
|
+
export declare function removeBlockedTarget(target: string): Promise<string[]>;
|
|
5
|
+
export declare function isTargetBlocked(ctx: {
|
|
6
|
+
path?: string;
|
|
7
|
+
command?: string;
|
|
8
|
+
}, blocked: string[]): boolean;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { getDataDirectory } from "./config.js";
|
|
4
|
+
// A global, human-maintained denylist -- the "Block always" choice on a warn prompt writes here.
|
|
5
|
+
// Lives under the data directory, same as trusted-paths.json: isBeamSelfProtectionTarget() already
|
|
6
|
+
// blocks an agent from writing to this directory, so only a human choosing it interactively (or
|
|
7
|
+
// running the CLI) can add an entry, never the agent whose own action got blocked.
|
|
8
|
+
export function blockedTargetsFile() { return join(getDataDirectory(), "blocked-targets.json"); }
|
|
9
|
+
export async function readBlockedTargets() {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(await readFile(blockedTargetsFile(), "utf8"));
|
|
12
|
+
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string" && p.trim().length > 0) : [];
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function writeBlockedTargets(targets) {
|
|
19
|
+
const dir = getDataDirectory();
|
|
20
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
21
|
+
const file = blockedTargetsFile();
|
|
22
|
+
const temp = `${file}.tmp`;
|
|
23
|
+
await writeFile(temp, JSON.stringify(targets, null, 2) + "\n", { mode: 0o600 });
|
|
24
|
+
await rename(temp, file);
|
|
25
|
+
}
|
|
26
|
+
function normalizeEntry(target) {
|
|
27
|
+
return isAbsolute(target) ? resolve(target) : target.trim();
|
|
28
|
+
}
|
|
29
|
+
export async function addBlockedTarget(target) {
|
|
30
|
+
const entry = normalizeEntry(target);
|
|
31
|
+
const current = await readBlockedTargets();
|
|
32
|
+
if (current.includes(entry))
|
|
33
|
+
return current;
|
|
34
|
+
const next = [...current, entry];
|
|
35
|
+
await writeBlockedTargets(next);
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
export async function removeBlockedTarget(target) {
|
|
39
|
+
const entry = normalizeEntry(target);
|
|
40
|
+
const next = (await readBlockedTargets()).filter(t => t !== entry);
|
|
41
|
+
await writeBlockedTargets(next);
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
// Path-glob semantics: "*" matches within one path segment, "**" crosses "/".
|
|
45
|
+
function pathGlobMatch(entry, value) {
|
|
46
|
+
const escaped = entry.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "§§").replace(/\*/g, "[^/]*").replace(/§§/g, ".*");
|
|
47
|
+
return new RegExp(`^${escaped}$`, "i").test(value);
|
|
48
|
+
}
|
|
49
|
+
// Command-glob semantics: a command line has no path-segment structure, so "*" matches anything,
|
|
50
|
+
// including "/" -- otherwise `rm -rf *` would never match `rm -rf /tmp/build`.
|
|
51
|
+
function commandGlobMatch(entry, value) {
|
|
52
|
+
const escaped = entry.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
53
|
+
return new RegExp(`^${escaped}$`, "i").test(value);
|
|
54
|
+
}
|
|
55
|
+
// Absolute-path entries (the common case -- a file/dir path a user chose "Block always" on) match
|
|
56
|
+
// by equality, subdirectory containment, or glob against the current action's path. Anything else
|
|
57
|
+
// was recorded from a command line with no clear path (e.g. a risky shell command), so it matches
|
|
58
|
+
// as a glob/substring against the current command instead.
|
|
59
|
+
export function isTargetBlocked(ctx, blocked) {
|
|
60
|
+
return blocked.some(entry => {
|
|
61
|
+
if (isAbsolute(entry)) {
|
|
62
|
+
if (!ctx.path)
|
|
63
|
+
return false;
|
|
64
|
+
const target = resolve(ctx.path);
|
|
65
|
+
if (target === entry)
|
|
66
|
+
return true;
|
|
67
|
+
const rel = relative(entry, target);
|
|
68
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
69
|
+
}
|
|
70
|
+
if (ctx.command) {
|
|
71
|
+
if (entry.includes("*")) {
|
|
72
|
+
if (commandGlobMatch(entry, ctx.command))
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
else if (ctx.command.toLowerCase().includes(entry.toLowerCase()))
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (ctx.path && entry.includes("*") && pathGlobMatch(entry, ctx.path))
|
|
79
|
+
return true;
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=blocked-targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocked-targets.js","sourceRoot":"","sources":["../src/blocked-targets.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -6,10 +6,11 @@ import { startServer } from "./serve.js";
|
|
|
6
6
|
import { captureHook, extractPreview, extractSave, importEvents, readToken, reloadRemoteRules, scanFile } from "./client.js";
|
|
7
7
|
import { supportsExtraction } from "./extract.js";
|
|
8
8
|
import { AGENTS } from "./agents.js";
|
|
9
|
-
import { installAllDetectedHooks, installHook, uninstallAllHooks } from "./install.js";
|
|
10
|
-
import { installService, serviceLogPaths, serviceStatus, startService, stopService, uninstallService } from "./service.js";
|
|
9
|
+
import { installAllDetectedHooks, installHook, uninstallAllHooks, uninstallHook } from "./install.js";
|
|
10
|
+
import { configuredPort, installService, serviceLogPaths, serviceStatus, startService, stopService, uninstallService } from "./service.js";
|
|
11
|
+
import { waitForCollectorUp } from "./collector-health.js";
|
|
11
12
|
import { getBeamHome, getCollectorUrl, getDataDirectory, getIdentityPath } from "./config.js";
|
|
12
|
-
import { clearIdentity,
|
|
13
|
+
import { clearIdentity, readIdentity } from "./enroll.js";
|
|
13
14
|
import { startConnect } from "./connect.js";
|
|
14
15
|
import { runSetup, promptYesNo } from "./setup.js";
|
|
15
16
|
import { installEnterprisePackage } from "./enterprise-install.js";
|
|
@@ -27,6 +28,10 @@ import { runAgent } from "./run.js";
|
|
|
27
28
|
import { AGENT_BINARIES, installShims, listShims, pathExportLine, shimDir, uninstallShims } from "./shims.js";
|
|
28
29
|
import { runMcpProxy } from "./mcp-proxy.js";
|
|
29
30
|
import { registerJevCommands } from "./jev-cli.js";
|
|
31
|
+
import { addTrustedPath, readTrustedPaths, removeTrustedPath, trustedPathsFile } from "./trusted-paths.js";
|
|
32
|
+
import { blockedTargetsFile, readBlockedTargets, removeBlockedTarget } from "./blocked-targets.js";
|
|
33
|
+
import { checkForUpdate, installUpdate } from "./update.js";
|
|
34
|
+
import { recordExpectedHooks, reportControlEvent } from "./tamper.js";
|
|
30
35
|
// Lets 'BEAM_API_URL=... beam connect' style overrides live in a .env file instead of the
|
|
31
36
|
// shell profile. Checked in cwd first (handy when developing from the repo), then in
|
|
32
37
|
// ~/.beam so overrides still apply when 'beam' is run globally from any directory.
|
|
@@ -70,7 +75,7 @@ program.command("start")
|
|
|
70
75
|
const rulesNote = result.customRules.loaded ? `\nCustom rules loaded: ${result.customRules.loaded} from ${result.customRules.path}` : "";
|
|
71
76
|
for (const message of result.customRules.errors)
|
|
72
77
|
console.error(`✖ ${message}`);
|
|
73
|
-
const policyNote = result.policySync ? "\nPolicy sync: on (every 60s)" : "\nPolicy sync: off (device not enrolled — run 'beam
|
|
78
|
+
const policyNote = result.policySync ? "\nPolicy sync: on (every 60s)" : "\nPolicy sync: off (device not enrolled — run 'beam setup')";
|
|
74
79
|
const newlyInstalled = result.agentInstalls.filter(r => r.status === "installed");
|
|
75
80
|
const installNote = newlyInstalled.length
|
|
76
81
|
? `\nAgent hooks installed: ${newlyInstalled.map(r => r.name).join(", ")}`
|
|
@@ -81,19 +86,80 @@ program.command("start")
|
|
|
81
86
|
}
|
|
82
87
|
console.log(`Beam collector running.\nMode: observe only\nLocal storage: ${result.directory}${rulesNote}${policyNote}${installNote}\nRun 'beam agent list' to see every detected agent's capture status.\nRun 'beam token' for the pairing token.`);
|
|
83
88
|
});
|
|
89
|
+
// Shared gate for every command that reduces or removes beam's visibility on this device
|
|
90
|
+
// (stopping/uninstalling the service, detaching an agent's hook, or logging out entirely). On an
|
|
91
|
+
// enrolled device these are exactly the actions an employee could otherwise use to go dark to
|
|
92
|
+
// their org's dashboard without anyone knowing -- see tamper.ts. So each one prints what will
|
|
93
|
+
// actually happen and, when enrolled, that the org admin will be notified this device did it,
|
|
94
|
+
// then requires an explicit "Yes" (or --yes, for scripted/CI use) before doing anything. There is
|
|
95
|
+
// no path to run the underlying action without either declining here or accepting the notice: the
|
|
96
|
+
// action and the report are the same decision, not two.
|
|
97
|
+
async function confirmControlAction(message, identity, yes) {
|
|
98
|
+
console.log(identity
|
|
99
|
+
? `${message}\nYour organization admin will be notified that this device (${identity.hostname}) did this.`
|
|
100
|
+
: message);
|
|
101
|
+
if (yes)
|
|
102
|
+
return true;
|
|
103
|
+
return promptYesNo("\nProceed?", false);
|
|
104
|
+
}
|
|
84
105
|
const service = program.command("service").description("Run the collector as a background service (launchd on macOS, systemd --user on Linux)");
|
|
85
106
|
service.command("install")
|
|
86
107
|
.description("Install and start the collector as a background service that survives reboots")
|
|
87
108
|
.option("-p, --port <port>", "port to listen on")
|
|
88
109
|
.action(async (options) => {
|
|
89
110
|
const result = await installService({ port: options.port ? Number(options.port) : undefined });
|
|
90
|
-
|
|
111
|
+
const up = await waitForCollectorUp(result.port ?? 4319);
|
|
112
|
+
if (up) {
|
|
113
|
+
console.log(`✔ Installed and started the beam service (${result.platform})\n config: ${result.configPath}`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(`✖ Installed the beam service (${result.platform}), but it isn't responding on port ${result.port ?? 4319} yet.\n config: ${result.configPath}\n Check the logs: beam service logs`);
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
}
|
|
91
119
|
});
|
|
92
120
|
service.command("uninstall")
|
|
93
121
|
.description("Stop and remove the background service")
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
.option("--yes", "skip the confirmation prompt")
|
|
123
|
+
.action(async (options) => {
|
|
124
|
+
const identity = await readIdentity();
|
|
125
|
+
const proceed = await confirmControlAction("This will stop and remove beam's background service. It will no longer observe or policy-enforce AI agent activity on this device.", identity, options.yes);
|
|
126
|
+
if (!proceed) {
|
|
127
|
+
console.log("Cancelled.");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (identity)
|
|
131
|
+
await reportControlEvent("service-uninstall", `Beam service removed on ${identity.hostname}`, "This device ran 'beam service uninstall'. Beam's background collector will no longer start automatically -- it is not observing or policy-enforcing until 'beam service install' runs again.");
|
|
132
|
+
await uninstallService();
|
|
133
|
+
console.log("✔ Removed the beam service.");
|
|
134
|
+
});
|
|
135
|
+
service.command("start")
|
|
136
|
+
.description("Start the installed background service")
|
|
137
|
+
.action(async () => {
|
|
138
|
+
await startService();
|
|
139
|
+
const port = await configuredPort();
|
|
140
|
+
const up = await waitForCollectorUp(port);
|
|
141
|
+
if (up)
|
|
142
|
+
console.log("✔ Started.");
|
|
143
|
+
else {
|
|
144
|
+
console.log(`✖ Told to start, but the collector isn't responding on port ${port} yet.\n Check the logs: beam service logs`);
|
|
145
|
+
process.exitCode = 1;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
service.command("stop")
|
|
149
|
+
.description("Stop the installed background service")
|
|
150
|
+
.option("--yes", "skip the confirmation prompt")
|
|
151
|
+
.action(async (options) => {
|
|
152
|
+
const identity = await readIdentity();
|
|
153
|
+
const proceed = await confirmControlAction("This will stop beam's background service. It will not observe or policy-enforce AI agent activity on this device until it's started again.", identity, options.yes);
|
|
154
|
+
if (!proceed) {
|
|
155
|
+
console.log("Cancelled.");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (identity)
|
|
159
|
+
await reportControlEvent("service-stop", `Beam service stopped on ${identity.hostname}`, "This device ran 'beam service stop'. Beam's background collector is not running -- it is not observing or policy-enforcing until it's started again.");
|
|
160
|
+
await stopService();
|
|
161
|
+
console.log("✔ Stopped.");
|
|
162
|
+
});
|
|
97
163
|
service.command("status")
|
|
98
164
|
.description("Show whether the background service is installed and running")
|
|
99
165
|
.action(async () => {
|
|
@@ -126,27 +192,12 @@ program.command("studio")
|
|
|
126
192
|
openBrowser(`${origin}/?token=${encodeURIComponent(token)}`);
|
|
127
193
|
console.log("Opening beam studio in your browser…");
|
|
128
194
|
});
|
|
129
|
-
program.command("enroll")
|
|
130
|
-
.description("Enroll this device with your Beam workspace using a code from your manager")
|
|
131
|
-
.requiredOption("--code <code>", "enrollment code, e.g. BEAM-XXXX-XXXX-XXXX")
|
|
132
|
-
.option("--url <url>", "Beam API base URL (default $BEAM_API_URL or https://collector.agentbeam.com)")
|
|
133
|
-
.action(async (options) => {
|
|
134
|
-
const existing = await readIdentity();
|
|
135
|
-
if (existing)
|
|
136
|
-
console.error(`Replacing the existing enrollment (device ${existing.deviceId}).`);
|
|
137
|
-
const identity = await enrollDevice(options);
|
|
138
|
-
console.log(`${green("✔")} Enrolled device ${identity.deviceId}\n` +
|
|
139
|
-
` org: ${identity.orgId}\n` +
|
|
140
|
-
` api: ${identity.apiBase}\n` +
|
|
141
|
-
` identity: ${getIdentityPath()} (0600)\n\n` +
|
|
142
|
-
cyan(`Next: beam agent install-all && beam start`));
|
|
143
|
-
});
|
|
144
195
|
program.command("whoami")
|
|
145
196
|
.description("Show this device's enrollment, if any")
|
|
146
197
|
.action(async () => {
|
|
147
198
|
const identity = await readIdentity();
|
|
148
199
|
if (!identity)
|
|
149
|
-
throw new Error("This device is not enrolled. Run 'beam
|
|
200
|
+
throw new Error("This device is not enrolled. Run 'beam setup'.");
|
|
150
201
|
console.log(`device: ${identity.deviceId}\norg: ${identity.orgId}\napi: ${identity.apiBase}\nhost: ${identity.hostname} (${identity.os})\nsince: ${identity.enrolledAt}`);
|
|
151
202
|
});
|
|
152
203
|
program.command("account")
|
|
@@ -154,7 +205,7 @@ program.command("account")
|
|
|
154
205
|
.action(async () => {
|
|
155
206
|
const identity = await readIdentity();
|
|
156
207
|
if (!identity)
|
|
157
|
-
throw new Error("This device is not enrolled. Run 'beam
|
|
208
|
+
throw new Error("This device is not enrolled. Run 'beam setup'.");
|
|
158
209
|
const account = await fetchAccount(identity);
|
|
159
210
|
console.log(`user: ${account.user.email}${account.user.name ? ` (${account.user.name})` : ""}\n` +
|
|
160
211
|
`org: ${account.org.name} (${account.org.slug})\n` +
|
|
@@ -163,12 +214,22 @@ program.command("account")
|
|
|
163
214
|
});
|
|
164
215
|
program.command("logout")
|
|
165
216
|
.description("Disconnect this device from your Beam workspace and remove local credentials")
|
|
166
|
-
.
|
|
217
|
+
.option("--yes", "skip the confirmation prompt")
|
|
218
|
+
.action(async (options) => {
|
|
167
219
|
const identity = await readIdentity();
|
|
168
220
|
if (!identity) {
|
|
169
221
|
console.log("This device is not enrolled -- nothing to do.");
|
|
170
222
|
return;
|
|
171
223
|
}
|
|
224
|
+
const proceed = await confirmControlAction("This will disconnect this device from your Beam workspace. It will stop reporting activity until it's re-enrolled.", identity, options.yes);
|
|
225
|
+
if (!proceed) {
|
|
226
|
+
console.log("Cancelled.");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Sent while identity.json still exists, so it authenticates -- and before revokeDevice()
|
|
230
|
+
// below, so it's an authenticated "I chose to log out" record rather than something an admin
|
|
231
|
+
// has to infer from the device simply going quiet (see tamper.ts).
|
|
232
|
+
await reportControlEvent("logout", `Beam logged out on ${identity.hostname}`, `This device was logged out via 'beam logout'. It is no longer enrolled and will stop reporting until re-enrolled.`);
|
|
172
233
|
try {
|
|
173
234
|
await revokeDevice(identity);
|
|
174
235
|
}
|
|
@@ -186,18 +247,23 @@ program.command("uninstall")
|
|
|
186
247
|
.action(async (options) => {
|
|
187
248
|
const beamHome = getBeamHome();
|
|
188
249
|
const dataDir = getDataDirectory();
|
|
189
|
-
|
|
250
|
+
const identityBeforeUninstall = await readIdentity();
|
|
251
|
+
const proceed = await confirmControlAction("This will remove:\n" +
|
|
190
252
|
" - the beam background service (if installed)\n" +
|
|
191
253
|
" - beam's hooks from every AI agent config it was wired into\n" +
|
|
192
254
|
` - ${beamHome} (identity, cached policy, event logs, custom rules)\n` +
|
|
193
255
|
" - this device's registration with your Beam workspace" +
|
|
194
|
-
(options.keepPackage ? "" : "\n - the globally installed beam (and beam-enterprise, if present) npm package"));
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
256
|
+
(options.keepPackage ? "" : "\n - the globally installed beam (and beam-enterprise, if present) npm package"), identityBeforeUninstall, options.yes);
|
|
257
|
+
if (!proceed) {
|
|
258
|
+
console.log("Cancelled.");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Sent first, before anything below touches identity.json, the service, or hooks -- so the
|
|
262
|
+
// workspace has an authenticated "this was a deliberate 'beam uninstall'" record even if a
|
|
263
|
+
// later step in this command fails partway through. See tamper.ts: this is what distinguishes
|
|
264
|
+
// an authorized removal from the silent kind checkForTampering() is built to catch.
|
|
265
|
+
if (identityBeforeUninstall) {
|
|
266
|
+
await reportControlEvent("uninstall", `Beam uninstalled on ${identityBeforeUninstall.hostname}`, "This device ran 'beam uninstall'. Its background service, agent hooks, and local data are being removed, and its registration is being revoked.");
|
|
201
267
|
}
|
|
202
268
|
try {
|
|
203
269
|
await uninstallService();
|
|
@@ -206,14 +272,23 @@ program.command("uninstall")
|
|
|
206
272
|
catch (e) {
|
|
207
273
|
console.log(`— Skipped background service (${e.message}).`);
|
|
208
274
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
275
|
+
// Wrapped like uninstallService() above: one agent's hook config being unreadable (invalid
|
|
276
|
+
// JSON, a permission error) must not abort the rest of 'beam uninstall' -- in particular it
|
|
277
|
+
// must not skip revokeDevice() below, or the device is wiped locally but the dashboard shows
|
|
278
|
+
// it as connected forever with nothing left running here to ever tell it otherwise.
|
|
279
|
+
try {
|
|
280
|
+
const hookResults = await uninstallAllHooks();
|
|
281
|
+
const removedHooks = hookResults.filter(r => r.removed);
|
|
282
|
+
if (removedHooks.length) {
|
|
283
|
+
for (const r of removedHooks)
|
|
284
|
+
console.log(`✔ Removed beam's hook from ${r.agent}\n → ${r.path}`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
console.log("— No installed agent hooks found.");
|
|
288
|
+
}
|
|
214
289
|
}
|
|
215
|
-
|
|
216
|
-
console.log(
|
|
290
|
+
catch (e) {
|
|
291
|
+
console.log(`— Could not remove every agent hook (${e.message}).`);
|
|
217
292
|
}
|
|
218
293
|
const identity = await readIdentity();
|
|
219
294
|
if (identity) {
|
|
@@ -254,11 +329,37 @@ program.command("sync")
|
|
|
254
329
|
.action(async () => {
|
|
255
330
|
const result = await syncPolicy();
|
|
256
331
|
if (result.status === "not-enrolled")
|
|
257
|
-
throw new Error("This device is not enrolled. Run 'beam
|
|
332
|
+
throw new Error("This device is not enrolled. Run 'beam setup'.");
|
|
258
333
|
if (result.status === "unreachable")
|
|
259
334
|
throw new Error("Could not reach the Beam workspace. Check the network or 'beam whoami'.");
|
|
260
335
|
console.log(result.status === "updated" ? `✔ Policy v${result.version} synced → ${result.path}` : "✔ Policy already up to date.");
|
|
261
336
|
});
|
|
337
|
+
program.command("update")
|
|
338
|
+
.description("Update the globally installed beam CLI to the latest published version")
|
|
339
|
+
.option("--check", "only check for an update, don't install it")
|
|
340
|
+
.action(async (options) => {
|
|
341
|
+
let check;
|
|
342
|
+
try {
|
|
343
|
+
check = await checkForUpdate(packageJson.version);
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
throw new Error(`Could not check for updates: ${e.message}`);
|
|
347
|
+
}
|
|
348
|
+
if (check.upToDate) {
|
|
349
|
+
console.log(`✔ beam is up to date (v${check.current}).`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
console.log(`A new version is available: v${check.current} → v${check.latest}`);
|
|
353
|
+
if (options.check)
|
|
354
|
+
return;
|
|
355
|
+
try {
|
|
356
|
+
await installUpdate(check.latest);
|
|
357
|
+
console.log(`✔ Updated to v${check.latest}. Restart any running beam service to pick it up: 'beam service stop && beam service start'.`);
|
|
358
|
+
}
|
|
359
|
+
catch (e) {
|
|
360
|
+
throw new Error(`Could not install the update automatically: ${e.message}\n Run manually: npm install -g @agent-beam/beam@${check.latest}`);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
262
363
|
const shims = program.command("shims").description("Make agent CLIs (claude, codex, ...) sandboxed automatically, without typing 'beam run'");
|
|
263
364
|
shims.command("install")
|
|
264
365
|
.description("Install PATH shims for the given agents (default: all known agents found on this machine)")
|
|
@@ -345,8 +446,26 @@ agent.command("install")
|
|
|
345
446
|
.argument("<agent>", "agent id, e.g. claude-code, codex, cursor, copilot-cli")
|
|
346
447
|
.action(async (agentId) => {
|
|
347
448
|
const result = await installHook(agentId);
|
|
449
|
+
await recordExpectedHooks();
|
|
348
450
|
console.log(result.alreadyInstalled ? `✔ Already installed for ${result.agent}\n → ${result.path}` : `✔ Installed beam hook for ${result.agent}\n → ${result.path}`);
|
|
349
451
|
});
|
|
452
|
+
agent.command("uninstall")
|
|
453
|
+
.description("Detach beam's hook from a single agent's own hook config, leaving everything else installed")
|
|
454
|
+
.argument("<agent>", "agent id, e.g. claude-code, codex, cursor, copilot-cli")
|
|
455
|
+
.option("--yes", "skip the confirmation prompt")
|
|
456
|
+
.action(async (agentId, options) => {
|
|
457
|
+
const identity = await readIdentity();
|
|
458
|
+
const proceed = await confirmControlAction(`This will detach beam's hook from ${agentId}. Its activity will no longer be observed or policy-enforced by beam.`, identity, options.yes);
|
|
459
|
+
if (!proceed) {
|
|
460
|
+
console.log("Cancelled.");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (identity)
|
|
464
|
+
await reportControlEvent(`agent-uninstall-${agentId}`, `Beam hook detached from ${agentId} on ${identity.hostname}`, `This device ran 'beam agent uninstall ${agentId}'. ${agentId} is no longer being observed or policy-enforced by beam.`);
|
|
465
|
+
const result = await uninstallHook(agentId);
|
|
466
|
+
await recordExpectedHooks();
|
|
467
|
+
console.log(result.removed ? `✔ Detached beam's hook from ${result.agent}\n → ${result.path}` : `— No beam hook found for ${result.agent}.`);
|
|
468
|
+
});
|
|
350
469
|
agent.command("extract")
|
|
351
470
|
.description("Forensic extraction: read an agent's existing session/transcript files directly, no hook or live capture required")
|
|
352
471
|
.argument("<agent>", `agent id (currently: claude-code, codex)`)
|
|
@@ -369,6 +488,7 @@ agent.command("install-all")
|
|
|
369
488
|
.description("Detect which agents are actually installed on this machine and wire beam's hook into all of them")
|
|
370
489
|
.action(async () => {
|
|
371
490
|
const results = await installAllDetectedHooks();
|
|
491
|
+
await recordExpectedHooks();
|
|
372
492
|
for (const r of results) {
|
|
373
493
|
if (r.status === "installed")
|
|
374
494
|
console.log(`✔ Installed for ${r.name}\n → ${r.path}`);
|
|
@@ -425,6 +545,52 @@ rule.command("reload")
|
|
|
425
545
|
for (const e of result.errors)
|
|
426
546
|
console.log(`✖ ${e}`);
|
|
427
547
|
});
|
|
548
|
+
const trust = program.command("trust").description("Manage the global allowlist of outside-workspace paths Beam won't ask about again (advisory/enforce mode only)");
|
|
549
|
+
trust.command("add <path>")
|
|
550
|
+
.description("Pre-approve a path or directory (or a glob containing '*') so Beam stops asking about it")
|
|
551
|
+
.action(async (path) => {
|
|
552
|
+
await addTrustedPath(path);
|
|
553
|
+
console.log(`✔ Trusted: ${path}`);
|
|
554
|
+
});
|
|
555
|
+
trust.command("remove <path>")
|
|
556
|
+
.alias("rm")
|
|
557
|
+
.description("Remove a path from the trusted allowlist")
|
|
558
|
+
.action(async (path) => {
|
|
559
|
+
await removeTrustedPath(path);
|
|
560
|
+
console.log(`✔ Untrusted: ${path}`);
|
|
561
|
+
});
|
|
562
|
+
trust.command("list")
|
|
563
|
+
.alias("ls")
|
|
564
|
+
.description("List all trusted outside-workspace paths")
|
|
565
|
+
.action(async () => {
|
|
566
|
+
const paths = await readTrustedPaths();
|
|
567
|
+
if (!paths.length) {
|
|
568
|
+
console.log(`No trusted paths (${trustedPathsFile()}).`);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
for (const p of paths)
|
|
572
|
+
console.log(p);
|
|
573
|
+
});
|
|
574
|
+
const block = program.command("block").description("Audit or undo entries added by the 'Block always' choice on a warn prompt");
|
|
575
|
+
block.command("remove <target>")
|
|
576
|
+
.alias("rm")
|
|
577
|
+
.description("Remove a target from the blocked list")
|
|
578
|
+
.action(async (target) => {
|
|
579
|
+
await removeBlockedTarget(target);
|
|
580
|
+
console.log(`✔ Unblocked: ${target}`);
|
|
581
|
+
});
|
|
582
|
+
block.command("list")
|
|
583
|
+
.alias("ls")
|
|
584
|
+
.description("List all blocked targets")
|
|
585
|
+
.action(async () => {
|
|
586
|
+
const targets = await readBlockedTargets();
|
|
587
|
+
if (!targets.length) {
|
|
588
|
+
console.log(`No blocked targets (${blockedTargetsFile()}).`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
for (const t of targets)
|
|
592
|
+
console.log(t);
|
|
593
|
+
});
|
|
428
594
|
try {
|
|
429
595
|
await program.parseAsync();
|
|
430
596
|
}
|