@datasynx/agentic-ai-cartography 2.4.0 → 2.6.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/cli.js CHANGED
@@ -11,21 +11,24 @@ import {
11
11
  runDrift,
12
12
  runLocalDiscovery,
13
13
  startMcp
14
- } from "./chunk-B4QWX7CP.js";
14
+ } from "./chunk-X3UWUX3G.js";
15
15
  import {
16
- startApi
17
- } from "./chunk-L4OSL7I6.js";
16
+ entitiesToYaml,
17
+ startApi,
18
+ toBackstageEntities
19
+ } from "./chunk-PQ7Q6MI5.js";
18
20
  import {
19
21
  CartographyDB,
20
22
  buildCartographyToolHandlers,
21
23
  createCartographyTools,
22
24
  deriveSessionName,
23
25
  diffTopology,
26
+ hashToken,
24
27
  normalizeTenant,
25
28
  redactValue,
26
29
  stableStringify,
27
30
  stripSensitive
28
- } from "./chunk-X5JA2UDT.js";
31
+ } from "./chunk-GA4427LB.js";
29
32
  import {
30
33
  ConfigFileSchema,
31
34
  CostEntrySchema,
@@ -52,6 +55,7 @@ import {
52
55
  } from "./chunk-2SZ5QHGH.js";
53
56
 
54
57
  // src/cli.ts
58
+ import { randomBytes } from "crypto";
55
59
  import { Command } from "commander";
56
60
 
57
61
  // src/preflight.ts
@@ -114,6 +118,30 @@ function checkClaudePrerequisites() {
114
118
  }
115
119
  }
116
120
 
121
+ // src/auth/types.ts
122
+ import { z } from "zod";
123
+ var ROLES = ["viewer", "operator", "admin"];
124
+ var RoleSchema = z.enum(ROLES);
125
+ var ACTIONS = ["read", "discovery", "admin"];
126
+ var ActionSchema = z.enum(ACTIONS);
127
+ var PrincipalSchema = z.object({
128
+ subject: z.string().min(1),
129
+ tenant: z.string().min(1),
130
+ role: RoleSchema
131
+ });
132
+ var CredentialConfigSchema = z.object({
133
+ token: z.string().min(1),
134
+ subject: z.string().min(1),
135
+ tenant: z.string().optional(),
136
+ role: RoleSchema.default("viewer")
137
+ });
138
+ var AuthConfigSchema = z.object({
139
+ /** Seed credentials (merged into the SQLite store on startup). */
140
+ credentials: z.array(CredentialConfigSchema).optional(),
141
+ /** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */
142
+ required: z.boolean().optional()
143
+ });
144
+
117
145
  // src/providers/types.ts
118
146
  var ProviderRegistry = class {
119
147
  factories = /* @__PURE__ */ new Map();
@@ -274,13 +302,13 @@ function createClaudeProvider() {
274
302
  }
275
303
 
276
304
  // src/providers/shell.ts
277
- import { z } from "zod";
305
+ import { z as z2 } from "zod";
278
306
  function createBashTool() {
279
307
  const shell = IS_WIN ? "powershell" : "posix";
280
308
  return {
281
309
  name: "Bash",
282
310
  description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
283
- inputShape: { command: z.string().describe("The read-only shell command to run") },
311
+ inputShape: { command: z2.string().describe("The read-only shell command to run") },
284
312
  annotations: { readOnlyHint: true, openWorldHint: true },
285
313
  handler: async (args) => {
286
314
  const command = String(args["command"] ?? "").trim();
@@ -1351,30 +1379,7 @@ function generateDiffMermaid(diff) {
1351
1379
  return lines.join("\n");
1352
1380
  }
1353
1381
  function exportBackstageYAML(nodes, edges, org) {
1354
- const owner = org ?? "unknown";
1355
- const docs = [];
1356
- for (const node of nodes) {
1357
- const isComponent = ["web_service", "container", "pod"].includes(node.type);
1358
- const isAPI = node.type === "api_endpoint";
1359
- const kind = isComponent ? "Component" : isAPI ? "API" : "Resource";
1360
- const deps = edges.filter((e) => e.sourceId === node.id).map((e) => ` - resource:default/${sanitize(e.targetId)}`);
1361
- const doc = [
1362
- `apiVersion: backstage.io/v1alpha1`,
1363
- `kind: ${kind}`,
1364
- `metadata:`,
1365
- ` name: ${sanitize(node.id)}`,
1366
- ` annotations:`,
1367
- ` cartography/discovered-at: "${node.discoveredAt}"`,
1368
- ` cartography/confidence: "${node.confidence}"`,
1369
- `spec:`,
1370
- ` type: ${node.type}`,
1371
- ` lifecycle: production`,
1372
- ` owner: ${node.owner ?? owner}`,
1373
- ...deps.length > 0 ? [" dependsOn:", ...deps] : []
1374
- ].join("\n");
1375
- docs.push(doc);
1376
- }
1377
- return docs.join("\n---\n");
1382
+ return entitiesToYaml(toBackstageEntities(nodes, edges, org !== void 0 ? { org } : {}));
1378
1383
  }
1379
1384
  function exportJSON(db, sessionId) {
1380
1385
  const nodes = db.getNodes(sessionId);
@@ -4961,7 +4966,7 @@ ${infraSummary.substring(0, 12e3)}`;
4961
4966
  db.close();
4962
4967
  }
4963
4968
  });
4964
- program.command("mcp").description("Run the Model Context Protocol server (stdio by default) \u2014 the primary interface for AI agents").option("--http", "Use Streamable HTTP transport instead of stdio", false).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--allowed-hosts <list>", "Comma-separated Host allowlist (required for non-loopback --host)").option("--token <secret>", "Bearer token required on HTTP requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--tenant <id>", "Tenant/organization whose topology to serve (alias: --org; default: local)").option("--org <id>", "Alias for --tenant").option("--no-semantic", "Disable semantic (vector) search").option("--plugins <list>", "Comma-separated scanner plugin package names to load (opt-in; or CARTOGRAPHY_PLUGINS)").option("--server-mode", "Run as a central collector: enable the authenticated POST /ingest write route + org-wide summary (implies --http; opt-in)", false).option("--anon-mode <mode>", "On ingest, reject|strip un-anonymized identifying fragments (server-mode)", "reject").action(async (opts) => {
4969
+ program.command("mcp").description("Run the Model Context Protocol server (stdio by default) \u2014 the primary interface for AI agents").option("--http", "Use Streamable HTTP transport instead of stdio", false).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--allowed-hosts <list>", "Comma-separated Host allowlist (required for non-loopback --host)").option("--token <secret>", "Bearer token required on HTTP requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--tenant <id>", "Tenant/organization whose topology to serve (alias: --org; default: local)").option("--org <id>", "Alias for --tenant").option("--no-semantic", "Disable semantic (vector) search").option("--plugins <list>", "Comma-separated scanner plugin package names to load (opt-in; or CARTOGRAPHY_PLUGINS)").option("--server-mode", "Run as a central collector: enable the authenticated POST /ingest write route + org-wide summary (implies --http; opt-in)", false).option("--anon-mode <mode>", "On ingest, reject|strip un-anonymized identifying fragments (server-mode)", "reject").option("--auth-required", "Reject unauthenticated requests even on loopback (RBAC required mode)", false).action(async (opts) => {
4965
4970
  try {
4966
4971
  const anonMode = opts.anonMode;
4967
4972
  if (anonMode !== "reject" && anonMode !== "strip") {
@@ -4983,7 +4988,8 @@ error: --anon-mode must be 'reject' or 'strip' (got '${anonMode}')
4983
4988
  semantic: opts.semantic,
4984
4989
  plugins: opts.plugins ? String(opts.plugins).split(",").map((p) => p.trim()).filter(Boolean) : void 0,
4985
4990
  serverMode: opts.serverMode === true,
4986
- anonMode
4991
+ anonMode,
4992
+ authRequired: opts.authRequired === true
4987
4993
  });
4988
4994
  } catch (err) {
4989
4995
  process.stderr.write(`
@@ -4992,9 +4998,10 @@ error: ${err instanceof Error ? err.message : String(err)}
4992
4998
  process.exitCode = 1;
4993
4999
  }
4994
5000
  });
4995
- program.command("api").description("Run the read-only REST/GraphQL API server over the topology store (4.2)").option("--http", "Use HTTP transport (default; kept for symmetry with mcp)", true).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--allowed-hosts <list>", "Comma-separated Host allowlist (required for non-loopback --host)").option("--allowed-origins <list>", "Comma-separated CORS Origin allowlist (default: same-origin only)").option("--token <secret>", "Bearer token required on requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--tenant <id>", "Default tenant whose topology to serve (alias: --org; default: local)").option("--org <id>", "Alias for --tenant").option("--no-graphql", "Disable the /graphql endpoint (REST only)").action(async (opts) => {
5001
+ program.command("api").description("Run the read-only REST/GraphQL API server over the topology store (4.2)").option("--http", "Use HTTP transport (default; kept for symmetry with mcp)", true).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--allowed-hosts <list>", "Comma-separated Host allowlist (required for non-loopback --host)").option("--allowed-origins <list>", "Comma-separated CORS Origin allowlist (default: same-origin only)").option("--token <secret>", "Bearer token required on requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--tenant <id>", "Default tenant whose topology to serve (alias: --org; default: local)").option("--org <id>", "Alias for --tenant").option("--no-graphql", "Disable the /graphql endpoint (REST only)").option("--auth-required", "Reject unauthenticated requests even on loopback (RBAC required mode)", false).action(async (opts) => {
4996
5002
  try {
4997
5003
  await startApi({
5004
+ authRequired: opts.authRequired === true,
4998
5005
  port: parseInt(opts.port, 10),
4999
5006
  host: opts.host,
5000
5007
  allowedHosts: opts.allowedHosts ? String(opts.allowedHosts).split(",").map((h) => h.trim()).filter(Boolean) : void 0,
@@ -5012,6 +5019,57 @@ error: ${err instanceof Error ? err.message : String(err)}
5012
5019
  process.exitCode = 1;
5013
5020
  }
5014
5021
  });
5022
+ const authCmd = program.command("auth").description("Manage RBAC credentials for the HTTP surfaces (MCP transport + API server, 4.5)");
5023
+ authCmd.command("add <subject>").description("Create a credential and print its bearer token ONCE (only the hash is stored)").option("--role <role>", "viewer | operator | admin", "viewer").option("--tenant <id>", "Tenant the credential is scoped to (default: local)").option("--token <secret>", "Use this token instead of generating one").option("--db <path>", "DB path").action((subject, opts) => {
5024
+ const role = RoleSchema.safeParse(opts.role);
5025
+ if (!role.success) {
5026
+ process.stderr.write(`
5027
+ error: --role must be one of viewer|operator|admin (got '${opts.role}')
5028
+ `);
5029
+ process.exitCode = 1;
5030
+ return;
5031
+ }
5032
+ const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);
5033
+ try {
5034
+ const token = opts.token ?? randomBytes(24).toString("base64url");
5035
+ const tenant = normalizeTenant(opts.tenant);
5036
+ db.addCredential({ tokenHash: hashToken(token), subject, tenant, role: role.data });
5037
+ process.stderr.write(`
5038
+ \u2713 credential added: subject=${subject} role=${role.data} tenant=${tenant}
5039
+ `);
5040
+ process.stderr.write(` Bearer token (shown once \u2014 store it now):
5041
+
5042
+ `);
5043
+ process.stdout.write(token + "\n");
5044
+ } finally {
5045
+ db.close();
5046
+ }
5047
+ });
5048
+ authCmd.command("list").description("List credentials (subjects/roles/tenants \u2014 token hashes only, never the raw token)").option("--db <path>", "DB path").action((opts) => {
5049
+ const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);
5050
+ try {
5051
+ const creds = db.listCredentials();
5052
+ if (creds.length === 0) {
5053
+ process.stderr.write("No credentials configured (open/shared-token mode).\n");
5054
+ return;
5055
+ }
5056
+ for (const c of creds) process.stdout.write(`${c.subject} ${c.role} ${c.tenant} ${c.createdAt}
5057
+ `);
5058
+ } finally {
5059
+ db.close();
5060
+ }
5061
+ });
5062
+ authCmd.command("revoke <subject>").description("Revoke all credentials for a subject").option("--db <path>", "DB path").action((subject, opts) => {
5063
+ const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);
5064
+ try {
5065
+ const n = db.revokeCredentialsBySubject(subject);
5066
+ process.stderr.write(n > 0 ? `\u2713 revoked ${n} credential(s) for ${subject}
5067
+ ` : `no credentials found for ${subject}
5068
+ `);
5069
+ } finally {
5070
+ db.close();
5071
+ }
5072
+ });
5015
5073
  const o = (s) => process.stderr.write(s);
5016
5074
  o("\n");
5017
5075
  o(cyan(" ____ _ ____ ") + "\n");