@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.
- package/LICENSE +661 -0
- package/README.md +188 -0
- package/dist/agents.d.ts +26 -0
- package/dist/agents.js +96 -0
- package/dist/agents.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +234 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +176 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +64 -0
- package/dist/core.js +176 -0
- package/dist/core.js.map +1 -0
- package/dist/custom-rules.d.ts +7 -0
- package/dist/custom-rules.js +80 -0
- package/dist/custom-rules.js.map +1 -0
- package/dist/enroll.d.ts +16 -0
- package/dist/enroll.js +68 -0
- package/dist/enroll.js.map +1 -0
- package/dist/extract.d.ts +6 -0
- package/dist/extract.js +148 -0
- package/dist/extract.js.map +1 -0
- package/dist/forward.d.ts +8 -0
- package/dist/forward.js +53 -0
- package/dist/forward.js.map +1 -0
- package/dist/hook-adapters.d.ts +2 -0
- package/dist/hook-adapters.js +36 -0
- package/dist/hook-adapters.js.map +1 -0
- package/dist/install.d.ts +15 -0
- package/dist/install.js +68 -0
- package/dist/install.js.map +1 -0
- package/dist/inventory.d.ts +12 -0
- package/dist/inventory.js +23 -0
- package/dist/inventory.js.map +1 -0
- package/dist/open-browser.d.ts +1 -0
- package/dist/open-browser.js +8 -0
- package/dist/open-browser.js.map +1 -0
- package/dist/os-monitor.d.ts +5 -0
- package/dist/os-monitor.js +26 -0
- package/dist/os-monitor.js.map +1 -0
- package/dist/policy.d.ts +27 -0
- package/dist/policy.js +59 -0
- package/dist/policy.js.map +1 -0
- package/dist/sequences.d.ts +13 -0
- package/dist/sequences.js +58 -0
- package/dist/sequences.js.map +1 -0
- package/dist/serve.d.ts +27 -0
- package/dist/serve.js +113 -0
- package/dist/serve.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +101 -0
- package/dist/server.js.map +1 -0
- package/dist/service.d.ts +27 -0
- package/dist/service.js +169 -0
- package/dist/service.js.map +1 -0
- package/dist/store.d.ts +19 -0
- package/dist/store.js +114 -0
- package/dist/store.js.map +1 -0
- package/dist/studio.d.ts +1 -0
- package/dist/studio.js +337 -0
- package/dist/studio.js.map +1 -0
- package/package.json +43 -0
package/dist/store.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { detectSequenceFindings } from "./sequences.js";
|
|
4
|
+
export class Store {
|
|
5
|
+
directory;
|
|
6
|
+
events = [];
|
|
7
|
+
scans = [];
|
|
8
|
+
reviews = {};
|
|
9
|
+
queue = Promise.resolve();
|
|
10
|
+
maxEvents = 10_000;
|
|
11
|
+
constructor(directory) {
|
|
12
|
+
this.directory = directory;
|
|
13
|
+
}
|
|
14
|
+
async init() {
|
|
15
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
16
|
+
await chmod(this.directory, 0o700);
|
|
17
|
+
for (const name of ["events", "scans", "reviews"]) {
|
|
18
|
+
const file = join(this.directory, `${name}.ndjson`);
|
|
19
|
+
let contents = "";
|
|
20
|
+
try {
|
|
21
|
+
if ((await stat(file)).size > 64_000_000)
|
|
22
|
+
throw new Error(`${name} store exceeds 64 MB; archive it before starting.`);
|
|
23
|
+
contents = await readFile(file, "utf8");
|
|
24
|
+
await chmod(file, 0o600);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
if (e.code !== "ENOENT")
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
const lines = contents.split("\n");
|
|
31
|
+
for (let i = 0; i < lines.length; i++) {
|
|
32
|
+
if (!lines[i].trim())
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
const row = JSON.parse(lines[i]);
|
|
36
|
+
if (name === "events")
|
|
37
|
+
this.events.push(row);
|
|
38
|
+
else if (name === "scans")
|
|
39
|
+
this.scans.push(row);
|
|
40
|
+
else
|
|
41
|
+
this.reviews[row.id] = row.reviewed;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error(`Invalid ${name} store at line ${i + 1}; preserve and repair this file before restarting.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.events = this.events.slice(-this.maxEvents);
|
|
49
|
+
this.scans = this.scans.slice(-500);
|
|
50
|
+
}
|
|
51
|
+
serialized(fn) {
|
|
52
|
+
const result = this.queue.then(fn);
|
|
53
|
+
this.queue = result.catch(() => { });
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
async replace(name, rows) {
|
|
57
|
+
const target = join(this.directory, `${name}.ndjson`);
|
|
58
|
+
const temp = `${target}.tmp`;
|
|
59
|
+
await writeFile(temp, rows.map(r => JSON.stringify(r)).join("\n") + "\n", { mode: 0o600 });
|
|
60
|
+
await rename(temp, target);
|
|
61
|
+
}
|
|
62
|
+
addEvents(rows) {
|
|
63
|
+
return this.serialized(async () => {
|
|
64
|
+
const ids = new Set(this.events.map(e => e.id));
|
|
65
|
+
const fresh = rows.filter(r => { if (ids.has(r.id))
|
|
66
|
+
return false; ids.add(r.id); return true; });
|
|
67
|
+
let next = [...this.events, ...fresh].slice(-this.maxEvents);
|
|
68
|
+
if (fresh.length) {
|
|
69
|
+
// Re-evaluate cross-event sequence rules, but only for sessions this batch actually
|
|
70
|
+
// touched -- bounds the cost regardless of total store size.
|
|
71
|
+
const touchedSessions = new Set(fresh.map(e => e.session));
|
|
72
|
+
const bySession = new Map();
|
|
73
|
+
for (const e of next) {
|
|
74
|
+
if (!touchedSessions.has(e.session))
|
|
75
|
+
continue;
|
|
76
|
+
const list = bySession.get(e.session);
|
|
77
|
+
if (list)
|
|
78
|
+
list.push(e);
|
|
79
|
+
else
|
|
80
|
+
bySession.set(e.session, [e]);
|
|
81
|
+
}
|
|
82
|
+
for (const sessionEvents of bySession.values()) {
|
|
83
|
+
const attach = detectSequenceFindings(sessionEvents);
|
|
84
|
+
if (attach.size)
|
|
85
|
+
next = next.map(e => attach.has(e.id) ? { ...e, findings: [...e.findings, ...attach.get(e.id)] } : e);
|
|
86
|
+
}
|
|
87
|
+
// Atomic replacement bounds disk use and makes retry after uncertain delivery idempotent.
|
|
88
|
+
await this.replace("events", next);
|
|
89
|
+
this.events = next;
|
|
90
|
+
}
|
|
91
|
+
return { accepted: fresh.length, duplicates: rows.length - fresh.length };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
addScan(scan) {
|
|
95
|
+
return this.serialized(async () => {
|
|
96
|
+
const next = [...this.scans, scan].slice(-500);
|
|
97
|
+
await this.replace("scans", next);
|
|
98
|
+
this.scans = next;
|
|
99
|
+
return scan;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
review(id, reviewed) {
|
|
103
|
+
return this.serialized(async () => {
|
|
104
|
+
if (!this.events.some(e => e.id === id))
|
|
105
|
+
throw new Error("Event not found.");
|
|
106
|
+
const next = { ...this.reviews, [id]: reviewed };
|
|
107
|
+
const ids = new Set(this.events.map(e => e.id));
|
|
108
|
+
const rows = Object.entries(next).filter(([key]) => ids.has(key)).map(([key, value]) => ({ id: key, reviewed: value }));
|
|
109
|
+
await this.replace("reviews", rows);
|
|
110
|
+
this.reviews = Object.fromEntries(rows.map(r => [r.id, r.reviewed]));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/studio.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function studioPage(): string;
|
package/dist/studio.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// Served directly by the collector at GET / and GET /studio. Self-contained: no build step,
|
|
2
|
+
// no external resources (fonts/CDNs/analytics) — everything this local security tool touches
|
|
3
|
+
// stays on this machine. The page itself carries no secret; API calls still require the token.
|
|
4
|
+
export function studioPage() {
|
|
5
|
+
return `<!doctype html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="utf-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
|
+
<title>beam studio</title>
|
|
11
|
+
<style>
|
|
12
|
+
:root { color-scheme: dark; --bg:#0b0d10; --panel:#14171b; --border:#242830; --text:#e6e8eb; --muted:#8b93a1; --accent:#6ee7b7; --amber:#f5b942; --red:#ef6f6f; --blue:#7aa2f7; }
|
|
13
|
+
* { box-sizing: border-box; }
|
|
14
|
+
body { margin:0; background:var(--bg); color:var(--text); font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
|
|
15
|
+
header { display:flex; align-items:center; justify-content:space-between; padding:14px 20px; border-bottom:1px solid var(--border); }
|
|
16
|
+
header h1 { font-size:15px; margin:0; letter-spacing:.02em; }
|
|
17
|
+
header h1 b { color:var(--accent); }
|
|
18
|
+
.status { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); }
|
|
19
|
+
.dot { width:8px; height:8px; border-radius:50%; background:#555; }
|
|
20
|
+
.dot.on { background:var(--accent); }
|
|
21
|
+
.dot.off { background:var(--red); }
|
|
22
|
+
main { max-width:1100px; margin:0 auto; padding:20px; }
|
|
23
|
+
nav.tabs { display:flex; gap:4px; margin-bottom:16px; border-bottom:1px solid var(--border); }
|
|
24
|
+
nav.tabs button { background:none; border:none; color:var(--muted); padding:10px 14px; cursor:pointer; font-size:13px; border-bottom:2px solid transparent; }
|
|
25
|
+
nav.tabs button.active { color:var(--text); border-color:var(--accent); }
|
|
26
|
+
nav.tabs button .n { background:var(--border); border-radius:10px; padding:1px 6px; margin-left:6px; font-size:11px; }
|
|
27
|
+
.panel { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:16px; margin-bottom:16px; }
|
|
28
|
+
.pair { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
|
|
29
|
+
.pair input[type=password] { flex:1; min-width:220px; background:#0e1013; border:1px solid var(--border); color:var(--text); padding:8px 10px; border-radius:6px; font-family:monospace; }
|
|
30
|
+
button.btn { background:var(--accent); color:#08110d; border:none; padding:8px 14px; border-radius:6px; font-weight:600; cursor:pointer; font-size:13px; }
|
|
31
|
+
button.btn.secondary { background:none; border:1px solid var(--border); color:var(--text); }
|
|
32
|
+
button.btn:disabled { opacity:.5; cursor:default; }
|
|
33
|
+
.filters { display:flex; gap:8px; margin-bottom:12px; flex-wrap:wrap; }
|
|
34
|
+
.filters input, .filters select { background:#0e1013; border:1px solid var(--border); color:var(--text); padding:7px 10px; border-radius:6px; font-size:13px; }
|
|
35
|
+
.filters input { flex:1; min-width:200px; }
|
|
36
|
+
table { width:100%; border-collapse:collapse; font-size:13px; }
|
|
37
|
+
th { text-align:left; color:var(--muted); font-weight:500; padding:6px 8px; border-bottom:1px solid var(--border); font-size:11px; text-transform:uppercase; letter-spacing:.04em; }
|
|
38
|
+
td { padding:8px; border-bottom:1px solid var(--border); vertical-align:top; }
|
|
39
|
+
tr.row:hover { background:#0e1013; cursor:pointer; }
|
|
40
|
+
.mono { font-family:monospace; color:var(--muted); }
|
|
41
|
+
.badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:11px; text-transform:capitalize; }
|
|
42
|
+
.badge.critical { background:#3a1414; color:var(--red); }
|
|
43
|
+
.badge.high { background:#3a2712; color:var(--amber); }
|
|
44
|
+
.badge.medium { background:#1c2a3a; color:var(--blue); }
|
|
45
|
+
.badge.info { background:#132018; color:var(--accent); }
|
|
46
|
+
.empty { text-align:center; padding:40px 10px; color:var(--muted); }
|
|
47
|
+
.scan-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
|
|
48
|
+
.scan-grid label { display:block; margin-bottom:10px; font-size:12px; color:var(--muted); }
|
|
49
|
+
.scan-grid input[type=text], .scan-grid select, .scan-grid textarea { width:100%; background:#0e1013; border:1px solid var(--border); color:var(--text); padding:8px; border-radius:6px; font-size:13px; margin-top:4px; font-family:inherit; }
|
|
50
|
+
.scan-grid textarea { font-family:monospace; min-height:220px; resize:vertical; }
|
|
51
|
+
.finding { border:1px solid var(--border); border-radius:8px; padding:10px 12px; margin-bottom:10px; }
|
|
52
|
+
.finding pre { white-space:pre-wrap; word-break:break-word; background:#0e1013; padding:8px; border-radius:6px; font-size:12px; margin:8px 0 0; }
|
|
53
|
+
.drawer-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.5); display:flex; justify-content:flex-end; z-index:10; }
|
|
54
|
+
.drawer { width:min(480px,100%); background:var(--panel); border-left:1px solid var(--border); padding:20px; overflow-y:auto; }
|
|
55
|
+
.drawer dl { display:grid; grid-template-columns:110px 1fr; gap:6px 10px; font-size:12px; margin:12px 0; }
|
|
56
|
+
.drawer dt { color:var(--muted); }
|
|
57
|
+
.close { float:right; background:none; border:none; color:var(--muted); font-size:18px; cursor:pointer; }
|
|
58
|
+
.muted { color:var(--muted); }
|
|
59
|
+
.stats { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:16px; }
|
|
60
|
+
.stats article { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:12px 14px; }
|
|
61
|
+
.stats span { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.03em; }
|
|
62
|
+
.stats strong { display:block; font-size:22px; margin-top:4px; }
|
|
63
|
+
.notice { padding:10px 14px; border-radius:8px; margin-bottom:14px; font-size:13px; }
|
|
64
|
+
.notice.error { background:#3a1414; color:var(--red); }
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<header>
|
|
69
|
+
<h1><b>beam</b> studio <span class="muted" style="font-weight:400">— local machine, observe only</span></h1>
|
|
70
|
+
<div class="status"><span class="dot" id="dot"></span><span id="statusText">Not connected</span><button class="btn secondary" id="disconnectBtn" style="display:none;padding:4px 10px;font-size:12px;margin-left:8px">Disconnect</button></div>
|
|
71
|
+
</header>
|
|
72
|
+
<main>
|
|
73
|
+
<div id="notice"></div>
|
|
74
|
+
<div class="panel" id="pairPanel">
|
|
75
|
+
<div class="pair">
|
|
76
|
+
<input type="password" id="tokenInput" autocomplete="off" placeholder="Paste the pairing token from 'beam token'">
|
|
77
|
+
<button class="btn" id="connectBtn">Connect</button>
|
|
78
|
+
</div>
|
|
79
|
+
<p class="muted" style="margin:8px 0 0">Saved in this browser's local storage so reloading the page stays connected. It's only readable by pages served from this collector (127.0.0.1) — use Disconnect to clear it.</p>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div id="app" style="display:none">
|
|
83
|
+
<div class="stats" id="stats"></div>
|
|
84
|
+
<nav class="tabs">
|
|
85
|
+
<button data-tab="activity" class="active">Activity</button>
|
|
86
|
+
<button data-tab="findings">Findings <span class="n" id="flagCount">0</span></button>
|
|
87
|
+
<button data-tab="scan">Skill & MCP scan</button>
|
|
88
|
+
</nav>
|
|
89
|
+
|
|
90
|
+
<div id="tab-activity" class="tabpanel">
|
|
91
|
+
<div class="filters">
|
|
92
|
+
<input id="search" placeholder="Search actions, sessions, or tools…">
|
|
93
|
+
<select id="agentFilter"><option value="all">All agents</option></select>
|
|
94
|
+
<select id="severityFilter"><option value="all">All risk levels</option><option>critical</option><option>high</option><option>medium</option><option>info</option></select>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="panel"><table><thead><tr><th>Time</th><th>Action / context</th><th>Agent</th><th>Risk</th><th>Review</th></tr></thead><tbody id="eventRows"></tbody></table><div class="empty" id="eventsEmpty" style="display:none">Your timeline starts with the first event. Run <code>beam agent install <agent></code> or <code>beam hook <agent></code> to send one.</div></div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div id="tab-findings" class="tabpanel" style="display:none">
|
|
100
|
+
<div class="panel"><table><thead><tr><th>Time</th><th>Action / context</th><th>Agent</th><th>Risk</th><th>Review</th></tr></thead><tbody id="findingRows"></tbody></table><div class="empty" id="findingsEmpty" style="display:none">No flagged activity yet.</div></div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div id="tab-scan" class="tabpanel" style="display:none">
|
|
104
|
+
<div class="scan-grid">
|
|
105
|
+
<div class="panel">
|
|
106
|
+
<label>What are you checking?
|
|
107
|
+
<select id="scanKind"><option value="skill">Skill instructions / SKILL.md</option><option value="mcp">MCP configuration (JSON)</option></select>
|
|
108
|
+
</label>
|
|
109
|
+
<label>File name<input type="text" id="scanName" value="SKILL.md" maxlength="200"></label>
|
|
110
|
+
<label>Content<textarea id="scanContent" maxlength="500000" placeholder="Paste the content you want to inspect…"></textarea></label>
|
|
111
|
+
<button class="btn" id="scanBtn" style="width:100%">Scan for risky instructions</button>
|
|
112
|
+
<p class="muted" style="margin-top:10px">Content is analyzed locally and never executed. Heuristic rules can miss attacks and flag legitimate instructions.</p>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="panel" id="scanResult"><p class="muted">Findings will appear here after you run a scan.</p></div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</main>
|
|
119
|
+
<div id="drawerRoot"></div>
|
|
120
|
+
<script>
|
|
121
|
+
(function () {
|
|
122
|
+
var STORAGE_KEY = "beam-studio-token";
|
|
123
|
+
var token = "";
|
|
124
|
+
var origin = window.location.origin;
|
|
125
|
+
var state = { events: [], scans: [], reviews: {}, retention: 0 };
|
|
126
|
+
var pollTimer = null;
|
|
127
|
+
|
|
128
|
+
// localStorage can throw (private browsing, blocked site data) -- never let that break the page.
|
|
129
|
+
function saveToken(t) { try { localStorage.setItem(STORAGE_KEY, t); } catch (e) { /* ignore */ } }
|
|
130
|
+
function loadToken() { try { return localStorage.getItem(STORAGE_KEY) || ""; } catch (e) { return ""; } }
|
|
131
|
+
function clearToken() { try { localStorage.removeItem(STORAGE_KEY); } catch (e) { /* ignore */ } }
|
|
132
|
+
|
|
133
|
+
var params = new URLSearchParams(window.location.search);
|
|
134
|
+
var qsToken = params.get("token");
|
|
135
|
+
if (qsToken) {
|
|
136
|
+
history.replaceState(null, "", window.location.pathname);
|
|
137
|
+
connect(qsToken);
|
|
138
|
+
} else {
|
|
139
|
+
var stored = loadToken();
|
|
140
|
+
if (stored) connect(stored);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
document.getElementById("connectBtn").addEventListener("click", function () {
|
|
144
|
+
var t = document.getElementById("tokenInput").value.trim();
|
|
145
|
+
if (t) connect(t);
|
|
146
|
+
});
|
|
147
|
+
document.getElementById("tokenInput").addEventListener("keydown", function (e) {
|
|
148
|
+
if (e.key === "Enter") document.getElementById("connectBtn").click();
|
|
149
|
+
});
|
|
150
|
+
document.getElementById("disconnectBtn").addEventListener("click", disconnect);
|
|
151
|
+
|
|
152
|
+
function notice(message, isError) {
|
|
153
|
+
var el = document.getElementById("notice");
|
|
154
|
+
if (!message) { el.innerHTML = ""; return; }
|
|
155
|
+
el.innerHTML = '<div class="notice ' + (isError ? "error" : "") + '">' + escapeHtml(message) + "</div>";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function api(path, init) {
|
|
159
|
+
return fetch(origin + path, Object.assign({ cache: "no-store" }, init, {
|
|
160
|
+
headers: Object.assign({ "Authorization": "Bearer " + token, "Content-Type": "application/json" }, (init && init.headers) || {})
|
|
161
|
+
})).then(function (res) {
|
|
162
|
+
if (!res.ok) return res.json().then(function (d) { throw new Error(d.error || "Request failed (" + res.status + ")"); });
|
|
163
|
+
return res.json();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function connect(t) {
|
|
168
|
+
token = t;
|
|
169
|
+
api("/state").then(function (data) {
|
|
170
|
+
state = data;
|
|
171
|
+
saveToken(t);
|
|
172
|
+
document.getElementById("pairPanel").style.display = "none";
|
|
173
|
+
document.getElementById("app").style.display = "block";
|
|
174
|
+
document.getElementById("disconnectBtn").style.display = "inline-block";
|
|
175
|
+
setStatus(true);
|
|
176
|
+
notice("");
|
|
177
|
+
render();
|
|
178
|
+
if (pollTimer) clearInterval(pollTimer);
|
|
179
|
+
pollTimer = setInterval(poll, 3000);
|
|
180
|
+
}).catch(function (e) {
|
|
181
|
+
var reachable = e.message !== "Failed to fetch";
|
|
182
|
+
setStatus(false);
|
|
183
|
+
// A stored token that the collector now rejects (e.g. it was reinstalled with a fresh
|
|
184
|
+
// one) would otherwise silently fail on every reload; clear it so the pair form works.
|
|
185
|
+
if (reachable) clearToken();
|
|
186
|
+
notice(reachable ? e.message : "Cannot reach the collector. Is 'beam start' or 'beam service install' running?", true);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function disconnect() {
|
|
191
|
+
token = "";
|
|
192
|
+
clearToken();
|
|
193
|
+
if (pollTimer) clearInterval(pollTimer);
|
|
194
|
+
pollTimer = null;
|
|
195
|
+
state = { events: [], scans: [], reviews: {}, retention: 0 };
|
|
196
|
+
document.getElementById("pairPanel").style.display = "block";
|
|
197
|
+
document.getElementById("app").style.display = "none";
|
|
198
|
+
document.getElementById("disconnectBtn").style.display = "none";
|
|
199
|
+
document.getElementById("tokenInput").value = "";
|
|
200
|
+
setStatus(false);
|
|
201
|
+
notice("");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function poll() {
|
|
205
|
+
api("/state").then(function (data) { state = data; setStatus(true); render(); }).catch(function () { setStatus(false); });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function setStatus(on) {
|
|
209
|
+
document.getElementById("dot").className = "dot " + (on ? "on" : "off");
|
|
210
|
+
document.getElementById("statusText").textContent = on ? "Collector connected" : "Connection lost — retrying";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function risk(event) {
|
|
214
|
+
var order = { critical: 3, high: 2, medium: 1, info: 0 };
|
|
215
|
+
var top = "info";
|
|
216
|
+
(event.findings || []).forEach(function (f) { if (order[f.severity] > order[top]) top = f.severity; });
|
|
217
|
+
return top;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function escapeHtml(s) {
|
|
221
|
+
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
|
|
222
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function timeOf(iso) { try { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } catch (e) { return iso; } }
|
|
227
|
+
|
|
228
|
+
var activeTab = "activity";
|
|
229
|
+
document.querySelectorAll("nav.tabs button").forEach(function (btn) {
|
|
230
|
+
btn.addEventListener("click", function () {
|
|
231
|
+
activeTab = btn.getAttribute("data-tab");
|
|
232
|
+
document.querySelectorAll("nav.tabs button").forEach(function (b) { b.classList.toggle("active", b === btn); });
|
|
233
|
+
document.querySelectorAll(".tabpanel").forEach(function (p) { p.style.display = "none"; });
|
|
234
|
+
document.getElementById("tab-" + activeTab).style.display = "block";
|
|
235
|
+
render();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
["search", "agentFilter", "severityFilter"].forEach(function (id) {
|
|
239
|
+
document.getElementById(id).addEventListener("input", render);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
function render() {
|
|
243
|
+
var events = state.events.slice().sort(function (a, b) { return Date.parse(b.timestamp) - Date.parse(a.timestamp); });
|
|
244
|
+
var agents = Array.from(new Set(events.map(function (e) { return e.agent; }))).sort();
|
|
245
|
+
var agentSelect = document.getElementById("agentFilter");
|
|
246
|
+
var currentAgent = agentSelect.value;
|
|
247
|
+
agentSelect.innerHTML = '<option value="all">All agents</option>' + agents.map(function (a) { return "<option" + (a === currentAgent ? " selected" : "") + ">" + escapeHtml(a) + "</option>"; }).join("");
|
|
248
|
+
|
|
249
|
+
var flagged = events.filter(function (e) { return e.findings && e.findings.length && !state.reviews[e.id]; });
|
|
250
|
+
document.getElementById("flagCount").textContent = flagged.length;
|
|
251
|
+
|
|
252
|
+
document.getElementById("stats").innerHTML = [
|
|
253
|
+
["Recorded actions", events.length, "Latest " + (state.retention || 0).toLocaleString() + " retained locally"],
|
|
254
|
+
["Needs review", flagged.length, "Flagged events, not blocked actions"],
|
|
255
|
+
["Observed agents", agents.length, "Distinct source_agent values"],
|
|
256
|
+
["Saved scans", state.scans.length, "Skill / MCP reports"]
|
|
257
|
+
].map(function (s) { return "<article><span>" + s[0] + "</span><strong>" + s[1] + '</strong><small class="muted">' + s[2] + "</small></article>"; }).join("");
|
|
258
|
+
|
|
259
|
+
if (activeTab === "activity" || activeTab === "findings") {
|
|
260
|
+
var q = document.getElementById("search").value.toLowerCase();
|
|
261
|
+
var agentFilter = agentSelect.value;
|
|
262
|
+
var sevFilter = document.getElementById("severityFilter").value;
|
|
263
|
+
var visible = events.filter(function (e) {
|
|
264
|
+
if (activeTab === "findings" && (!e.findings || !e.findings.length)) return false;
|
|
265
|
+
if (agentFilter !== "all" && e.agent !== agentFilter) return false;
|
|
266
|
+
if (sevFilter !== "all" && risk(e) !== sevFilter) return false;
|
|
267
|
+
var haystack = (e.summary + " " + e.agent + " " + e.session + " " + e.type).toLowerCase();
|
|
268
|
+
return !q || haystack.indexOf(q) !== -1;
|
|
269
|
+
});
|
|
270
|
+
var rows = visible.slice(0, 500).map(function (e) {
|
|
271
|
+
var r = risk(e);
|
|
272
|
+
var reviewCell = (e.findings && e.findings.length)
|
|
273
|
+
? '<button class="btn secondary" data-review="' + e.id + '" style="padding:4px 10px;font-size:12px">' + (state.reviews[e.id] ? "✓ Reviewed" : "Mark reviewed") + "</button>"
|
|
274
|
+
: '<span class="muted">—</span>';
|
|
275
|
+
return '<tr class="row" data-id="' + e.id + '"><td class="mono">' + timeOf(e.timestamp) + "</td><td><strong>" + escapeHtml(e.summary).slice(0, 140) + '</strong><br><small class="muted">' + escapeHtml(e.type) + " · " + escapeHtml(e.session) + "</small></td><td>" + escapeHtml(e.agent) + '</td><td><span class="badge ' + r + '">' + (r === "info" ? "no flags" : r) + "</span></td><td>" + reviewCell + "</td></tr>";
|
|
276
|
+
}).join("");
|
|
277
|
+
var bodyId = activeTab === "findings" ? "findingRows" : "eventRows";
|
|
278
|
+
var emptyId = activeTab === "findings" ? "findingsEmpty" : "eventsEmpty";
|
|
279
|
+
document.getElementById(bodyId).innerHTML = rows;
|
|
280
|
+
document.getElementById(emptyId).style.display = visible.length ? "none" : "block";
|
|
281
|
+
document.querySelectorAll("#" + bodyId + " tr.row").forEach(function (tr) {
|
|
282
|
+
tr.addEventListener("click", function (ev) {
|
|
283
|
+
if (ev.target.hasAttribute("data-review")) return;
|
|
284
|
+
openDrawer(events.find(function (e) { return e.id === tr.getAttribute("data-id"); }));
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
document.querySelectorAll("#" + bodyId + " [data-review]").forEach(function (btn) {
|
|
288
|
+
btn.addEventListener("click", function () {
|
|
289
|
+
var id = btn.getAttribute("data-review");
|
|
290
|
+
var reviewed = !state.reviews[id];
|
|
291
|
+
api("/review", { method: "POST", body: JSON.stringify({ id: id, reviewed: reviewed }) }).then(poll).catch(function (e) { notice(e.message, true); });
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function openDrawer(event) {
|
|
299
|
+
if (!event) return;
|
|
300
|
+
var root = document.getElementById("drawerRoot");
|
|
301
|
+
var findings = (event.findings || []).map(function (f) {
|
|
302
|
+
return '<div class="finding"><span class="badge ' + f.severity + '">' + f.severity + "</span><h3>" + escapeHtml(f.title) + '</h3><p class="muted">' + escapeHtml(f.explanation) + "</p><pre>" + escapeHtml(f.evidence) + "</pre></div>";
|
|
303
|
+
}).join("") || '<p class="muted">No configured rule matched. This does not establish that an action is safe.</p>';
|
|
304
|
+
root.innerHTML = '<div class="drawer-backdrop"><section class="drawer"><button class="close">×</button><h2>Action detail</h2><span class="badge ' + risk(event) + '">' + risk(event) + '</span><pre style="white-space:pre-wrap;background:#0e1013;padding:10px;border-radius:6px;margin-top:10px">' + escapeHtml(event.summary) + '</pre><dl>' +
|
|
305
|
+
[["Agent", event.agent], ["Session", event.session], ["Time", new Date(event.timestamp).toLocaleString()], ["Source", event.source], ["Status", event.phase], ["Project", event.project || "Not supplied"], ["Endpoint", event.endpoint], ["Model", event.model || "Not supplied"]]
|
|
306
|
+
.map(function (kv) { return "<dt>" + kv[0] + "</dt><dd>" + escapeHtml(kv[1]) + "</dd>"; }).join("") +
|
|
307
|
+
"</dl>" + findings + "</section></div>";
|
|
308
|
+
root.querySelector(".close").addEventListener("click", function () { root.innerHTML = ""; });
|
|
309
|
+
root.querySelector(".drawer-backdrop").addEventListener("click", function (e) { if (e.target === e.currentTarget) root.innerHTML = ""; });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
document.getElementById("scanBtn").addEventListener("click", function () {
|
|
313
|
+
var name = document.getElementById("scanName").value.trim() || "SKILL.md";
|
|
314
|
+
var kind = document.getElementById("scanKind").value;
|
|
315
|
+
var content = document.getElementById("scanContent").value;
|
|
316
|
+
if (!content.trim()) { notice("Paste some content to scan first.", true); return; }
|
|
317
|
+
api("/scan", { method: "POST", body: JSON.stringify({ name: name, kind: kind, content: content }) }).then(function (scan) {
|
|
318
|
+
renderScanResult(scan);
|
|
319
|
+
poll();
|
|
320
|
+
}).catch(function (e) { notice(e.message, true); });
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
function renderScanResult(scan) {
|
|
324
|
+
var findings = (scan.findings || []).map(function (f) {
|
|
325
|
+
return '<div class="finding"><span class="badge ' + f.severity + '">' + f.severity + "</span><h3>" + escapeHtml(f.title) + '</h3><p class="muted">' + escapeHtml(f.explanation) + "</p>" + (f.line ? '<small class="muted">Near line ' + f.line + "</small>" : "") + "<pre>" + escapeHtml(f.evidence) + "</pre></div>";
|
|
326
|
+
}).join("") || '<p class="muted">No configured rule matched. This is not a safety guarantee.</p>';
|
|
327
|
+
document.getElementById("scanResult").innerHTML = '<p class="muted">' + escapeHtml(scan.kind) + " · " + scan.lines + ' lines</p><h3 style="margin-top:0">' + escapeHtml(scan.name) + "</h3>" + findings + '<p class="mono muted" style="margin-top:10px">sha256 ' + scan.hash + "</p>";
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
setStatus(false);
|
|
331
|
+
})();
|
|
332
|
+
</script>
|
|
333
|
+
</body>
|
|
334
|
+
</html>
|
|
335
|
+
`;
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=studio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio.js","sourceRoot":"","sources":["../src/studio.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,6FAA6F;AAC7F,+FAA+F;AAC/F,MAAM,UAAU,UAAU;IACxB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0UR,CAAC;AACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-beam/beam",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Beam - local observation and heuristic risk scanning for AI agent activity",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"beam": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/cli.js",
|
|
15
|
+
"build:watch": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
16
|
+
"dev": "tsx src/cli.ts",
|
|
17
|
+
"test": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
|
|
18
|
+
"test:watch": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
19
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
21
|
+
"setup:global": "bash scripts/setup-global.sh"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20.17.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"commander": "^13.0.0"
|
|
31
|
+
},
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"beam-enterprise": "^0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/jest": "^30.0.0",
|
|
37
|
+
"@types/node": "^22.10.5",
|
|
38
|
+
"jest": "^30.2.0",
|
|
39
|
+
"ts-jest": "^29.4.5",
|
|
40
|
+
"tsx": "^4.19.2",
|
|
41
|
+
"typescript": "^5.7.2"
|
|
42
|
+
}
|
|
43
|
+
}
|