blitzstrike 1.0.46 → 1.0.48
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/CHANGELOG.md +34 -0
- package/dist/index.js +203 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to Blitz Strike are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [1.0.48] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added — Autonomous Security Engine (phase 8: coverage-driven scheduler)
|
|
11
|
+
|
|
12
|
+
- **Coverage-driven Scheduler** (`src/mission/coverage.ts`, §13):
|
|
13
|
+
- `computeCoverage()` — 10 dimensions (attack surface, endpoints, technologies,
|
|
14
|
+
source files, sink classes, auth/authorization boundaries, vulnerability
|
|
15
|
+
classes, attack vectors, escalation chains).
|
|
16
|
+
- `coverageGaps()` — dimensions below a threshold.
|
|
17
|
+
- `scheduleCoverageTasks()` — auto-generates tasks for under-covered
|
|
18
|
+
dimensions, skipping already-attempted agents and mode-inappropriate ones
|
|
19
|
+
(e.g. no `@web` for source audits; endpoints/technologies resolve locally).
|
|
20
|
+
- `gapAgent()` — mode-aware dimension→agent mapping.
|
|
21
|
+
- Mission Control's `replan` now runs the coverage scheduler and persists
|
|
22
|
+
per-dimension coverage into `mission.coverage.dimensions`.
|
|
23
|
+
- `@recon` now links discovered endpoints into the Security Graph (so coverage
|
|
24
|
+
reflects real endpoint exploration).
|
|
25
|
+
|
|
26
|
+
## [1.0.47] — 2026-09-13
|
|
27
|
+
|
|
28
|
+
### Added — Autonomous Security Engine (phase 7: adaptive planner)
|
|
29
|
+
|
|
30
|
+
- **Adaptive Attack Planner** (`src/mission/planner.ts`, §11):
|
|
31
|
+
- `ATTACK_BRANCHES` — data-driven technique library across SSRF / LFI / SQLi /
|
|
32
|
+
RCE / XSS / IDOR / XXE / traversal (each with rationale, expected value,
|
|
33
|
+
risk, evidence requirements, and tool).
|
|
34
|
+
- `planNextActions()` — generates ranked next actions from unresolved
|
|
35
|
+
hypotheses (confidence × expected-value − risk), evidence-driven branching.
|
|
36
|
+
- `classifyType()` — maps hypothesis type strings to a technique class.
|
|
37
|
+
- **Escalation Graph** (`buildEscalationGraph`, §12) — integrates the 57
|
|
38
|
+
escalation chains into the Security Graph (`vuln` → `enables` → `attack_chain`),
|
|
39
|
+
seeded into every mission's world model.
|
|
40
|
+
- `@commander` now emits ranked `next_actions` from the adaptive planner.
|
|
41
|
+
|
|
8
42
|
## [1.0.46] — 2026-09-13
|
|
9
43
|
|
|
10
44
|
### Fixed (autonomous-engine milestone review)
|
package/dist/index.js
CHANGED
|
@@ -18139,6 +18139,124 @@ function mergeGraphs(a, b) {
|
|
|
18139
18139
|
return out;
|
|
18140
18140
|
}
|
|
18141
18141
|
|
|
18142
|
+
// src/mission/planner.ts
|
|
18143
|
+
function classifyType(type) {
|
|
18144
|
+
const t = type.toLowerCase();
|
|
18145
|
+
if (/ssrf|server-side request|url fetch/.test(t))
|
|
18146
|
+
return "ssrf";
|
|
18147
|
+
if (/lfi|include|local file/.test(t))
|
|
18148
|
+
return "lfi";
|
|
18149
|
+
if (/sql|query injection/.test(t))
|
|
18150
|
+
return "sqli";
|
|
18151
|
+
if (/command|exec|rce|deserializ|template|ssti/.test(t))
|
|
18152
|
+
return "rce";
|
|
18153
|
+
if (/xss|cross-site|reflected|stored|dom/.test(t))
|
|
18154
|
+
return "xss";
|
|
18155
|
+
if (/idor|authorization|object access|missing_authorization/.test(t))
|
|
18156
|
+
return "idor";
|
|
18157
|
+
if (/xxe|xml/.test(t))
|
|
18158
|
+
return "xxe";
|
|
18159
|
+
if (/traversal|path|directory/.test(t))
|
|
18160
|
+
return "traversal";
|
|
18161
|
+
return "xss";
|
|
18162
|
+
}
|
|
18163
|
+
function planNextActions(hypotheses, _mission) {
|
|
18164
|
+
const actions = [];
|
|
18165
|
+
for (const h of hypotheses) {
|
|
18166
|
+
if (h.status !== "pending")
|
|
18167
|
+
continue;
|
|
18168
|
+
const cls = classifyType(h.type);
|
|
18169
|
+
const branches = ATTACK_BRANCHES[cls] ?? [];
|
|
18170
|
+
for (const b of branches) {
|
|
18171
|
+
const score = h.confidence * 0.5 + VALUE_RANK[b.expected_value] * 0.3 - RISK_PENALTY[b.risk];
|
|
18172
|
+
actions.push({
|
|
18173
|
+
hypothesis_id: h.id,
|
|
18174
|
+
hypothesis_type: h.type,
|
|
18175
|
+
technique: b.technique,
|
|
18176
|
+
rationale: b.rationale,
|
|
18177
|
+
expected_value: b.expected_value,
|
|
18178
|
+
risk: b.risk,
|
|
18179
|
+
evidence_required: b.evidence_required,
|
|
18180
|
+
tool: b.tool,
|
|
18181
|
+
score: Number(score.toFixed(3))
|
|
18182
|
+
});
|
|
18183
|
+
}
|
|
18184
|
+
}
|
|
18185
|
+
return actions.sort((a, b) => b.score - a.score);
|
|
18186
|
+
}
|
|
18187
|
+
function buildEscalationGraph(chains, graph) {
|
|
18188
|
+
let out = graph;
|
|
18189
|
+
for (const chain of chains) {
|
|
18190
|
+
const chainId = `chain:${chain.id}`;
|
|
18191
|
+
out = upsertNode(out, chainId, "attack_chain", {
|
|
18192
|
+
name: chain.name,
|
|
18193
|
+
severity: chain.severity,
|
|
18194
|
+
prerequisites: chain.required_findings ?? [],
|
|
18195
|
+
steps: chain.steps.length,
|
|
18196
|
+
tools: chain.tools ?? []
|
|
18197
|
+
});
|
|
18198
|
+
for (const rf of chain.required_findings ?? []) {
|
|
18199
|
+
const prereqId = `vuln:${rf}`;
|
|
18200
|
+
out = upsertNode(out, prereqId, "vulnerability", { class: rf });
|
|
18201
|
+
out = addEdge2(out, prereqId, chainId, "enables");
|
|
18202
|
+
}
|
|
18203
|
+
}
|
|
18204
|
+
return out;
|
|
18205
|
+
}
|
|
18206
|
+
function loadEscalationGraph(graph) {
|
|
18207
|
+
return buildEscalationGraph(loadChains(), graph);
|
|
18208
|
+
}
|
|
18209
|
+
var ATTACK_BRANCHES, VALUE_RANK, RISK_PENALTY;
|
|
18210
|
+
var init_planner = __esm(() => {
|
|
18211
|
+
init_orchestrator();
|
|
18212
|
+
ATTACK_BRANCHES = {
|
|
18213
|
+
ssrf: [
|
|
18214
|
+
{ technique: "probe localhost (127.0.0.1 / ::1)", rationale: "confirm server-side fetch of loopback", expected_value: "high", risk: "low", evidence_required: ["server-side fetch"], tool: "strike_verify" },
|
|
18215
|
+
{ technique: "probe internal services (10.0.0.0/8, 172.16/12, 192.168/16)", rationale: "map internal reachability", expected_value: "medium", risk: "low", evidence_required: ["server-side fetch"], tool: "strike_verify" },
|
|
18216
|
+
{ technique: "probe cloud metadata (169.254.169.254)", rationale: "exfiltrate IAM creds on cloud hosts", expected_value: "high", risk: "medium", evidence_required: ["cloud-hosted target"], tool: "strike_verify" },
|
|
18217
|
+
{ technique: "test redirect behavior (open redirect → SSRF)", rationale: "bypass URL allowlist via redirect", expected_value: "medium", risk: "low", evidence_required: ["redirect endpoint"], tool: "strike_verify" },
|
|
18218
|
+
{ technique: "test alternate protocol (gopher:// file:// dict://)", rationale: "escalate SSRF to file read / RCE", expected_value: "high", risk: "high", evidence_required: ["raw socket support"], tool: "strike_verify" }
|
|
18219
|
+
],
|
|
18220
|
+
lfi: [
|
|
18221
|
+
{ technique: "read /etc/passwd", rationale: "confirm file read primitive", expected_value: "high", risk: "low", evidence_required: ["file include sink"], tool: "strike_verify" },
|
|
18222
|
+
{ technique: "read application logs", rationale: "harvest secrets/session data", expected_value: "medium", risk: "low", evidence_required: ["file include sink"], tool: "strike_verify" },
|
|
18223
|
+
{ technique: "read session files", rationale: "session hijack", expected_value: "high", risk: "medium", evidence_required: ["session path known"], tool: "strike_verify" },
|
|
18224
|
+
{ technique: "read upload locations", rationale: "chain to RCE via uploaded file", expected_value: "high", risk: "medium", evidence_required: ["upload path"], tool: "strike_verify" },
|
|
18225
|
+
{ technique: "test log poisoning / wrapper execution", rationale: "escalate LFI to RCE", expected_value: "high", risk: "high", evidence_required: ["writable log"], tool: "strike_verify" }
|
|
18226
|
+
],
|
|
18227
|
+
sqli: [
|
|
18228
|
+
{ technique: "error-based extraction", rationale: "confirm SQLi via error leakage", expected_value: "high", risk: "low", evidence_required: ["query sink"], tool: "strike_verify" },
|
|
18229
|
+
{ technique: "UNION-based extraction", rationale: "dump columns/tables", expected_value: "high", risk: "medium", evidence_required: ["query sink"], tool: "strike_verify" },
|
|
18230
|
+
{ technique: "blind boolean extraction", rationale: "infer data without errors", expected_value: "medium", risk: "medium", evidence_required: ["query sink"], tool: "strike_verify" },
|
|
18231
|
+
{ technique: "time-based blind extraction", rationale: "infer data via timing", expected_value: "medium", risk: "medium", evidence_required: ["query sink"], tool: "strike_verify" }
|
|
18232
|
+
],
|
|
18233
|
+
rce: [
|
|
18234
|
+
{ technique: "command injection (separators)", rationale: "chain to OS command execution", expected_value: "high", risk: "high", evidence_required: ["command sink"], tool: "strike_verify" },
|
|
18235
|
+
{ technique: "deserialization gadget chain", rationale: "POP chain to code execution", expected_value: "high", risk: "high", evidence_required: ["unserialize sink"], tool: "strike_verify" },
|
|
18236
|
+
{ technique: "template injection", rationale: "SSTI to code execution", expected_value: "high", risk: "high", evidence_required: ["template render sink"], tool: "strike_verify" }
|
|
18237
|
+
],
|
|
18238
|
+
xss: [
|
|
18239
|
+
{ technique: "reflected XSS (marker reflection)", rationale: "confirm reflected script execution", expected_value: "high", risk: "low", evidence_required: ["reflection"], tool: "strike_verify" },
|
|
18240
|
+
{ technique: "stored XSS (persistence)", rationale: "confirm stored script execution", expected_value: "high", risk: "medium", evidence_required: ["persistence"], tool: "browser_validate" },
|
|
18241
|
+
{ technique: "DOM XSS (sink execution)", rationale: "confirm client-side sink", expected_value: "high", risk: "low", evidence_required: ["DOM sink"], tool: "browser_validate" }
|
|
18242
|
+
],
|
|
18243
|
+
idor: [
|
|
18244
|
+
{ technique: "enumerate sequential object ids", rationale: "find adjacent objects", expected_value: "high", risk: "low", evidence_required: ["object id"], tool: "strike_verify" },
|
|
18245
|
+
{ technique: "cross-tenant object access", rationale: "break authorization boundary", expected_value: "high", risk: "medium", evidence_required: ["multi-tenant"], tool: "strike_verify" }
|
|
18246
|
+
],
|
|
18247
|
+
xxe: [
|
|
18248
|
+
{ technique: "internal entity (file read)", rationale: "read local files via XXE", expected_value: "high", risk: "medium", evidence_required: ["XML parse sink"], tool: "strike_verify" },
|
|
18249
|
+
{ technique: "external entity (SSRF via XXE)", rationale: "chain XXE to SSRF", expected_value: "medium", risk: "medium", evidence_required: ["XML parse sink"], tool: "strike_verify" }
|
|
18250
|
+
],
|
|
18251
|
+
traversal: [
|
|
18252
|
+
{ technique: "dot-dot-slash traversal", rationale: "escape web root", expected_value: "high", risk: "low", evidence_required: ["file path sink"], tool: "strike_verify" },
|
|
18253
|
+
{ technique: "encoded traversal (%2e%2e%2f)", rationale: "bypass naive filters", expected_value: "medium", risk: "low", evidence_required: ["file path sink"], tool: "strike_verify" }
|
|
18254
|
+
]
|
|
18255
|
+
};
|
|
18256
|
+
VALUE_RANK = { low: 0.3, medium: 0.6, high: 1 };
|
|
18257
|
+
RISK_PENALTY = { low: 0, medium: 0.1, high: 0.2 };
|
|
18258
|
+
});
|
|
18259
|
+
|
|
18142
18260
|
// src/agents.ts
|
|
18143
18261
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
18144
18262
|
function emptyGraph() {
|
|
@@ -18156,6 +18274,7 @@ function agentFor(type) {
|
|
|
18156
18274
|
}
|
|
18157
18275
|
var reconAgent, webAgent, sourceAgent, analysisAgent, authAgent, validatorAgent, reviewerAgent, reporterAgent, mobileAgent, cloudAgent, commanderAgent, apiAgent, AGENTS;
|
|
18158
18276
|
var init_agents = __esm(() => {
|
|
18277
|
+
init_planner();
|
|
18159
18278
|
init_orchestrator();
|
|
18160
18279
|
init_scanner();
|
|
18161
18280
|
init_eagle2();
|
|
@@ -18182,11 +18301,18 @@ var init_agents = __esm(() => {
|
|
|
18182
18301
|
};
|
|
18183
18302
|
}
|
|
18184
18303
|
const r = enrichScan(ctx.mission.target, 500);
|
|
18304
|
+
let graph = { nodes: [], edges: [] };
|
|
18305
|
+
const tid = `target:${ctx.mission.target}`;
|
|
18306
|
+
for (const ep of r.hits.filter((h) => h.type === "endpoint").slice(0, 50)) {
|
|
18307
|
+
const epId = `endpoint:${ep.file}:${ep.line}`;
|
|
18308
|
+
graph = upsertNode(graph, epId, "endpoint", { file: ep.file, line: ep.line, hook: ep.detail });
|
|
18309
|
+
graph = addEdge2(graph, tid, epId, "exposes");
|
|
18310
|
+
}
|
|
18185
18311
|
return {
|
|
18186
18312
|
ok: true,
|
|
18187
18313
|
output: { files_scanned: r.files_scanned, hits: r.hits.length, sink_signals: r.sink_signals, endpoints: r.hits.filter((h) => h.type === "endpoint").length },
|
|
18188
18314
|
hypotheses: [],
|
|
18189
|
-
graph
|
|
18315
|
+
graph
|
|
18190
18316
|
};
|
|
18191
18317
|
}
|
|
18192
18318
|
};
|
|
@@ -18433,7 +18559,8 @@ ${m.hypotheses.map((h) => `- [${h.status}] ${h.type} @ ${h.target} (confidence $
|
|
|
18433
18559
|
const prioritized = [...hypotheses].sort((a, b) => b.confidence - a.confidence).slice(0, 10).map((h) => h.id);
|
|
18434
18560
|
const sinks = nodesOfType(ctx.mission.graph, "sink").length;
|
|
18435
18561
|
const exposed = outgoing(ctx.mission.graph, `target:${ctx.mission.target}`).length;
|
|
18436
|
-
|
|
18562
|
+
const nextActions = planNextActions(ctx.mission.hypotheses, ctx.mission).slice(0, 10).map((a) => ({ technique: a.technique, type: a.hypothesis_type, tool: a.tool, score: a.score }));
|
|
18563
|
+
return { ok: true, output: { prioritized, pending: hypotheses.length, sinks, exposed_edges: exposed, next_actions: nextActions }, hypotheses: [], graph: emptyGraph() };
|
|
18437
18564
|
}
|
|
18438
18565
|
};
|
|
18439
18566
|
apiAgent = {
|
|
@@ -18462,6 +18589,71 @@ ${m.hypotheses.map((h) => `- [${h.status}] ${h.type} @ ${h.target} (confidence $
|
|
|
18462
18589
|
};
|
|
18463
18590
|
});
|
|
18464
18591
|
|
|
18592
|
+
// src/mission/coverage.ts
|
|
18593
|
+
function clamp012(n) {
|
|
18594
|
+
return Math.max(0, Math.min(1, n));
|
|
18595
|
+
}
|
|
18596
|
+
function computeCoverage(mission) {
|
|
18597
|
+
const byType = graphStats(mission.graph).by_type;
|
|
18598
|
+
const hyps = mission.hypotheses;
|
|
18599
|
+
const sinkCategories = new Set(mission.graph.nodes.filter((n) => n.type === "sink").map((n) => String(n.properties.category ?? n.properties.sink ?? "unknown")));
|
|
18600
|
+
const hypTypes = new Set(hyps.map((h) => h.type));
|
|
18601
|
+
return {
|
|
18602
|
+
attack_surface: clamp012(mission.graph.nodes.length / 25),
|
|
18603
|
+
endpoints: clamp012((byType.endpoint ?? 0) / 10),
|
|
18604
|
+
technologies: clamp012(mission.technologies.length / 6),
|
|
18605
|
+
source_files: clamp012((byType.sink ?? 0) / 15),
|
|
18606
|
+
sink_classes: clamp012(sinkCategories.size / 10),
|
|
18607
|
+
auth_boundaries: hyps.length ? clamp012(hyps.filter((h) => /auth/i.test(h.evidence)).length / hyps.length) : 0,
|
|
18608
|
+
authorization_boundaries: hyps.length ? clamp012(hyps.filter((h) => /missing_authorization/i.test(h.type)).length / hyps.length) : 0,
|
|
18609
|
+
vulnerability_classes: clamp012(hypTypes.size / 10),
|
|
18610
|
+
attack_vectors: clamp012(planNextActions(hyps, mission).length / 20),
|
|
18611
|
+
escalation_chains: clamp012((byType.attack_chain ?? 0) / 57)
|
|
18612
|
+
};
|
|
18613
|
+
}
|
|
18614
|
+
function coverageGaps(coverage, threshold = 0.5) {
|
|
18615
|
+
return Object.entries(coverage).filter(([, v]) => v < threshold).map(([k]) => k);
|
|
18616
|
+
}
|
|
18617
|
+
function gapAgent(dimension, isUrl) {
|
|
18618
|
+
if (dimension === "endpoints" && !isUrl)
|
|
18619
|
+
return "recon";
|
|
18620
|
+
if (dimension === "technologies" && !isUrl)
|
|
18621
|
+
return "source";
|
|
18622
|
+
return GAP_AGENT[dimension] ?? "analysis";
|
|
18623
|
+
}
|
|
18624
|
+
function scheduleCoverageTasks(mission, threshold = 0.5) {
|
|
18625
|
+
const coverage = computeCoverage(mission);
|
|
18626
|
+
const gaps = coverageGaps(coverage, threshold);
|
|
18627
|
+
const attempted = new Set(mission.tasks.map((t) => t.agent_type));
|
|
18628
|
+
const isUrl = /^https?:\/\//i.test(mission.target);
|
|
18629
|
+
const tasks = [];
|
|
18630
|
+
for (const gap of gaps) {
|
|
18631
|
+
const agent = gapAgent(gap, isUrl);
|
|
18632
|
+
if (attempted.has(agent))
|
|
18633
|
+
continue;
|
|
18634
|
+
if (!isUrl && agent === "web")
|
|
18635
|
+
continue;
|
|
18636
|
+
tasks.push(createTask({ objective: `close coverage gap: ${gap} (${coverage[gap].toFixed(2)})`, agent_type: agent, priority: 8 }));
|
|
18637
|
+
}
|
|
18638
|
+
return tasks;
|
|
18639
|
+
}
|
|
18640
|
+
var GAP_AGENT;
|
|
18641
|
+
var init_coverage = __esm(() => {
|
|
18642
|
+
init_planner();
|
|
18643
|
+
GAP_AGENT = {
|
|
18644
|
+
attack_surface: "recon",
|
|
18645
|
+
endpoints: "api",
|
|
18646
|
+
technologies: "web",
|
|
18647
|
+
source_files: "source",
|
|
18648
|
+
sink_classes: "source",
|
|
18649
|
+
auth_boundaries: "auth",
|
|
18650
|
+
authorization_boundaries: "auth",
|
|
18651
|
+
vulnerability_classes: "analysis",
|
|
18652
|
+
attack_vectors: "analysis",
|
|
18653
|
+
escalation_chains: "commander"
|
|
18654
|
+
};
|
|
18655
|
+
});
|
|
18656
|
+
|
|
18465
18657
|
// src/mission/control.ts
|
|
18466
18658
|
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
18467
18659
|
function scopeAllowed(mission) {
|
|
@@ -18514,7 +18706,12 @@ function replan(mission) {
|
|
|
18514
18706
|
tasks = [...tasks, createTask({ objective: "generate the final report", agent_type: "reporter", risk: "low" })];
|
|
18515
18707
|
}
|
|
18516
18708
|
}
|
|
18517
|
-
|
|
18709
|
+
const coverage = computeCoverage(mission);
|
|
18710
|
+
const coverageTasks = scheduleCoverageTasks(mission);
|
|
18711
|
+
if (coverageTasks.length > 0) {
|
|
18712
|
+
tasks = [...tasks, ...coverageTasks];
|
|
18713
|
+
}
|
|
18714
|
+
return { ...mission, tasks, coverage: { dimensions: { ...mission.coverage.dimensions, ...coverage } } };
|
|
18518
18715
|
}
|
|
18519
18716
|
async function executeTask(task, mission, scope) {
|
|
18520
18717
|
const agent = agentFor(task.agent_type);
|
|
@@ -18535,6 +18732,7 @@ async function runMission(mission) {
|
|
|
18535
18732
|
}
|
|
18536
18733
|
let m = { ...mission, status: "running" };
|
|
18537
18734
|
m.tasks = buildInitialTasks(m);
|
|
18735
|
+
m.graph = loadEscalationGraph(m.graph);
|
|
18538
18736
|
m = saveMission(m);
|
|
18539
18737
|
const scope = m.scope.join(",");
|
|
18540
18738
|
let iterations = 0;
|
|
@@ -18616,6 +18814,8 @@ var MODE_PROFILES;
|
|
|
18616
18814
|
var init_control = __esm(() => {
|
|
18617
18815
|
init_state();
|
|
18618
18816
|
init_agents();
|
|
18817
|
+
init_planner();
|
|
18818
|
+
init_coverage();
|
|
18619
18819
|
MODE_PROFILES = {
|
|
18620
18820
|
web: { agents: ["recon", "web", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
|
|
18621
18821
|
api: { agents: ["recon", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|