@agent-beam/beam 0.2.21 → 0.2.23
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 +2 -0
- package/dist/cli.js +179 -2
- package/dist/cli.js.map +1 -1
- package/dist/client.js +52 -6
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +9 -0
- package/dist/core.js +5 -1
- package/dist/core.js.map +1 -1
- package/dist/edit-stats.d.ts +6 -0
- package/dist/edit-stats.js +74 -0
- package/dist/edit-stats.js.map +1 -0
- 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 +18 -3
- package/dist/hook-adapters.js.map +1 -1
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +23 -1
- package/dist/serve.js.map +1 -1
- package/dist/session-title.d.ts +25 -0
- package/dist/session-title.js +185 -0
- package/dist/session-title.js.map +1 -0
- package/dist/setup.js +32 -1
- 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/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 +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { getDataDirectory } from "./config.js";
|
|
5
|
+
import { readIdentity } from "./enroll.js";
|
|
6
|
+
import { forwardSkillInventory } from "./forward.js";
|
|
7
|
+
import { readInventory, trustedHashes } from "./skill-inventory.js";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
export const MAX_REPORT_SKILLS = 1000;
|
|
10
|
+
// The matched line of each finding is sent so the dashboard can show what exactly triggered it.
|
|
11
|
+
// That is skill text leaving the machine (secrets are redacted first, and it is capped at a few
|
|
12
|
+
// lines per skill), so it can be switched off: BEAM_SKILL_SEND_LINES=0 sends titles, locations
|
|
13
|
+
// and explanations only.
|
|
14
|
+
export function sendMatchedLines(env = process.env) {
|
|
15
|
+
return !["0", "false", "off", "no"].includes(String(env.BEAM_SKILL_SEND_LINES ?? "").trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
const stateFile = () => join(getDataDirectory(), "skills-sync.json");
|
|
18
|
+
const today = () => new Date().toISOString().slice(0, 10);
|
|
19
|
+
export function redactPath(dir, home = homedir()) {
|
|
20
|
+
return dir === home ? "~" : dir.startsWith(home + "/") || dir.startsWith(home + "\\") ? `~${dir.slice(home.length)}` : dir;
|
|
21
|
+
}
|
|
22
|
+
export async function buildSkillReport(home = homedir()) {
|
|
23
|
+
const inventory = await readInventory();
|
|
24
|
+
if (!inventory)
|
|
25
|
+
return null;
|
|
26
|
+
const trusted = await trustedHashes();
|
|
27
|
+
const lines = sendMatchedLines();
|
|
28
|
+
// Riskiest first, so the cap (a safety limit) only ever drops the least interesting skills.
|
|
29
|
+
const skills = inventory.skills.slice(0, MAX_REPORT_SKILLS).map((s) => ({
|
|
30
|
+
id: s.id ?? createHash("sha256").update(s.dir).digest("hex").slice(0, 16), name: s.name, agent: s.agent, scope: s.scope,
|
|
31
|
+
path: redactPath(s.dir, home), repo: s.repo ? basename(s.repo) : null, hash: s.hash, score: s.score, level: s.level,
|
|
32
|
+
findings: s.findings, injection: s.injection, trusted: trusted.has(s.hash), allFindings: s.allFindings ?? [], topFindings: (s.topFindings ?? []).map(({ evidence, ...rest }) => (lines && evidence ? { ...rest, evidence } : rest)),
|
|
33
|
+
files: s.files, firstSeen: s.firstSeen, lastSeen: s.lastSeen, modifiedAt: s.modifiedAt,
|
|
34
|
+
}));
|
|
35
|
+
return { generatedAt: inventory.generatedAt, day: today(), skills };
|
|
36
|
+
}
|
|
37
|
+
// What the workspace already knows. Dates are left out on purpose: re-indexing bumps lastSeen on
|
|
38
|
+
// every skill, and that alone must never count as an update. A skill added, removed, edited (new
|
|
39
|
+
// hash), re-scored, or trusted/untrusted does change it.
|
|
40
|
+
export function fingerprint(report) {
|
|
41
|
+
// The findings are hashed in too, so a report that gains matched lines (an upgrade, or the switch
|
|
42
|
+
// above being flipped) is sent once even though no skill's score changed.
|
|
43
|
+
const detail = (s) => createHash("sha256").update(JSON.stringify([s.topFindings, s.allFindings])).digest("hex").slice(0, 12);
|
|
44
|
+
return createHash("sha256").update(report.skills.map(s => `${s.id}|${s.name}|${s.score}|${s.level}|${s.hash}|${s.trusted}|${detail(s)}`).sort().join("\n")).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
async function lastSent() {
|
|
47
|
+
try {
|
|
48
|
+
const state = JSON.parse(await readFile(stateFile(), "utf8"));
|
|
49
|
+
return { fingerprint: typeof state.fingerprint === "string" ? state.fingerprint : null };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { fingerprint: null };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function recordSent(print, day) {
|
|
56
|
+
await mkdir(getDataDirectory(), { recursive: true, mode: 0o700 });
|
|
57
|
+
const temp = `${stateFile()}.tmp`;
|
|
58
|
+
await writeFile(temp, JSON.stringify({ fingerprint: print, day, sentAt: new Date().toISOString() }) + "\n", { mode: 0o600 });
|
|
59
|
+
await rename(temp, stateFile());
|
|
60
|
+
}
|
|
61
|
+
// Sends the skill names and risk scores to the workspace collector only when they differ from what
|
|
62
|
+
// was last sent -- a new skill, a changed one, or a removed one. An unchanged machine sends nothing,
|
|
63
|
+
// however often this runs. A failed send isn't recorded, so the next run retries it.
|
|
64
|
+
export async function syncSkillInventory(options = {}) {
|
|
65
|
+
if (!await readIdentity())
|
|
66
|
+
return { status: "not-enrolled" };
|
|
67
|
+
const report = await buildSkillReport(options.home);
|
|
68
|
+
if (!report)
|
|
69
|
+
return { status: "no-inventory" };
|
|
70
|
+
const print = fingerprint(report);
|
|
71
|
+
if (!options.force && (await lastSent()).fingerprint === print)
|
|
72
|
+
return { status: "unchanged" };
|
|
73
|
+
const sent = await forwardSkillInventory(report);
|
|
74
|
+
if (!sent.ok)
|
|
75
|
+
return { status: "failed", reason: sent.reason };
|
|
76
|
+
await recordSent(print, report.day);
|
|
77
|
+
return { status: "sent", skills: report.skills.length };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=skill-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-sync.js","sourceRoot":"","sources":["../src/skill-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type IndexChanges } from "./skill-inventory.js";
|
|
2
|
+
import { type SkillSyncResult } from "./skill-sync.js";
|
|
3
|
+
export declare const CHECK_EVERY_MS: number;
|
|
4
|
+
export type SkillCheck = {
|
|
5
|
+
scanned: "agents" | "full" | "none";
|
|
6
|
+
changes: IndexChanges;
|
|
7
|
+
sync: SkillSyncResult | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function skillCheckDue(now?: number): Promise<boolean>;
|
|
10
|
+
export declare function checkSkills(options?: {
|
|
11
|
+
home?: string;
|
|
12
|
+
}): Promise<SkillCheck>;
|
|
13
|
+
export declare function checkSkillsIfDue(options?: {
|
|
14
|
+
home?: string;
|
|
15
|
+
now?: number;
|
|
16
|
+
}): Promise<SkillCheck | null>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { agentSkillRoots, indexSkills, readInventory } from "./skill-inventory.js";
|
|
2
|
+
import { syncSkillInventory } from "./skill-sync.js";
|
|
3
|
+
export const CHECK_EVERY_MS = 24 * 60 * 60_000;
|
|
4
|
+
// Due when there is no inventory yet, or the last index (this watch, or a manual `beam skills
|
|
5
|
+
// index` / `beam skills sync`) is a day old. Reading one small file is all this costs.
|
|
6
|
+
export async function skillCheckDue(now = Date.now()) {
|
|
7
|
+
const inventory = await readInventory();
|
|
8
|
+
const last = inventory ? Date.parse(inventory.generatedAt) : NaN;
|
|
9
|
+
return !Number.isFinite(last) || now - last >= CHECK_EVERY_MS;
|
|
10
|
+
}
|
|
11
|
+
// The daily skill scan:
|
|
12
|
+
// - if you have already searched your whole home folder (`beam skills index` or `beam skills
|
|
13
|
+
// list`), repeat that search, so skills added in project folders are found too;
|
|
14
|
+
// - otherwise only check the agents' own skill folders. It never starts the whole-home search on
|
|
15
|
+
// its own: that search is what makes macOS ask for access to Desktop, Documents and
|
|
16
|
+
// Downloads, and it should only happen when you asked for it;
|
|
17
|
+
// - then report to the workspace if anything changed (skill-sync.ts sends nothing otherwise).
|
|
18
|
+
export async function checkSkills(options = {}) {
|
|
19
|
+
const previous = await readInventory();
|
|
20
|
+
let scanned = "none";
|
|
21
|
+
let changes = { added: [], modified: [], removed: [] };
|
|
22
|
+
if (previous?.lastFullScanAt) {
|
|
23
|
+
({ changes } = await indexSkills({}, options.home));
|
|
24
|
+
scanned = "full";
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const roots = await agentSkillRoots(options.home);
|
|
28
|
+
if (roots.length) {
|
|
29
|
+
({ changes } = await indexSkills({ roots }, options.home));
|
|
30
|
+
scanned = "agents";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (scanned === "none" && !previous)
|
|
34
|
+
return { scanned, changes, sync: null };
|
|
35
|
+
return { scanned, changes, sync: await syncSkillInventory({ home: options.home }).catch(() => null) };
|
|
36
|
+
}
|
|
37
|
+
// What the background service calls: does nothing unless a day has passed.
|
|
38
|
+
export async function checkSkillsIfDue(options = {}) {
|
|
39
|
+
return await skillCheckDue(options.now) ? checkSkills(options) : null;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=skill-watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-watch.js","sourceRoot":"","sources":["../src/skill-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;AACtG,OAAO,EAAE,kBAAkB,EAAwB,MAAM,iBAAiB,CAAC;AAE3E,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;AAI/C,8FAA8F;AAC9F,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAClD,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACjE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,IAAI,IAAI,cAAc,CAAC;AAChE,CAAC;AAED,wBAAwB;AACxB,8FAA8F;AAC9F,mFAAmF;AACnF,kGAAkG;AAClG,uFAAuF;AACvF,iEAAiE;AACjE,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAA6B,EAAE;IAC/D,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;IACvC,IAAI,OAAO,GAA0B,MAAM,CAAC;IAC5C,IAAI,OAAO,GAAiB,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACrE,IAAI,QAAQ,EAAE,cAAc,EAAE,CAAC;QAC7B,CAAC,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAAC,CAAC,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAAC,OAAO,GAAG,QAAQ,CAAC;QAAC,CAAC;IACvG,CAAC;IACD,IAAI,OAAO,KAAK,MAAM,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;AACxG,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,UAA2C,EAAE;IAClF,OAAO,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC"}
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SkillScan } from "./skill-scan.js";
|
|
2
|
+
export declare const SKILL_TARGETS: Record<string, {
|
|
3
|
+
project: string;
|
|
4
|
+
global: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const ALL_SKILL_AGENTS: string[];
|
|
7
|
+
export declare const SKILL_DIRS: string[];
|
|
8
|
+
export type AddSkillOptions = {
|
|
9
|
+
skill?: string;
|
|
10
|
+
agent?: string[];
|
|
11
|
+
allAgents?: boolean;
|
|
12
|
+
global?: boolean;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
scanOnly?: boolean;
|
|
15
|
+
cwd?: string;
|
|
16
|
+
home?: string;
|
|
17
|
+
};
|
|
18
|
+
export type AddSkillResult = {
|
|
19
|
+
source: string;
|
|
20
|
+
scans: SkillScan[];
|
|
21
|
+
installed: Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
path: string;
|
|
24
|
+
}>;
|
|
25
|
+
blocked: string[];
|
|
26
|
+
};
|
|
27
|
+
export declare const isBlocked: (scan: SkillScan) => boolean;
|
|
28
|
+
export declare function discoverSkills(root: string): Promise<Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
dir: string;
|
|
31
|
+
}>>;
|
|
32
|
+
export declare function addSkill(source: string, options?: AddSkillOptions): Promise<AddSkillResult>;
|
|
33
|
+
export declare function scanInstalledSkills(home?: string, cwd?: string): Promise<SkillScan[]>;
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { cp, lstat, mkdir, mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { severityRank } from "./core.js";
|
|
7
|
+
import { scanSkillDir } from "./skill-scan.js";
|
|
8
|
+
const run = promisify(execFile);
|
|
9
|
+
// Skill directory per agent, relative to the project (or home with --global). Codex, Cursor and
|
|
10
|
+
// Gemini CLI and OpenCode share the universal .agents/skills location. Copilot CLI is the one
|
|
11
|
+
// agent whose project and user folders differ (.github/skills vs ~/.copilot/skills).
|
|
12
|
+
export const SKILL_TARGETS = {
|
|
13
|
+
"claude-code": { project: ".claude/skills", global: ".claude/skills" },
|
|
14
|
+
codex: { project: ".agents/skills", global: ".agents/skills" },
|
|
15
|
+
cursor: { project: ".agents/skills", global: ".agents/skills" },
|
|
16
|
+
gemini: { project: ".agents/skills", global: ".agents/skills" },
|
|
17
|
+
opencode: { project: ".agents/skills", global: ".agents/skills" },
|
|
18
|
+
"copilot-cli": { project: ".github/skills", global: ".copilot/skills" },
|
|
19
|
+
};
|
|
20
|
+
export const ALL_SKILL_AGENTS = Object.keys(SKILL_TARGETS);
|
|
21
|
+
// Every folder skills can live in, for scanning what's already installed.
|
|
22
|
+
export const SKILL_DIRS = [...new Set(Object.values(SKILL_TARGETS).flatMap(t => [t.project, t.global]))];
|
|
23
|
+
// Blocks on prompt-injection findings at high+, or any critical finding. Other high findings
|
|
24
|
+
// (production changes, metadata endpoints...) only warn: legitimate skills, defensive security
|
|
25
|
+
// ones especially, describe those things without being malicious.
|
|
26
|
+
export const isBlocked = (scan) => scan.findings.some(f => f.severity === "critical" || (f.id.startsWith("injection.") && severityRank[f.severity] >= severityRank.high));
|
|
27
|
+
const SHORTHAND = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/;
|
|
28
|
+
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
29
|
+
async function isDir(path) { try {
|
|
30
|
+
return (await stat(path)).isDirectory();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
} }
|
|
35
|
+
async function hasSkillMd(dir) { try {
|
|
36
|
+
return (await stat(join(dir, "SKILL.md"))).isFile();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
} }
|
|
41
|
+
function cloneUrl(source) {
|
|
42
|
+
const short = source.match(SHORTHAND);
|
|
43
|
+
if (short)
|
|
44
|
+
return `https://github.com/${short[1]}/${short[2].replace(/\.git$/, "")}.git`;
|
|
45
|
+
if (/^https:\/\/(?:github\.com|gitlab\.com)\/[\w.-]+\/[\w.-]+?(?:\.git)?\/?$/.test(source))
|
|
46
|
+
return source.replace(/\/$/, "");
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
// SKILL.md at the root, or under skills/<name>/ (and .claude/skills, .agents/skills), matching
|
|
50
|
+
// the layouts explainx and the Vercel skills CLI both use.
|
|
51
|
+
export async function discoverSkills(root) {
|
|
52
|
+
if (await hasSkillMd(root))
|
|
53
|
+
return [{ name: basename(root), dir: root }];
|
|
54
|
+
const found = [];
|
|
55
|
+
for (const base of ["skills", ".claude/skills", ".agents/skills"]) {
|
|
56
|
+
const parent = join(root, base);
|
|
57
|
+
if (!await isDir(parent))
|
|
58
|
+
continue;
|
|
59
|
+
for (const entry of await readdir(parent, { withFileTypes: true })) {
|
|
60
|
+
if (entry.isDirectory() && await hasSkillMd(join(parent, entry.name)))
|
|
61
|
+
found.push({ name: entry.name, dir: join(parent, entry.name) });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
export async function addSkill(source, options = {}) {
|
|
67
|
+
const cwd = options.cwd ?? process.cwd();
|
|
68
|
+
const agents = options.allAgents ? ALL_SKILL_AGENTS : options.agent?.length ? options.agent : ["claude-code"];
|
|
69
|
+
for (const a of agents)
|
|
70
|
+
if (!SKILL_TARGETS[a])
|
|
71
|
+
throw new Error(`Unknown agent "${a}". Choose from: ${Object.keys(SKILL_TARGETS).join(", ")}.`);
|
|
72
|
+
const staging = await mkdtemp(join(tmpdir(), "beam-skill-"));
|
|
73
|
+
try {
|
|
74
|
+
let root;
|
|
75
|
+
const local = resolve(cwd, source);
|
|
76
|
+
if (await isDir(local))
|
|
77
|
+
root = local;
|
|
78
|
+
else {
|
|
79
|
+
const url = cloneUrl(source);
|
|
80
|
+
if (!url)
|
|
81
|
+
throw new Error(`"${source}" is neither a local folder nor an owner/repo or https GitHub/GitLab URL.`);
|
|
82
|
+
root = join(staging, "repo");
|
|
83
|
+
// hooksPath=/dev/null: a cloned repo must never get to run its own git hooks.
|
|
84
|
+
await run("git", ["-c", "core.hooksPath=/dev/null", "clone", "--depth", "1", "--quiet", url, root], { timeout: 60_000 });
|
|
85
|
+
}
|
|
86
|
+
let skills = await discoverSkills(root);
|
|
87
|
+
if (options.skill)
|
|
88
|
+
skills = skills.filter(s => s.name === options.skill);
|
|
89
|
+
if (!skills.length)
|
|
90
|
+
throw new Error(options.skill ? `No skill named "${options.skill}" found in ${source}.` : `No SKILL.md found in ${source}.`);
|
|
91
|
+
if (skills.length > 1 && !options.skill && !options.scanOnly) {
|
|
92
|
+
throw new Error(`${source} contains ${skills.length} skills (${skills.map(s => s.name).join(", ")}). Pick one with --skill <name>.`);
|
|
93
|
+
}
|
|
94
|
+
const scans = await Promise.all(skills.map(s => scanSkillDir(s.dir, s.name)));
|
|
95
|
+
const result = { source, scans, installed: [], blocked: [] };
|
|
96
|
+
if (options.scanOnly)
|
|
97
|
+
return result;
|
|
98
|
+
const base = options.global ? (options.home ?? homedir()) : cwd;
|
|
99
|
+
for (const scan of scans) {
|
|
100
|
+
if (isBlocked(scan) && !options.force) {
|
|
101
|
+
result.blocked.push(scan.name);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!SAFE_NAME.test(scan.name))
|
|
105
|
+
throw new Error(`Refusing unsafe skill name "${scan.name}".`);
|
|
106
|
+
for (const dest of new Set(agents.map(a => join(base, SKILL_TARGETS[a][options.global ? "global" : "project"], scan.name)))) {
|
|
107
|
+
await mkdir(join(dest, ".."), { recursive: true });
|
|
108
|
+
// Symlinks are dropped so a skill can't point an installed file at something outside itself.
|
|
109
|
+
await cp(scan.dir, dest, { recursive: true, force: true, filter: async (src) => !/[\\/]\.git(?:[\\/]|$)/.test(src) && !(await lstat(src)).isSymbolicLink() });
|
|
110
|
+
result.installed.push({ name: scan.name, path: dest });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await rm(staging, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// `beam skills scan` with no path: every skill already installed for the supported agents.
|
|
120
|
+
export async function scanInstalledSkills(home = homedir(), cwd = process.cwd()) {
|
|
121
|
+
const scans = [];
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
for (const base of [home, cwd]) {
|
|
124
|
+
for (const rel of SKILL_DIRS) {
|
|
125
|
+
const parent = join(base, rel);
|
|
126
|
+
if (!await isDir(parent))
|
|
127
|
+
continue;
|
|
128
|
+
for (const entry of await readdir(parent, { withFileTypes: true })) {
|
|
129
|
+
const dir = join(parent, entry.name);
|
|
130
|
+
if (!entry.isDirectory() || seen.has(dir) || !await hasSkillMd(dir))
|
|
131
|
+
continue;
|
|
132
|
+
seen.add(dir);
|
|
133
|
+
scans.push(await scanSkillDir(dir, entry.name));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return scans;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type TrustedSkill = {
|
|
2
|
+
name: string;
|
|
3
|
+
hash: string;
|
|
4
|
+
trustedAt: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const trustedSkillsFile: () => string;
|
|
7
|
+
export declare function readTrustedSkills(): Promise<TrustedSkill[]>;
|
|
8
|
+
export declare function trustSkill(name: string, hash: string): Promise<void>;
|
|
9
|
+
export declare function untrustSkill(nameOrHash: string): Promise<number>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getDataDirectory } from "./config.js";
|
|
4
|
+
export const trustedSkillsFile = () => join(getDataDirectory(), "trusted-skills.json");
|
|
5
|
+
export async function readTrustedSkills() {
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(await readFile(trustedSkillsFile(), "utf8"));
|
|
8
|
+
return Array.isArray(parsed) ? parsed.filter((t) => typeof t?.hash === "string" && typeof t?.name === "string") : [];
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function write(list) {
|
|
15
|
+
await mkdir(getDataDirectory(), { recursive: true, mode: 0o700 });
|
|
16
|
+
const file = trustedSkillsFile();
|
|
17
|
+
const temp = `${file}.tmp`;
|
|
18
|
+
await writeFile(temp, JSON.stringify(list, null, 2) + "\n", { mode: 0o600 });
|
|
19
|
+
await rename(temp, file);
|
|
20
|
+
}
|
|
21
|
+
export async function trustSkill(name, hash) {
|
|
22
|
+
const list = (await readTrustedSkills()).filter(t => t.hash !== hash);
|
|
23
|
+
await write([...list, { name, hash, trustedAt: new Date().toISOString() }]);
|
|
24
|
+
}
|
|
25
|
+
export async function untrustSkill(nameOrHash) {
|
|
26
|
+
const list = await readTrustedSkills();
|
|
27
|
+
const next = list.filter(t => t.name !== nameOrHash && t.hash !== nameOrHash);
|
|
28
|
+
await write(next);
|
|
29
|
+
return list.length - next.length;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=trusted-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trusted-skills.js","sourceRoot":"","sources":["../src/trusted-skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,qBAAqB,CAAC,CAAC;AAEvF,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,iBAAiB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAqB,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1I,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxB,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,IAAoB;IACvC,MAAM,KAAK,CAAC,gBAAgB,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IAAC,MAAM,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC;IAC7D,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7E,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY;IACzD,MAAM,IAAI,GAAG,CAAC,MAAM,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACtE,MAAM,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;AAC9E,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAAkB;IACnD,MAAM,IAAI,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC9E,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;AACnC,CAAC"}
|
package/dist/tty-prompt.js
CHANGED
|
@@ -35,7 +35,9 @@ export async function promptTty(message, choices) {
|
|
|
35
35
|
clearTimeout(timer);
|
|
36
36
|
rl.close();
|
|
37
37
|
input.destroy();
|
|
38
|
-
|
|
38
|
+
// Flush the prompt before closing the terminal stream. Destroying it immediately can
|
|
39
|
+
// interrupt an in-flight write and emit ERR_STREAM_DESTROYED after the choice is read.
|
|
40
|
+
output.end();
|
|
39
41
|
resolvePromise(value);
|
|
40
42
|
};
|
|
41
43
|
const timer = setTimeout(() => finish(null), PROMPT_TIMEOUT_MS);
|
package/dist/tty-prompt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tty-prompt.js","sourceRoot":"","sources":["../src/tty-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIhD,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAElC,8FAA8F;AAC9F,8FAA8F;AAC9F,4FAA4F;AAC5F,6FAA6F;AAC7F,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe,EAAE,OAAoB;IACnE,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,CAAC;QAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAErE,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC,EAAE;QAClC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,KAA0C,CAAC;QAC/C,IAAI,MAA4C,CAAC;QACjD,IAAI,CAAC;YACH,KAAK,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;YACrC,MAAM,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,cAAc,CAAC,IAAI,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,EAAE;YACtC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"tty-prompt.js","sourceRoot":"","sources":["../src/tty-prompt.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-beam/beam",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.23",
|
|
4
4
|
"description": "Local-first observation and heuristic risk scanning for AI coding agents (Claude Code, Codex, Cursor, Copilot CLI, Gemini). Redacts secrets, flags risky tool calls, scans skills and MCP configs offline.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai agent security",
|