@datasynx/agentic-ai-cartography 2.10.0 → 2.12.1
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 +142 -2
- package/dist/api-bin.js +2 -2
- package/dist/{chunk-YVV6NIT2.js → chunk-LO6YFS6H.js} +2 -1
- package/dist/{chunk-ASCA3UFM.js → chunk-OIDAXUW5.js} +340 -204
- package/dist/chunk-OIDAXUW5.js.map +1 -0
- package/dist/{chunk-W4Q3TXHR.js → chunk-PD67MOKR.js} +2 -2
- package/dist/cli.js +97 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +241 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -3
- package/dist/index.d.ts +126 -3
- package/dist/index.js +217 -16
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +2 -2
- package/llms-full.txt +305 -25
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-ASCA3UFM.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/index.cjs
CHANGED
|
@@ -31,9 +31,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
ACTIONS: () => ACTIONS,
|
|
34
|
+
ANON_TOKEN: () => ANON_TOKEN,
|
|
34
35
|
ActionSchema: () => ActionSchema,
|
|
35
36
|
AuthConfigSchema: () => AuthConfigSchema,
|
|
36
37
|
AuthorizationError: () => AuthorizationError,
|
|
38
|
+
BARE_INTERNAL_HOST: () => BARE_INTERNAL_HOST,
|
|
37
39
|
CLIENTS: () => CLIENTS,
|
|
38
40
|
CONFIDENCE: () => CONFIDENCE,
|
|
39
41
|
CORRELATION_CONFIDENCE: () => CORRELATION_CONFIDENCE,
|
|
@@ -197,11 +199,13 @@ __export(src_exports, {
|
|
|
197
199
|
hostname: () => hostname,
|
|
198
200
|
ingestEnvelope: () => ingestEnvelope,
|
|
199
201
|
installedAppsScanner: () => installedAppsScanner,
|
|
202
|
+
isInCluster: () => isInCluster,
|
|
200
203
|
isLoopbackHost: () => isLoopbackHost,
|
|
201
204
|
isPersonalHost: () => isPersonalHost,
|
|
202
205
|
isReadOnlyCommand: () => isReadOnlyCommand,
|
|
203
206
|
isRemembered: () => isRemembered,
|
|
204
207
|
isSecureWebhookUrl: () => isSecureWebhookUrl,
|
|
208
|
+
k8sRegistry: () => k8sRegistry,
|
|
205
209
|
k8sScanner: () => k8sScanner,
|
|
206
210
|
keyMetaOf: () => keyMetaOf,
|
|
207
211
|
layoutClusters: () => layoutClusters,
|
|
@@ -238,6 +242,7 @@ __export(src_exports, {
|
|
|
238
242
|
parseNginxUpstreams: () => parseNginxUpstreams,
|
|
239
243
|
parseNlQuery: () => parseNlQuery,
|
|
240
244
|
parseScanHint: () => parseScanHint,
|
|
245
|
+
parseTerraformState: () => parseTerraformState,
|
|
241
246
|
pixelToHex: () => pixelToHex,
|
|
242
247
|
planInstall: () => planInstall,
|
|
243
248
|
portsScanner: () => portsScanner,
|
|
@@ -245,6 +250,7 @@ __export(src_exports, {
|
|
|
245
250
|
previewShare: () => previewShare,
|
|
246
251
|
pseudonymize: () => pseudonymize,
|
|
247
252
|
pseudonymizeFragment: () => pseudonymizeFragment,
|
|
253
|
+
pseudonymizeId: () => pseudonymizeId,
|
|
248
254
|
pseudonymizeString: () => pseudonymizeString,
|
|
249
255
|
pushDeltas: () => pushDeltas,
|
|
250
256
|
readConfigFile: () => readConfigFile,
|
|
@@ -267,6 +273,8 @@ __export(src_exports, {
|
|
|
267
273
|
runHttp: () => runHttp,
|
|
268
274
|
runLocalDiscovery: () => runLocalDiscovery,
|
|
269
275
|
runOnce: () => runOnce,
|
|
276
|
+
runOperator: () => runOperator,
|
|
277
|
+
runOperatorCycle: () => runOperatorCycle,
|
|
270
278
|
runStdio: () => runStdio,
|
|
271
279
|
runSyncClassify: () => runSyncClassify,
|
|
272
280
|
safeEnv: () => safeEnv,
|
|
@@ -287,6 +295,8 @@ __export(src_exports, {
|
|
|
287
295
|
stableStringify: () => stableStringify,
|
|
288
296
|
startApi: () => startApi,
|
|
289
297
|
stripSensitive: () => stripSensitive,
|
|
298
|
+
terraformScanner: () => terraformScanner,
|
|
299
|
+
terraformTypeToNode: () => terraformTypeToNode,
|
|
290
300
|
timingSafeEqual: () => timingSafeEqual,
|
|
291
301
|
toBackstageEntities: () => toBackstageEntities,
|
|
292
302
|
validateScanner: () => validateScanner,
|
|
@@ -4949,6 +4959,8 @@ function reversalKey(orgKey) {
|
|
|
4949
4959
|
// src/anonymize.ts
|
|
4950
4960
|
var PRIVATE_IP = /\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b/g;
|
|
4951
4961
|
var HOSTNAME = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}\b/gi;
|
|
4962
|
+
var BARE_INTERNAL_HOST = /^[a-z0-9]+(?:-[a-z0-9]+)+$|^[a-z]+\d+$|^\d+[a-z]+$/i;
|
|
4963
|
+
var ANON_TOKEN = /^anon:(?:host|user|path|ip):[a-z2-7]+$/;
|
|
4952
4964
|
var POSIX_PATH = /(?:^|(?<=\s|=|:|"|'|\())(\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+)/g;
|
|
4953
4965
|
var WIN_PATH = /\b[A-Za-z]:\\[A-Za-z0-9._\\-]+/g;
|
|
4954
4966
|
var B32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
@@ -5003,8 +5015,18 @@ function pseudonymizeString(s, orgKey, db) {
|
|
|
5003
5015
|
(_m, user, host2) => `${pseudonymizeFragment(user, "user", orgKey, db)}@${pseudonymizeFragment(host2, "host", orgKey, db)}`
|
|
5004
5016
|
);
|
|
5005
5017
|
out = out.replace(HOSTNAME, (m) => pseudonymizeFragment(m, "host", orgKey, db));
|
|
5018
|
+
const trimmed = out.trim();
|
|
5019
|
+
if (out === s && !ANON_TOKEN.test(trimmed) && BARE_INTERNAL_HOST.test(trimmed)) {
|
|
5020
|
+
out = pseudonymizeFragment(trimmed, "host", orgKey, db);
|
|
5021
|
+
}
|
|
5006
5022
|
return out;
|
|
5007
5023
|
}
|
|
5024
|
+
function pseudonymizeId(id, orgKey, db) {
|
|
5025
|
+
const segments = id.split(":");
|
|
5026
|
+
if (segments.length <= 1) return pseudonymizeString(id, orgKey, db);
|
|
5027
|
+
const [type, ...rest] = segments;
|
|
5028
|
+
return [type, ...rest.map((seg) => pseudonymizeString(seg, orgKey, db))].join(":");
|
|
5029
|
+
}
|
|
5008
5030
|
function pseudonymize(value, orgKey, db) {
|
|
5009
5031
|
if (typeof value === "string") return pseudonymizeString(value, orgKey, db);
|
|
5010
5032
|
if (Array.isArray(value)) return value.map((v) => pseudonymize(v, orgKey, db));
|
|
@@ -5031,8 +5053,6 @@ var FQDN = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}\b/gi;
|
|
|
5031
5053
|
var POSIX_PATH2 = /(?:^|(?<=\s|=|:|"|'|\())(\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+)/g;
|
|
5032
5054
|
var WIN_PATH2 = /\b[A-Za-z]:\\[A-Za-z0-9._\\-]+/g;
|
|
5033
5055
|
var HOME_USER = /(?:\/home\/|\/Users\/|[A-Za-z]:\\Users\\)([A-Za-z0-9._-]+)/g;
|
|
5034
|
-
var BARE_INTERNAL_HOST = /^[a-z0-9]+(?:-[a-z0-9]+)+$|^[a-z]+\d+$|^\d+[a-z]+$/i;
|
|
5035
|
-
var ANON_TOKEN = /^anon:(?:host|user|path|ip):[a-z2-7]+$/;
|
|
5036
5056
|
function violationsInString(s, path) {
|
|
5037
5057
|
const out = [];
|
|
5038
5058
|
const trimmed = s.trim();
|
|
@@ -5422,9 +5442,10 @@ function resolveEffectiveLevel(node, policy) {
|
|
|
5422
5442
|
function applySharingLevel(node, level, orgKey, db) {
|
|
5423
5443
|
if (level === "none") return null;
|
|
5424
5444
|
if (level === "full") return { ...node, metadata: { ...node.metadata ?? {} }, tags: [...node.tags ?? []] };
|
|
5445
|
+
const { globalId: _g, contentHash: _h, ...rest } = node;
|
|
5425
5446
|
return {
|
|
5426
|
-
...
|
|
5427
|
-
id:
|
|
5447
|
+
...rest,
|
|
5448
|
+
id: pseudonymizeId(node.id, orgKey, db),
|
|
5428
5449
|
name: pseudonymizeString(node.name, orgKey, db),
|
|
5429
5450
|
metadata: pseudonymize(node.metadata ?? {}, orgKey, db),
|
|
5430
5451
|
tags: (node.tags ?? []).map((t) => pseudonymizeString(t, orgKey, db))
|
|
@@ -6276,7 +6297,7 @@ function correlateTopology(nodes, _edges = []) {
|
|
|
6276
6297
|
|
|
6277
6298
|
// src/mcp/server.ts
|
|
6278
6299
|
var SERVER_NAME = "cartography";
|
|
6279
|
-
var SERVER_VERSION = "2.
|
|
6300
|
+
var SERVER_VERSION = "2.12.1";
|
|
6280
6301
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
6281
6302
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
6282
6303
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -7686,9 +7707,132 @@ var serviceConfigScanner = {
|
|
|
7686
7707
|
}
|
|
7687
7708
|
};
|
|
7688
7709
|
|
|
7710
|
+
// src/scanners/terraform.ts
|
|
7711
|
+
var import_node_fs5 = require("fs");
|
|
7712
|
+
var TYPE_RULES = [
|
|
7713
|
+
[/(db_instance|_rds|sql_database|sql_instance|database_instance|cosmosdb|dynamodb|spanner|bigtable|documentdb|redshift)/, "database_server"],
|
|
7714
|
+
[/(elasticache|_redis|memcached|memorystore)/, "cache_server"],
|
|
7715
|
+
[/(s3_bucket|storage_bucket|gcs_bucket|storage_account|_blob)/, "database"],
|
|
7716
|
+
[/(_sqs|_queue|servicebus_queue)/, "queue"],
|
|
7717
|
+
[/(_sns|_topic|pubsub_topic|servicebus_topic)/, "topic"],
|
|
7718
|
+
[/(kafka|_msk|event_hub|kinesis)/, "message_broker"],
|
|
7719
|
+
[/(_eks|_gke|_aks|kubernetes_cluster|container_cluster)/, "k8s_cluster"],
|
|
7720
|
+
[/(ecs_|_container|fargate)/, "container"],
|
|
7721
|
+
[/(lambda|cloud_function|cloudfunctions|function_app|cloud_run)/, "web_service"],
|
|
7722
|
+
[/(_lb$|load_balancer|_alb|_elb|application_gateway)/, "web_service"],
|
|
7723
|
+
[/(api_gateway|apigateway)/, "api_endpoint"],
|
|
7724
|
+
[/(_instance|virtual_machine|_vm$|compute_instance)/, "host"]
|
|
7725
|
+
];
|
|
7726
|
+
function terraformTypeToNode(tfType) {
|
|
7727
|
+
const t = tfType.toLowerCase();
|
|
7728
|
+
for (const [re, nt] of TYPE_RULES) if (re.test(t)) return nt;
|
|
7729
|
+
return "unknown";
|
|
7730
|
+
}
|
|
7731
|
+
var IDENTITY_ATTRS = ["id", "arn", "region", "location", "instance_type", "engine", "machine_type"];
|
|
7732
|
+
var OWNER_TAGS = ["Owner", "owner", "Team", "team"];
|
|
7733
|
+
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"]);
|
|
7734
|
+
var SECRET_KEY = /pass|secret|token|key|pwd|cred|private/i;
|
|
7735
|
+
function attrTags(tags) {
|
|
7736
|
+
if (!tags || typeof tags !== "object") return [];
|
|
7737
|
+
return Object.entries(tags).filter(([k]) => SAFE_TAG_KEYS.has(k) && !SECRET_KEY.test(k)).map(([k, v]) => `${k}:${redactSecrets(String(v))}`);
|
|
7738
|
+
}
|
|
7739
|
+
function parseTerraformState(json2) {
|
|
7740
|
+
let state;
|
|
7741
|
+
try {
|
|
7742
|
+
state = JSON.parse(json2);
|
|
7743
|
+
} catch {
|
|
7744
|
+
return { nodes: [], edges: [] };
|
|
7745
|
+
}
|
|
7746
|
+
const resources = Array.isArray(state?.resources) ? state.resources : [];
|
|
7747
|
+
const nodes = [];
|
|
7748
|
+
const edges = [];
|
|
7749
|
+
const addrToId = /* @__PURE__ */ new Map();
|
|
7750
|
+
for (const raw of resources) {
|
|
7751
|
+
const r = raw;
|
|
7752
|
+
if (r.mode && r.mode !== "managed") continue;
|
|
7753
|
+
if (typeof r.type !== "string" || typeof r.name !== "string") continue;
|
|
7754
|
+
const address = `${r.type}.${r.name}`;
|
|
7755
|
+
const nt = terraformTypeToNode(r.type);
|
|
7756
|
+
const id = `${nt}:terraform:${address}`;
|
|
7757
|
+
if (addrToId.has(address)) continue;
|
|
7758
|
+
addrToId.set(address, id);
|
|
7759
|
+
const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
|
|
7760
|
+
const attrs = inst?.attributes ?? {};
|
|
7761
|
+
const identity = { source: "terraform", tfType: r.type };
|
|
7762
|
+
for (const k of IDENTITY_ATTRS) if (attrs[k] !== void 0) identity[k] = attrs[k];
|
|
7763
|
+
const owner = OWNER_TAGS.map((k) => attrs.tags?.[k]).find((v) => typeof v === "string");
|
|
7764
|
+
nodes.push({
|
|
7765
|
+
id,
|
|
7766
|
+
type: nt,
|
|
7767
|
+
name: address,
|
|
7768
|
+
discoveredVia: "terraform-state",
|
|
7769
|
+
confidence: 0.9,
|
|
7770
|
+
// IaC is authoritative declared intent.
|
|
7771
|
+
metadata: redactValue(identity),
|
|
7772
|
+
tags: attrTags(attrs.tags),
|
|
7773
|
+
...owner ? { owner } : {}
|
|
7774
|
+
});
|
|
7775
|
+
}
|
|
7776
|
+
for (const raw of resources) {
|
|
7777
|
+
const r = raw;
|
|
7778
|
+
if (r.mode && r.mode !== "managed") continue;
|
|
7779
|
+
if (typeof r.type !== "string" || typeof r.name !== "string") continue;
|
|
7780
|
+
const srcId = addrToId.get(`${r.type}.${r.name}`);
|
|
7781
|
+
if (!srcId) continue;
|
|
7782
|
+
const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
|
|
7783
|
+
const deps = Array.isArray(inst?.dependencies) ? inst.dependencies : [];
|
|
7784
|
+
for (const dep of deps) {
|
|
7785
|
+
if (typeof dep !== "string") continue;
|
|
7786
|
+
const tgtId = addrToId.get(dep) ?? addrToId.get(dep.split("[")[0]);
|
|
7787
|
+
if (!tgtId || tgtId === srcId) continue;
|
|
7788
|
+
edges.push({ sourceId: srcId, targetId: tgtId, relationship: "depends_on", evidence: evidenceLine("config-declared", `terraform depends_on ${dep}`), confidence: 0.85 });
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7791
|
+
return { nodes, edges };
|
|
7792
|
+
}
|
|
7793
|
+
function stateDirs() {
|
|
7794
|
+
return [".", "./terraform", "./infra", "./infrastructure", "./deploy", "./terraform/environments"];
|
|
7795
|
+
}
|
|
7796
|
+
function hintPath(hint) {
|
|
7797
|
+
if (!hint) return void 0;
|
|
7798
|
+
const m = /(?:^|[\s,])tfstate=([^\s,]+)/.exec(hint);
|
|
7799
|
+
return m ? m[1] : void 0;
|
|
7800
|
+
}
|
|
7801
|
+
function resolveStatePath(ctx) {
|
|
7802
|
+
const explicit = hintPath(ctx.hint);
|
|
7803
|
+
if (explicit) return explicit;
|
|
7804
|
+
const found = (ctx.findFiles ?? findFiles)(stateDirs(), ["*.tfstate"], 4, 20).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
7805
|
+
return found[0];
|
|
7806
|
+
}
|
|
7807
|
+
function readStateFile(path) {
|
|
7808
|
+
try {
|
|
7809
|
+
return (0, import_node_fs5.readFileSync)(path, "utf8");
|
|
7810
|
+
} catch {
|
|
7811
|
+
return "";
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
var terraformScanner = {
|
|
7815
|
+
id: "terraform-state",
|
|
7816
|
+
title: "Terraform state (IaC)",
|
|
7817
|
+
platforms: "all",
|
|
7818
|
+
// No shell commands — the state file is read directly via node:fs, so an
|
|
7819
|
+
// operator-supplied path can never inject a command (no `cat "${path}"` interpolation).
|
|
7820
|
+
detect(ctx) {
|
|
7821
|
+
return resolveStatePath(ctx) !== void 0;
|
|
7822
|
+
},
|
|
7823
|
+
async scan(ctx) {
|
|
7824
|
+
const path = resolveStatePath(ctx);
|
|
7825
|
+
if (!path) return { nodes: [], edges: [] };
|
|
7826
|
+
const json2 = (ctx.readFile ?? readStateFile)(path);
|
|
7827
|
+
if (!json2) return { nodes: [], edges: [] };
|
|
7828
|
+
const result = parseTerraformState(json2);
|
|
7829
|
+
return { ...result, report: `terraform-state: ${result.nodes.length} resources, ${result.edges.length} dependencies from ${path}` };
|
|
7830
|
+
}
|
|
7831
|
+
};
|
|
7832
|
+
|
|
7689
7833
|
// src/scanners/registry.ts
|
|
7690
7834
|
function defaultRegistry() {
|
|
7691
|
-
return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner);
|
|
7835
|
+
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);
|
|
7692
7836
|
}
|
|
7693
7837
|
|
|
7694
7838
|
// src/scanners/loader.ts
|
|
@@ -9056,14 +9200,14 @@ var AuthConfigSchema = import_zod9.z.object({
|
|
|
9056
9200
|
});
|
|
9057
9201
|
|
|
9058
9202
|
// src/api/start.ts
|
|
9059
|
-
var
|
|
9203
|
+
var import_node_fs6 = require("fs");
|
|
9060
9204
|
var import_node_path5 = require("path");
|
|
9061
9205
|
var import_node_url = require("url");
|
|
9062
9206
|
var import_meta = {};
|
|
9063
9207
|
function readVersion() {
|
|
9064
9208
|
try {
|
|
9065
9209
|
const dir = import_meta.dirname ?? (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
9066
|
-
return JSON.parse((0,
|
|
9210
|
+
return JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.resolve)(dir, "..", "package.json"), "utf-8")).version ?? "0.0.0";
|
|
9067
9211
|
} catch {
|
|
9068
9212
|
return "0.0.0";
|
|
9069
9213
|
}
|
|
@@ -9194,7 +9338,7 @@ function defaultServerEntry(opts = {}) {
|
|
|
9194
9338
|
}
|
|
9195
9339
|
|
|
9196
9340
|
// src/installer/install.ts
|
|
9197
|
-
var
|
|
9341
|
+
var import_node_fs7 = require("fs");
|
|
9198
9342
|
var import_node_path6 = require("path");
|
|
9199
9343
|
var import_node_os4 = require("os");
|
|
9200
9344
|
function currentOs() {
|
|
@@ -9210,8 +9354,8 @@ function planInstall(spec, ctx, opts) {
|
|
|
9210
9354
|
if (!path) {
|
|
9211
9355
|
throw new Error(`${spec.label} does not support the "${ctx.scope}" scope.`);
|
|
9212
9356
|
}
|
|
9213
|
-
const fileExists = (0,
|
|
9214
|
-
const before = fileExists ? (0,
|
|
9357
|
+
const fileExists = (0, import_node_fs7.existsSync)(path);
|
|
9358
|
+
const before = fileExists ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
|
|
9215
9359
|
const existing = parseConfig(before, spec.format);
|
|
9216
9360
|
const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);
|
|
9217
9361
|
const after = serializeConfig(merged, spec.format);
|
|
@@ -9228,8 +9372,8 @@ function planInstall(spec, ctx, opts) {
|
|
|
9228
9372
|
};
|
|
9229
9373
|
}
|
|
9230
9374
|
function applyInstall(plan) {
|
|
9231
|
-
(0,
|
|
9232
|
-
(0,
|
|
9375
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(plan.path), { recursive: true });
|
|
9376
|
+
(0, import_node_fs7.writeFileSync)(plan.path, plan.after, "utf8");
|
|
9233
9377
|
}
|
|
9234
9378
|
function renderDiff(before, after) {
|
|
9235
9379
|
if (before === after) return " (no changes)";
|
|
@@ -10076,8 +10220,65 @@ Use ask_user when you need context from the user.`;
|
|
|
10076
10220
|
}
|
|
10077
10221
|
}
|
|
10078
10222
|
|
|
10223
|
+
// src/k8s/operator.ts
|
|
10224
|
+
function k8sRegistry() {
|
|
10225
|
+
return new ScannerRegistry().register(k8sScanner);
|
|
10226
|
+
}
|
|
10227
|
+
function isInCluster(env = process.env) {
|
|
10228
|
+
return typeof env["KUBERNETES_SERVICE_HOST"] === "string" && env["KUBERNETES_SERVICE_HOST"].length > 0;
|
|
10229
|
+
}
|
|
10230
|
+
function pruneToRetention(db, keep, tenant) {
|
|
10231
|
+
const stale = db.getSessions(tenant).slice(Math.max(1, keep));
|
|
10232
|
+
for (const s of stale) db.deleteSession(s.id);
|
|
10233
|
+
return stale.length;
|
|
10234
|
+
}
|
|
10235
|
+
async function runOperatorCycle(db, config, opts = {}) {
|
|
10236
|
+
const sessionId = db.createSession("discover", config);
|
|
10237
|
+
const discover = opts.discover ?? ((d, s) => runLocalDiscovery(d, s, { registry: k8sRegistry() }));
|
|
10238
|
+
const res = await discover(db, sessionId);
|
|
10239
|
+
const sess = db.getSession(sessionId);
|
|
10240
|
+
if (sess && !sess.name) db.setSessionName(sessionId, deriveSessionName(db.getGraphSummary(sessionId), sess.startedAt));
|
|
10241
|
+
const driftFn = opts.drift ?? ((d, c) => runDrift(d, c));
|
|
10242
|
+
const drift = await driftFn(db, config);
|
|
10243
|
+
pruneToRetention(db, opts.retain ?? 10, normalizeTenant(config.organization));
|
|
10244
|
+
return { sessionId, nodes: res.nodes, edges: res.edges, drift };
|
|
10245
|
+
}
|
|
10246
|
+
async function runOperator(db, config, opts = {}) {
|
|
10247
|
+
const log2 = opts.log ?? ((m) => process.stderr.write(m + "\n"));
|
|
10248
|
+
const intervalMs = opts.intervalMs ?? 5 * 6e4;
|
|
10249
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((resolve3) => {
|
|
10250
|
+
if (opts.signal?.aborted) {
|
|
10251
|
+
resolve3();
|
|
10252
|
+
return;
|
|
10253
|
+
}
|
|
10254
|
+
const t = setTimeout(() => {
|
|
10255
|
+
opts.signal?.removeEventListener?.("abort", onAbort);
|
|
10256
|
+
resolve3();
|
|
10257
|
+
}, ms);
|
|
10258
|
+
const onAbort = () => {
|
|
10259
|
+
clearTimeout(t);
|
|
10260
|
+
resolve3();
|
|
10261
|
+
};
|
|
10262
|
+
opts.signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
10263
|
+
}));
|
|
10264
|
+
log2(`Cartograph Kubernetes operator (in-cluster: ${isInCluster()}, interval: ${Math.round(intervalMs / 1e3)}s${opts.once ? ", single pass" : ""})`);
|
|
10265
|
+
for (; ; ) {
|
|
10266
|
+
try {
|
|
10267
|
+
const c = await runOperatorCycle(db, config, opts);
|
|
10268
|
+
log2(
|
|
10269
|
+
`reconcile: session ${c.sessionId} \u2014 ${c.nodes} nodes, ${c.edges} edges` + (c.drift ? `, drift ${c.drift.severity} (${c.drift.items.length} change${c.drift.items.length === 1 ? "" : "s"})` : ", no drift")
|
|
10270
|
+
);
|
|
10271
|
+
} catch (err) {
|
|
10272
|
+
log2(`reconcile failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10273
|
+
}
|
|
10274
|
+
if (opts.once || opts.signal?.aborted) return;
|
|
10275
|
+
await sleep(intervalMs);
|
|
10276
|
+
if (opts.signal?.aborted) return;
|
|
10277
|
+
}
|
|
10278
|
+
}
|
|
10279
|
+
|
|
10079
10280
|
// src/cost.ts
|
|
10080
|
-
var
|
|
10281
|
+
var import_node_fs8 = require("fs");
|
|
10081
10282
|
var import_node_path8 = require("path");
|
|
10082
10283
|
function splitCsvLine(line) {
|
|
10083
10284
|
const out = [];
|
|
@@ -10156,7 +10357,7 @@ var CsvCostSource = class {
|
|
|
10156
10357
|
}
|
|
10157
10358
|
id;
|
|
10158
10359
|
async fetch() {
|
|
10159
|
-
const text = (0,
|
|
10360
|
+
const text = (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(this.opts.filePath), "utf-8");
|
|
10160
10361
|
const records = parseCostCsv(text);
|
|
10161
10362
|
const match = this.opts.match ?? "nodeId";
|
|
10162
10363
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -10198,7 +10399,7 @@ async function enrichCosts(db, sessionId, source) {
|
|
|
10198
10399
|
}
|
|
10199
10400
|
|
|
10200
10401
|
// src/exporter.ts
|
|
10201
|
-
var
|
|
10402
|
+
var import_node_fs9 = require("fs");
|
|
10202
10403
|
var import_node_path9 = require("path");
|
|
10203
10404
|
|
|
10204
10405
|
// src/hex.ts
|
|
@@ -11868,28 +12069,28 @@ function exportComplianceReport(report, format) {
|
|
|
11868
12069
|
return lines.join("\n");
|
|
11869
12070
|
}
|
|
11870
12071
|
function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "map", "discovery"]) {
|
|
11871
|
-
(0,
|
|
12072
|
+
(0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
|
|
11872
12073
|
const nodes = db.getNodes(sessionId);
|
|
11873
12074
|
const edges = db.getEdges(sessionId);
|
|
11874
12075
|
const jgfPath = (0, import_node_path9.join)(outputDir, "cartography-graph.jgf.json");
|
|
11875
|
-
(0,
|
|
12076
|
+
(0, import_node_fs9.writeFileSync)(jgfPath, exportJGF(nodes, edges));
|
|
11876
12077
|
if (formats.includes("mermaid")) {
|
|
11877
|
-
(0,
|
|
11878
|
-
(0,
|
|
12078
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
|
|
12079
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
|
|
11879
12080
|
}
|
|
11880
12081
|
if (formats.includes("json")) {
|
|
11881
|
-
(0,
|
|
12082
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog.json"), exportJSON(db, sessionId));
|
|
11882
12083
|
}
|
|
11883
12084
|
if (formats.includes("yaml")) {
|
|
11884
|
-
(0,
|
|
12085
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
|
|
11885
12086
|
}
|
|
11886
12087
|
if (formats.includes("html") || formats.includes("map") || formats.includes("discovery")) {
|
|
11887
|
-
(0,
|
|
12088
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
|
|
11888
12089
|
}
|
|
11889
12090
|
if (formats.includes("cost")) {
|
|
11890
12091
|
const summary = db.getGraphSummary(sessionId);
|
|
11891
|
-
(0,
|
|
11892
|
-
(0,
|
|
12092
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-by-domain.csv"), exportCostCSV(summary));
|
|
12093
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-summary.json"), exportCostSummary(summary));
|
|
11893
12094
|
}
|
|
11894
12095
|
}
|
|
11895
12096
|
|
|
@@ -11921,7 +12122,7 @@ function formatComplianceText(report) {
|
|
|
11921
12122
|
}
|
|
11922
12123
|
|
|
11923
12124
|
// src/config.ts
|
|
11924
|
-
var
|
|
12125
|
+
var import_node_fs10 = require("fs");
|
|
11925
12126
|
var ConfigError = class extends Error {
|
|
11926
12127
|
constructor(message) {
|
|
11927
12128
|
super(message);
|
|
@@ -11946,7 +12147,7 @@ function loadConfig(path) {
|
|
|
11946
12147
|
function readConfigFile(path) {
|
|
11947
12148
|
let raw;
|
|
11948
12149
|
try {
|
|
11949
|
-
raw = (0,
|
|
12150
|
+
raw = (0, import_node_fs10.readFileSync)(path, "utf-8");
|
|
11950
12151
|
} catch (err) {
|
|
11951
12152
|
throw new ConfigError(
|
|
11952
12153
|
`Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -12304,14 +12505,14 @@ function runSyncClassify(db, sessionId, config, opts = {}) {
|
|
|
12304
12505
|
|
|
12305
12506
|
// src/preflight.ts
|
|
12306
12507
|
var import_node_child_process2 = require("child_process");
|
|
12307
|
-
var
|
|
12508
|
+
var import_node_fs11 = require("fs");
|
|
12308
12509
|
var import_node_path10 = require("path");
|
|
12309
12510
|
function isOAuthLoggedIn() {
|
|
12310
12511
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
12311
12512
|
const credFile = (0, import_node_path10.join)(home, ".claude", ".credentials.json");
|
|
12312
|
-
if (!(0,
|
|
12513
|
+
if (!(0, import_node_fs11.existsSync)(credFile)) return false;
|
|
12313
12514
|
try {
|
|
12314
|
-
const creds = JSON.parse((0,
|
|
12515
|
+
const creds = JSON.parse((0, import_node_fs11.readFileSync)(credFile, "utf8"));
|
|
12315
12516
|
const oauth = creds["claudeAiOauth"];
|
|
12316
12517
|
return typeof oauth?.["accessToken"] === "string" && oauth["accessToken"].length > 0;
|
|
12317
12518
|
} catch {
|
|
@@ -12364,9 +12565,11 @@ function checkClaudePrerequisites() {
|
|
|
12364
12565
|
// Annotate the CommonJS export names for ESM import in node:
|
|
12365
12566
|
0 && (module.exports = {
|
|
12366
12567
|
ACTIONS,
|
|
12568
|
+
ANON_TOKEN,
|
|
12367
12569
|
ActionSchema,
|
|
12368
12570
|
AuthConfigSchema,
|
|
12369
12571
|
AuthorizationError,
|
|
12572
|
+
BARE_INTERNAL_HOST,
|
|
12370
12573
|
CLIENTS,
|
|
12371
12574
|
CONFIDENCE,
|
|
12372
12575
|
CORRELATION_CONFIDENCE,
|
|
@@ -12530,11 +12733,13 @@ function checkClaudePrerequisites() {
|
|
|
12530
12733
|
hostname,
|
|
12531
12734
|
ingestEnvelope,
|
|
12532
12735
|
installedAppsScanner,
|
|
12736
|
+
isInCluster,
|
|
12533
12737
|
isLoopbackHost,
|
|
12534
12738
|
isPersonalHost,
|
|
12535
12739
|
isReadOnlyCommand,
|
|
12536
12740
|
isRemembered,
|
|
12537
12741
|
isSecureWebhookUrl,
|
|
12742
|
+
k8sRegistry,
|
|
12538
12743
|
k8sScanner,
|
|
12539
12744
|
keyMetaOf,
|
|
12540
12745
|
layoutClusters,
|
|
@@ -12571,6 +12776,7 @@ function checkClaudePrerequisites() {
|
|
|
12571
12776
|
parseNginxUpstreams,
|
|
12572
12777
|
parseNlQuery,
|
|
12573
12778
|
parseScanHint,
|
|
12779
|
+
parseTerraformState,
|
|
12574
12780
|
pixelToHex,
|
|
12575
12781
|
planInstall,
|
|
12576
12782
|
portsScanner,
|
|
@@ -12578,6 +12784,7 @@ function checkClaudePrerequisites() {
|
|
|
12578
12784
|
previewShare,
|
|
12579
12785
|
pseudonymize,
|
|
12580
12786
|
pseudonymizeFragment,
|
|
12787
|
+
pseudonymizeId,
|
|
12581
12788
|
pseudonymizeString,
|
|
12582
12789
|
pushDeltas,
|
|
12583
12790
|
readConfigFile,
|
|
@@ -12600,6 +12807,8 @@ function checkClaudePrerequisites() {
|
|
|
12600
12807
|
runHttp,
|
|
12601
12808
|
runLocalDiscovery,
|
|
12602
12809
|
runOnce,
|
|
12810
|
+
runOperator,
|
|
12811
|
+
runOperatorCycle,
|
|
12603
12812
|
runStdio,
|
|
12604
12813
|
runSyncClassify,
|
|
12605
12814
|
safeEnv,
|
|
@@ -12620,6 +12829,8 @@ function checkClaudePrerequisites() {
|
|
|
12620
12829
|
stableStringify,
|
|
12621
12830
|
startApi,
|
|
12622
12831
|
stripSensitive,
|
|
12832
|
+
terraformScanner,
|
|
12833
|
+
terraformTypeToNode,
|
|
12623
12834
|
timingSafeEqual,
|
|
12624
12835
|
toBackstageEntities,
|
|
12625
12836
|
validateScanner,
|