@venos-inc/venos 0.1.3 → 0.1.5
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/dist/index.js +82 -55
- package/etc/mcp-server/client.js +25 -2
- package/etc/mcp-server/node_modules/@eslint/eslintrc/node_modules/.bin/js-yaml +2 -2
- package/etc/mcp-server/node_modules/@eslint-community/eslint-utils/node_modules/.bin/eslint +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/eslint-plugin/node_modules/.bin/eslint +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/eslint-plugin/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/eslint-plugin/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/parser/node_modules/.bin/eslint +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/parser/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/parser/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/project-service/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/project-service/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/tsconfig-utils/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/tsconfig-utils/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/type-utils/node_modules/.bin/eslint +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/type-utils/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/type-utils/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/typescript-estree/node_modules/.bin/semver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/typescript-estree/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/typescript-estree/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/utils/node_modules/.bin/eslint +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/utils/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/@typescript-eslint/utils/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/@vitest/mocker/node_modules/.bin/vite +4 -4
- package/etc/mcp-server/node_modules/acorn-jsx/node_modules/.bin/acorn +2 -2
- package/etc/mcp-server/node_modules/cross-spawn/node_modules/.bin/node-which +2 -2
- package/etc/mcp-server/node_modules/espree/node_modules/.bin/acorn +2 -2
- package/etc/mcp-server/node_modules/node-addon-api/LICENSE.md +6 -2
- package/etc/mcp-server/node_modules/node-addon-api/README.md +10 -12
- package/etc/mcp-server/node_modules/node-addon-api/common.gypi +2 -1
- package/etc/mcp-server/node_modules/node-addon-api/index.js +1 -2
- package/etc/mcp-server/node_modules/node-addon-api/napi-inl.h +72 -91
- package/etc/mcp-server/node_modules/node-addon-api/napi.h +5 -15
- package/etc/mcp-server/node_modules/node-addon-api/package.json +2 -18
- package/etc/mcp-server/node_modules/postcss/node_modules/.bin/nanoid +2 -2
- package/etc/mcp-server/node_modules/sharp/node_modules/.bin/semver +2 -2
- package/etc/mcp-server/node_modules/source-map/LICENSE +28 -0
- package/etc/mcp-server/node_modules/source-map/README.md +837 -0
- package/etc/mcp-server/node_modules/source-map/lib/array-set.js +100 -0
- package/etc/mcp-server/node_modules/source-map/lib/base64-vlq.js +94 -0
- package/etc/mcp-server/node_modules/source-map/lib/base64.js +19 -0
- package/etc/mcp-server/node_modules/source-map/lib/binary-search.js +113 -0
- package/etc/mcp-server/node_modules/source-map/lib/mapping-list.js +83 -0
- package/etc/mcp-server/node_modules/source-map/lib/mappings.wasm +0 -0
- package/etc/mcp-server/node_modules/source-map/lib/read-wasm-browser.js +23 -0
- package/etc/mcp-server/node_modules/source-map/lib/read-wasm.js +27 -0
- package/etc/mcp-server/node_modules/source-map/lib/source-map-consumer.js +1081 -0
- package/etc/mcp-server/node_modules/source-map/lib/source-map-generator.js +439 -0
- package/etc/mcp-server/node_modules/source-map/lib/source-node.js +430 -0
- package/etc/mcp-server/node_modules/source-map/lib/url.js +13 -0
- package/etc/mcp-server/node_modules/source-map/lib/util.js +444 -0
- package/etc/mcp-server/node_modules/source-map/lib/wasm.js +138 -0
- package/etc/mcp-server/node_modules/source-map/package.json +79 -0
- package/etc/mcp-server/node_modules/source-map/source-map.d.ts +423 -0
- package/etc/mcp-server/node_modules/source-map/source-map.js +10 -0
- package/etc/mcp-server/node_modules/ts-api-utils/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/ts-api-utils/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/typescript-eslint/node_modules/.bin/eslint +2 -2
- package/etc/mcp-server/node_modules/typescript-eslint/node_modules/.bin/tsc +2 -2
- package/etc/mcp-server/node_modules/typescript-eslint/node_modules/.bin/tsserver +2 -2
- package/etc/mcp-server/node_modules/vite/node_modules/.bin/esbuild +2 -2
- package/etc/mcp-server/node_modules/vite/node_modules/.bin/rollup +2 -2
- package/etc/mcp-server/node_modules/vite-node/node_modules/.bin/vite +2 -2
- package/etc/mcp-server/node_modules/vitest/node_modules/.bin/vite +2 -2
- package/etc/mcp-server/node_modules/vitest/node_modules/.bin/vite-node +2 -2
- package/etc/mcp-server/node_modules/vitest/node_modules/.bin/why-is-node-running +2 -2
- package/etc/mcp-server/skill-spector.js +104 -0
- package/etc/mcp-server/tools.js +157 -2
- package/package.json +1 -1
- package/etc/mcp-server/node_modules/node-addon-api/node_addon_api.gyp +0 -32
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// SkillSpector scanner integration.
|
|
2
|
+
// Runs the `skillspector` CLI as a subprocess and returns a structured summary
|
|
3
|
+
// of skill-security findings. Requires skillspector to be in PATH.
|
|
4
|
+
// Static analysis runs by default; pass useLlm to opt into the LLM stage.
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"];
|
|
9
|
+
function emptySummary(target, installed, errorMessage) {
|
|
10
|
+
return {
|
|
11
|
+
target,
|
|
12
|
+
skillName: "",
|
|
13
|
+
riskScore: 0,
|
|
14
|
+
riskSeverity: "",
|
|
15
|
+
recommendation: "",
|
|
16
|
+
critical: 0,
|
|
17
|
+
high: 0,
|
|
18
|
+
medium: 0,
|
|
19
|
+
low: 0,
|
|
20
|
+
total: 0,
|
|
21
|
+
topFindings: [],
|
|
22
|
+
skillspectorInstalled: installed,
|
|
23
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// summarize maps SkillSpector's JSON report to the tool's stable summary shape.
|
|
27
|
+
// Pure function so it can be unit-tested without the scanner installed.
|
|
28
|
+
export function summarize(raw, target) {
|
|
29
|
+
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, UNKNOWN: 0 };
|
|
30
|
+
const issues = raw.issues ?? [];
|
|
31
|
+
for (const issue of issues) {
|
|
32
|
+
const sev = (issue.severity ?? "UNKNOWN").toUpperCase();
|
|
33
|
+
if (sev in counts)
|
|
34
|
+
counts[sev]++;
|
|
35
|
+
else
|
|
36
|
+
counts.UNKNOWN++;
|
|
37
|
+
}
|
|
38
|
+
const sorted = [...issues].sort((a, b) => SEVERITY_ORDER.indexOf((a.severity ?? "UNKNOWN").toUpperCase()) -
|
|
39
|
+
SEVERITY_ORDER.indexOf((b.severity ?? "UNKNOWN").toUpperCase()));
|
|
40
|
+
return {
|
|
41
|
+
target,
|
|
42
|
+
skillName: raw.skill?.name ?? "",
|
|
43
|
+
riskScore: raw.risk_assessment?.score ?? 0,
|
|
44
|
+
riskSeverity: raw.risk_assessment?.severity ?? "",
|
|
45
|
+
recommendation: raw.risk_assessment?.recommendation ?? "",
|
|
46
|
+
critical: counts.CRITICAL,
|
|
47
|
+
high: counts.HIGH,
|
|
48
|
+
medium: counts.MEDIUM,
|
|
49
|
+
low: counts.LOW,
|
|
50
|
+
total: issues.length,
|
|
51
|
+
topFindings: sorted.slice(0, 10).map((issue) => ({
|
|
52
|
+
id: issue.id ?? "",
|
|
53
|
+
category: issue.category ?? "",
|
|
54
|
+
severity: issue.severity ?? "UNKNOWN",
|
|
55
|
+
file: issue.location?.file ?? "",
|
|
56
|
+
startLine: issue.location?.start_line ?? null,
|
|
57
|
+
finding: issue.finding ?? "",
|
|
58
|
+
explanation: issue.explanation ?? "",
|
|
59
|
+
remediation: issue.remediation ?? "",
|
|
60
|
+
})),
|
|
61
|
+
skillspectorInstalled: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// isSkillspectorAvailable returns true if the scanner CLI is reachable.
|
|
65
|
+
export async function isSkillspectorAvailable() {
|
|
66
|
+
try {
|
|
67
|
+
await execFileAsync("skillspector", ["--version"], { timeout: 5000 });
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// scanSkill runs `skillspector scan` on a local path and returns a summary.
|
|
75
|
+
export async function scanSkill(path, useLlm = false) {
|
|
76
|
+
if (!(await isSkillspectorAvailable())) {
|
|
77
|
+
return emptySummary(path, false, "skillspector is not installed. Install the venos skillspector CLI and re-run.");
|
|
78
|
+
}
|
|
79
|
+
const args = ["scan", path, "--format", "json", ...(useLlm ? [] : ["--no-llm"])];
|
|
80
|
+
let stdout;
|
|
81
|
+
try {
|
|
82
|
+
({ stdout } = await execFileAsync("skillspector", args, {
|
|
83
|
+
timeout: 180_000,
|
|
84
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const e = err;
|
|
89
|
+
// skillspector exits non-zero when risk is high — stdout still holds JSON.
|
|
90
|
+
stdout = e.stdout ?? "";
|
|
91
|
+
if (!stdout) {
|
|
92
|
+
return emptySummary(path, true, `skillspector failed: ${e.message ?? "unknown error"}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(stdout);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return emptySummary(path, true, "Failed to parse skillspector JSON output.");
|
|
101
|
+
}
|
|
102
|
+
return summarize(parsed, path);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=skill-spector.js.map
|
package/etc/mcp-server/tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// MCP tool definitions and handlers for the venos MCP server.
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { scanPath, scanImage } from "./trivy.js";
|
|
4
|
+
import { scanSkill } from "./skill-spector.js";
|
|
4
5
|
// Render the control scorecard as markdown. Pure (no I/O) so it is unit-tested
|
|
5
6
|
// directly. An optional classFilter narrows the table to one class; the summary
|
|
6
7
|
// counts always reflect the full registry.
|
|
@@ -24,6 +25,44 @@ export function formatControls(data, classFilter) {
|
|
|
24
25
|
lines.push(`| Class | Control | Category | Enforced by |`, `|-------|---------|----------|-------------|`, ...rows.map((ctrl) => `| ${ctrl.class} | ${ctrl.name} | ${ctrl.category} | ${ctrl.enforcedBy} |`));
|
|
25
26
|
return lines.join("\n");
|
|
26
27
|
}
|
|
28
|
+
// Render the Claude Code usage rollup as markdown. Pure (no I/O) so it is
|
|
29
|
+
// unit-tested directly. Renders an empty-state line when no usage has been
|
|
30
|
+
// reported yet rather than an empty table.
|
|
31
|
+
export function formatClaudeUsage(u) {
|
|
32
|
+
const t = u.totals;
|
|
33
|
+
if (t.sessionCount === 0 && u.byModel.length === 0) {
|
|
34
|
+
return [
|
|
35
|
+
`## Venos Claude Code usage (last ${u.sinceHours}h)`,
|
|
36
|
+
``,
|
|
37
|
+
`No Claude Code usage reported yet. Run \`venos usage-sync\` on a device with ~/.claude transcripts to populate this.`,
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
const totalCost = u.byModel.reduce((sum, m) => sum + m.costUsd, 0);
|
|
41
|
+
const lines = [
|
|
42
|
+
`## Venos Claude Code usage (last ${u.sinceHours}h)`,
|
|
43
|
+
``,
|
|
44
|
+
`- **Sessions**: ${t.sessionCount.toLocaleString()}`,
|
|
45
|
+
`- **Models**: ${t.modelCount.toLocaleString()}`,
|
|
46
|
+
`- **Messages**: ${t.messageCount.toLocaleString()}`,
|
|
47
|
+
`- **Tokens in / out**: ${t.tokensIn.toLocaleString()} / ${t.tokensOut.toLocaleString()}`,
|
|
48
|
+
`- **Cache read / write**: ${t.cacheReadTokens.toLocaleString()} / ${t.cacheWriteTokens.toLocaleString()}`,
|
|
49
|
+
`- **Total cost**: $${totalCost.toFixed(4)}`,
|
|
50
|
+
``,
|
|
51
|
+
`### By model`,
|
|
52
|
+
`| Model | Tokens in | Tokens out | Cache read | Cache write | Cost (USD) |`,
|
|
53
|
+
`|-------|-----------|------------|------------|-------------|-----------|`,
|
|
54
|
+
...u.byModel.map((m) => `| ${m.model} | ${m.tokensIn.toLocaleString()} | ${m.tokensOut.toLocaleString()} | ${m.cacheReadTokens.toLocaleString()} | ${m.cacheWriteTokens.toLocaleString()} | $${m.costUsd.toFixed(4)} |`),
|
|
55
|
+
];
|
|
56
|
+
const section = (title, rows) => {
|
|
57
|
+
if (rows.length === 0)
|
|
58
|
+
return;
|
|
59
|
+
lines.push(``, `### ${title}`, `| Name | Invocations |`, `|------|-------------|`, ...rows.map((r) => `| ${r.name} | ${r.invocationCount.toLocaleString()} |`));
|
|
60
|
+
};
|
|
61
|
+
section("Skills", u.skills);
|
|
62
|
+
section("Subagents", u.subagents);
|
|
63
|
+
section("MCP servers", u.mcpServers);
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
27
66
|
// Render a budget cell as "used / ceiling" — or "used / -" when no ceiling is
|
|
28
67
|
// set on that dimension. Pure so it is unit-tested directly.
|
|
29
68
|
function budgetCell(used, ceiling, fixed = 0) {
|
|
@@ -167,6 +206,22 @@ export function formatClosedLoop(loop) {
|
|
|
167
206
|
lines.push(`- **Reason**: ${loop.terminationReason}`);
|
|
168
207
|
return lines.join("\n");
|
|
169
208
|
}
|
|
209
|
+
// Render internal-knowledge search hits as a plain-ASCII markdown table. Pure
|
|
210
|
+
// (no I/O) so it is unit-tested directly. A single newline replaces newlines in
|
|
211
|
+
// chunk text so each hit stays on one table row. No emoji or pictographic
|
|
212
|
+
// glyphs (security/compliance product convention).
|
|
213
|
+
export function formatKnowledgeHits(query, hits) {
|
|
214
|
+
const lines = [`## Internal knowledge: "${query}"`, ``];
|
|
215
|
+
if (hits.length === 0) {
|
|
216
|
+
lines.push(`No matching knowledge found.`);
|
|
217
|
+
return lines.join("\n");
|
|
218
|
+
}
|
|
219
|
+
lines.push(`| Source | Chunk | Similarity | Text |`, `|--------|-------|------------|------|`, ...hits.map((h) => {
|
|
220
|
+
const text = h.chunkText.replace(/\s*\n+\s*/g, " ").trim();
|
|
221
|
+
return `| ${h.sourceId} | ${h.chunkIndex} | ${h.similarity.toFixed(2)} | ${text} |`;
|
|
222
|
+
}));
|
|
223
|
+
return lines.join("\n");
|
|
224
|
+
}
|
|
170
225
|
export function registerTools(server, client) {
|
|
171
226
|
// ── Trivy: scan a local filesystem path ─────────────────────────────────
|
|
172
227
|
server.tool("trivy_scan_path", "Scan a local directory or file for vulnerabilities using Trivy. Returns a summary of CVEs grouped by severity.", {
|
|
@@ -213,6 +268,57 @@ export function registerTools(server, client) {
|
|
|
213
268
|
}
|
|
214
269
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
215
270
|
});
|
|
271
|
+
// ── SkillSpector: scan an agent skill for security findings ──────────────
|
|
272
|
+
server.tool("venos_scan_skill", "Scan an agent skill or plugin directory for security findings (prompt injection, data exfiltration, supply-chain, excessive agency) using SkillSpector. Returns a risk verdict and the top findings.", {
|
|
273
|
+
path: z.string().describe("Path to the skill directory or SKILL.md (e.g. ~/.claude/skills/my-skill)"),
|
|
274
|
+
useLlm: z
|
|
275
|
+
.boolean()
|
|
276
|
+
.optional()
|
|
277
|
+
.describe("Opt into the slower LLM semantic stage. Defaults to static-only analysis."),
|
|
278
|
+
}, async ({ path, useLlm }) => {
|
|
279
|
+
const result = await scanSkill(path, useLlm ?? false);
|
|
280
|
+
if (!result.skillspectorInstalled) {
|
|
281
|
+
return { content: [{ type: "text", text: `Error: ${result.errorMessage}` }], isError: true };
|
|
282
|
+
}
|
|
283
|
+
if (result.errorMessage) {
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: `Error scanning ${path}: ${result.errorMessage}` }],
|
|
286
|
+
isError: true,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const lines = [
|
|
290
|
+
`## Skill scan: \`${result.skillName || result.target}\``,
|
|
291
|
+
``,
|
|
292
|
+
`| Risk | Value |`,
|
|
293
|
+
`|------|-------|`,
|
|
294
|
+
`| Score | ${result.riskScore}/100 |`,
|
|
295
|
+
`| Severity | ${result.riskSeverity || "n/a"} |`,
|
|
296
|
+
`| Recommendation | ${result.recommendation || "n/a"} |`,
|
|
297
|
+
``,
|
|
298
|
+
`| Severity | Count |`,
|
|
299
|
+
`|----------|-------|`,
|
|
300
|
+
`| Critical | ${result.critical} |`,
|
|
301
|
+
`| High | ${result.high} |`,
|
|
302
|
+
`| Medium | ${result.medium} |`,
|
|
303
|
+
`| Low | ${result.low} |`,
|
|
304
|
+
`| **Total** | **${result.total}** |`,
|
|
305
|
+
``,
|
|
306
|
+
];
|
|
307
|
+
if (result.topFindings.length > 0) {
|
|
308
|
+
lines.push(`### Top findings`);
|
|
309
|
+
for (const f of result.topFindings) {
|
|
310
|
+
const loc = f.file ? ` in \`${f.file}${f.startLine ? `:${f.startLine}` : ""}\`` : "";
|
|
311
|
+
lines.push(`- **${f.id}** (${f.severity}) ${f.category}${loc} — ${f.finding}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
lines.push("No security findings detected.");
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
319
|
+
isError: result.recommendation === "DO_NOT_INSTALL",
|
|
320
|
+
};
|
|
321
|
+
});
|
|
216
322
|
// ── Trivy: scan a container image ────────────────────────────────────────
|
|
217
323
|
server.tool("trivy_scan_image", "Scan a container image for OS and library vulnerabilities using Trivy.", {
|
|
218
324
|
image: z.string().describe("Container image reference (e.g. nginx:latest, node:20-alpine)"),
|
|
@@ -355,8 +461,12 @@ export function registerTools(server, client) {
|
|
|
355
461
|
// ── Venos: classify text against the engine ──────────────────────────────
|
|
356
462
|
server.tool("venos_classify", "Run a piece of text (a prompt, a shell command, a tool argument) through venos and get back the policy decision (allow/deny). Call this BEFORE executing anything destructive or sensitive (rm, curl to unknown hosts, writes outside the workspace) so the decision is logged as a Cursor event in the dashboard.", {
|
|
357
463
|
text: z.string().describe("The prompt, command, or content to classify."),
|
|
358
|
-
|
|
359
|
-
|
|
464
|
+
origin: z
|
|
465
|
+
.string()
|
|
466
|
+
.optional()
|
|
467
|
+
.describe("Optional label for where this content came from (e.g. a filename like 'insurance-database.xlsx'). Recorded on the event so it can be shown and filtered in the dashboard's ORIGIN column."),
|
|
468
|
+
}, async ({ text, origin }) => {
|
|
469
|
+
const r = await client.classify(text, origin);
|
|
360
470
|
const lines = [
|
|
361
471
|
`## Venos decision: \`${r.decision}\``,
|
|
362
472
|
``,
|
|
@@ -385,6 +495,37 @@ export function registerTools(server, client) {
|
|
|
385
495
|
];
|
|
386
496
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
387
497
|
});
|
|
498
|
+
// ── Venos: Claude Code usage rollup ──────────────────────────────────────
|
|
499
|
+
server.tool("venos_usage", "Show the organization's Claude Code usage tracked by venos: per-model token counts (input/output and cache read/write) with read-time-derived cost, plus which skills, subagents, and MCP servers were invoked most. Populated by `venos usage-sync` from device ~/.claude transcripts. Returns an empty-state note when nothing has been reported yet.", {
|
|
500
|
+
sinceHours: z
|
|
501
|
+
.number()
|
|
502
|
+
.int()
|
|
503
|
+
.min(1)
|
|
504
|
+
.optional()
|
|
505
|
+
.describe("Lookback window in hours (default: 30 days)."),
|
|
506
|
+
}, async ({ sinceHours }) => {
|
|
507
|
+
const usage = await client.usage(sinceHours);
|
|
508
|
+
return { content: [{ type: "text", text: formatClaudeUsage(usage) }] };
|
|
509
|
+
});
|
|
510
|
+
// ── Venos: report Claude Code usage ──────────────────────────────────────
|
|
511
|
+
// The aggregation reads the per-device ~/.claude transcripts, which only the
|
|
512
|
+
// device CLI can do reliably (the stdio MCP server may be running on a host
|
|
513
|
+
// without access to those files). Rather than fake a sync from here, this
|
|
514
|
+
// tool returns the exact command to run; venos_usage then reads the result.
|
|
515
|
+
server.tool("venos_usage_sync", "Report this device's Claude Code usage (tokens, cost, skill/subagent/MCP attribution) to venos. The usage data lives in ~/.claude transcripts, which the venos CLI reads directly; run the command this tool returns, then call venos_usage to see the rollup.", {}, async () => {
|
|
516
|
+
const lines = [
|
|
517
|
+
`## Report Claude Code usage to venos`,
|
|
518
|
+
``,
|
|
519
|
+
`Claude Code usage is aggregated from this device's \`~/.claude\` transcripts by the venos CLI. Run:`,
|
|
520
|
+
``,
|
|
521
|
+
"```",
|
|
522
|
+
`venos usage-sync`,
|
|
523
|
+
"```",
|
|
524
|
+
``,
|
|
525
|
+
`Then call \`venos_usage\` to see the per-model cost and skill/subagent/MCP attribution rollup.`,
|
|
526
|
+
];
|
|
527
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
528
|
+
});
|
|
388
529
|
// ── Venos: control scorecard ─────────────────────────────────────────────
|
|
389
530
|
server.tool("venos_controls", "List venos's active security controls from the control scorecard, each classified as a barrier (impossible-class: structural/cryptographic) or friction (tedious: detection/heuristics) per the Zero-Trust-for-AI-Agents design test.", {
|
|
390
531
|
class: z
|
|
@@ -395,6 +536,20 @@ export function registerTools(server, client) {
|
|
|
395
536
|
const data = await client.controls();
|
|
396
537
|
return { content: [{ type: "text", text: formatControls(data, classFilter) }] };
|
|
397
538
|
});
|
|
539
|
+
// ── Venos: internal knowledge search (RAG) ───────────────────────────────
|
|
540
|
+
server.tool("venos_knowledge_search", "Search the organization's internal knowledge base (runbooks, docs, policies indexed into venos) and return the most relevant chunks for a natural-language query, ranked by semantic similarity. Use to answer 'how do we do X here?' from org-specific material rather than guessing. Returns an empty result when nothing matches.", {
|
|
541
|
+
query: z.string().describe("Natural-language question or phrase to search the internal knowledge base for."),
|
|
542
|
+
limit: z
|
|
543
|
+
.number()
|
|
544
|
+
.int()
|
|
545
|
+
.min(1)
|
|
546
|
+
.max(20)
|
|
547
|
+
.optional()
|
|
548
|
+
.describe("Max number of chunks to return (default 8, capped at 20)."),
|
|
549
|
+
}, async ({ query, limit }) => {
|
|
550
|
+
const hits = await client.knowledgeSearch(query, limit);
|
|
551
|
+
return { content: [{ type: "text", text: formatKnowledgeHits(query, hits) }] };
|
|
552
|
+
});
|
|
398
553
|
// ── Venos: recent delegation chains ──────────────────────────────────────
|
|
399
554
|
server.tool("venos_delegation", "List recent multi-agent delegation chains tracked by venos — each is one agent delegating to another (e.g. a Claude Code Task sub-agent), with the hop count and last activity. Use to see which agents are delegating work to which, and to find a chain (session) to inspect in the dashboard.", {
|
|
400
555
|
limit: z.number().int().min(1).max(50).optional().describe("Max chains to return (default 20)"),
|
package/package.json
CHANGED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
'targets': [
|
|
3
|
-
{
|
|
4
|
-
'target_name': 'node_addon_api',
|
|
5
|
-
'type': 'none',
|
|
6
|
-
'sources': [ 'napi.h', 'napi-inl.h' ],
|
|
7
|
-
'direct_dependent_settings': {
|
|
8
|
-
'include_dirs': [ '.' ],
|
|
9
|
-
'includes': ['noexcept.gypi'],
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
'target_name': 'node_addon_api_except',
|
|
14
|
-
'type': 'none',
|
|
15
|
-
'sources': [ 'napi.h', 'napi-inl.h' ],
|
|
16
|
-
'direct_dependent_settings': {
|
|
17
|
-
'include_dirs': [ '.' ],
|
|
18
|
-
'includes': ['except.gypi'],
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
'target_name': 'node_addon_api_maybe',
|
|
23
|
-
'type': 'none',
|
|
24
|
-
'sources': [ 'napi.h', 'napi-inl.h' ],
|
|
25
|
-
'direct_dependent_settings': {
|
|
26
|
-
'include_dirs': [ '.' ],
|
|
27
|
-
'includes': ['noexcept.gypi'],
|
|
28
|
-
'defines': ['NODE_ADDON_API_ENABLE_MAYBE']
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
]
|
|
32
|
-
}
|