@zivis/cli 0.1.0-alpha.4 → 0.1.0-alpha.40

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.
Files changed (124) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +31 -0
  3. package/dist/commands/app/index.d.ts +0 -8
  4. package/dist/commands/app/index.js +4 -16
  5. package/dist/commands/auth/bind.d.ts +4 -0
  6. package/dist/commands/auth/bind.js +97 -0
  7. package/dist/commands/auth/index.js +20 -2
  8. package/dist/commands/auth/init.d.ts +1 -0
  9. package/dist/commands/auth/init.js +245 -73
  10. package/dist/commands/auth/login.d.ts +2 -0
  11. package/dist/commands/auth/login.js +136 -18
  12. package/dist/commands/auth/logout.js +19 -7
  13. package/dist/commands/auth/sessions.js +7 -4
  14. package/dist/commands/auth/status.js +33 -12
  15. package/dist/commands/auth/token.d.ts +4 -0
  16. package/dist/commands/auth/token.js +73 -0
  17. package/dist/commands/check/index.d.ts +2 -0
  18. package/dist/commands/check/index.js +463 -0
  19. package/dist/commands/credits/index.js +2 -3
  20. package/dist/commands/inspect/index.d.ts +2 -0
  21. package/dist/commands/inspect/index.js +136 -0
  22. package/dist/commands/mcp/index.js +81 -14
  23. package/dist/commands/open/index.d.ts +0 -8
  24. package/dist/commands/open/index.js +0 -11
  25. package/dist/commands/scan/index.js +3 -8
  26. package/dist/commands/target/index.js +0 -3
  27. package/dist/commands/tm/index.js +3 -10
  28. package/dist/index.js +13 -1
  29. package/dist/internal/binding-token-mint.d.ts +33 -0
  30. package/dist/internal/binding-token-mint.js +174 -0
  31. package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
  32. package/dist/internal/extractor/adapters/go/index.js +0 -6
  33. package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
  34. package/dist/internal/extractor/adapters/go/manifest.js +19 -25
  35. package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
  36. package/dist/internal/extractor/adapters/go/parser.js +0 -5
  37. package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
  38. package/dist/internal/extractor/adapters/go/scanner.js +0 -43
  39. package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
  40. package/dist/internal/extractor/adapters/python/index.js +0 -4
  41. package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
  42. package/dist/internal/extractor/adapters/python/manifest.js +21 -18
  43. package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
  44. package/dist/internal/extractor/adapters/python/parser.js +0 -4
  45. package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
  46. package/dist/internal/extractor/adapters/python/scanner.js +1 -54
  47. package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
  48. package/dist/internal/extractor/adapters/typescript/index.js +1 -21
  49. package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
  50. package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
  51. package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
  52. package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
  53. package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
  54. package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
  55. package/dist/internal/extractor/graph-builder.d.ts +10 -0
  56. package/dist/internal/extractor/graph-builder.js +245 -0
  57. package/dist/internal/extractor/graph-types.d.ts +89 -0
  58. package/dist/internal/extractor/graph-types.js +1 -0
  59. package/dist/internal/extractor/index.d.ts +0 -14
  60. package/dist/internal/extractor/index.js +0 -13
  61. package/dist/internal/extractor/types.d.ts +0 -45
  62. package/dist/internal/extractor/types.js +0 -27
  63. package/dist/internal/extractor/walker.d.ts +0 -18
  64. package/dist/internal/extractor/walker.js +0 -18
  65. package/dist/internal/github-linkage-prompt.d.ts +7 -0
  66. package/dist/internal/github-linkage-prompt.js +95 -0
  67. package/dist/internal/ide-setup.d.ts +16 -7
  68. package/dist/internal/ide-setup.js +320 -46
  69. package/dist/internal/org-picker.d.ts +11 -0
  70. package/dist/internal/org-picker.js +34 -0
  71. package/dist/internal/orgs.js +0 -1
  72. package/dist/internal/project-binding.d.ts +1 -0
  73. package/dist/internal/project-binding.js +14 -9
  74. package/dist/internal/target-auth/api-config.d.ts +0 -17
  75. package/dist/internal/target-auth/api-config.js +0 -18
  76. package/dist/internal/target-auth/config.d.ts +0 -12
  77. package/dist/internal/target-auth/config.js +0 -13
  78. package/dist/internal/target-auth/index.d.ts +0 -18
  79. package/dist/internal/target-auth/index.js +0 -19
  80. package/dist/internal/target-auth/keychain.d.ts +0 -18
  81. package/dist/internal/target-auth/keychain.js +3 -23
  82. package/dist/internal/target-auth/types.d.ts +0 -26
  83. package/dist/internal/target-auth/types.js +0 -10
  84. package/dist/internal/target-auth/workos.d.ts +0 -15
  85. package/dist/internal/target-auth/workos.js +9 -18
  86. package/package.json +19 -5
  87. package/dist/commands/app/index.js.map +0 -1
  88. package/dist/commands/auth/index.js.map +0 -1
  89. package/dist/commands/auth/init.js.map +0 -1
  90. package/dist/commands/auth/login.js.map +0 -1
  91. package/dist/commands/auth/logout.js.map +0 -1
  92. package/dist/commands/auth/sessions.js.map +0 -1
  93. package/dist/commands/auth/status.js.map +0 -1
  94. package/dist/commands/credits/index.js.map +0 -1
  95. package/dist/commands/mcp/index.js.map +0 -1
  96. package/dist/commands/open/index.js.map +0 -1
  97. package/dist/commands/scan/index.js.map +0 -1
  98. package/dist/commands/target/index.js.map +0 -1
  99. package/dist/commands/tm/index.js.map +0 -1
  100. package/dist/index.js.map +0 -1
  101. package/dist/internal/extractor/adapters/go/index.js.map +0 -1
  102. package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
  103. package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
  104. package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
  105. package/dist/internal/extractor/adapters/python/index.js.map +0 -1
  106. package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
  107. package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
  108. package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
  109. package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
  110. package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
  111. package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
  112. package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
  113. package/dist/internal/extractor/index.js.map +0 -1
  114. package/dist/internal/extractor/types.js.map +0 -1
  115. package/dist/internal/extractor/walker.js.map +0 -1
  116. package/dist/internal/ide-setup.js.map +0 -1
  117. package/dist/internal/orgs.js.map +0 -1
  118. package/dist/internal/project-binding.js.map +0 -1
  119. package/dist/internal/target-auth/api-config.js.map +0 -1
  120. package/dist/internal/target-auth/config.js.map +0 -1
  121. package/dist/internal/target-auth/index.js.map +0 -1
  122. package/dist/internal/target-auth/keychain.js.map +0 -1
  123. package/dist/internal/target-auth/types.js.map +0 -1
  124. package/dist/internal/target-auth/workos.js.map +0 -1
@@ -0,0 +1,136 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { exec } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import { detectProjectBinding } from "@zivis/mcp/project-binding";
8
+ import { runExtractor } from "../../internal/extractor/index.js";
9
+ import { buildRepoGraphArtifact } from "../../internal/extractor/graph-builder.js";
10
+ const execAsync = promisify(exec);
11
+ function collect(value, previous) {
12
+ return previous.concat([value]);
13
+ }
14
+ export function registerInspectCommands(program) {
15
+ program
16
+ .command("inspect")
17
+ .description("build a local repo graph artifact (services, endpoints, external deps) — local-only, no network")
18
+ .option("--root <dir>", "repo root to inspect (default: cwd)")
19
+ .option("--out <file>", "write artifact JSON to a file (default: <user-cache>/repos/<repo-id>/inspect/runs/<run-id>.json; '-' for stdout)")
20
+ .option("--privacy-audit", "print exactly what would be sent without writing to cache or sending — never makes network calls")
21
+ .option("--lang <list>", "comma-separated subset of adapters: typescript,go,python")
22
+ .option("--ignore <pattern>", "extra ignore pattern (repeatable)", collect, [])
23
+ .option("--application <id>", "set provenance.application_id (default: from .zivis/project.json if present)")
24
+ .option("--repo-id <id>", "override repo identifier (default: hash of git remote, fallback to hash of root path)")
25
+ .option("--commit <sha>", "override commit SHA (default: from `git rev-parse HEAD`)")
26
+ .option("--branch <name>", "override branch (default: from `git rev-parse --abbrev-ref HEAD`)")
27
+ .action(async (opts) => {
28
+ const rootDir = path.resolve(opts.root ?? process.cwd());
29
+ const cliPkg = await readCliPackage();
30
+ const langs = opts.lang
31
+ ? String(opts.lang).split(",").map((s) => s.trim()).filter(Boolean)
32
+ : undefined;
33
+ const result = await runExtractor({
34
+ rootDir,
35
+ languages: langs,
36
+ extraIgnores: opts.ignore,
37
+ extractorVersion: cliPkg.version,
38
+ });
39
+ const gitMeta = await readGitMetadata(rootDir);
40
+ const applicationId = opts.application ?? detectProjectBinding(rootDir)?.binding.applicationId;
41
+ const repoId = opts.repoId ?? deriveRepoId(gitMeta.remoteUrl, rootDir);
42
+ const commitSha = opts.commit ?? gitMeta.commitSha;
43
+ const branch = opts.branch ?? gitMeta.branch;
44
+ const artifact = buildRepoGraphArtifact(result, {
45
+ applicationId,
46
+ repoId,
47
+ commitSha,
48
+ branch,
49
+ analysisMode: "local",
50
+ });
51
+ if (opts.privacyAudit) {
52
+ process.stdout.write(JSON.stringify(artifact, null, 2) + "\n");
53
+ printSummary(artifact, "PRIVACY AUDIT — nothing was written or sent");
54
+ return;
55
+ }
56
+ if (opts.out === "-") {
57
+ process.stdout.write(JSON.stringify(artifact, null, 2) + "\n");
58
+ printSummary(artifact);
59
+ return;
60
+ }
61
+ const outPath = opts.out
62
+ ? path.isAbsolute(opts.out)
63
+ ? opts.out
64
+ : path.join(rootDir, opts.out)
65
+ : defaultCacheOutPath(repoId, commitSha, artifact.extracted_at);
66
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
67
+ await fs.writeFile(outPath, JSON.stringify(artifact, null, 2) + "\n");
68
+ printSummary(artifact, `wrote ${outPath}`);
69
+ });
70
+ }
71
+ async function readCliPackage() {
72
+ const here = path.dirname(new URL(import.meta.url).pathname);
73
+ const pkgPath = path.join(here, "../../../package.json");
74
+ const raw = await fs.readFile(pkgPath, "utf8");
75
+ return JSON.parse(raw);
76
+ }
77
+ async function readGitMetadata(rootDir) {
78
+ const out = {};
79
+ out.remoteUrl = await tryGit(rootDir, "remote get-url origin");
80
+ out.commitSha = await tryGit(rootDir, "rev-parse HEAD");
81
+ out.branch = await tryGit(rootDir, "rev-parse --abbrev-ref HEAD");
82
+ return out;
83
+ }
84
+ async function tryGit(cwd, args) {
85
+ try {
86
+ const { stdout } = await execAsync(`git ${args}`, { cwd });
87
+ const value = stdout.trim();
88
+ return value || undefined;
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ function deriveRepoId(remoteUrl, rootDir) {
95
+ if (remoteUrl) {
96
+ return "remote-" + sha256Short(remoteUrl);
97
+ }
98
+ return "path-" + sha256Short(rootDir);
99
+ }
100
+ function sha256Short(value) {
101
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
102
+ }
103
+ function userCacheRoot() {
104
+ const home = os.homedir();
105
+ if (process.platform === "darwin") {
106
+ return path.join(home, "Library", "Caches", "zivis");
107
+ }
108
+ if (process.platform === "win32") {
109
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
110
+ return path.join(localAppData, "zivis", "Cache");
111
+ }
112
+ const xdg = process.env.XDG_CACHE_HOME;
113
+ return path.join(xdg ?? path.join(home, ".cache"), "zivis");
114
+ }
115
+ function defaultCacheOutPath(repoId, commitSha, extractedAt) {
116
+ const runId = formatRunId(commitSha, extractedAt);
117
+ return path.join(userCacheRoot(), "repos", repoId, "inspect", "runs", `${runId}.json`);
118
+ }
119
+ function formatRunId(commitSha, extractedAt) {
120
+ const compactTs = extractedAt.replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
121
+ if (!commitSha)
122
+ return `nogit-${compactTs}`;
123
+ return `${commitSha.slice(0, 7)}-${compactTs}`;
124
+ }
125
+ function printSummary(artifact, suffix) {
126
+ const services = artifact.nodes.filter((n) => n.type === "service").length;
127
+ const endpoints = artifact.nodes.filter((n) => n.type === "endpoint").length;
128
+ const deps = artifact.nodes.filter((n) => n.type === "external_dependency").length;
129
+ const exposes = artifact.edges.filter((e) => e.type === "exposes").length;
130
+ const dependsOn = artifact.edges.filter((e) => e.type === "depends_on").length;
131
+ const langs = artifact.scope.languages?.join(",") ?? "(none)";
132
+ process.stderr.write(`\n[zivis inspect] ${artifact.scope.files_scanned} files scanned in ${artifact.scope.duration_ms}ms (langs: ${langs})\n` +
133
+ ` ${artifact.nodes.length} nodes (${services} services, ${endpoints} endpoints, ${deps} external deps)\n` +
134
+ ` ${artifact.edges.length} edges (${exposes} exposes, ${dependsOn} depends_on)\n` +
135
+ (suffix ? ` ${suffix}\n` : ""));
136
+ }
@@ -1,6 +1,8 @@
1
+ import * as path from "node:path";
2
+ import * as readline from "node:readline/promises";
1
3
  import { startServer } from "@zivis/mcp/server";
2
4
  import { DEFAULT_CONFIG } from "@zivis/mcp/types";
3
- import { setupCursor, setupVsCodeCopilot, setupVsCodeClaude, setupClaudeCodeCli, setupCline, setupWindsurf, printClaudeCodeManual, } from "../../internal/ide-setup.js";
5
+ import { setupCursorWorkspace, setupVsCodeCopilot, setupVsCodeClaude, setupClaudeCodeCli, setupCline, setupWindsurf, printClaudeCodeManual, writeGuidance, } from "../../internal/ide-setup.js";
4
6
  const KNOWN_CLIENTS = [
5
7
  "cursor",
6
8
  "claude-code",
@@ -10,50 +12,84 @@ const KNOWN_CLIENTS = [
10
12
  "vscode-copilot",
11
13
  "vscode-claude",
12
14
  ];
15
+ function toGuidanceClient(client) {
16
+ switch (client) {
17
+ case "cursor": return "cursor";
18
+ case "claude-code-cli": return "claude-code-cli";
19
+ case "vscode-copilot": return "vscode-copilot";
20
+ case "vscode-claude": return "vscode-claude";
21
+ case "cline": return "cline";
22
+ case "windsurf": return "windsurf";
23
+ default: return null;
24
+ }
25
+ }
26
+ async function promptYesNo(question) {
27
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
28
+ try {
29
+ const answer = (await rl.question(question)).trim().toLowerCase();
30
+ return answer !== "n" && answer !== "no";
31
+ }
32
+ finally {
33
+ rl.close();
34
+ }
35
+ }
13
36
  export function registerMcpCommands(program) {
14
37
  const mcp = program.command("mcp").description("MCP server for IDE integration");
15
38
  mcp
16
39
  .command("serve")
17
40
  .description("start the stdio MCP server (called by your IDE, not directly)")
18
- .action(async () => {
19
- await startServer(DEFAULT_CONFIG);
41
+ .option("-w, --workspace <path>", "workspace root for project binding detection (auto-set by IDE config, do not pass manually)")
42
+ .action(async (opts) => {
43
+ const config = { ...DEFAULT_CONFIG };
44
+ if (opts.workspace) {
45
+ config.workspacePath = path.resolve(opts.workspace);
46
+ }
47
+ await startServer(config);
20
48
  });
21
49
  mcp
22
50
  .command("install")
23
- .description("write MCP config for a supported IDE client")
51
+ .description("write MCP config for a supported IDE client (run from your project root)")
24
52
  .option("--client <name>", `target client (${KNOWN_CLIENTS.join(" | ")})`)
25
- .option("-s, --session <name>", "include ZIVIS_SESSION env var")
53
+ .option("--package <name>", "which ZIVIS MCP package to register: 'builder' (default, @zivis/mcp-builder, citizen-dev surface) or 'appsec' (@zivis/mcp, DevSecOps surface)", "builder")
54
+ .option("--with-guidance", "also write IDE guidance files so the AI knows when to suggest security checks (no prompt). Only applies to the appsec package; builder ships its own cadence block via zivis_setup.")
55
+ .option("--no-guidance", "skip writing IDE guidance files (no prompt)")
26
56
  .action(async (opts) => {
27
57
  const client = opts.client?.toLowerCase();
28
- const session = opts.session;
58
+ const pkgArg = opts.package?.toLowerCase() ?? "builder";
29
59
  if (!client) {
30
60
  console.error("Specify a client with --client <name>:");
31
61
  for (const c of KNOWN_CLIENTS)
32
62
  console.error(` zivis mcp install --client ${c}`);
33
63
  process.exit(1);
34
64
  }
65
+ if (pkgArg !== "appsec" && pkgArg !== "builder") {
66
+ console.error(`Unknown --package value: ${pkgArg}`);
67
+ console.error("Supported: appsec, builder");
68
+ process.exit(1);
69
+ }
70
+ const pkg = pkgArg;
35
71
  try {
36
72
  switch (client) {
37
73
  case "cursor":
38
- await setupCursor(session);
74
+ await setupCursorWorkspace(pkg);
39
75
  break;
40
76
  case "claude-code":
41
- printClaudeCodeManual(session);
77
+ printClaudeCodeManual(pkg);
42
78
  break;
43
79
  case "claude-code-cli":
44
- await setupClaudeCodeCli(session);
80
+ await setupClaudeCodeCli(pkg);
45
81
  break;
46
82
  case "cline":
47
- await setupCline(session);
83
+ await setupCline(pkg);
48
84
  break;
49
85
  case "windsurf":
50
- await setupWindsurf(session);
86
+ await setupWindsurf(pkg);
51
87
  break;
52
88
  case "vscode-copilot":
53
- await setupVsCodeCopilot(session);
89
+ await setupVsCodeCopilot(pkg);
54
90
  break;
55
91
  case "vscode-claude":
56
- await setupVsCodeClaude(session);
92
+ await setupVsCodeClaude(pkg);
57
93
  break;
58
94
  default:
59
95
  console.error(`Unknown client: ${client}`);
@@ -65,6 +101,37 @@ export function registerMcpCommands(program) {
65
101
  console.error("Install failed:", err instanceof Error ? err.message : err);
66
102
  process.exit(1);
67
103
  }
104
+ if (client === "claude-code")
105
+ return;
106
+ if (pkg === "builder")
107
+ return;
108
+ const guidanceClient = toGuidanceClient(client);
109
+ if (!guidanceClient)
110
+ return;
111
+ let shouldWrite;
112
+ if (opts.withGuidance) {
113
+ shouldWrite = true;
114
+ }
115
+ else if (opts.guidance === false) {
116
+ shouldWrite = false;
117
+ }
118
+ else {
119
+ console.log("");
120
+ shouldWrite = await promptYesNo("Write ZIVIS guidance files so your IDE knows when to suggest security checks?\n" +
121
+ "(Adds a rule/instruction file to the project. You can edit or remove it anytime.) [Y/n] ");
122
+ }
123
+ if (shouldWrite) {
124
+ try {
125
+ const files = writeGuidance({ cwd: process.cwd(), client: guidanceClient });
126
+ if (files.length > 0) {
127
+ console.log(`✓ Wrote guidance files:`);
128
+ for (const f of files)
129
+ console.log(` ${f}`);
130
+ }
131
+ }
132
+ catch (err) {
133
+ console.warn(` Warning: Could not write guidance files: ${err instanceof Error ? err.message : err}`);
134
+ }
135
+ }
68
136
  });
69
137
  }
70
- //# sourceMappingURL=index.js.map
@@ -1,10 +1,2 @@
1
- /**
2
- * `zivis open` — Build and (by default) launch a vscode:// or cursor:// deep link
3
- * that opens a ZIVIS artifact in the ZIVIS Code IDE extension. Use `--ide cursor`
4
- * for Cursor. With --print, emit the URI to stdout for piping/sharing.
5
- *
6
- * Mirrors the URI shape emitted by the `zivis_open_in_ide` MCP tool and the
7
- * web app's buildIdeLink() helper. See docs/platform/integrations/IDE-DEEP-LINKS.md.
8
- */
9
1
  import type { Command } from "commander";
10
2
  export declare function registerOpenCommands(program: Command): void;
@@ -1,11 +1,3 @@
1
- /**
2
- * `zivis open` — Build and (by default) launch a vscode:// or cursor:// deep link
3
- * that opens a ZIVIS artifact in the ZIVIS Code IDE extension. Use `--ide cursor`
4
- * for Cursor. With --print, emit the URI to stdout for piping/sharing.
5
- *
6
- * Mirrors the URI shape emitted by the `zivis_open_in_ide` MCP tool and the
7
- * web app's buildIdeLink() helper. See docs/platform/integrations/IDE-DEEP-LINKS.md.
8
- */
9
1
  const KINDS = ["diagram", "document", "threat-model"];
10
2
  export function registerOpenCommands(program) {
11
3
  program
@@ -47,11 +39,8 @@ export function registerOpenCommands(program) {
47
39
  console.log(uri);
48
40
  return;
49
41
  }
50
- // Lazy-import `open` so --print stays dependency-free in environments
51
- // (e.g. headless CI) where launching a URL handler would fail or hang.
52
42
  const { default: open } = await import("open");
53
43
  await open(uri);
54
44
  console.log(uri);
55
45
  });
56
46
  }
57
- //# sourceMappingURL=index.js.map
@@ -1,8 +1,8 @@
1
1
  import { ApiClient } from "@zivis/mcp/api-client";
2
- import { DEFAULT_CONFIG } from "@zivis/mcp/types";
2
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
3
3
  const VALID_TARGET_TYPES = ["ai_chat", "web_app", "api"];
4
4
  function buildClient(opts) {
5
- const config = opts.session ? { ...DEFAULT_CONFIG, session: opts.session } : DEFAULT_CONFIG;
5
+ const config = opts.session ? { ...resolveConfigFromBinding(process.cwd()), session: opts.session } : resolveConfigFromBinding(process.cwd());
6
6
  return new ApiClient(config);
7
7
  }
8
8
  export function registerScanCommands(program) {
@@ -118,11 +118,6 @@ export function registerScanCommands(program) {
118
118
  }
119
119
  });
120
120
  }
121
- /**
122
- * Map customer-facing ApplicationType to the RT scan's targetType enum.
123
- * The two enums differ historically: ApplicationType is product-shape,
124
- * targetType is what the RT runner needs to know about the wire format.
125
- */
126
121
  function appTypeToTargetType(appType) {
127
122
  switch (appType) {
128
123
  case "agent":
@@ -131,10 +126,10 @@ function appTypeToTargetType(appType) {
131
126
  return "ai_chat";
132
127
  case "rest_api":
133
128
  return "api";
129
+ case "application":
134
130
  case "ad_hoc_url":
135
131
  return "web_app";
136
132
  default:
137
133
  return undefined;
138
134
  }
139
135
  }
140
- //# sourceMappingURL=index.js.map
@@ -4,8 +4,6 @@ import { getStorageName } from "../../internal/target-auth/keychain.js";
4
4
  import { decodeJwtPayload } from "../../internal/target-auth/workos.js";
5
5
  const DEFAULT_PERSONA = "default";
6
6
  function warnDeprecated() {
7
- // stderr so it doesn't pollute stdout (CLI scripts pipe `zivis target token`
8
- // straight into curl). One line, no color codes — keeps grep-friendly.
9
7
  console.error("[zivis] WARNING: 'zivis target' is deprecated; use 'zivis app' instead.");
10
8
  }
11
9
  export function registerTargetCommands(program) {
@@ -143,4 +141,3 @@ export function registerTargetCommands(program) {
143
141
  console.log(`✓ Cleared tokens for target ${targetId} (persona: ${opts.persona})`);
144
142
  });
145
143
  }
146
- //# sourceMappingURL=index.js.map
@@ -1,20 +1,19 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { ApiClient } from "@zivis/mcp/api-client";
4
- import { DEFAULT_CONFIG } from "@zivis/mcp/types";
4
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
5
5
  import { getEphemeralCreds } from "../../internal/target-auth/index.js";
6
6
  import { runExtractor } from "../../internal/extractor/index.js";
7
7
  function buildClient(opts) {
8
8
  const config = opts.session
9
- ? { ...DEFAULT_CONFIG, session: opts.session }
10
- : DEFAULT_CONFIG;
9
+ ? { ...resolveConfigFromBinding(process.cwd()), session: opts.session }
10
+ : resolveConfigFromBinding(process.cwd());
11
11
  return new ApiClient(config);
12
12
  }
13
13
  function collect(value, previous) {
14
14
  return previous.concat([value]);
15
15
  }
16
16
  function defaultOutPath(extractedAt) {
17
- // .zivis/extracted/<iso-no-colons>.json — matches the doc's example.
18
17
  const ts = extractedAt.replace(/[:.]/g, "-");
19
18
  return `.zivis/extracted/${ts}.json`;
20
19
  }
@@ -80,9 +79,6 @@ export function registerTmCommands(program) {
80
79
  process.exit(1);
81
80
  });
82
81
  const parsed = JSON.parse(raw);
83
- // Only fields the PATCH endpoint accepts. tm pull dumps the full
84
- // record; tm push only sends the editable subset, so a round-trip
85
- // edit cycle Just Works.
86
82
  const payload = {};
87
83
  for (const k of ["name", "description", "status", "campaignId"]) {
88
84
  if (parsed[k] !== undefined)
@@ -123,8 +119,6 @@ export function registerTmCommands(program) {
123
119
  extractorVersion: cliPkg.version,
124
120
  });
125
121
  if (opts.privacyAudit) {
126
- // Prints the entire object that WOULD be sent to ZIVIS via --apply.
127
- // Never makes a network call; the extractor itself is local-only.
128
122
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
129
123
  process.stderr.write(`\n[zivis] Privacy audit: ${result.scope.filesScanned} files scanned, ` +
130
124
  `${result.components.length} component(s), ${result.externalServices.length} external service(s), ` +
@@ -263,4 +257,3 @@ export function registerTmCommands(program) {
263
257
  }
264
258
  });
265
259
  }
266
- //# sourceMappingURL=index.js.map
package/dist/index.js CHANGED
@@ -4,10 +4,13 @@ import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { registerAuthCommands } from "./commands/auth/index.js";
7
+ import { initCommand as authInitCommand } from "./commands/auth/init.js";
7
8
  import { registerAppCommands } from "./commands/app/index.js";
8
9
  import { registerTargetCommands } from "./commands/target/index.js";
9
10
  import { registerTmCommands } from "./commands/tm/index.js";
10
11
  import { registerScanCommands } from "./commands/scan/index.js";
12
+ import { registerCheckCommands } from "./commands/check/index.js";
13
+ import { registerInspectCommands } from "./commands/inspect/index.js";
11
14
  import { registerMcpCommands } from "./commands/mcp/index.js";
12
15
  import { registerCreditsCommands } from "./commands/credits/index.js";
13
16
  import { registerOpenCommands } from "./commands/open/index.js";
@@ -26,11 +29,21 @@ program
26
29
  .name("zivis")
27
30
  .description("ZIVIS CLI — threat modeling, scans, and MCP server")
28
31
  .version(getVersion(), "-v, --version", "show version");
32
+ program
33
+ .command("init")
34
+ .description("interactive setup: authenticate, pick org, and bind this repo (alias for `zivis auth init`)")
35
+ .option("-s, --session <name>", "named session for multi-tenant")
36
+ .option("--skip-mint-key", "skip MCP API key mint after successful OAuth")
37
+ .action(async (opts) => {
38
+ await authInitCommand(opts);
39
+ });
29
40
  registerAuthCommands(program);
41
+ registerCheckCommands(program);
30
42
  registerAppCommands(program);
31
43
  registerTargetCommands(program);
32
44
  registerTmCommands(program);
33
45
  registerScanCommands(program);
46
+ registerInspectCommands(program);
34
47
  registerMcpCommands(program);
35
48
  registerCreditsCommands(program);
36
49
  registerOpenCommands(program);
@@ -38,4 +51,3 @@ program.parseAsync(process.argv).catch((err) => {
38
51
  console.error(err instanceof Error ? err.message : err);
39
52
  process.exit(1);
40
53
  });
41
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,33 @@
1
+ import { type ZivisConfig } from "@zivis/mcp/types";
2
+ export type MintBindingTokenResult = {
3
+ ok: true;
4
+ keyName: string;
5
+ key: string;
6
+ orgName: string;
7
+ workosOrgId: string;
8
+ } | {
9
+ ok: false;
10
+ error: string;
11
+ };
12
+ export declare function mintLocalProjectBindingToken(params: {
13
+ config: ZivisConfig;
14
+ session: string;
15
+ accessToken: string;
16
+ cwd: string;
17
+ }): Promise<MintBindingTokenResult>;
18
+ export interface TryAutoMintOptions {
19
+ cwd: string;
20
+ sessionName: string;
21
+ accessToken: string;
22
+ skip?: boolean;
23
+ warnOnFailure: boolean;
24
+ }
25
+ export declare function tryAutoMintBindingTokenAfterLogin(opts: TryAutoMintOptions): Promise<{
26
+ ran: boolean;
27
+ ok: boolean;
28
+ message?: string;
29
+ }>;
30
+ export declare function discardOauthAfterLogin(params: {
31
+ session: string | undefined;
32
+ config: ZivisConfig;
33
+ }): Promise<void>;
@@ -0,0 +1,174 @@
1
+ import * as os from "node:os";
2
+ import * as crypto from "node:crypto";
3
+ import * as child_process from "node:child_process";
4
+ import { getSessionAccount, keychainClearSession, keychainRead, keychainStore, } from "@zivis/mcp/auth";
5
+ import { decodeJwtPayload } from "@zivis/mcp/auth/jwt";
6
+ import { DEFAULT_CONFIG, credentialStorageFromConfig } from "@zivis/mcp/types";
7
+ import { detectProjectBinding, resolveConfigFromBinding } from "@zivis/mcp/project-binding";
8
+ function getRepoFingerprint(cwd) {
9
+ try {
10
+ const remote = child_process
11
+ .execSync("git remote get-url origin 2>/dev/null", { cwd, encoding: "utf8" })
12
+ .trim();
13
+ if (remote) {
14
+ return crypto.createHash("sha256").update(remote).digest("hex").slice(0, 12);
15
+ }
16
+ }
17
+ catch {
18
+ }
19
+ return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
20
+ }
21
+ export async function mintLocalProjectBindingToken(params) {
22
+ const { config, session, accessToken, cwd } = params;
23
+ const credentialStorage = credentialStorageFromConfig(config);
24
+ const repoFingerprint = getRepoFingerprint(cwd);
25
+ const hostname = os.hostname();
26
+ let bindResult;
27
+ try {
28
+ const response = await fetch(`${config.apiBaseUrl}/api/auth/bind`, {
29
+ method: "POST",
30
+ headers: {
31
+ Authorization: `Bearer ${accessToken}`,
32
+ "Content-Type": "application/json",
33
+ "User-Agent": "@zivis/cli",
34
+ },
35
+ body: JSON.stringify({ repoFingerprint, hostname }),
36
+ });
37
+ if (!response.ok) {
38
+ const body = (await response.json().catch(() => ({})));
39
+ return {
40
+ ok: false,
41
+ error: body.message ?? body.error ?? `HTTP ${response.status}`,
42
+ };
43
+ }
44
+ bindResult = (await response.json());
45
+ }
46
+ catch (err) {
47
+ return {
48
+ ok: false,
49
+ error: err instanceof Error ? err.message : String(err),
50
+ };
51
+ }
52
+ const apiKeyAccount = `apikey.${session}`;
53
+ try {
54
+ await keychainStore(apiKeyAccount, bindResult.key, credentialStorage);
55
+ }
56
+ catch (err) {
57
+ return {
58
+ ok: false,
59
+ error: err instanceof Error ? err.message : "Failed to store key in OS keychain",
60
+ };
61
+ }
62
+ return {
63
+ ok: true,
64
+ keyName: bindResult.keyName,
65
+ key: bindResult.key,
66
+ orgName: bindResult.orgName,
67
+ workosOrgId: bindResult.workosOrgId,
68
+ };
69
+ }
70
+ export async function tryAutoMintBindingTokenAfterLogin(opts) {
71
+ if (opts.skip) {
72
+ return { ran: false, ok: true };
73
+ }
74
+ const fullConfig = { ...DEFAULT_CONFIG, ...resolveConfigFromBinding(opts.cwd) };
75
+ const claims = decodeJwtPayload(opts.accessToken);
76
+ const jwtOrgId = claims?.org_id;
77
+ if (!jwtOrgId) {
78
+ return {
79
+ ran: false,
80
+ ok: true,
81
+ message: "JWT has no org_id — skipped MCP binding-key mint.",
82
+ };
83
+ }
84
+ const detected = detectProjectBinding(opts.cwd);
85
+ const bindingOrg = detected?.binding.workosOrgId;
86
+ const bindingSession = detected?.binding.session;
87
+ if (!bindingOrg || !bindingSession) {
88
+ return {
89
+ ran: false,
90
+ ok: true,
91
+ message: "No project binding with workosOrgId — skipped MCP binding-key mint.",
92
+ };
93
+ }
94
+ if (bindingOrg !== jwtOrgId) {
95
+ const msg = `JWT org does not match project binding — skipped MCP binding-key mint.`;
96
+ if (opts.warnOnFailure) {
97
+ console.log("");
98
+ console.log(` WARNING: ${msg}`);
99
+ }
100
+ return { ran: false, ok: false, message: msg };
101
+ }
102
+ if (bindingSession !== opts.sessionName) {
103
+ const msg = `Session "${opts.sessionName}" does not match binding session "${bindingSession}" — skipped MCP binding-key mint.`;
104
+ if (opts.warnOnFailure) {
105
+ console.log("");
106
+ console.log(` WARNING: ${msg}`);
107
+ }
108
+ return { ran: false, ok: false, message: msg };
109
+ }
110
+ const st = credentialStorageFromConfig(fullConfig);
111
+ const existing = await keychainRead(`apikey.${bindingSession}`, st).catch(() => null);
112
+ if (existing) {
113
+ console.log("");
114
+ console.log(`✓ MCP API key already present for session "${bindingSession}" — skipped mint.`);
115
+ return { ran: false, ok: true };
116
+ }
117
+ console.log("");
118
+ console.log("Minting Local Project Binding Token for MCP (one-time per machine/session)…");
119
+ const result = await mintLocalProjectBindingToken({
120
+ config: fullConfig,
121
+ session: bindingSession,
122
+ accessToken: opts.accessToken,
123
+ cwd: opts.cwd,
124
+ });
125
+ if (!result.ok) {
126
+ const msg = result.error;
127
+ if (opts.warnOnFailure) {
128
+ printMintFailureBanner(msg);
129
+ }
130
+ return { ran: true, ok: false, message: msg };
131
+ }
132
+ console.log(`✓ MCP API key stored (${result.keyName}; org: ${result.orgName})`);
133
+ return { ran: true, ok: true };
134
+ }
135
+ export async function discardOauthAfterLogin(params) {
136
+ const storage = credentialStorageFromConfig(params.config);
137
+ try {
138
+ await keychainClearSession(params.session, storage);
139
+ }
140
+ catch {
141
+ }
142
+ const leftover = await keychainRead(getSessionAccount(params.session), storage).catch(() => null);
143
+ if (leftover) {
144
+ const account = getSessionAccount(params.session);
145
+ const service = `${storage.keychainPrefix}.${account}`;
146
+ console.error("");
147
+ console.error(` WARNING: OAuth tokens are still present in the keychain (${service}).`);
148
+ console.error(" Possible cause: keychain access was denied, or older builds left");
149
+ console.error(" duplicate entries that the OS doesn't surface in a single delete.");
150
+ console.error(" Force-clean with:");
151
+ console.error(` while security delete-generic-password -a zivis-mcp -s '${service}' 2>/dev/null; do :; done`);
152
+ console.error("");
153
+ }
154
+ }
155
+ function printMintFailureBanner(message) {
156
+ const bar = "═".repeat(70);
157
+ console.error("");
158
+ console.error(bar);
159
+ console.error(" ACTION REQUIRED — MCP API KEY WAS NOT MINTED");
160
+ console.error(bar);
161
+ console.error("");
162
+ console.error(` Reason: ${message}`);
163
+ console.error("");
164
+ console.error(" Why this matters:");
165
+ console.error(" The MCP server in your IDE authenticates with an org-scoped");
166
+ console.error(" API key. Without it, every zivis_* tool call will fail with");
167
+ console.error(" \"Not authenticated\" until you mint one.");
168
+ console.error("");
169
+ console.error(" To fix, run from this repo:");
170
+ console.error(" zivis auth bind");
171
+ console.error("");
172
+ console.error(bar);
173
+ console.error("");
174
+ }
@@ -1,7 +1,3 @@
1
- /**
2
- * Go language adapter. Same shape as the TypeScript adapter — manifest
3
- * pass + AST pass — different parser + framework table.
4
- */
5
1
  import type { LanguageAdapter } from "../../types.js";
6
2
  export declare const goAdapter: LanguageAdapter;
7
3
  export declare const _stdlibFrameworkSet: Set<string>;