@agent-beam/beam 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +188 -0
  3. package/dist/agents.d.ts +26 -0
  4. package/dist/agents.js +96 -0
  5. package/dist/agents.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +234 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/client.d.ts +28 -0
  10. package/dist/client.js +176 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/config.d.ts +5 -0
  13. package/dist/config.js +23 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/core.d.ts +64 -0
  16. package/dist/core.js +176 -0
  17. package/dist/core.js.map +1 -0
  18. package/dist/custom-rules.d.ts +7 -0
  19. package/dist/custom-rules.js +80 -0
  20. package/dist/custom-rules.js.map +1 -0
  21. package/dist/enroll.d.ts +16 -0
  22. package/dist/enroll.js +68 -0
  23. package/dist/enroll.js.map +1 -0
  24. package/dist/extract.d.ts +6 -0
  25. package/dist/extract.js +148 -0
  26. package/dist/extract.js.map +1 -0
  27. package/dist/forward.d.ts +8 -0
  28. package/dist/forward.js +53 -0
  29. package/dist/forward.js.map +1 -0
  30. package/dist/hook-adapters.d.ts +2 -0
  31. package/dist/hook-adapters.js +36 -0
  32. package/dist/hook-adapters.js.map +1 -0
  33. package/dist/install.d.ts +15 -0
  34. package/dist/install.js +68 -0
  35. package/dist/install.js.map +1 -0
  36. package/dist/inventory.d.ts +12 -0
  37. package/dist/inventory.js +23 -0
  38. package/dist/inventory.js.map +1 -0
  39. package/dist/open-browser.d.ts +1 -0
  40. package/dist/open-browser.js +8 -0
  41. package/dist/open-browser.js.map +1 -0
  42. package/dist/os-monitor.d.ts +5 -0
  43. package/dist/os-monitor.js +26 -0
  44. package/dist/os-monitor.js.map +1 -0
  45. package/dist/policy.d.ts +27 -0
  46. package/dist/policy.js +59 -0
  47. package/dist/policy.js.map +1 -0
  48. package/dist/sequences.d.ts +13 -0
  49. package/dist/sequences.js +58 -0
  50. package/dist/sequences.js.map +1 -0
  51. package/dist/serve.d.ts +27 -0
  52. package/dist/serve.js +113 -0
  53. package/dist/serve.js.map +1 -0
  54. package/dist/server.d.ts +10 -0
  55. package/dist/server.js +101 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/service.d.ts +27 -0
  58. package/dist/service.js +169 -0
  59. package/dist/service.js.map +1 -0
  60. package/dist/store.d.ts +19 -0
  61. package/dist/store.js +114 -0
  62. package/dist/store.js.map +1 -0
  63. package/dist/studio.d.ts +1 -0
  64. package/dist/studio.js +337 -0
  65. package/dist/studio.js.map +1 -0
  66. package/package.json +43 -0
package/dist/enroll.js ADDED
@@ -0,0 +1,68 @@
1
+ import { generateKeyPairSync } from "node:crypto";
2
+ import { hostname } from "node:os";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ import { getApiUrl, getIdentityPath } from "./config.js";
6
+ const CODE_RE = /^BEAM-[A-Z2-9]{4}-[A-Z2-9]{4}-[A-Z2-9]{4}$/;
7
+ export async function readIdentity() {
8
+ try {
9
+ return JSON.parse(await readFile(getIdentityPath(), "utf8"));
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ // Never blocks anything; enrollment just writes credentials this machine uses later.
16
+ export async function enrollDevice(options) {
17
+ const code = options.code.trim().toUpperCase();
18
+ if (!CODE_RE.test(code))
19
+ throw new Error("Code must look like BEAM-XXXX-XXXX-XXXX.");
20
+ const base = (options.url ?? getApiUrl()).replace(/\/+$/, "");
21
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
22
+ const pub = publicKey.export({ type: "spki", format: "der" }).toString("base64");
23
+ const priv = privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
24
+ const os = process.platform;
25
+ const host = hostname();
26
+ let response;
27
+ try {
28
+ response = await fetch(`${base}/v1/enroll`, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ code, device: { hostname: host, os, public_key: pub } }),
32
+ signal: AbortSignal.timeout(10_000),
33
+ });
34
+ }
35
+ catch {
36
+ throw new Error(`Could not reach ${base}. Pass --url or set BEAM_API_URL.`);
37
+ }
38
+ if (!response.ok) {
39
+ let message = `Enrollment rejected (${response.status}).`;
40
+ try {
41
+ const body = (await response.json());
42
+ if (body.error)
43
+ message = body.error;
44
+ }
45
+ catch { /* keep the status-only message */ }
46
+ throw new Error(message);
47
+ }
48
+ const data = (await response.json());
49
+ if (!data.device_id || !data.device_secret || !data.org_id) {
50
+ throw new Error("Enrollment response is missing device credentials.");
51
+ }
52
+ const identity = {
53
+ deviceId: data.device_id,
54
+ deviceSecret: data.device_secret,
55
+ orgId: data.org_id,
56
+ apiBase: (data.api_base ?? base).replace(/\/+$/, ""),
57
+ publicKey: pub,
58
+ privateKey: priv,
59
+ hostname: host,
60
+ os,
61
+ enrolledAt: new Date().toISOString(),
62
+ };
63
+ const path = getIdentityPath();
64
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
65
+ await writeFile(path, `${JSON.stringify(identity, null, 2)}\n`, { mode: 0o600 });
66
+ return identity;
67
+ }
68
+ //# sourceMappingURL=enroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enroll.js","sourceRoot":"","sources":["../src/enroll.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export type Obj = Record<string, unknown>;
2
+ export declare function extractClaudeCode(home: string): Promise<Obj[]>;
3
+ export declare function extractCodex(home: string): Promise<Obj[]>;
4
+ export type ExtractorId = "claude-code" | "codex";
5
+ export declare function supportsExtraction(agentId: string): agentId is ExtractorId;
6
+ export declare function extractAgent(agentId: string, home: string): Promise<Obj[]>;
@@ -0,0 +1,148 @@
1
+ import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ // Forensic extraction reads an agent's own existing session/transcript files -- no hook
4
+ // required, and it can see activity from before beam was ever installed. Bounded generously so
5
+ // a huge or pathological transcript directory can't exhaust memory or flood the collector.
6
+ const MAX_FILE_SIZE = 50_000_000; // 50 MB per transcript file
7
+ const MAX_RECORDS_PER_FILE = 20_000; // parsed JSONL lines kept per file
8
+ const MAX_FILES = 2000; // transcript files walked per extraction run
9
+ function obj(v) { return v && typeof v === "object" && !Array.isArray(v) ? v : {}; }
10
+ function str(v) { return typeof v === "string" ? v : ""; }
11
+ function arr(v) { return Array.isArray(v) ? v : []; }
12
+ function isFinitePastTimestamp(v) { return v.length > 0 && Number.isFinite(Date.parse(v)); }
13
+ async function walk(dir, out = []) {
14
+ let entries;
15
+ try {
16
+ entries = await readdir(dir, { withFileTypes: true });
17
+ }
18
+ catch {
19
+ return out;
20
+ }
21
+ for (const entry of entries) {
22
+ if (out.length >= MAX_FILES)
23
+ break;
24
+ const full = join(dir, entry.name);
25
+ if (entry.isDirectory())
26
+ await walk(full, out);
27
+ else if (entry.isFile() && entry.name.endsWith(".jsonl"))
28
+ out.push(full);
29
+ }
30
+ return out;
31
+ }
32
+ async function readJsonlRows(path) {
33
+ try {
34
+ if ((await stat(path)).size > MAX_FILE_SIZE)
35
+ return [];
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ let content;
41
+ try {
42
+ content = await readFile(path, "utf8");
43
+ }
44
+ catch {
45
+ return [];
46
+ }
47
+ const rows = [];
48
+ for (const line of content.split("\n")) {
49
+ const trimmed = line.trim();
50
+ if (!trimmed)
51
+ continue;
52
+ try {
53
+ rows.push(JSON.parse(trimmed));
54
+ }
55
+ catch { /* skip a malformed line, not the whole file */ }
56
+ if (rows.length >= MAX_RECORDS_PER_FILE)
57
+ break;
58
+ }
59
+ return rows;
60
+ }
61
+ // ~/.claude/projects/<encoded-project-path>/<session-uuid>.jsonl -- an "assistant" line's
62
+ // message.content array carries tool_use blocks alongside prose/thinking blocks.
63
+ export async function extractClaudeCode(home) {
64
+ const files = await walk(join(home, ".claude", "projects"));
65
+ const records = [];
66
+ for (const file of files) {
67
+ for (const row of await readJsonlRows(file)) {
68
+ if (row.type !== "assistant")
69
+ continue;
70
+ const message = obj(row.message);
71
+ const timestamp = str(row.timestamp);
72
+ if (!isFinitePastTimestamp(timestamp))
73
+ continue;
74
+ for (const item of arr(message.content)) {
75
+ const block = obj(item);
76
+ if (block.type !== "tool_use")
77
+ continue;
78
+ const eventId = str(block.id);
79
+ records.push({
80
+ ...(eventId ? { event_id: eventId } : {}),
81
+ tool_name: block.name,
82
+ tool_input: block.input,
83
+ session_id: str(row.session_id) || str(row.sessionId),
84
+ project_path: str(row.cwd),
85
+ timestamp,
86
+ model: str(message.model),
87
+ source_agent: "claude-code",
88
+ source_type: "extract",
89
+ });
90
+ }
91
+ }
92
+ }
93
+ return records;
94
+ }
95
+ // ~/.codex/{sessions,archived_sessions}/rollout-*.jsonl -- a leading "session_meta" record
96
+ // carries session_id/cwd; "response_item" records of payload.type "function_call" (shell/exec
97
+ // tools; arguments is a JSON *string*) or "custom_tool_call" (e.g. apply_patch; input is raw
98
+ // text, not JSON) are the actions. Everything else (message/reasoning/output records) is
99
+ // conversation, not an action -- skipped, matching what a live PreToolUse hook would ever see.
100
+ export async function extractCodex(home) {
101
+ const roots = [join(home, ".codex", "sessions"), join(home, ".codex", "archived_sessions")];
102
+ const records = [];
103
+ for (const root of roots) {
104
+ for (const file of await walk(root)) {
105
+ let sessionId = "";
106
+ let cwd = "";
107
+ for (const row of await readJsonlRows(file)) {
108
+ if (row.type === "session_meta") {
109
+ const payload = obj(row.payload);
110
+ sessionId = str(payload.session_id) || str(payload.id);
111
+ cwd = str(payload.cwd);
112
+ continue;
113
+ }
114
+ if (row.type !== "response_item")
115
+ continue;
116
+ const timestamp = str(row.timestamp);
117
+ if (!isFinitePastTimestamp(timestamp))
118
+ continue;
119
+ const payload = obj(row.payload);
120
+ const callId = str(payload.call_id);
121
+ const base = { ...(callId ? { event_id: callId } : {}), session_id: sessionId, project_path: cwd, timestamp, source_agent: "codex", source_type: "extract" };
122
+ if (payload.type === "function_call") {
123
+ let args = {};
124
+ try {
125
+ args = JSON.parse(str(payload.arguments));
126
+ }
127
+ catch { /* arguments wasn't JSON */ }
128
+ records.push({ ...base, tool_name: payload.name, command: args.cmd ?? args.command, tool_input: args });
129
+ }
130
+ else if (payload.type === "custom_tool_call") {
131
+ const input = payload.input;
132
+ records.push({ ...base, tool_name: payload.name, content_preview: typeof input === "string" ? input : JSON.stringify(input ?? {}) });
133
+ }
134
+ }
135
+ }
136
+ }
137
+ return records;
138
+ }
139
+ const extractors = { "claude-code": extractClaudeCode, codex: extractCodex };
140
+ export function supportsExtraction(agentId) {
141
+ return Object.prototype.hasOwnProperty.call(extractors, agentId);
142
+ }
143
+ export async function extractAgent(agentId, home) {
144
+ if (!supportsExtraction(agentId))
145
+ throw new Error(`Forensic extraction isn't built for '${agentId}' yet. Supported: ${Object.keys(extractors).join(", ")}.`);
146
+ return extractors[agentId](home);
147
+ }
148
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,wFAAwF;AACxF,+FAA+F;AAC/F,2FAA2F;AAC3F,MAAM,aAAa,GAAG,UAAU,CAAC,CAAC,4BAA4B;AAC9D,MAAM,oBAAoB,GAAG,MAAM,CAAC,CAAC,mCAAmC;AACxE,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,6CAA6C;AAErE,SAAS,GAAG,CAAC,CAAU,IAAS,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACzG,SAAS,GAAG,CAAC,CAAU,IAAY,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3E,SAAS,GAAG,CAAC,CAAU,IAAe,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,SAAS,qBAAqB,CAAC,CAAS,IAAa,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7G,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,MAAgB,EAAE;IACjD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QAAC,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,GAAG,CAAC;IAAC,CAAC;IACpF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,MAAM,IAAI,SAAS;YAAE,MAAM;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;aAC1C,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAY;IACvC,IAAI,CAAC;QAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,aAAa;YAAE,OAAO,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;IACpF,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QAAC,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;IACpE,MAAM,IAAI,GAAU,EAAE,CAAC;IACvB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,CAAC;YAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAQ,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,+CAA+C,CAAC,CAAC;QACxG,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAoB;YAAE,MAAM;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0FAA0F;AAC1F,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAClD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAU,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YACvC,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACjC,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACrC,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC;gBAAE,SAAS;YAChD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;gBACxB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;oBAAE,SAAS;gBACxC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAC9B,OAAO,CAAC,IAAI,CAAC;oBACX,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzC,SAAS,EAAE,KAAK,CAAC,IAAI;oBACrB,UAAU,EAAE,KAAK,CAAC,KAAK;oBACvB,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;oBACrD,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;oBAC1B,SAAS;oBACT,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC;oBACzB,YAAY,EAAE,aAAa;oBAC3B,WAAW,EAAE,SAAS;iBACvB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,2FAA2F;AAC3F,8FAA8F;AAC9F,6FAA6F;AAC7F,yFAAyF;AACzF,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAU,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,IAAI,SAAS,GAAG,EAAE,CAAC;YAAC,IAAI,GAAG,GAAG,EAAE,CAAC;YACjC,KAAK,MAAM,GAAG,IAAI,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBAChC,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACjC,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACvD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;oBACvB,SAAS;gBACX,CAAC;gBACD,IAAI,GAAG,CAAC,IAAI,KAAK,eAAe;oBAAE,SAAS;gBAC3C,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACrC,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC;oBAAE,SAAS;gBAChD,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACjC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACpC,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;gBAC7J,IAAI,OAAO,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;oBACrC,IAAI,IAAI,GAAQ,EAAE,CAAC;oBACnB,IAAI,CAAC;wBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAQ,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;oBAC/F,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC1G,CAAC;qBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;oBAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;oBAC5B,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;gBACvI,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAGD,MAAM,UAAU,GAA0D,EAAE,aAAa,EAAE,iBAAiB,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;AAEpI,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAe,EAAE,IAAY;IAC9D,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,OAAO,qBAAqB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7J,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Event } from "./core.js";
2
+ export declare function forwardEvents(events: Event | Event[]): Promise<void>;
3
+ export interface PolicySyncResult {
4
+ status: "updated" | "unchanged" | "not-enrolled" | "unreachable";
5
+ version?: number;
6
+ path?: string;
7
+ }
8
+ export declare function syncPolicy(): Promise<PolicySyncResult>;
@@ -0,0 +1,53 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { getDataDirectory } from "./config.js";
4
+ import { readIdentity } from "./enroll.js";
5
+ function policyPath() {
6
+ return join(getDataDirectory(), "policy.json");
7
+ }
8
+ // Fire-and-forget: forwarding to the workspace must never block or fail the agent.
9
+ export async function forwardEvents(events) {
10
+ const identity = await readIdentity();
11
+ if (!identity)
12
+ return;
13
+ try {
14
+ await fetch(`${identity.apiBase}/v1/ingest`, {
15
+ method: "POST",
16
+ headers: {
17
+ "Content-Type": "application/json",
18
+ Authorization: `Bearer ${identity.deviceSecret}`,
19
+ },
20
+ body: JSON.stringify(events),
21
+ signal: AbortSignal.timeout(5000),
22
+ });
23
+ }
24
+ catch {
25
+ /* offline / unreachable — the local collector still has the record */
26
+ }
27
+ }
28
+ // Pull the current policy bundle and cache it locally for the hook to enforce.
29
+ export async function syncPolicy() {
30
+ const identity = await readIdentity();
31
+ if (!identity)
32
+ return { status: "not-enrolled" };
33
+ let response;
34
+ try {
35
+ response = await fetch(`${identity.apiBase}/v1/policy`, {
36
+ headers: { Authorization: `Bearer ${identity.deviceSecret}` },
37
+ signal: AbortSignal.timeout(5000),
38
+ });
39
+ }
40
+ catch {
41
+ return { status: "unreachable" };
42
+ }
43
+ if (response.status === 304)
44
+ return { status: "unchanged" };
45
+ if (!response.ok)
46
+ return { status: "unreachable" };
47
+ const bundle = await response.json();
48
+ const path = policyPath();
49
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
50
+ await writeFile(path, `${JSON.stringify(bundle, null, 2)}\n`, { mode: 0o600 });
51
+ return { status: "updated", version: bundle.version, path };
52
+ }
53
+ //# sourceMappingURL=forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward.js","sourceRoot":"","sources":["../src/forward.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,gBAAgB,EAAE,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAuB;IACzD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,OAAO,YAAY,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,QAAQ,CAAC,YAAY,EAAE;aACjD;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YAC5B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;IACxE,CAAC;AACH,CAAC;AAQD,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAEjD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,OAAO,YAAY,EAAE;YACtD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,QAAQ,CAAC,YAAY,EAAE,EAAE;YAC7D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACnC,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC5D,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAEnD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type Obj } from "./agents.js";
2
+ export declare function adaptHookPayload(sourceAgent: string, raw: Obj): Obj;
@@ -0,0 +1,36 @@
1
+ import { findAgent } from "./agents.js";
2
+ // Claude Code, Codex, and Cursor all send tool_name/tool_input/session_id/cwd/hook_event_name
3
+ // (core.normalize already reads exactly these fields, case-insensitively for hook_event_name).
4
+ function passthrough(raw) {
5
+ return raw;
6
+ }
7
+ // GitHub Copilot CLI's preToolUse payload is camelCase and has no hook_event_name field —
8
+ // it's implied by which hooks.json array the handler is registered under.
9
+ function copilotCamel(raw) {
10
+ return {
11
+ ...raw,
12
+ session_id: raw.sessionId ?? raw.session_id,
13
+ cwd: raw.cwd,
14
+ tool_name: raw.toolName ?? raw.tool_name,
15
+ tool_input: raw.toolArgs ?? raw.tool_input,
16
+ hook_event_name: raw.hook_event_name ?? "PreToolUse",
17
+ };
18
+ }
19
+ // Best-effort fallback for agents whose stdin schema is not (yet) verified against vendor docs.
20
+ function generic(raw) {
21
+ return {
22
+ ...raw,
23
+ session_id: raw.session_id ?? raw.sessionId ?? raw.session,
24
+ cwd: raw.cwd ?? raw.project_path ?? raw.workingDirectory ?? raw.working_directory,
25
+ tool_name: raw.tool_name ?? raw.toolName ?? raw.tool,
26
+ tool_input: raw.tool_input ?? raw.toolArgs ?? raw.toolInput ?? raw.input,
27
+ hook_event_name: raw.hook_event_name ?? raw.hookEventName ?? raw.event,
28
+ };
29
+ }
30
+ const adapters = { passthrough, "copilot-camel": copilotCamel, generic };
31
+ export function adaptHookPayload(sourceAgent, raw) {
32
+ const agent = findAgent(sourceAgent);
33
+ const adapter = adapters[agent?.adapter ?? "generic"];
34
+ return adapter(raw);
35
+ }
36
+ //# sourceMappingURL=hook-adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-adapters.js","sourceRoot":"","sources":["../src/hook-adapters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AAElD,8FAA8F;AAC9F,+FAA+F;AAC/F,SAAS,WAAW,CAAC,GAAQ;IAC3B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0FAA0F;AAC1F,0EAA0E;AAC1E,SAAS,YAAY,CAAC,GAAQ;IAC5B,OAAO;QACL,GAAG,GAAG;QACN,UAAU,EAAE,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,UAAU;QAC3C,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,SAAS,EAAE,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,SAAS;QACxC,UAAU,EAAE,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,UAAU;QAC1C,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,YAAY;KACrD,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,SAAS,OAAO,CAAC,GAAQ;IACvB,OAAO;QACL,GAAG,GAAG;QACN,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,OAAO;QAC1D,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,iBAAiB;QACjF,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI;QACpD,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,KAAK;QACxE,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,KAAK;KACvE,CAAC;AACJ,CAAC;AAED,MAAM,QAAQ,GAAG,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;AAEzE,MAAM,UAAU,gBAAgB,CAAC,WAAmB,EAAE,GAAQ;IAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,SAAS,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface InstallResult {
2
+ agent: string;
3
+ path: string;
4
+ alreadyInstalled: boolean;
5
+ }
6
+ export declare function installHook(agentId: string, home?: string): Promise<InstallResult>;
7
+ export type InstallAllStatus = "installed" | "already-installed" | "not-detected" | "not-supported" | "error";
8
+ export interface InstallAllResult {
9
+ agent: string;
10
+ name: string;
11
+ status: InstallAllStatus;
12
+ path?: string;
13
+ error?: string;
14
+ }
15
+ export declare function installAllDetectedHooks(home?: string): Promise<InstallAllResult[]>;
@@ -0,0 +1,68 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { AGENTS, findAgent, hookConfigFullPath } from "./agents.js";
5
+ export async function installHook(agentId, home = homedir()) {
6
+ const agent = findAgent(agentId);
7
+ if (!agent)
8
+ throw new Error(`Unknown agent '${agentId}'. Run 'beam agent list' to see supported agents.`);
9
+ if (!agent.hookConfigPath || !agent.mergeHookConfig)
10
+ throw new Error(`${agent.name} has no supported hook install path yet; see 'beam agent list' for its status.`);
11
+ const path = hookConfigFullPath(agent, home);
12
+ const command = `beam hook ${agent.id}`;
13
+ let existing = {};
14
+ let raw = "";
15
+ try {
16
+ raw = await readFile(path, "utf8");
17
+ existing = raw.trim() ? JSON.parse(raw) : {};
18
+ }
19
+ catch (e) {
20
+ if (e.code !== "ENOENT") {
21
+ if (e instanceof SyntaxError)
22
+ throw new Error(`${path} contains invalid JSON; fix or remove it before installing.`);
23
+ throw e;
24
+ }
25
+ }
26
+ const merged = agent.mergeHookConfig(existing, command);
27
+ const alreadyInstalled = JSON.stringify(merged) === JSON.stringify(existing) && raw.trim() !== "";
28
+ if (!alreadyInstalled) {
29
+ await mkdir(dirname(path), { recursive: true });
30
+ await writeFile(path, `${JSON.stringify(merged, null, 2)}\n`);
31
+ }
32
+ return { agent: agent.id, path, alreadyInstalled };
33
+ }
34
+ async function isDetected(agent, home) {
35
+ for (const relativePath of agent.configs) {
36
+ try {
37
+ await access(join(home, relativePath));
38
+ return true;
39
+ }
40
+ catch { /* check the next path */ }
41
+ }
42
+ return false;
43
+ }
44
+ // Detects which agents are actually present on this machine (via agents.ts's real, non-circular
45
+ // config-presence signals — never the hook file beam itself would write) and installs beam's
46
+ // hook into every one that's both detected and supported, skipping the rest without erroring.
47
+ export async function installAllDetectedHooks(home = homedir()) {
48
+ const results = [];
49
+ for (const agent of AGENTS) {
50
+ if (!(await isDetected(agent, home))) {
51
+ results.push({ agent: agent.id, name: agent.name, status: "not-detected" });
52
+ continue;
53
+ }
54
+ if (!agent.hookConfigPath || !agent.mergeHookConfig) {
55
+ results.push({ agent: agent.id, name: agent.name, status: "not-supported" });
56
+ continue;
57
+ }
58
+ try {
59
+ const result = await installHook(agent.id, home);
60
+ results.push({ agent: agent.id, name: agent.name, status: result.alreadyInstalled ? "already-installed" : "installed", path: result.path });
61
+ }
62
+ catch (e) {
63
+ results.push({ agent: agent.id, name: agent.name, status: "error", error: e instanceof Error ? e.message : String(e) });
64
+ }
65
+ }
66
+ return results;
67
+ }
68
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Event } from "./core.js";
2
+ export declare function inventory(events: Event[], home?: string): Promise<{
3
+ agent: string;
4
+ name: string;
5
+ configPresent: boolean;
6
+ artifactsPresent: boolean;
7
+ captureStatus: string;
8
+ lastReceived: string | null;
9
+ hookInstallable: boolean;
10
+ payloadVerified: boolean;
11
+ integration: string;
12
+ }[]>;
@@ -0,0 +1,23 @@
1
+ import { access } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { AGENTS } from "./agents.js";
5
+ export async function inventory(events, home = homedir()) {
6
+ const exists = async (paths) => (await Promise.all(paths.map(async (p) => { try {
7
+ await access(join(home, p));
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ } }))).some(Boolean);
13
+ return Promise.all(AGENTS.map(async (d) => {
14
+ const matches = events.filter(e => e.agent === d.id || e.agent === d.id.replace("-code", "").replace("-cli", ""));
15
+ const last = matches.reduce((value, e) => !value || e.receivedAt > value ? e.receivedAt : value, null);
16
+ return {
17
+ agent: d.id, name: d.name, configPresent: await exists(d.configs), artifactsPresent: await exists(d.artifacts),
18
+ captureStatus: last ? "records received" : "not verified", lastReceived: last,
19
+ hookInstallable: Boolean(d.hookConfigPath), payloadVerified: d.verifiedPayload, integration: d.notes,
20
+ };
21
+ }));
22
+ }
23
+ //# sourceMappingURL=inventory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inventory.js","sourceRoot":"","sources":["../src/inventory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAe,EAAE,IAAI,GAAG,OAAO,EAAE;IAC/D,MAAM,MAAM,GAAG,KAAK,EAAE,KAAe,EAAE,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAC,CAAC,EAAC,EAAE,GAAG,IAAI,CAAC;QAAC,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjL,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAC,CAAC,EAAC,EAAE;QACtC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;QAClH,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAgB,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACtH,OAAO;YACL,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,gBAAgB,EAAE,MAAM,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;YAC9G,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,EAAE,YAAY,EAAE,IAAI;YAC7E,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,eAAe,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK;SACrG,CAAC;IACJ,CAAC,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function openBrowser(url: string): void;
@@ -0,0 +1,8 @@
1
+ import { spawn } from "node:child_process";
2
+ export function openBrowser(url) {
3
+ const platform = process.platform;
4
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
5
+ const args = platform === "win32" ? ["", url] : [url];
6
+ spawn(command, args, { detached: true, stdio: "ignore", shell: platform === "win32" }).unref();
7
+ }
8
+ //# sourceMappingURL=open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,OAAO,GAAG,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IAC7F,MAAM,IAAI,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,KAAK,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AACjG,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type Event } from "./core.js";
2
+ export interface OsMonitorHandle {
3
+ stop: () => void;
4
+ }
5
+ export declare function startOsMonitor(sink: (events: Event[]) => void | Promise<void>): Promise<OsMonitorHandle>;
@@ -0,0 +1,26 @@
1
+ // Enterprise plugin loader. OS-level observation of AI agent activity (watching `ps`/`lsof` for
2
+ // process starts/exits, independent of any agent's own hook cooperation) is NOT part of this
3
+ // open-source repo -- that detection logic (os-detect.ts, os-process.ts, os-network.ts, and the
4
+ // monitor loop itself) lives in the private `beam-enterprise` package.
5
+ //
6
+ // This file just tries to load that package at runtime. If it isn't installed -- the normal case
7
+ // for OSS users, since it's a private/paid npm package -- startOsMonitor() is a silent no-op:
8
+ // `beam start` works exactly the same without it, just without OS-level rows. See
9
+ // beam-enterprise/README.md for the plugin contract this depends on.
10
+ import { normalize } from "./core.js";
11
+ // Not a string literal type on purpose: a literal specifier makes TypeScript try to resolve the
12
+ // module at compile time and fail typecheck for every OSS dev/CI run that (correctly) doesn't
13
+ // have this private package installed. Widening to `string` makes it a genuine runtime-only
14
+ // dynamic import.
15
+ const ENTERPRISE_PACKAGE = "beam-enterprise";
16
+ export async function startOsMonitor(sink) {
17
+ let plugin;
18
+ try {
19
+ plugin = (await import(ENTERPRISE_PACKAGE));
20
+ }
21
+ catch {
22
+ return { stop: () => { } };
23
+ }
24
+ return plugin.startOsMonitor(async (records) => { await sink(records.map(normalize)); });
25
+ }
26
+ //# sourceMappingURL=os-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"os-monitor.js","sourceRoot":"","sources":["../src/os-monitor.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,6FAA6F;AAC7F,gGAAgG;AAChG,uEAAuE;AACvE,EAAE;AACF,iGAAiG;AACjG,8FAA8F;AAC9F,kFAAkF;AAClF,qEAAqE;AACrE,OAAO,EAAE,SAAS,EAAc,MAAM,WAAW,CAAC;AAQlD,gGAAgG;AAChG,8FAA8F;AAC9F,4FAA4F;AAC5F,kBAAkB;AAClB,MAAM,kBAAkB,GAAW,iBAAiB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAA+C;IAClF,IAAI,MAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAqB,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC,cAAc,CAAC,KAAK,EAAC,OAAO,EAAC,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1,27 @@
1
+ export type PolicyMode = "observe" | "advisory" | "enforce";
2
+ export interface PolicyRules {
3
+ mode: PolicyMode;
4
+ blockedTools: string[];
5
+ blockedCommandPatterns: string[];
6
+ disabledAgents: string[];
7
+ }
8
+ export interface PolicyBundle {
9
+ version: number;
10
+ rules: PolicyRules;
11
+ notAfter: string | null;
12
+ }
13
+ export declare function policyPath(): string;
14
+ export declare function readPolicy(): Promise<PolicyBundle | null>;
15
+ export type DecisionAction = "allow" | "warn" | "deny";
16
+ export interface Decision {
17
+ action: DecisionAction;
18
+ reason?: string;
19
+ mode: PolicyMode;
20
+ policyVersion: number;
21
+ }
22
+ export interface ActionContext {
23
+ agent: string;
24
+ tool: string;
25
+ command: string;
26
+ }
27
+ export declare function evaluate(bundle: PolicyBundle | null, ctx: ActionContext): Decision;
package/dist/policy.js ADDED
@@ -0,0 +1,59 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { getDataDirectory } from "./config.js";
4
+ const EMPTY = { mode: "observe", blockedTools: [], blockedCommandPatterns: [], disabledAgents: [] };
5
+ export function policyPath() {
6
+ return join(getDataDirectory(), "policy.json");
7
+ }
8
+ export async function readPolicy() {
9
+ try {
10
+ const parsed = JSON.parse(await readFile(policyPath(), "utf8"));
11
+ if (!parsed.rules)
12
+ return null;
13
+ return {
14
+ version: typeof parsed.version === "number" ? parsed.version : 0,
15
+ notAfter: parsed.notAfter ?? null,
16
+ rules: { ...EMPTY, ...parsed.rules },
17
+ };
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ // Evaluate a proposed action. No policy, observe mode, or an expired bundle => allow.
24
+ export function evaluate(bundle, ctx) {
25
+ if (!bundle)
26
+ return { action: "allow", mode: "observe", policyVersion: 0 };
27
+ const { rules, version } = bundle;
28
+ const base = { mode: rules.mode, policyVersion: version };
29
+ if (rules.mode === "observe")
30
+ return { action: "allow", ...base };
31
+ // Stale cached policy: fail open rather than trap the user offline.
32
+ if (bundle.notAfter && Date.parse(bundle.notAfter) < Date.now())
33
+ return { action: "allow", ...base };
34
+ const hit = (reason) => ({
35
+ action: rules.mode === "enforce" ? "deny" : "warn",
36
+ reason,
37
+ ...base,
38
+ });
39
+ if (rules.disabledAgents.some((a) => a.toLowerCase() === ctx.agent.toLowerCase())) {
40
+ return hit(`${ctx.agent} is disabled for your account by workspace policy.`);
41
+ }
42
+ if (ctx.tool && rules.blockedTools.some((t) => t.toLowerCase() === ctx.tool.toLowerCase())) {
43
+ return hit(`The ${ctx.tool} tool is blocked by workspace policy.`);
44
+ }
45
+ for (const pattern of rules.blockedCommandPatterns) {
46
+ if (!ctx.command)
47
+ break;
48
+ try {
49
+ if (new RegExp(pattern, "i").test(ctx.command)) {
50
+ return hit(`This command matches a blocked pattern: /${pattern}/`);
51
+ }
52
+ }
53
+ catch {
54
+ /* ignore an unparseable pattern */
55
+ }
56
+ }
57
+ return { action: "allow", ...base };
58
+ }
59
+ //# sourceMappingURL=policy.js.map