@datasynx/agentic-ai-cartography 2.9.0 → 2.11.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/dist/api-bin.js +2 -2
- package/dist/{chunk-LRUWWHMQ.js → chunk-FFUUNSWP.js} +276 -6
- package/dist/chunk-FFUUNSWP.js.map +1 -0
- package/dist/{chunk-YVV6NIT2.js → chunk-LO6YFS6H.js} +2 -1
- package/dist/{chunk-W4Q3TXHR.js → chunk-PD67MOKR.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.cjs +307 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -1
- package/dist/index.d.ts +112 -1
- package/dist/index.js +287 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +2 -2
- package/llms-full.txt +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-LRUWWHMQ.js.map +0 -1
- /package/dist/{chunk-YVV6NIT2.js.map → chunk-LO6YFS6H.js.map} +0 -0
- /package/dist/{chunk-W4Q3TXHR.js.map → chunk-PD67MOKR.js.map} +0 -0
package/dist/api-bin.js
CHANGED
|
@@ -20,12 +20,13 @@ import {
|
|
|
20
20
|
k8sScanner,
|
|
21
21
|
keyMetaOf,
|
|
22
22
|
normalizeTenant,
|
|
23
|
+
redactSecrets,
|
|
23
24
|
redactValue,
|
|
24
25
|
resolvePrincipal,
|
|
25
26
|
sanitizeUntrusted,
|
|
26
27
|
stableStringify,
|
|
27
28
|
stripSensitive
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LO6YFS6H.js";
|
|
29
30
|
import {
|
|
30
31
|
EdgeSchema,
|
|
31
32
|
NODE_TYPES,
|
|
@@ -653,9 +654,132 @@ var serviceConfigScanner = {
|
|
|
653
654
|
}
|
|
654
655
|
};
|
|
655
656
|
|
|
657
|
+
// src/scanners/terraform.ts
|
|
658
|
+
import { readFileSync } from "fs";
|
|
659
|
+
var TYPE_RULES = [
|
|
660
|
+
[/(db_instance|_rds|sql_database|sql_instance|database_instance|cosmosdb|dynamodb|spanner|bigtable|documentdb|redshift)/, "database_server"],
|
|
661
|
+
[/(elasticache|_redis|memcached|memorystore)/, "cache_server"],
|
|
662
|
+
[/(s3_bucket|storage_bucket|gcs_bucket|storage_account|_blob)/, "database"],
|
|
663
|
+
[/(_sqs|_queue|servicebus_queue)/, "queue"],
|
|
664
|
+
[/(_sns|_topic|pubsub_topic|servicebus_topic)/, "topic"],
|
|
665
|
+
[/(kafka|_msk|event_hub|kinesis)/, "message_broker"],
|
|
666
|
+
[/(_eks|_gke|_aks|kubernetes_cluster|container_cluster)/, "k8s_cluster"],
|
|
667
|
+
[/(ecs_|_container|fargate)/, "container"],
|
|
668
|
+
[/(lambda|cloud_function|cloudfunctions|function_app|cloud_run)/, "web_service"],
|
|
669
|
+
[/(_lb$|load_balancer|_alb|_elb|application_gateway)/, "web_service"],
|
|
670
|
+
[/(api_gateway|apigateway)/, "api_endpoint"],
|
|
671
|
+
[/(_instance|virtual_machine|_vm$|compute_instance)/, "host"]
|
|
672
|
+
];
|
|
673
|
+
function terraformTypeToNode(tfType) {
|
|
674
|
+
const t = tfType.toLowerCase();
|
|
675
|
+
for (const [re, nt] of TYPE_RULES) if (re.test(t)) return nt;
|
|
676
|
+
return "unknown";
|
|
677
|
+
}
|
|
678
|
+
var IDENTITY_ATTRS = ["id", "arn", "region", "location", "instance_type", "engine", "machine_type"];
|
|
679
|
+
var OWNER_TAGS = ["Owner", "owner", "Team", "team"];
|
|
680
|
+
var SAFE_TAG_KEYS = /* @__PURE__ */ new Set(["Name", "name", "Owner", "owner", "Team", "team", "Env", "env", "Environment", "environment", "Service", "service", "Component", "component", "App", "app", "Project", "project", "Tier", "tier", "Role", "role"]);
|
|
681
|
+
var SECRET_KEY = /pass|secret|token|key|pwd|cred|private/i;
|
|
682
|
+
function attrTags(tags) {
|
|
683
|
+
if (!tags || typeof tags !== "object") return [];
|
|
684
|
+
return Object.entries(tags).filter(([k]) => SAFE_TAG_KEYS.has(k) && !SECRET_KEY.test(k)).map(([k, v]) => `${k}:${redactSecrets(String(v))}`);
|
|
685
|
+
}
|
|
686
|
+
function parseTerraformState(json2) {
|
|
687
|
+
let state;
|
|
688
|
+
try {
|
|
689
|
+
state = JSON.parse(json2);
|
|
690
|
+
} catch {
|
|
691
|
+
return { nodes: [], edges: [] };
|
|
692
|
+
}
|
|
693
|
+
const resources = Array.isArray(state?.resources) ? state.resources : [];
|
|
694
|
+
const nodes = [];
|
|
695
|
+
const edges = [];
|
|
696
|
+
const addrToId = /* @__PURE__ */ new Map();
|
|
697
|
+
for (const raw of resources) {
|
|
698
|
+
const r = raw;
|
|
699
|
+
if (r.mode && r.mode !== "managed") continue;
|
|
700
|
+
if (typeof r.type !== "string" || typeof r.name !== "string") continue;
|
|
701
|
+
const address = `${r.type}.${r.name}`;
|
|
702
|
+
const nt = terraformTypeToNode(r.type);
|
|
703
|
+
const id = `${nt}:terraform:${address}`;
|
|
704
|
+
if (addrToId.has(address)) continue;
|
|
705
|
+
addrToId.set(address, id);
|
|
706
|
+
const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
|
|
707
|
+
const attrs = inst?.attributes ?? {};
|
|
708
|
+
const identity = { source: "terraform", tfType: r.type };
|
|
709
|
+
for (const k of IDENTITY_ATTRS) if (attrs[k] !== void 0) identity[k] = attrs[k];
|
|
710
|
+
const owner = OWNER_TAGS.map((k) => attrs.tags?.[k]).find((v) => typeof v === "string");
|
|
711
|
+
nodes.push({
|
|
712
|
+
id,
|
|
713
|
+
type: nt,
|
|
714
|
+
name: address,
|
|
715
|
+
discoveredVia: "terraform-state",
|
|
716
|
+
confidence: 0.9,
|
|
717
|
+
// IaC is authoritative declared intent.
|
|
718
|
+
metadata: redactValue(identity),
|
|
719
|
+
tags: attrTags(attrs.tags),
|
|
720
|
+
...owner ? { owner } : {}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
for (const raw of resources) {
|
|
724
|
+
const r = raw;
|
|
725
|
+
if (r.mode && r.mode !== "managed") continue;
|
|
726
|
+
if (typeof r.type !== "string" || typeof r.name !== "string") continue;
|
|
727
|
+
const srcId = addrToId.get(`${r.type}.${r.name}`);
|
|
728
|
+
if (!srcId) continue;
|
|
729
|
+
const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
|
|
730
|
+
const deps = Array.isArray(inst?.dependencies) ? inst.dependencies : [];
|
|
731
|
+
for (const dep of deps) {
|
|
732
|
+
if (typeof dep !== "string") continue;
|
|
733
|
+
const tgtId = addrToId.get(dep) ?? addrToId.get(dep.split("[")[0]);
|
|
734
|
+
if (!tgtId || tgtId === srcId) continue;
|
|
735
|
+
edges.push({ sourceId: srcId, targetId: tgtId, relationship: "depends_on", evidence: evidenceLine("config-declared", `terraform depends_on ${dep}`), confidence: 0.85 });
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return { nodes, edges };
|
|
739
|
+
}
|
|
740
|
+
function stateDirs() {
|
|
741
|
+
return [".", "./terraform", "./infra", "./infrastructure", "./deploy", "./terraform/environments"];
|
|
742
|
+
}
|
|
743
|
+
function hintPath(hint) {
|
|
744
|
+
if (!hint) return void 0;
|
|
745
|
+
const m = /(?:^|[\s,])tfstate=([^\s,]+)/.exec(hint);
|
|
746
|
+
return m ? m[1] : void 0;
|
|
747
|
+
}
|
|
748
|
+
function resolveStatePath(ctx) {
|
|
749
|
+
const explicit = hintPath(ctx.hint);
|
|
750
|
+
if (explicit) return explicit;
|
|
751
|
+
const found = (ctx.findFiles ?? findFiles)(stateDirs(), ["*.tfstate"], 4, 20).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
752
|
+
return found[0];
|
|
753
|
+
}
|
|
754
|
+
function readStateFile(path) {
|
|
755
|
+
try {
|
|
756
|
+
return readFileSync(path, "utf8");
|
|
757
|
+
} catch {
|
|
758
|
+
return "";
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
var terraformScanner = {
|
|
762
|
+
id: "terraform-state",
|
|
763
|
+
title: "Terraform state (IaC)",
|
|
764
|
+
platforms: "all",
|
|
765
|
+
// No shell commands — the state file is read directly via node:fs, so an
|
|
766
|
+
// operator-supplied path can never inject a command (no `cat "${path}"` interpolation).
|
|
767
|
+
detect(ctx) {
|
|
768
|
+
return resolveStatePath(ctx) !== void 0;
|
|
769
|
+
},
|
|
770
|
+
async scan(ctx) {
|
|
771
|
+
const path = resolveStatePath(ctx);
|
|
772
|
+
if (!path) return { nodes: [], edges: [] };
|
|
773
|
+
const json2 = (ctx.readFile ?? readStateFile)(path);
|
|
774
|
+
if (!json2) return { nodes: [], edges: [] };
|
|
775
|
+
const result = parseTerraformState(json2);
|
|
776
|
+
return { ...result, report: `terraform-state: ${result.nodes.length} resources, ${result.edges.length} dependencies from ${path}` };
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
656
780
|
// src/scanners/registry.ts
|
|
657
781
|
function defaultRegistry() {
|
|
658
|
-
return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner);
|
|
782
|
+
return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner).register(terraformScanner);
|
|
659
783
|
}
|
|
660
784
|
|
|
661
785
|
// src/scanners/loader.ts
|
|
@@ -1317,7 +1441,7 @@ async function runDrift(db, config, opts = {}) {
|
|
|
1317
1441
|
|
|
1318
1442
|
// src/orgkey.ts
|
|
1319
1443
|
import { randomBytes, hkdfSync } from "crypto";
|
|
1320
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from "fs";
|
|
1444
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, statSync } from "fs";
|
|
1321
1445
|
import { homedir } from "os";
|
|
1322
1446
|
import { dirname, join } from "path";
|
|
1323
1447
|
function orgKeyPath(home = homedir()) {
|
|
@@ -1333,7 +1457,7 @@ function loadFileSecret(path) {
|
|
|
1333
1457
|
}
|
|
1334
1458
|
} catch {
|
|
1335
1459
|
}
|
|
1336
|
-
const hex =
|
|
1460
|
+
const hex = readFileSync2(path, "utf8").trim();
|
|
1337
1461
|
const buf = Buffer.from(hex, "hex");
|
|
1338
1462
|
if (buf.length === KEY_BYTES) return buf;
|
|
1339
1463
|
logWarn("org-key file was malformed \u2014 regenerating a fresh org key");
|
|
@@ -1537,9 +1661,146 @@ async function executeNlQuery(db, sessionId, search, intent, opts = {}) {
|
|
|
1537
1661
|
return { intent, anchors, nodes, paths: trav.edges };
|
|
1538
1662
|
}
|
|
1539
1663
|
|
|
1664
|
+
// src/correlation/signals.ts
|
|
1665
|
+
var IPV4 = /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g;
|
|
1666
|
+
var DNS = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.){1,8}[a-z]{2,24}\b/gi;
|
|
1667
|
+
var ENDPOINT = /\b((?:[a-z0-9][a-z0-9.-]{0,253})):(\d{1,5})\b/gi;
|
|
1668
|
+
function isPrivateIp(ip) {
|
|
1669
|
+
return /^(?:10\.|127\.|169\.254\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(ip);
|
|
1670
|
+
}
|
|
1671
|
+
function sources(node) {
|
|
1672
|
+
const out = [node.id, node.name];
|
|
1673
|
+
const meta = node.metadata;
|
|
1674
|
+
if (meta) {
|
|
1675
|
+
for (const k of ["host", "hostname", "ip", "address", "dns", "dnsName", "endpoint", "fqdn", "publicIp", "privateIp", "url"]) {
|
|
1676
|
+
const v = meta[k];
|
|
1677
|
+
if (typeof v === "string") out.push(v);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return out;
|
|
1681
|
+
}
|
|
1682
|
+
var KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["aws", "gcp", "azure", "k8s", "kubernetes", "localhost", "docker"]);
|
|
1683
|
+
function parseProvider(id) {
|
|
1684
|
+
const seg = id.split(":");
|
|
1685
|
+
if (seg.length >= 3 && KNOWN_PROVIDERS.has(seg[1])) return seg[1];
|
|
1686
|
+
return void 0;
|
|
1687
|
+
}
|
|
1688
|
+
function extractSignals(node) {
|
|
1689
|
+
const text = sources(node).join(" \n ");
|
|
1690
|
+
const publicIps = /* @__PURE__ */ new Set();
|
|
1691
|
+
const privateIps = /* @__PURE__ */ new Set();
|
|
1692
|
+
const dnsNames = /* @__PURE__ */ new Set();
|
|
1693
|
+
const endpoints = /* @__PURE__ */ new Set();
|
|
1694
|
+
for (const m of text.matchAll(IPV4)) (isPrivateIp(m[0]) ? privateIps : publicIps).add(m[0]);
|
|
1695
|
+
for (const m of text.matchAll(DNS)) dnsNames.add(m[0].toLowerCase());
|
|
1696
|
+
for (const m of text.matchAll(ENDPOINT)) endpoints.add(`${m[1].toLowerCase()}:${m[2]}`);
|
|
1697
|
+
const provider = parseProvider(node.id);
|
|
1698
|
+
const sortUniq = (s) => [...s].sort();
|
|
1699
|
+
return {
|
|
1700
|
+
...provider ? { provider } : {},
|
|
1701
|
+
endpoints: sortUniq(endpoints),
|
|
1702
|
+
publicIps: sortUniq(publicIps),
|
|
1703
|
+
privateIps: sortUniq(privateIps),
|
|
1704
|
+
// The DNS regex requires an alphabetic TLD, so pure IPv4s never land here.
|
|
1705
|
+
dnsNames: sortUniq(dnsNames)
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/correlation/correlate.ts
|
|
1710
|
+
var CORRELATION_CONFIDENCE = {
|
|
1711
|
+
"global-identity": 1,
|
|
1712
|
+
"dns-name": 0.95,
|
|
1713
|
+
"public-ip": 0.9,
|
|
1714
|
+
"endpoint": 0.85,
|
|
1715
|
+
"private-ip": 0.5
|
|
1716
|
+
};
|
|
1717
|
+
var MERGE_THRESHOLD = 0.85;
|
|
1718
|
+
var DSU = class {
|
|
1719
|
+
parent;
|
|
1720
|
+
constructor(n) {
|
|
1721
|
+
this.parent = Array.from({ length: n }, (_, i) => i);
|
|
1722
|
+
}
|
|
1723
|
+
find(x) {
|
|
1724
|
+
while (this.parent[x] !== x) {
|
|
1725
|
+
this.parent[x] = this.parent[this.parent[x]];
|
|
1726
|
+
x = this.parent[x];
|
|
1727
|
+
}
|
|
1728
|
+
return x;
|
|
1729
|
+
}
|
|
1730
|
+
union(a, b) {
|
|
1731
|
+
const ra = this.find(a), rb = this.find(b);
|
|
1732
|
+
if (ra !== rb) this.parent[Math.max(ra, rb)] = Math.min(ra, rb);
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
function correlateTopology(nodes, _edges = []) {
|
|
1736
|
+
const n = nodes.length;
|
|
1737
|
+
const signals = nodes.map(extractSignals);
|
|
1738
|
+
const dsu = new DSU(n);
|
|
1739
|
+
const correlations = [];
|
|
1740
|
+
const merged = /* @__PURE__ */ new Set();
|
|
1741
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1742
|
+
const add = (sig, val, i) => {
|
|
1743
|
+
const k = `${sig}|${val}`;
|
|
1744
|
+
const arr = buckets.get(k);
|
|
1745
|
+
if (arr) arr.push(i);
|
|
1746
|
+
else buckets.set(k, [i]);
|
|
1747
|
+
};
|
|
1748
|
+
nodes.forEach((node, i) => {
|
|
1749
|
+
if (node.globalId) add("global-identity", node.globalId, i);
|
|
1750
|
+
for (const d of signals[i].dnsNames) add("dns-name", d, i);
|
|
1751
|
+
for (const ip of signals[i].publicIps) add("public-ip", ip, i);
|
|
1752
|
+
for (const e of signals[i].endpoints) add("endpoint", e, i);
|
|
1753
|
+
});
|
|
1754
|
+
const order = ["global-identity", "dns-name", "public-ip", "endpoint"];
|
|
1755
|
+
for (const sig of order) {
|
|
1756
|
+
const conf = CORRELATION_CONFIDENCE[sig];
|
|
1757
|
+
const keys = [...buckets.keys()].filter((k) => k.startsWith(`${sig}|`)).sort();
|
|
1758
|
+
for (const key of keys) {
|
|
1759
|
+
const members = buckets.get(key).slice().sort((x, y) => nodes[x].id.localeCompare(nodes[y].id));
|
|
1760
|
+
const value = key.slice(sig.length + 1);
|
|
1761
|
+
for (let j = 1; j < members.length; j++) {
|
|
1762
|
+
const a = members[0], b = members[j];
|
|
1763
|
+
if (conf < MERGE_THRESHOLD) continue;
|
|
1764
|
+
dsu.union(a, b);
|
|
1765
|
+
merged.add(a);
|
|
1766
|
+
merged.add(b);
|
|
1767
|
+
const [s, t] = [nodes[a].id, nodes[b].id].sort();
|
|
1768
|
+
correlations.push({ sourceId: s, targetId: t, relationship: "same_as", signal: sig, confidence: conf, evidence: `[${sig}] shared ${value}` });
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
1773
|
+
for (let i = 0; i < n; i++) {
|
|
1774
|
+
const r = dsu.find(i);
|
|
1775
|
+
const arr = clusters.get(r);
|
|
1776
|
+
if (arr) arr.push(i);
|
|
1777
|
+
else clusters.set(r, [i]);
|
|
1778
|
+
}
|
|
1779
|
+
const canonical = [];
|
|
1780
|
+
let crossCloud = 0;
|
|
1781
|
+
for (const idxs of clusters.values()) {
|
|
1782
|
+
const memberNodes = idxs.map((i) => nodes[i]);
|
|
1783
|
+
const members = memberNodes.map((m) => m.id).sort();
|
|
1784
|
+
const providers = [...new Set(idxs.map((i) => signals[i].provider).filter((p) => !!p))].sort();
|
|
1785
|
+
const rep = memberNodes.reduce((a, b) => a.id < b.id ? a : b);
|
|
1786
|
+
const memberIds = new Set(members);
|
|
1787
|
+
const internal = correlations.filter((c) => memberIds.has(c.sourceId) && memberIds.has(c.targetId));
|
|
1788
|
+
const confidence = internal.length ? Math.min(...internal.map((c) => c.confidence)) : 1;
|
|
1789
|
+
if (providers.length > 1) crossCloud += 1;
|
|
1790
|
+
canonical.push({ id: rep.id, type: rep.type, name: rep.name, members, providers, confidence });
|
|
1791
|
+
}
|
|
1792
|
+
canonical.sort((a, b) => a.id.localeCompare(b.id));
|
|
1793
|
+
correlations.sort((a, b) => b.confidence - a.confidence || a.sourceId.localeCompare(b.sourceId) || a.targetId.localeCompare(b.targetId));
|
|
1794
|
+
return {
|
|
1795
|
+
canonical,
|
|
1796
|
+
correlations,
|
|
1797
|
+
summary: { rawNodes: n, canonicalNodes: canonical.length, collapsed: n - canonical.length, crossCloud }
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1540
1801
|
// src/mcp/server.ts
|
|
1541
1802
|
var SERVER_NAME = "cartography";
|
|
1542
|
-
var SERVER_VERSION = "2.
|
|
1803
|
+
var SERVER_VERSION = "2.11.0";
|
|
1543
1804
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
1544
1805
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
1545
1806
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -1699,6 +1960,15 @@ function createMcpServer(opts = {}) {
|
|
|
1699
1960
|
return json(db.getGraphSummary(sid));
|
|
1700
1961
|
}
|
|
1701
1962
|
);
|
|
1963
|
+
server.registerTool(
|
|
1964
|
+
"correlate_topology",
|
|
1965
|
+
{ title: "Correlate multi-cloud topology", description: "Collapse the same logical resource discovered across clouds/on-prem (by global identity or a shared DNS name / public IP / endpoint) into canonical entities with confidence-scored same_as links. Read-only, non-destructive (5.1).", inputSchema: {}, annotations: readOnly },
|
|
1966
|
+
() => {
|
|
1967
|
+
const sid = resolveSession();
|
|
1968
|
+
if (!sid) return json({ error: "No discovery session found." });
|
|
1969
|
+
return json(correlateTopology(db.getNodes(sid), db.getEdges(sid)));
|
|
1970
|
+
}
|
|
1971
|
+
);
|
|
1702
1972
|
server.registerTool(
|
|
1703
1973
|
"get_cost_summary",
|
|
1704
1974
|
{ title: "Get cost summary", description: "FinOps rollup: cost by domain and owner, currency/period-bucketed (3.3).", inputSchema: {}, annotations: readOnly },
|
|
@@ -2882,4 +3152,4 @@ export {
|
|
|
2882
3152
|
parseMcpArgs,
|
|
2883
3153
|
startMcp
|
|
2884
3154
|
};
|
|
2885
|
-
//# sourceMappingURL=chunk-
|
|
3155
|
+
//# sourceMappingURL=chunk-FFUUNSWP.js.map
|