@agent-beam/beam 0.2.20 → 0.2.22
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/cli.js +289 -41
- package/dist/cli.js.map +1 -1
- package/dist/client.js +23 -6
- package/dist/client.js.map +1 -1
- package/dist/connect.js +1 -1
- 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 +2 -0
- package/dist/forward.js +19 -0
- package/dist/forward.js.map +1 -1
- package/dist/hook-adapters.js +14 -1
- package/dist/hook-adapters.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.d.ts +2 -0
- package/dist/serve.js +47 -2
- package/dist/serve.js.map +1 -1
- package/dist/setup.js +33 -2
- package/dist/setup.js.map +1 -1
- package/dist/skill-context.d.ts +11 -0
- package/dist/skill-context.js +78 -0
- package/dist/skill-context.js.map +1 -0
- package/dist/skill-gate.d.ts +21 -0
- package/dist/skill-gate.js +180 -0
- package/dist/skill-gate.js.map +1 -0
- package/dist/skill-inventory.d.ts +81 -0
- package/dist/skill-inventory.js +167 -0
- package/dist/skill-inventory.js.map +1 -0
- package/dist/skill-risk-rules.d.ts +17 -0
- package/dist/skill-risk-rules.js +61 -0
- package/dist/skill-risk-rules.js.map +1 -0
- package/dist/skill-scan.d.ts +27 -0
- package/dist/skill-scan.js +157 -0
- package/dist/skill-scan.js.map +1 -0
- package/dist/skill-setup.d.ts +11 -0
- package/dist/skill-setup.js +26 -0
- package/dist/skill-setup.js.map +1 -0
- package/dist/skill-sync.d.ts +40 -0
- package/dist/skill-sync.js +79 -0
- package/dist/skill-sync.js.map +1 -0
- package/dist/skill-watch.d.ts +16 -0
- package/dist/skill-watch.js +41 -0
- package/dist/skill-watch.js.map +1 -0
- package/dist/skills.d.ts +33 -0
- package/dist/skills.js +139 -0
- package/dist/skills.js.map +1 -0
- package/dist/tamper.d.ts +9 -0
- package/dist/tamper.js +116 -0
- package/dist/tamper.js.map +1 -0
- package/dist/trusted-skills.d.ts +9 -0
- package/dist/trusted-skills.js +31 -0
- package/dist/trusted-skills.js.map +1 -0
- package/dist/tty-prompt.js +3 -1
- package/dist/tty-prompt.js.map +1 -1
- package/package.json +2 -2
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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"}
|
package/dist/cli.js
CHANGED
|
@@ -6,11 +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";
|
|
9
|
+
import { installAllDetectedHooks, installHook, uninstallAllHooks, uninstallHook } from "./install.js";
|
|
10
10
|
import { configuredPort, installService, serviceLogPaths, serviceStatus, startService, stopService, uninstallService } from "./service.js";
|
|
11
11
|
import { waitForCollectorUp } from "./collector-health.js";
|
|
12
12
|
import { getBeamHome, getCollectorUrl, getDataDirectory, getIdentityPath } from "./config.js";
|
|
13
|
-
import { clearIdentity,
|
|
13
|
+
import { clearIdentity, readIdentity } from "./enroll.js";
|
|
14
14
|
import { startConnect } from "./connect.js";
|
|
15
15
|
import { runSetup, promptYesNo } from "./setup.js";
|
|
16
16
|
import { installEnterprisePackage } from "./enterprise-install.js";
|
|
@@ -22,8 +22,13 @@ import { loadCustomRules } from "./custom-rules.js";
|
|
|
22
22
|
import { sequenceRuleCatalog } from "./sequences.js";
|
|
23
23
|
import { printBanner } from "./banner.js";
|
|
24
24
|
import { runWithSudoFallback } from "./elevate.js";
|
|
25
|
-
import { renderColumns, withSpinner } from "./prompts.js";
|
|
26
|
-
import { cyan, green } from "./color.js";
|
|
25
|
+
import { confirm, renderColumns, withSpinner } from "./prompts.js";
|
|
26
|
+
import { cyan, dim, green, red } from "./color.js";
|
|
27
|
+
import { ALL_SKILL_AGENTS, addSkill, isBlocked, scanInstalledSkills } from "./skills.js";
|
|
28
|
+
import { scanSkillDir } from "./skill-scan.js";
|
|
29
|
+
import { syncSkillInventory } from "./skill-sync.js";
|
|
30
|
+
import { agentSkillRoots, deviceSkillsScore, indexSkills, inventoryFile, readInventory, trustedHashes } from "./skill-inventory.js";
|
|
31
|
+
import { readTrustedSkills, trustSkill, untrustSkill } from "./trusted-skills.js";
|
|
27
32
|
import { runAgent } from "./run.js";
|
|
28
33
|
import { AGENT_BINARIES, installShims, listShims, pathExportLine, shimDir, uninstallShims } from "./shims.js";
|
|
29
34
|
import { runMcpProxy } from "./mcp-proxy.js";
|
|
@@ -31,6 +36,7 @@ import { registerJevCommands } from "./jev-cli.js";
|
|
|
31
36
|
import { addTrustedPath, readTrustedPaths, removeTrustedPath, trustedPathsFile } from "./trusted-paths.js";
|
|
32
37
|
import { blockedTargetsFile, readBlockedTargets, removeBlockedTarget } from "./blocked-targets.js";
|
|
33
38
|
import { checkForUpdate, installUpdate } from "./update.js";
|
|
39
|
+
import { recordExpectedHooks, reportControlEvent } from "./tamper.js";
|
|
34
40
|
// Lets 'BEAM_API_URL=... beam connect' style overrides live in a .env file instead of the
|
|
35
41
|
// shell profile. Checked in cwd first (handy when developing from the repo), then in
|
|
36
42
|
// ~/.beam so overrides still apply when 'beam' is run globally from any directory.
|
|
@@ -74,7 +80,7 @@ program.command("start")
|
|
|
74
80
|
const rulesNote = result.customRules.loaded ? `\nCustom rules loaded: ${result.customRules.loaded} from ${result.customRules.path}` : "";
|
|
75
81
|
for (const message of result.customRules.errors)
|
|
76
82
|
console.error(`✖ ${message}`);
|
|
77
|
-
const policyNote = result.policySync ? "\nPolicy sync: on (every 60s)" : "\nPolicy sync: off (device not enrolled — run 'beam
|
|
83
|
+
const policyNote = result.policySync ? "\nPolicy sync: on (every 60s)" : "\nPolicy sync: off (device not enrolled — run 'beam setup')";
|
|
78
84
|
const newlyInstalled = result.agentInstalls.filter(r => r.status === "installed");
|
|
79
85
|
const installNote = newlyInstalled.length
|
|
80
86
|
? `\nAgent hooks installed: ${newlyInstalled.map(r => r.name).join(", ")}`
|
|
@@ -85,6 +91,22 @@ program.command("start")
|
|
|
85
91
|
}
|
|
86
92
|
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.`);
|
|
87
93
|
});
|
|
94
|
+
// Shared gate for every command that reduces or removes beam's visibility on this device
|
|
95
|
+
// (stopping/uninstalling the service, detaching an agent's hook, or logging out entirely). On an
|
|
96
|
+
// enrolled device these are exactly the actions an employee could otherwise use to go dark to
|
|
97
|
+
// their org's dashboard without anyone knowing -- see tamper.ts. So each one prints what will
|
|
98
|
+
// actually happen and, when enrolled, that the org admin will be notified this device did it,
|
|
99
|
+
// then requires an explicit "Yes" (or --yes, for scripted/CI use) before doing anything. There is
|
|
100
|
+
// no path to run the underlying action without either declining here or accepting the notice: the
|
|
101
|
+
// action and the report are the same decision, not two.
|
|
102
|
+
async function confirmControlAction(message, identity, yes) {
|
|
103
|
+
console.log(identity
|
|
104
|
+
? `${message}\nYour organization admin will be notified that this device (${identity.hostname}) did this.`
|
|
105
|
+
: message);
|
|
106
|
+
if (yes)
|
|
107
|
+
return true;
|
|
108
|
+
return promptYesNo("\nProceed?", false);
|
|
109
|
+
}
|
|
88
110
|
const service = program.command("service").description("Run the collector as a background service (launchd on macOS, systemd --user on Linux)");
|
|
89
111
|
service.command("install")
|
|
90
112
|
.description("Install and start the collector as a background service that survives reboots")
|
|
@@ -102,7 +124,19 @@ service.command("install")
|
|
|
102
124
|
});
|
|
103
125
|
service.command("uninstall")
|
|
104
126
|
.description("Stop and remove the background service")
|
|
105
|
-
.
|
|
127
|
+
.option("--yes", "skip the confirmation prompt")
|
|
128
|
+
.action(async (options) => {
|
|
129
|
+
const identity = await readIdentity();
|
|
130
|
+
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);
|
|
131
|
+
if (!proceed) {
|
|
132
|
+
console.log("Cancelled.");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (identity)
|
|
136
|
+
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.");
|
|
137
|
+
await uninstallService();
|
|
138
|
+
console.log("✔ Removed the beam service.");
|
|
139
|
+
});
|
|
106
140
|
service.command("start")
|
|
107
141
|
.description("Start the installed background service")
|
|
108
142
|
.action(async () => {
|
|
@@ -116,7 +150,21 @@ service.command("start")
|
|
|
116
150
|
process.exitCode = 1;
|
|
117
151
|
}
|
|
118
152
|
});
|
|
119
|
-
service.command("stop")
|
|
153
|
+
service.command("stop")
|
|
154
|
+
.description("Stop the installed background service")
|
|
155
|
+
.option("--yes", "skip the confirmation prompt")
|
|
156
|
+
.action(async (options) => {
|
|
157
|
+
const identity = await readIdentity();
|
|
158
|
+
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);
|
|
159
|
+
if (!proceed) {
|
|
160
|
+
console.log("Cancelled.");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (identity)
|
|
164
|
+
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.");
|
|
165
|
+
await stopService();
|
|
166
|
+
console.log("✔ Stopped.");
|
|
167
|
+
});
|
|
120
168
|
service.command("status")
|
|
121
169
|
.description("Show whether the background service is installed and running")
|
|
122
170
|
.action(async () => {
|
|
@@ -149,27 +197,12 @@ program.command("studio")
|
|
|
149
197
|
openBrowser(`${origin}/?token=${encodeURIComponent(token)}`);
|
|
150
198
|
console.log("Opening beam studio in your browser…");
|
|
151
199
|
});
|
|
152
|
-
program.command("enroll")
|
|
153
|
-
.description("Enroll this device with your Beam workspace using a code from your manager")
|
|
154
|
-
.requiredOption("--code <code>", "enrollment code, e.g. BEAM-XXXX-XXXX-XXXX")
|
|
155
|
-
.option("--url <url>", "Beam API base URL (default $BEAM_API_URL or https://collector.agentbeam.com)")
|
|
156
|
-
.action(async (options) => {
|
|
157
|
-
const existing = await readIdentity();
|
|
158
|
-
if (existing)
|
|
159
|
-
console.error(`Replacing the existing enrollment (device ${existing.deviceId}).`);
|
|
160
|
-
const identity = await enrollDevice(options);
|
|
161
|
-
console.log(`${green("✔")} Enrolled device ${identity.deviceId}\n` +
|
|
162
|
-
` org: ${identity.orgId}\n` +
|
|
163
|
-
` api: ${identity.apiBase}\n` +
|
|
164
|
-
` identity: ${getIdentityPath()} (0600)\n\n` +
|
|
165
|
-
cyan(`Next: beam agent install-all && beam start`));
|
|
166
|
-
});
|
|
167
200
|
program.command("whoami")
|
|
168
201
|
.description("Show this device's enrollment, if any")
|
|
169
202
|
.action(async () => {
|
|
170
203
|
const identity = await readIdentity();
|
|
171
204
|
if (!identity)
|
|
172
|
-
throw new Error("This device is not enrolled. Run 'beam
|
|
205
|
+
throw new Error("This device is not enrolled. Run 'beam setup'.");
|
|
173
206
|
console.log(`device: ${identity.deviceId}\norg: ${identity.orgId}\napi: ${identity.apiBase}\nhost: ${identity.hostname} (${identity.os})\nsince: ${identity.enrolledAt}`);
|
|
174
207
|
});
|
|
175
208
|
program.command("account")
|
|
@@ -177,7 +210,7 @@ program.command("account")
|
|
|
177
210
|
.action(async () => {
|
|
178
211
|
const identity = await readIdentity();
|
|
179
212
|
if (!identity)
|
|
180
|
-
throw new Error("This device is not enrolled. Run 'beam
|
|
213
|
+
throw new Error("This device is not enrolled. Run 'beam setup'.");
|
|
181
214
|
const account = await fetchAccount(identity);
|
|
182
215
|
console.log(`user: ${account.user.email}${account.user.name ? ` (${account.user.name})` : ""}\n` +
|
|
183
216
|
`org: ${account.org.name} (${account.org.slug})\n` +
|
|
@@ -186,12 +219,22 @@ program.command("account")
|
|
|
186
219
|
});
|
|
187
220
|
program.command("logout")
|
|
188
221
|
.description("Disconnect this device from your Beam workspace and remove local credentials")
|
|
189
|
-
.
|
|
222
|
+
.option("--yes", "skip the confirmation prompt")
|
|
223
|
+
.action(async (options) => {
|
|
190
224
|
const identity = await readIdentity();
|
|
191
225
|
if (!identity) {
|
|
192
226
|
console.log("This device is not enrolled -- nothing to do.");
|
|
193
227
|
return;
|
|
194
228
|
}
|
|
229
|
+
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);
|
|
230
|
+
if (!proceed) {
|
|
231
|
+
console.log("Cancelled.");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Sent while identity.json still exists, so it authenticates -- and before revokeDevice()
|
|
235
|
+
// below, so it's an authenticated "I chose to log out" record rather than something an admin
|
|
236
|
+
// has to infer from the device simply going quiet (see tamper.ts).
|
|
237
|
+
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.`);
|
|
195
238
|
try {
|
|
196
239
|
await revokeDevice(identity);
|
|
197
240
|
}
|
|
@@ -209,18 +252,23 @@ program.command("uninstall")
|
|
|
209
252
|
.action(async (options) => {
|
|
210
253
|
const beamHome = getBeamHome();
|
|
211
254
|
const dataDir = getDataDirectory();
|
|
212
|
-
|
|
255
|
+
const identityBeforeUninstall = await readIdentity();
|
|
256
|
+
const proceed = await confirmControlAction("This will remove:\n" +
|
|
213
257
|
" - the beam background service (if installed)\n" +
|
|
214
258
|
" - beam's hooks from every AI agent config it was wired into\n" +
|
|
215
259
|
` - ${beamHome} (identity, cached policy, event logs, custom rules)\n` +
|
|
216
260
|
" - this device's registration with your Beam workspace" +
|
|
217
|
-
(options.keepPackage ? "" : "\n - the globally installed beam (and beam-enterprise, if present) npm package"));
|
|
218
|
-
if (!
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
261
|
+
(options.keepPackage ? "" : "\n - the globally installed beam (and beam-enterprise, if present) npm package"), identityBeforeUninstall, options.yes);
|
|
262
|
+
if (!proceed) {
|
|
263
|
+
console.log("Cancelled.");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Sent first, before anything below touches identity.json, the service, or hooks -- so the
|
|
267
|
+
// workspace has an authenticated "this was a deliberate 'beam uninstall'" record even if a
|
|
268
|
+
// later step in this command fails partway through. See tamper.ts: this is what distinguishes
|
|
269
|
+
// an authorized removal from the silent kind checkForTampering() is built to catch.
|
|
270
|
+
if (identityBeforeUninstall) {
|
|
271
|
+
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.");
|
|
224
272
|
}
|
|
225
273
|
try {
|
|
226
274
|
await uninstallService();
|
|
@@ -229,14 +277,23 @@ program.command("uninstall")
|
|
|
229
277
|
catch (e) {
|
|
230
278
|
console.log(`— Skipped background service (${e.message}).`);
|
|
231
279
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
280
|
+
// Wrapped like uninstallService() above: one agent's hook config being unreadable (invalid
|
|
281
|
+
// JSON, a permission error) must not abort the rest of 'beam uninstall' -- in particular it
|
|
282
|
+
// must not skip revokeDevice() below, or the device is wiped locally but the dashboard shows
|
|
283
|
+
// it as connected forever with nothing left running here to ever tell it otherwise.
|
|
284
|
+
try {
|
|
285
|
+
const hookResults = await uninstallAllHooks();
|
|
286
|
+
const removedHooks = hookResults.filter(r => r.removed);
|
|
287
|
+
if (removedHooks.length) {
|
|
288
|
+
for (const r of removedHooks)
|
|
289
|
+
console.log(`✔ Removed beam's hook from ${r.agent}\n → ${r.path}`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log("— No installed agent hooks found.");
|
|
293
|
+
}
|
|
237
294
|
}
|
|
238
|
-
|
|
239
|
-
console.log(
|
|
295
|
+
catch (e) {
|
|
296
|
+
console.log(`— Could not remove every agent hook (${e.message}).`);
|
|
240
297
|
}
|
|
241
298
|
const identity = await readIdentity();
|
|
242
299
|
if (identity) {
|
|
@@ -277,7 +334,7 @@ program.command("sync")
|
|
|
277
334
|
.action(async () => {
|
|
278
335
|
const result = await syncPolicy();
|
|
279
336
|
if (result.status === "not-enrolled")
|
|
280
|
-
throw new Error("This device is not enrolled. Run 'beam
|
|
337
|
+
throw new Error("This device is not enrolled. Run 'beam setup'.");
|
|
281
338
|
if (result.status === "unreachable")
|
|
282
339
|
throw new Error("Could not reach the Beam workspace. Check the network or 'beam whoami'.");
|
|
283
340
|
console.log(result.status === "updated" ? `✔ Policy v${result.version} synced → ${result.path}` : "✔ Policy already up to date.");
|
|
@@ -366,6 +423,178 @@ program.command("scan")
|
|
|
366
423
|
.option("--mcp", "treat the file as an MCP configuration instead of a skill")
|
|
367
424
|
.option("--save", "also save the report to the running collector")
|
|
368
425
|
.action(async (file, options) => console.log(JSON.stringify(await scanFile(file, options), null, 2)));
|
|
426
|
+
function printSkillScan(scan) {
|
|
427
|
+
const badge = scan.risk === "critical" || scan.risk === "high" ? red(scan.risk.toUpperCase()) : scan.risk === "medium" ? scan.risk : green(scan.findings.length ? scan.risk : "clean");
|
|
428
|
+
console.log(`\n${scan.name} [${badge}] risk score ${scan.score}/100 (${scan.level}) ${scan.filesScanned} files scanned`);
|
|
429
|
+
for (const f of scan.findings.slice(0, 10))
|
|
430
|
+
console.log(` ${f.severity.padEnd(8)} ${f.title} ${dim(`${f.file}${f.line ? `:${f.line}` : ""}`)}\n ${dim(f.evidence.split("\n")[0].slice(0, 100))}`);
|
|
431
|
+
if (scan.findings.length > 10)
|
|
432
|
+
console.log(dim(` … ${scan.findings.length - 10} more (use --json for all)`));
|
|
433
|
+
for (const s of scan.skipped)
|
|
434
|
+
console.log(dim(` skipped ${s}`));
|
|
435
|
+
}
|
|
436
|
+
const add = program.command("add").description("Add reusable AI assets, scanned before they are installed");
|
|
437
|
+
add.command("skill")
|
|
438
|
+
.description("Download a skill, scan it for prompt injection and risky instructions, then install it only if it passes")
|
|
439
|
+
.argument("<source>", "owner/repo, a GitHub/GitLab https URL, or a local folder")
|
|
440
|
+
.option("--skill <name>", "pick one skill when the source contains several")
|
|
441
|
+
.option("--agent <agent...>", `target agent(s): ${ALL_SKILL_AGENTS.join(", ")} (default: claude-code)`)
|
|
442
|
+
.option("--all-agents", "install for every supported agent")
|
|
443
|
+
.option("-g, --global", "install for your user instead of the current project")
|
|
444
|
+
.option("--force", "install even when the scan finds prompt injection or critical risk")
|
|
445
|
+
.option("--scan-only", "scan without installing anything")
|
|
446
|
+
.option("--json", "print the scan result as JSON")
|
|
447
|
+
.action(async (source, options) => {
|
|
448
|
+
let result;
|
|
449
|
+
try {
|
|
450
|
+
result = await withSpinner(`Fetching and scanning ${source}`, () => addSkill(source, options));
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
console.error(`✖ ${e instanceof Error ? e.message : e}`);
|
|
454
|
+
process.exitCode = 1;
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (options.json) {
|
|
458
|
+
console.log(JSON.stringify(result, null, 2));
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
result.scans.forEach(printSkillScan);
|
|
462
|
+
console.log("");
|
|
463
|
+
for (const i of result.installed)
|
|
464
|
+
console.log(`${green("✔")} Installed ${i.name} → ${i.path}`);
|
|
465
|
+
if (result.blocked.length)
|
|
466
|
+
console.error(`✖ Not installed (prompt injection or critical risk): ${result.blocked.join(", ")}\n Review the findings above. If you trust the source, re-run with --force.`);
|
|
467
|
+
if (options.scanOnly)
|
|
468
|
+
console.log(dim("Scan only: nothing was installed."));
|
|
469
|
+
}
|
|
470
|
+
if (result.blocked.length)
|
|
471
|
+
process.exitCode = 2;
|
|
472
|
+
});
|
|
473
|
+
const skills = program.command("skills").description("Inspect installed agent skills");
|
|
474
|
+
skills.command("scan")
|
|
475
|
+
.description("Scan installed skills (or one skill folder) for prompt injection and risky instructions")
|
|
476
|
+
.argument("[path]", "a skill folder; omit to scan every installed skill for the supported agents")
|
|
477
|
+
.option("--json", "print results as JSON")
|
|
478
|
+
.action(async (target, options) => {
|
|
479
|
+
const scans = target ? [await scanSkillDir(path.resolve(target), path.basename(path.resolve(target)))] : await scanInstalledSkills();
|
|
480
|
+
if (options.json)
|
|
481
|
+
console.log(JSON.stringify(scans, null, 2));
|
|
482
|
+
else if (!scans.length)
|
|
483
|
+
console.log("No installed skills found.");
|
|
484
|
+
else
|
|
485
|
+
scans.forEach(printSkillScan);
|
|
486
|
+
if (scans.some(isBlocked))
|
|
487
|
+
process.exitCode = 2;
|
|
488
|
+
});
|
|
489
|
+
skills.command("index")
|
|
490
|
+
.description("Find every skill on this machine (repos, nested folders, plugins), scan them, and write the inventory file")
|
|
491
|
+
.option("--root <dir...>", "folders to search instead of your home directory")
|
|
492
|
+
.option("--agents-only", "only check the agents' own skill folders (~/.claude, ~/.agents, ...), not the rest of your home directory")
|
|
493
|
+
.option("--depth <n>", "maximum folder depth (default 10)")
|
|
494
|
+
.option("--json", "print the inventory as JSON")
|
|
495
|
+
.action(async (options) => {
|
|
496
|
+
const roots = options.agentsOnly ? await agentSkillRoots() : options.root?.map(r => path.resolve(r));
|
|
497
|
+
if (!options.json && !roots)
|
|
498
|
+
console.error(dim("Searching your home folder for skills. macOS may ask you to allow access to Desktop, Documents or Downloads; use --agents-only to skip that."));
|
|
499
|
+
const search = () => indexSkills({ roots, maxDepth: options.depth ? Number(options.depth) : undefined });
|
|
500
|
+
const { inventory, changes } = options.json ? await search() : await withSpinner("Searching for skills", search);
|
|
501
|
+
if (options.json) {
|
|
502
|
+
console.log(JSON.stringify({ inventory, changes }, null, 2));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const risky = inventory.skills.filter(s => s.level === "HIGH" || s.level === "CRITICAL").length;
|
|
506
|
+
console.log(`${green("✔")} ${inventory.skills.length} skills indexed (${risky} high/critical) → ${inventoryFile()}`);
|
|
507
|
+
if (changes.added.length || changes.modified.length || changes.removed.length)
|
|
508
|
+
console.log(` ${changes.added.length} new, ${changes.modified.length} modified, ${changes.removed.length} removed since last index`);
|
|
509
|
+
if (inventory.truncated)
|
|
510
|
+
console.log(dim(" Search hit its time/size budget; some folders were not visited. Narrow it with --root."));
|
|
511
|
+
console.log(dim("Run 'beam skills list' to view them."));
|
|
512
|
+
// A new or changed skill is reported to the workspace right away (enrolled devices only).
|
|
513
|
+
const sent = await syncSkillInventory().catch(() => null);
|
|
514
|
+
if (sent?.status === "sent")
|
|
515
|
+
console.log(dim(`Reported ${sent.skills} skills to your workspace.`));
|
|
516
|
+
});
|
|
517
|
+
skills.command("list")
|
|
518
|
+
.alias("ls")
|
|
519
|
+
.description("List every skill in the inventory with its risk score (run 'beam skills index' first)")
|
|
520
|
+
.option("--risk <level>", "only show LOW, MEDIUM, HIGH or CRITICAL (that level and above)")
|
|
521
|
+
.option("--agent <agent>", "only show one agent, e.g. claude-code, codex, universal")
|
|
522
|
+
.option("--json", "print as JSON")
|
|
523
|
+
.action(async (options) => {
|
|
524
|
+
let inv = await readInventory();
|
|
525
|
+
// Nothing has searched beyond the agents' own folders yet. Ask here, at the moment the list is
|
|
526
|
+
// wanted, rather than at install or startup -- this is what makes macOS ask for folder access.
|
|
527
|
+
if (!inv?.lastFullScanAt && process.stdin.isTTY && process.stdout.isTTY && !options.json) {
|
|
528
|
+
const ok = await confirm(`${inv ? "Skills inside your project folders haven't been searched." : "No skill list yet."} Search your home folder now? (macOS may ask for access to Desktop, Documents and Downloads)`, true);
|
|
529
|
+
if (ok) {
|
|
530
|
+
await withSpinner("Searching for skills", () => indexSkills());
|
|
531
|
+
inv = await readInventory();
|
|
532
|
+
}
|
|
533
|
+
else if (!inv) {
|
|
534
|
+
inv = (await indexSkills({ roots: await agentSkillRoots() })).inventory;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (!inv) {
|
|
538
|
+
console.error("No inventory yet. Run: beam skills index");
|
|
539
|
+
process.exitCode = 1;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const order = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
543
|
+
const min = options.risk ? order.indexOf(options.risk.toUpperCase()) : 0;
|
|
544
|
+
if (min < 0) {
|
|
545
|
+
console.error(`--risk must be one of ${order.join(", ")}`);
|
|
546
|
+
process.exitCode = 1;
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const trusted = await trustedHashes();
|
|
550
|
+
const rows = inv.skills.filter(s => order.indexOf(s.level) >= min && (!options.agent || s.agent === options.agent));
|
|
551
|
+
if (options.json) {
|
|
552
|
+
console.log(JSON.stringify(rows.map(s => ({ ...s, trusted: trusted.has(s.hash) })), null, 2));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
console.log(renderColumns(["skill", "agent", "scope", "risk", "trusted", "path"], rows.map(s => [s.name, s.agent, s.scope, `${s.score} ${s.level}${s.injection ? " ⚠inj" : ""}`, trusted.has(s.hash) ? "yes" : "", s.dir])));
|
|
556
|
+
console.log(dim(`\n${rows.length} of ${inv.skills.length} skills · indexed ${inv.generatedAt}${inv.truncated ? " (partial)" : ""} · ${inventoryFile()}`));
|
|
557
|
+
const device = deviceSkillsScore(inv.skills, trusted);
|
|
558
|
+
console.log(`Device skills score: ${device.score}/100${device.highCount ? ` (${device.highCount} untrusted high/critical skill${device.highCount > 1 ? "s" : ""})` : ""}`);
|
|
559
|
+
if (!inv.lastFullScanAt)
|
|
560
|
+
console.log(dim("Only the agents' own skill folders were checked. Run 'beam skills index' to search your project folders too."));
|
|
561
|
+
});
|
|
562
|
+
skills.command("sync")
|
|
563
|
+
.description("Send skill names and risk scores (never contents) to your workspace, if they changed since the last send")
|
|
564
|
+
.option("--force", "send even if nothing changed")
|
|
565
|
+
.action(async (options) => {
|
|
566
|
+
// Refreshes only the agents' own folders, so this never triggers a macOS folder-access prompt.
|
|
567
|
+
// Run 'beam skills index' first if you want project folders included.
|
|
568
|
+
const roots = await agentSkillRoots();
|
|
569
|
+
if (roots.length || !await readInventory())
|
|
570
|
+
await indexSkills({ roots });
|
|
571
|
+
const result = await syncSkillInventory({ force: options.force });
|
|
572
|
+
if (result.status === "sent")
|
|
573
|
+
console.log(`${green("✔")} Sent ${result.skills} skills to your workspace.`);
|
|
574
|
+
else if (result.status === "unchanged")
|
|
575
|
+
console.log("Nothing changed since the last send. Use --force to send anyway.");
|
|
576
|
+
else {
|
|
577
|
+
console.error(`✖ ${result.status === "not-enrolled" ? "This device isn't connected to a workspace. Run: beam setup" : result.reason ?? result.status}`);
|
|
578
|
+
process.exitCode = 1;
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
skills.command("trust")
|
|
582
|
+
.description("Mark a reviewed skill as trusted so the hook stops holding it for approval (pinned to its current content)")
|
|
583
|
+
.argument("<path>", "skill folder")
|
|
584
|
+
.action(async (target) => {
|
|
585
|
+
const dir = path.resolve(target);
|
|
586
|
+
const scan = await scanSkillDir(dir, path.basename(dir));
|
|
587
|
+
printSkillScan(scan);
|
|
588
|
+
await trustSkill(scan.name, scan.hash);
|
|
589
|
+
console.log(`\n${green("✔")} Trusted ${scan.name} (score ${scan.score}/100). Any later edit to it will be scanned again.`);
|
|
590
|
+
});
|
|
591
|
+
skills.command("untrust")
|
|
592
|
+
.description("Remove a skill from the trusted list")
|
|
593
|
+
.argument("<name-or-hash>")
|
|
594
|
+
.action(async (key) => console.log(`Removed ${await untrustSkill(key)} trusted skill(s).`));
|
|
595
|
+
skills.command("trusted")
|
|
596
|
+
.description("List trusted skills")
|
|
597
|
+
.action(async () => { const l = await readTrustedSkills(); console.log(l.length ? renderColumns(["name", "hash", "trusted"], l.map(t => [t.name, t.hash.slice(0, 12), t.trustedAt])) : "No trusted skills."); });
|
|
369
598
|
const mcp = program.command("mcp").description("Run and protect local MCP integrations");
|
|
370
599
|
mcp.command("proxy")
|
|
371
600
|
.description("Run an MCP server through Beam response filtering")
|
|
@@ -394,8 +623,26 @@ agent.command("install")
|
|
|
394
623
|
.argument("<agent>", "agent id, e.g. claude-code, codex, cursor, copilot-cli")
|
|
395
624
|
.action(async (agentId) => {
|
|
396
625
|
const result = await installHook(agentId);
|
|
626
|
+
await recordExpectedHooks();
|
|
397
627
|
console.log(result.alreadyInstalled ? `✔ Already installed for ${result.agent}\n → ${result.path}` : `✔ Installed beam hook for ${result.agent}\n → ${result.path}`);
|
|
398
628
|
});
|
|
629
|
+
agent.command("uninstall")
|
|
630
|
+
.description("Detach beam's hook from a single agent's own hook config, leaving everything else installed")
|
|
631
|
+
.argument("<agent>", "agent id, e.g. claude-code, codex, cursor, copilot-cli")
|
|
632
|
+
.option("--yes", "skip the confirmation prompt")
|
|
633
|
+
.action(async (agentId, options) => {
|
|
634
|
+
const identity = await readIdentity();
|
|
635
|
+
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);
|
|
636
|
+
if (!proceed) {
|
|
637
|
+
console.log("Cancelled.");
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (identity)
|
|
641
|
+
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.`);
|
|
642
|
+
const result = await uninstallHook(agentId);
|
|
643
|
+
await recordExpectedHooks();
|
|
644
|
+
console.log(result.removed ? `✔ Detached beam's hook from ${result.agent}\n → ${result.path}` : `— No beam hook found for ${result.agent}.`);
|
|
645
|
+
});
|
|
399
646
|
agent.command("extract")
|
|
400
647
|
.description("Forensic extraction: read an agent's existing session/transcript files directly, no hook or live capture required")
|
|
401
648
|
.argument("<agent>", `agent id (currently: claude-code, codex)`)
|
|
@@ -418,6 +665,7 @@ agent.command("install-all")
|
|
|
418
665
|
.description("Detect which agents are actually installed on this machine and wire beam's hook into all of them")
|
|
419
666
|
.action(async () => {
|
|
420
667
|
const results = await installAllDetectedHooks();
|
|
668
|
+
await recordExpectedHooks();
|
|
421
669
|
for (const r of results) {
|
|
422
670
|
if (r.status === "installed")
|
|
423
671
|
console.log(`✔ Installed for ${r.name}\n → ${r.path}`);
|