@zivis/mcp 0.1.0-alpha.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.
Files changed (253) hide show
  1. package/dist/api-client.d.ts +42 -0
  2. package/dist/api-client.js +145 -0
  3. package/dist/api-client.js.map +1 -0
  4. package/dist/auth/index.d.ts +29 -0
  5. package/dist/auth/index.js +73 -0
  6. package/dist/auth/index.js.map +1 -0
  7. package/dist/auth/jwt.d.ts +24 -0
  8. package/dist/auth/jwt.js +25 -0
  9. package/dist/auth/jwt.js.map +1 -0
  10. package/dist/auth/keychain.d.ts +34 -0
  11. package/dist/auth/keychain.js +272 -0
  12. package/dist/auth/keychain.js.map +1 -0
  13. package/dist/auth/oauth.d.ts +38 -0
  14. package/dist/auth/oauth.js +241 -0
  15. package/dist/auth/oauth.js.map +1 -0
  16. package/dist/auth/sessions.d.ts +28 -0
  17. package/dist/auth/sessions.js +69 -0
  18. package/dist/auth/sessions.js.map +1 -0
  19. package/dist/auth/token-refresh.d.ts +24 -0
  20. package/dist/auth/token-refresh.js +91 -0
  21. package/dist/auth/token-refresh.js.map +1 -0
  22. package/dist/cli.d.ts +23 -0
  23. package/dist/cli.js +962 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/index.d.ts +8 -0
  26. package/dist/index.js +15 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/project-binding.d.ts +25 -0
  29. package/dist/project-binding.js +67 -0
  30. package/dist/project-binding.js.map +1 -0
  31. package/dist/resolve-application-id.d.ts +12 -0
  32. package/dist/resolve-application-id.js +21 -0
  33. package/dist/resolve-application-id.js.map +1 -0
  34. package/dist/sanitize.d.ts +23 -0
  35. package/dist/sanitize.js +125 -0
  36. package/dist/sanitize.js.map +1 -0
  37. package/dist/server.d.ts +14 -0
  38. package/dist/server.js +447 -0
  39. package/dist/server.js.map +1 -0
  40. package/dist/tools/check-project.d.ts +32 -0
  41. package/dist/tools/check-project.js +180 -0
  42. package/dist/tools/check-project.js.map +1 -0
  43. package/dist/tools/check-repo-trust.d.ts +33 -0
  44. package/dist/tools/check-repo-trust.js +212 -0
  45. package/dist/tools/check-repo-trust.js.map +1 -0
  46. package/dist/tools/create-diagram.d.ts +114 -0
  47. package/dist/tools/create-diagram.js +176 -0
  48. package/dist/tools/create-diagram.js.map +1 -0
  49. package/dist/tools/create-document.d.ts +28 -0
  50. package/dist/tools/create-document.js +53 -0
  51. package/dist/tools/create-document.js.map +1 -0
  52. package/dist/tools/discover-local-infra.d.ts +24 -0
  53. package/dist/tools/discover-local-infra.js +763 -0
  54. package/dist/tools/discover-local-infra.js.map +1 -0
  55. package/dist/tools/enterprise/capture-evidence.d.ts +54 -0
  56. package/dist/tools/enterprise/capture-evidence.js +72 -0
  57. package/dist/tools/enterprise/capture-evidence.js.map +1 -0
  58. package/dist/tools/enterprise/create-report-ticket.d.ts +34 -0
  59. package/dist/tools/enterprise/create-report-ticket.js +44 -0
  60. package/dist/tools/enterprise/create-report-ticket.js.map +1 -0
  61. package/dist/tools/enterprise/end-pentest-session.d.ts +33 -0
  62. package/dist/tools/enterprise/end-pentest-session.js +42 -0
  63. package/dist/tools/enterprise/end-pentest-session.js.map +1 -0
  64. package/dist/tools/enterprise/generate-report.d.ts +55 -0
  65. package/dist/tools/enterprise/generate-report.js +111 -0
  66. package/dist/tools/enterprise/generate-report.js.map +1 -0
  67. package/dist/tools/enterprise/get-org-zat.d.ts +27 -0
  68. package/dist/tools/enterprise/get-org-zat.js +62 -0
  69. package/dist/tools/enterprise/get-org-zat.js.map +1 -0
  70. package/dist/tools/enterprise/get-report-status.d.ts +27 -0
  71. package/dist/tools/enterprise/get-report-status.js +38 -0
  72. package/dist/tools/enterprise/get-report-status.js.map +1 -0
  73. package/dist/tools/enterprise/get-threat-model.d.ts +29 -0
  74. package/dist/tools/enterprise/get-threat-model.js +83 -0
  75. package/dist/tools/enterprise/get-threat-model.js.map +1 -0
  76. package/dist/tools/enterprise/list-agents.d.ts +42 -0
  77. package/dist/tools/enterprise/list-agents.js +95 -0
  78. package/dist/tools/enterprise/list-agents.js.map +1 -0
  79. package/dist/tools/enterprise/list-report-issues.d.ts +36 -0
  80. package/dist/tools/enterprise/list-report-issues.js +50 -0
  81. package/dist/tools/enterprise/list-report-issues.js.map +1 -0
  82. package/dist/tools/enterprise/list-threat-models.d.ts +36 -0
  83. package/dist/tools/enterprise/list-threat-models.js +82 -0
  84. package/dist/tools/enterprise/list-threat-models.js.map +1 -0
  85. package/dist/tools/enterprise/manage-actor.d.ts +58 -0
  86. package/dist/tools/enterprise/manage-actor.js +99 -0
  87. package/dist/tools/enterprise/manage-actor.js.map +1 -0
  88. package/dist/tools/enterprise/manage-attack-scenario.d.ts +70 -0
  89. package/dist/tools/enterprise/manage-attack-scenario.js +89 -0
  90. package/dist/tools/enterprise/manage-attack-scenario.js.map +1 -0
  91. package/dist/tools/enterprise/manage-component.d.ts +58 -0
  92. package/dist/tools/enterprise/manage-component.js +90 -0
  93. package/dist/tools/enterprise/manage-component.js.map +1 -0
  94. package/dist/tools/enterprise/manage-data-asset.d.ts +53 -0
  95. package/dist/tools/enterprise/manage-data-asset.js +90 -0
  96. package/dist/tools/enterprise/manage-data-asset.js.map +1 -0
  97. package/dist/tools/enterprise/manage-data-flow.d.ts +57 -0
  98. package/dist/tools/enterprise/manage-data-flow.js +98 -0
  99. package/dist/tools/enterprise/manage-data-flow.js.map +1 -0
  100. package/dist/tools/enterprise/manage-finding.d.ts +69 -0
  101. package/dist/tools/enterprise/manage-finding.js +106 -0
  102. package/dist/tools/enterprise/manage-finding.js.map +1 -0
  103. package/dist/tools/enterprise/manage-security-control.d.ts +59 -0
  104. package/dist/tools/enterprise/manage-security-control.js +96 -0
  105. package/dist/tools/enterprise/manage-security-control.js.map +1 -0
  106. package/dist/tools/enterprise/manage-test-case.d.ts +88 -0
  107. package/dist/tools/enterprise/manage-test-case.js +119 -0
  108. package/dist/tools/enterprise/manage-test-case.js.map +1 -0
  109. package/dist/tools/enterprise/promote-finding-to-issue.d.ts +36 -0
  110. package/dist/tools/enterprise/promote-finding-to-issue.js +65 -0
  111. package/dist/tools/enterprise/promote-finding-to-issue.js.map +1 -0
  112. package/dist/tools/enterprise/publish-report-version.d.ts +34 -0
  113. package/dist/tools/enterprise/publish-report-version.js +75 -0
  114. package/dist/tools/enterprise/publish-report-version.js.map +1 -0
  115. package/dist/tools/enterprise/publish-test-to-library.d.ts +31 -0
  116. package/dist/tools/enterprise/publish-test-to-library.js +40 -0
  117. package/dist/tools/enterprise/publish-test-to-library.js.map +1 -0
  118. package/dist/tools/enterprise/record-test-result.d.ts +67 -0
  119. package/dist/tools/enterprise/record-test-result.js +75 -0
  120. package/dist/tools/enterprise/record-test-result.js.map +1 -0
  121. package/dist/tools/enterprise/start-pentest-session.d.ts +35 -0
  122. package/dist/tools/enterprise/start-pentest-session.js +50 -0
  123. package/dist/tools/enterprise/start-pentest-session.js.map +1 -0
  124. package/dist/tools/enterprise/sync-report-section.d.ts +41 -0
  125. package/dist/tools/enterprise/sync-report-section.js +79 -0
  126. package/dist/tools/enterprise/sync-report-section.js.map +1 -0
  127. package/dist/tools/enterprise/update-report-issue.d.ts +55 -0
  128. package/dist/tools/enterprise/update-report-issue.js +69 -0
  129. package/dist/tools/enterprise/update-report-issue.js.map +1 -0
  130. package/dist/tools/generate-diagram.d.ts +36 -0
  131. package/dist/tools/generate-diagram.js +153 -0
  132. package/dist/tools/generate-diagram.js.map +1 -0
  133. package/dist/tools/get-agent-test-results.d.ts +41 -0
  134. package/dist/tools/get-agent-test-results.js +86 -0
  135. package/dist/tools/get-agent-test-results.js.map +1 -0
  136. package/dist/tools/get-application-overview.d.ts +27 -0
  137. package/dist/tools/get-application-overview.js +143 -0
  138. package/dist/tools/get-application-overview.js.map +1 -0
  139. package/dist/tools/get-application.d.ts +27 -0
  140. package/dist/tools/get-application.js +83 -0
  141. package/dist/tools/get-application.js.map +1 -0
  142. package/dist/tools/get-artifact-content.d.ts +29 -0
  143. package/dist/tools/get-artifact-content.js +86 -0
  144. package/dist/tools/get-artifact-content.js.map +1 -0
  145. package/dist/tools/get-diagram.d.ts +27 -0
  146. package/dist/tools/get-diagram.js +109 -0
  147. package/dist/tools/get-diagram.js.map +1 -0
  148. package/dist/tools/get-document.d.ts +22 -0
  149. package/dist/tools/get-document.js +43 -0
  150. package/dist/tools/get-document.js.map +1 -0
  151. package/dist/tools/get-finding-details.d.ts +28 -0
  152. package/dist/tools/get-finding-details.js +43 -0
  153. package/dist/tools/get-finding-details.js.map +1 -0
  154. package/dist/tools/get-findings.d.ts +57 -0
  155. package/dist/tools/get-findings.js +103 -0
  156. package/dist/tools/get-findings.js.map +1 -0
  157. package/dist/tools/get-oss-zat.d.ts +27 -0
  158. package/dist/tools/get-oss-zat.js +105 -0
  159. package/dist/tools/get-oss-zat.js.map +1 -0
  160. package/dist/tools/get-recon-scan.d.ts +27 -0
  161. package/dist/tools/get-recon-scan.js +46 -0
  162. package/dist/tools/get-recon-scan.js.map +1 -0
  163. package/dist/tools/get-scan-output.d.ts +27 -0
  164. package/dist/tools/get-scan-output.js +64 -0
  165. package/dist/tools/get-scan-output.js.map +1 -0
  166. package/dist/tools/get-scenario-details.d.ts +27 -0
  167. package/dist/tools/get-scenario-details.js +46 -0
  168. package/dist/tools/get-scenario-details.js.map +1 -0
  169. package/dist/tools/get-test-case-results.d.ts +37 -0
  170. package/dist/tools/get-test-case-results.js +66 -0
  171. package/dist/tools/get-test-case-results.js.map +1 -0
  172. package/dist/tools/get-test-details.d.ts +29 -0
  173. package/dist/tools/get-test-details.js +49 -0
  174. package/dist/tools/get-test-details.js.map +1 -0
  175. package/dist/tools/get-trust-keys.d.ts +22 -0
  176. package/dist/tools/get-trust-keys.js +67 -0
  177. package/dist/tools/get-trust-keys.js.map +1 -0
  178. package/dist/tools/import-openapi-endpoints.d.ts +33 -0
  179. package/dist/tools/import-openapi-endpoints.js +93 -0
  180. package/dist/tools/import-openapi-endpoints.js.map +1 -0
  181. package/dist/tools/inspect-zat.d.ts +26 -0
  182. package/dist/tools/inspect-zat.js +247 -0
  183. package/dist/tools/inspect-zat.js.map +1 -0
  184. package/dist/tools/list-agent-test-cases.d.ts +41 -0
  185. package/dist/tools/list-agent-test-cases.js +73 -0
  186. package/dist/tools/list-agent-test-cases.js.map +1 -0
  187. package/dist/tools/list-applications.d.ts +34 -0
  188. package/dist/tools/list-applications.js +74 -0
  189. package/dist/tools/list-applications.js.map +1 -0
  190. package/dist/tools/list-diagrams.d.ts +42 -0
  191. package/dist/tools/list-diagrams.js +81 -0
  192. package/dist/tools/list-diagrams.js.map +1 -0
  193. package/dist/tools/list-documents.d.ts +24 -0
  194. package/dist/tools/list-documents.js +50 -0
  195. package/dist/tools/list-documents.js.map +1 -0
  196. package/dist/tools/list-endpoints.d.ts +47 -0
  197. package/dist/tools/list-endpoints.js +98 -0
  198. package/dist/tools/list-endpoints.js.map +1 -0
  199. package/dist/tools/list-recon-scans.d.ts +36 -0
  200. package/dist/tools/list-recon-scans.js +74 -0
  201. package/dist/tools/list-recon-scans.js.map +1 -0
  202. package/dist/tools/list-scan-artifacts.d.ts +41 -0
  203. package/dist/tools/list-scan-artifacts.js +63 -0
  204. package/dist/tools/list-scan-artifacts.js.map +1 -0
  205. package/dist/tools/list-scans.d.ts +44 -0
  206. package/dist/tools/list-scans.js +91 -0
  207. package/dist/tools/list-scans.js.map +1 -0
  208. package/dist/tools/list-test-library.d.ts +44 -0
  209. package/dist/tools/list-test-library.js +83 -0
  210. package/dist/tools/list-test-library.js.map +1 -0
  211. package/dist/tools/list-test-scenarios.d.ts +31 -0
  212. package/dist/tools/list-test-scenarios.js +66 -0
  213. package/dist/tools/list-test-scenarios.js.map +1 -0
  214. package/dist/tools/local-scan.d.ts +40 -0
  215. package/dist/tools/local-scan.js +261 -0
  216. package/dist/tools/local-scan.js.map +1 -0
  217. package/dist/tools/manage-diagram.d.ts +109 -0
  218. package/dist/tools/manage-diagram.js +316 -0
  219. package/dist/tools/manage-diagram.js.map +1 -0
  220. package/dist/tools/open-in-ide.d.ts +39 -0
  221. package/dist/tools/open-in-ide.js +61 -0
  222. package/dist/tools/open-in-ide.js.map +1 -0
  223. package/dist/tools/run-test-scenario.d.ts +37 -0
  224. package/dist/tools/run-test-scenario.js +70 -0
  225. package/dist/tools/run-test-scenario.js.map +1 -0
  226. package/dist/tools/scan-pr.d.ts +27 -0
  227. package/dist/tools/scan-pr.js +51 -0
  228. package/dist/tools/scan-pr.js.map +1 -0
  229. package/dist/tools/security-review.d.ts +65 -0
  230. package/dist/tools/security-review.js +378 -0
  231. package/dist/tools/security-review.js.map +1 -0
  232. package/dist/tools/setup-red-team-target.d.ts +79 -0
  233. package/dist/tools/setup-red-team-target.js +430 -0
  234. package/dist/tools/setup-red-team-target.js.map +1 -0
  235. package/dist/tools/triage-finding.d.ts +47 -0
  236. package/dist/tools/triage-finding.js +116 -0
  237. package/dist/tools/triage-finding.js.map +1 -0
  238. package/dist/tools/update-document.d.ts +28 -0
  239. package/dist/tools/update-document.js +63 -0
  240. package/dist/tools/update-document.js.map +1 -0
  241. package/dist/tools/update-endpoint.d.ts +65 -0
  242. package/dist/tools/update-endpoint.js +145 -0
  243. package/dist/tools/update-endpoint.js.map +1 -0
  244. package/dist/tools/update-mermaid-source.d.ts +34 -0
  245. package/dist/tools/update-mermaid-source.js +76 -0
  246. package/dist/tools/update-mermaid-source.js.map +1 -0
  247. package/dist/tools/verify-trust-mark.d.ts +29 -0
  248. package/dist/tools/verify-trust-mark.js +108 -0
  249. package/dist/tools/verify-trust-mark.js.map +1 -0
  250. package/dist/types.d.ts +220 -0
  251. package/dist/types.js +33 -0
  252. package/dist/types.js.map +1 -0
  253. package/package.json +50 -0
package/dist/cli.js ADDED
@@ -0,0 +1,962 @@
1
+ /**
2
+ * CLI Commands
3
+ *
4
+ * Handles all user-facing CLI interactions:
5
+ * - login: OAuth PKCE browser login (with optional --session for multi-tenant)
6
+ * - configure: API key setup (interactive or --api-key flag)
7
+ * - status: Show auth state, org, plan usage
8
+ * - logout: Clear stored credentials (with optional --session)
9
+ * - sessions: List all stored sessions
10
+ * - serve: Start MCP server (called by IDE, not by user directly)
11
+ */
12
+ import { login, keychainClearAll, keychainClearSession, getStorageName, keychainStore, keychainRead, loadTokens, saveSessionEntry, removeSessionEntry, listSessions, } from "./auth/index.js";
13
+ import { resolveAuth } from "./auth/index.js";
14
+ import { decodeJwtPayload } from "./auth/jwt.js";
15
+ import { getValidAccessToken } from "./auth/token-refresh.js";
16
+ import { detectProjectBinding } from "./project-binding.js";
17
+ import { startServer } from "./server.js";
18
+ import { DEFAULT_CONFIG } from "./types.js";
19
+ import * as readline from "node:readline/promises";
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ /**
25
+ * Get version from package.json
26
+ */
27
+ function getVersion() {
28
+ try {
29
+ const packagePath = path.join(__dirname, "../package.json");
30
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
31
+ return `@zivis/mcp v${packageJson.version}`;
32
+ }
33
+ catch {
34
+ // Fallback if package.json not found (shouldn't happen)
35
+ return "@zivis/mcp v0.1.2";
36
+ }
37
+ }
38
+ /**
39
+ * Parse CLI arguments into a command and options
40
+ */
41
+ export function parseArgs(argv) {
42
+ // argv[0] = node, argv[1] = script, argv[2] = command
43
+ const command = argv[2] || "help";
44
+ const options = {};
45
+ for (let i = 3; i < argv.length; i++) {
46
+ const arg = argv[i];
47
+ if (arg.startsWith("--")) {
48
+ const key = arg.slice(2);
49
+ const next = argv[i + 1];
50
+ if (next && !next.startsWith("--")) {
51
+ options[key] = next;
52
+ i++;
53
+ }
54
+ else {
55
+ options[key] = "true";
56
+ }
57
+ }
58
+ }
59
+ return { command, options };
60
+ }
61
+ /**
62
+ * Run the appropriate CLI command
63
+ */
64
+ export async function runCli(command, options, config = DEFAULT_CONFIG) {
65
+ switch (command) {
66
+ case "login":
67
+ return handleLogin(options, config);
68
+ case "configure":
69
+ return handleConfigure(options, config);
70
+ case "status":
71
+ return handleStatus(options, config);
72
+ case "logout":
73
+ return handleLogout(options);
74
+ case "sessions":
75
+ return handleSessions(options);
76
+ case "init":
77
+ return handleInit(options, config);
78
+ case "setup-ide":
79
+ return handleSetupIde(options);
80
+ case "serve":
81
+ return handleServe(config);
82
+ case "help":
83
+ case "--help":
84
+ case "-h":
85
+ return handleHelp();
86
+ case "version":
87
+ case "--version":
88
+ case "-v":
89
+ return handleVersion();
90
+ default:
91
+ console.error(`Unknown command: ${command}`);
92
+ console.error('Run "zivis help" for usage information.');
93
+ process.exit(1);
94
+ }
95
+ }
96
+ // ── Helpers ──
97
+ /**
98
+ * Extract email from an OAuth ID token JWT
99
+ */
100
+ function extractEmail(idToken) {
101
+ if (!idToken)
102
+ return "unknown";
103
+ try {
104
+ const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
105
+ return payload.email || "unknown";
106
+ }
107
+ catch {
108
+ return "unknown";
109
+ }
110
+ }
111
+ // ── Command Handlers ──
112
+ async function handleLogin(options, config) {
113
+ // Resolve session and org from project binding if available
114
+ const detected = detectProjectBinding(process.cwd());
115
+ const session = options["session"] || detected?.binding.session || config.session;
116
+ // Resolve WorkOS org ID for org-scoped OAuth:
117
+ // 1. Explicit --org flag (WorkOS org ID)
118
+ // 2. Project binding's workosOrgId
119
+ let organizationId = options["org"];
120
+ if (!organizationId && detected?.binding.workosOrgId) {
121
+ organizationId = detected.binding.workosOrgId;
122
+ console.log(`Using org from project binding: ${organizationId}${detected.binding.orgName ? ` (${detected.binding.orgName})` : ""}`);
123
+ console.log(`To pick a different org interactively, run: zivis auth init`);
124
+ }
125
+ if (detected?.binding.session && !options["session"]) {
126
+ console.log(`Using session from project binding: ${session}`);
127
+ }
128
+ try {
129
+ const tokens = await login(config, session, organizationId, {
130
+ orgName: detected?.binding.orgName,
131
+ skipConfirm: options["yes"] === "true",
132
+ });
133
+ const storageName = getStorageName();
134
+ const email = extractEmail(tokens.id_token);
135
+ // Update session index
136
+ const sessionName = session || "default";
137
+ await saveSessionEntry(sessionName, email);
138
+ const sessionLabel = session ? ` (session: ${session})` : "";
139
+ console.log(`\u2713 Logged in as ${email}${sessionLabel}`);
140
+ console.log(`\u2713 Credentials stored in ${storageName}`);
141
+ if (organizationId) {
142
+ console.log(`\u2713 JWT scoped to organization: ${organizationId}`);
143
+ }
144
+ // Auto-write IDE MCP configs
145
+ try {
146
+ await setupVsCodeClaude(session);
147
+ await setupVsCodeCopilot(session);
148
+ }
149
+ catch (err) {
150
+ console.log(` WARNING: Failed to update .vscode/mcp.json: ${err instanceof Error ? err.message : err}`);
151
+ }
152
+ try {
153
+ await setupClaudeCodeCli(session);
154
+ }
155
+ catch (err) {
156
+ console.log(` WARNING: Failed to update Claude Code CLI config: ${err instanceof Error ? err.message : err}`);
157
+ }
158
+ // Ensure the current directory has a project binding matching the JWT org.
159
+ // This is the foolproof gate: every login in a project dir leaves behind
160
+ // a .zivis/project.json that later MCP server starts + check_project will verify.
161
+ await ensureProjectBinding({
162
+ config,
163
+ session: sessionName,
164
+ accessToken: tokens.access_token,
165
+ organizationId,
166
+ cwd: process.cwd(),
167
+ autoBind: options["bind"] === "true",
168
+ skipBind: options["no-bind"] === "true",
169
+ });
170
+ process.exit(0);
171
+ }
172
+ catch (err) {
173
+ console.error("Login failed:", err instanceof Error ? err.message : err);
174
+ process.exit(1);
175
+ }
176
+ }
177
+ async function handleConfigure(options, _config) {
178
+ let apiKey = options["api-key"];
179
+ if (!apiKey) {
180
+ // Interactive mode — read from stdin
181
+ const rl = readline.createInterface({
182
+ input: process.stdin,
183
+ output: process.stderr, // Prompt goes to stderr (stdout is for MCP)
184
+ });
185
+ apiKey = await rl.question("Paste your API key: ");
186
+ rl.close();
187
+ if (!apiKey?.trim()) {
188
+ console.error("No API key provided.");
189
+ process.exit(1);
190
+ }
191
+ apiKey = apiKey.trim();
192
+ }
193
+ try {
194
+ await keychainStore("apikey", apiKey);
195
+ const storageName = getStorageName();
196
+ const masked = apiKey.startsWith("sk_")
197
+ ? `sk_...${apiKey.slice(-4)}`
198
+ : "***";
199
+ console.log(`\u2713 API key (${masked}) stored in ${storageName}`);
200
+ }
201
+ catch (err) {
202
+ console.error("Failed to store API key:", err instanceof Error ? err.message : err);
203
+ process.exit(1);
204
+ }
205
+ }
206
+ async function handleStatus(options, config) {
207
+ // Resolution order: --session flag > ZIVIS_SESSION env > .zivis/project.json binding
208
+ // The binding fallback makes `status` reflect the current project without any env setup.
209
+ const detected = detectProjectBinding(process.cwd());
210
+ const session = options["session"] || config.session || detected?.binding.session;
211
+ const resolveConfig = session ? { ...config, session } : config;
212
+ if (session) {
213
+ const source = options["session"]
214
+ ? "flag"
215
+ : config.session
216
+ ? "ZIVIS_SESSION"
217
+ : "project binding";
218
+ console.log(`Session: ${session} (from ${source})`);
219
+ }
220
+ const creds = await resolveAuth(resolveConfig);
221
+ switch (creds.method) {
222
+ case "oauth": {
223
+ const tokens = await loadTokens(session);
224
+ const email = extractEmail(tokens?.id_token);
225
+ console.log(`Auth: OAuth (${email})`);
226
+ if (tokens) {
227
+ const expiresIn = Math.round((tokens.expires_at - Date.now()) / 1000 / 60);
228
+ console.log(`Token: ${expiresIn > 0 ? `expires in ${expiresIn} min` : "expired (will refresh)"}`);
229
+ }
230
+ break;
231
+ }
232
+ case "apikey": {
233
+ const masked = creds.apiKey?.startsWith("sk_")
234
+ ? `sk_...${creds.apiKey.slice(-4)}`
235
+ : "***";
236
+ const source = (await keychainRead("apikey"))
237
+ ? getStorageName()
238
+ : "environment variable";
239
+ console.log(`Auth: API Key (${masked}) from ${source}`);
240
+ break;
241
+ }
242
+ case "none":
243
+ console.log("Auth: Not authenticated");
244
+ console.log("");
245
+ if (session) {
246
+ console.log(`Session "${session}" has no credentials.`);
247
+ console.log(`Run: zivis auth login --session ${session}`);
248
+ }
249
+ else {
250
+ console.log("To authenticate:");
251
+ console.log(" zivis auth login # OAuth (recommended)");
252
+ console.log(" zivis auth configure # API key");
253
+ }
254
+ break;
255
+ }
256
+ console.log(`API: ${resolveConfig.apiBaseUrl}`);
257
+ }
258
+ async function handleLogout(options) {
259
+ const session = options["session"];
260
+ if (session) {
261
+ await keychainClearSession(session);
262
+ await removeSessionEntry(session);
263
+ console.log(`\u2713 Session "${session}" credentials cleared`);
264
+ }
265
+ else {
266
+ await keychainClearAll();
267
+ console.log("\u2713 Default credentials cleared");
268
+ console.log(" Note: Named sessions are not affected. Use 'zivis auth sessions' to list them.");
269
+ }
270
+ }
271
+ async function handleSessions(options) {
272
+ // Sub-command: zivis auth sessions remove <name>
273
+ const subCommand = options["remove"] || options["delete"];
274
+ if (subCommand) {
275
+ await keychainClearSession(subCommand);
276
+ await removeSessionEntry(subCommand);
277
+ console.log(`\u2713 Session "${subCommand}" removed`);
278
+ return;
279
+ }
280
+ const sessions = await listSessions();
281
+ const activeSession = process.env.ZIVIS_SESSION;
282
+ if (sessions.length === 0) {
283
+ console.log("No sessions stored.");
284
+ console.log("");
285
+ console.log("To create a session:");
286
+ console.log(" zivis auth login # Default session");
287
+ console.log(" zivis auth login --session <name> # Named session");
288
+ return;
289
+ }
290
+ console.log("Sessions:");
291
+ console.log("");
292
+ for (const s of sessions) {
293
+ const active = s.name === (activeSession || "default") ? " (active)" : "";
294
+ const nameCol = s.name.padEnd(20);
295
+ console.log(` ${nameCol} ${s.email}${active}`);
296
+ }
297
+ console.log("");
298
+ console.log("To use a session in a project, add to .mcp.json:");
299
+ console.log(' "env": { "ZIVIS_SESSION": "<session-name>" }');
300
+ }
301
+ /**
302
+ * Fetch user's organizations from the API using a valid access token.
303
+ */
304
+ async function fetchUserOrgs(config, accessToken) {
305
+ const url = `${config.apiBaseUrl}/api/user/organizations`;
306
+ const response = await fetch(url, {
307
+ method: "GET",
308
+ headers: {
309
+ Authorization: `Bearer ${accessToken}`,
310
+ "Content-Type": "application/json",
311
+ "User-Agent": "@zivis/cli",
312
+ },
313
+ });
314
+ if (!response.ok) {
315
+ const body = await response.json().catch(() => ({}));
316
+ throw new Error(body.message ||
317
+ body.error ||
318
+ `Failed to fetch organizations (HTTP ${response.status})`);
319
+ }
320
+ const data = await response.json();
321
+ return data.organizations;
322
+ }
323
+ /**
324
+ * List RT applications for the current org (Bearer token must match org context).
325
+ */
326
+ async function fetchApplications(config, accessToken) {
327
+ const url = `${config.apiBaseUrl}/api/rt/applications?limit=100`;
328
+ const response = await fetch(url, {
329
+ method: "GET",
330
+ headers: {
331
+ Authorization: `Bearer ${accessToken}`,
332
+ "Content-Type": "application/json",
333
+ "User-Agent": "@zivis/cli",
334
+ },
335
+ });
336
+ if (!response.ok) {
337
+ const body = await response.json().catch(() => ({}));
338
+ throw new Error(body.message ||
339
+ body.error ||
340
+ `Failed to list applications (HTTP ${response.status})`);
341
+ }
342
+ const data = await response.json();
343
+ return data.applications || [];
344
+ }
345
+ /**
346
+ * After login, ensure `.zivis/project.json` in the current directory is bound
347
+ * to the org the user just authenticated as. This is the foolproof gate:
348
+ * every successful login leaves behind a binding that later MCP calls verify.
349
+ *
350
+ * Behavior:
351
+ * - Existing binding matches JWT org → no-op (already verified by the pre-login org scope)
352
+ * - Existing binding missing workosOrgId but session matches → upgrade it in-place
353
+ * - Existing binding mismatches → hard error (should never happen — login would've failed)
354
+ * - No binding found, TTY interactive → prompt to create one
355
+ * - No binding found, non-TTY or --no-bind → print warning, continue
356
+ * - --bind flag → create without prompting
357
+ */
358
+ async function ensureProjectBinding(opts) {
359
+ const { config, session, accessToken, cwd, autoBind, skipBind } = opts;
360
+ const claims = decodeJwtPayload(accessToken);
361
+ const jwtOrgId = claims?.org_id;
362
+ if (!jwtOrgId) {
363
+ console.log("");
364
+ console.log(" Note: JWT has no org_id — skipping project binding.");
365
+ return;
366
+ }
367
+ const detected = detectProjectBinding(cwd);
368
+ // Case A: binding exists and already has the correct workosOrgId
369
+ if (detected?.binding.workosOrgId === jwtOrgId) {
370
+ console.log(`\u2713 Project binding verified: ${detected.filePath}`);
371
+ return;
372
+ }
373
+ // Case B: binding exists but workosOrgId mismatches
374
+ // (Shouldn't happen — the pre-login org scope would have caught it — but guard anyway.)
375
+ if (detected?.binding.workosOrgId && detected.binding.workosOrgId !== jwtOrgId) {
376
+ console.error("");
377
+ console.error(`[zivis] ERROR: Project binding org mismatches JWT org.`);
378
+ console.error(`[zivis] Binding file: ${detected.filePath}`);
379
+ console.error(`[zivis] Binding org: ${detected.binding.workosOrgId}${detected.binding.orgName ? ` (${detected.binding.orgName})` : ""}`);
380
+ console.error(`[zivis] JWT org: ${jwtOrgId}`);
381
+ console.error(`[zivis] This should not happen — investigate before proceeding.`);
382
+ process.exit(1);
383
+ }
384
+ // We need the org's human-readable info. Fetch it.
385
+ let orgInfo;
386
+ try {
387
+ const orgs = await fetchUserOrgs(config, accessToken);
388
+ orgInfo = orgs.find((o) => o.workosOrgId === jwtOrgId);
389
+ }
390
+ catch (err) {
391
+ console.log(` WARNING: Could not fetch org info: ${err instanceof Error ? err.message : err}`);
392
+ }
393
+ const orgName = orgInfo?.name || detected?.binding.orgName;
394
+ const zivisOrgId = orgInfo?.id || detected?.binding.orgId;
395
+ const orgLabel = orgName ? `${orgName} (${jwtOrgId})` : jwtOrgId;
396
+ // Case C: binding exists but is missing workosOrgId — upgrade it
397
+ if (detected && !detected.binding.workosOrgId) {
398
+ const upgraded = {
399
+ ...detected.binding,
400
+ workosOrgId: jwtOrgId,
401
+ };
402
+ if (orgName)
403
+ upgraded.orgName = orgName;
404
+ if (zivisOrgId)
405
+ upgraded.orgId = zivisOrgId;
406
+ fs.writeFileSync(detected.filePath, JSON.stringify(upgraded, null, 2) + "\n");
407
+ console.log(`\u2713 Upgraded project binding with org: ${orgLabel}`);
408
+ console.log(` File: ${detected.filePath}`);
409
+ return;
410
+ }
411
+ // Case D: no binding found. Bind this directory.
412
+ const bindingPath = path.join(cwd, ".zivis", "project.json");
413
+ const interactive = process.stderr.isTTY && process.stdin.isTTY;
414
+ let shouldBind = autoBind;
415
+ if (!shouldBind && !skipBind && interactive) {
416
+ console.log("");
417
+ console.log(`This directory has no .zivis/project.json binding.`);
418
+ console.log(`Binding locks future MCP calls to org: ${orgLabel}`);
419
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
420
+ try {
421
+ const answer = (await rl.question("Create binding now? [Y/n] ")).trim().toLowerCase();
422
+ shouldBind = answer !== "n" && answer !== "no";
423
+ }
424
+ finally {
425
+ rl.close();
426
+ }
427
+ }
428
+ if (!shouldBind) {
429
+ if (!skipBind) {
430
+ console.log("");
431
+ console.log(` WARNING: No project binding. MCP calls will not verify the org for this directory.`);
432
+ console.log(` To bind later: zivis auth init`);
433
+ }
434
+ return;
435
+ }
436
+ const binding = {
437
+ session,
438
+ workosOrgId: jwtOrgId,
439
+ };
440
+ if (orgName)
441
+ binding.orgName = orgName;
442
+ if (zivisOrgId)
443
+ binding.orgId = zivisOrgId;
444
+ const zivisDir = path.join(cwd, ".zivis");
445
+ if (!fs.existsSync(zivisDir)) {
446
+ fs.mkdirSync(zivisDir, { recursive: true });
447
+ }
448
+ fs.writeFileSync(bindingPath, JSON.stringify(binding, null, 2) + "\n");
449
+ console.log(`\u2713 Project bound to ${orgLabel}`);
450
+ console.log(` File: ${bindingPath}`);
451
+ console.log(` Commit this file so future sessions in this repo are org-locked.`);
452
+ }
453
+ /**
454
+ * Derive a session name from an org name.
455
+ * "Acme Corp" → "acme-corp"
456
+ */
457
+ function deriveSessionName(orgName) {
458
+ return orgName
459
+ .toLowerCase()
460
+ .replace(/[^a-z0-9]+/g, "-")
461
+ .replace(/^-|-$/g, "");
462
+ }
463
+ async function handleInit(options, config) {
464
+ const rl = readline.createInterface({
465
+ input: process.stdin,
466
+ output: process.stderr,
467
+ });
468
+ try {
469
+ // Check for existing binding
470
+ const existing = detectProjectBinding(process.cwd());
471
+ if (existing) {
472
+ console.log(`Existing binding found: ${existing.filePath}`);
473
+ console.log(` session: ${existing.binding.session}`);
474
+ if (existing.binding.orgName) {
475
+ console.log(` org: ${existing.binding.orgName}`);
476
+ }
477
+ const answer = await rl.question("Overwrite? (y/N) ");
478
+ if (answer.toLowerCase() !== "y") {
479
+ console.log("Aborted.");
480
+ return;
481
+ }
482
+ console.log("");
483
+ }
484
+ // ── Step 1: Ensure we have a valid access token ──
485
+ console.log("Step 1: Authenticate");
486
+ console.log("");
487
+ let accessToken = await getValidAccessToken(config);
488
+ if (!accessToken) {
489
+ console.log(" No valid session found. Opening browser to log in...");
490
+ console.log("");
491
+ const tokens = await login(config);
492
+ accessToken = tokens.access_token;
493
+ const email = extractEmail(tokens.id_token);
494
+ await saveSessionEntry("default", email);
495
+ console.log(` \u2713 Logged in as ${email}`);
496
+ console.log("");
497
+ }
498
+ else {
499
+ console.log(" \u2713 Already authenticated");
500
+ console.log("");
501
+ }
502
+ // ── Step 2: Fetch and display orgs ──
503
+ console.log("Step 2: Select organization");
504
+ console.log("");
505
+ const orgs = await fetchUserOrgs(config, accessToken);
506
+ if (orgs.length === 0) {
507
+ console.error(" No organizations found for this account.");
508
+ console.error(" Join an organization at https://app.zivis.ai first.");
509
+ process.exit(1);
510
+ }
511
+ let selectedOrg;
512
+ if (orgs.length === 1) {
513
+ selectedOrg = orgs[0];
514
+ console.log(` Only one organization found: ${selectedOrg.name}`);
515
+ console.log(` \u2713 Auto-selected: ${selectedOrg.name}`);
516
+ }
517
+ else {
518
+ console.log(" Your organizations:");
519
+ console.log("");
520
+ for (let i = 0; i < orgs.length; i++) {
521
+ console.log(` ${i + 1}. ${orgs[i].name}`);
522
+ }
523
+ console.log("");
524
+ const choice = await rl.question(` Select organization (1-${orgs.length}): `);
525
+ const index = parseInt(choice, 10) - 1;
526
+ if (isNaN(index) || index < 0 || index >= orgs.length) {
527
+ console.error(" Invalid selection.");
528
+ process.exit(1);
529
+ }
530
+ selectedOrg = orgs[index];
531
+ console.log(` \u2713 Selected: ${selectedOrg.name}`);
532
+ }
533
+ console.log("");
534
+ // ── Step 3: Generate session name ──
535
+ const defaultSession = deriveSessionName(selectedOrg.name);
536
+ let session = options["session"] || config.session;
537
+ if (!session) {
538
+ const sessionInput = await rl.question(`Step 3: Session name [${defaultSession}]: `);
539
+ session = sessionInput.trim() || defaultSession;
540
+ }
541
+ console.log(` \u2713 Session: ${session}`);
542
+ console.log("");
543
+ let tokenForApi = accessToken;
544
+ // ── Step 4: Re-login scoped to the selected org ──
545
+ if (selectedOrg.workosOrgId) {
546
+ console.log("Step 4: Org-scoped login");
547
+ console.log(` Re-authenticating scoped to ${selectedOrg.name}...`);
548
+ console.log("");
549
+ const tokens = await login(config, session, selectedOrg.workosOrgId, {
550
+ orgName: selectedOrg.name,
551
+ skipConfirm: true,
552
+ });
553
+ tokenForApi = tokens.access_token;
554
+ const email = extractEmail(tokens.id_token);
555
+ await saveSessionEntry(session, email);
556
+ console.log(` \u2713 Logged in as ${email} (org: ${selectedOrg.name})`);
557
+ console.log(` \u2713 JWT scoped to organization: ${selectedOrg.workosOrgId}`);
558
+ console.log("");
559
+ }
560
+ else {
561
+ // No WorkOS org ID — just save the session
562
+ console.log("Step 4: Session setup");
563
+ console.log(" WARNING: This org has no WorkOS ID. JWT org validation will be skipped.");
564
+ console.log("");
565
+ }
566
+ // ── Step 5: Optional default application (for MCP tool defaults) ──
567
+ let boundApplicationId;
568
+ let boundApplicationName;
569
+ console.log("Step 5: Default application (optional)");
570
+ console.log("");
571
+ try {
572
+ const apps = await fetchApplications(config, tokenForApi);
573
+ if (apps.length === 0) {
574
+ console.log(" No applications in this org yet — create one in the ZIVIS web app, or add applicationId to .zivis/project.json later.");
575
+ }
576
+ else if (apps.length === 1) {
577
+ boundApplicationId = apps[0].id;
578
+ boundApplicationName = apps[0].name;
579
+ console.log(` One application: ${boundApplicationName}`);
580
+ const yn = (await rl.question(" Bind this folder to it for MCP defaults? [Y/n] ")).trim().toLowerCase();
581
+ if (yn === "n" || yn === "no") {
582
+ boundApplicationId = undefined;
583
+ boundApplicationName = undefined;
584
+ console.log(" Skipped.");
585
+ }
586
+ else {
587
+ console.log(` \u2713 Default application: ${boundApplicationName}`);
588
+ }
589
+ }
590
+ else {
591
+ console.log(" Applications in this org:");
592
+ console.log("");
593
+ for (let i = 0; i < apps.length; i++) {
594
+ console.log(` ${i + 1}. ${apps[i].name}`);
595
+ }
596
+ console.log("");
597
+ const pick = (await rl.question(` Pick application (1-${apps.length}) or press Enter to skip: `)).trim();
598
+ if (pick) {
599
+ const index = parseInt(pick, 10) - 1;
600
+ if (!isNaN(index) && index >= 0 && index < apps.length) {
601
+ boundApplicationId = apps[index].id;
602
+ boundApplicationName = apps[index].name;
603
+ console.log(` \u2713 Default application: ${boundApplicationName}`);
604
+ }
605
+ else {
606
+ console.log(" Invalid selection — skipped.");
607
+ }
608
+ }
609
+ else {
610
+ console.log(" Skipped — you can add applicationId to .zivis/project.json later.");
611
+ }
612
+ }
613
+ }
614
+ catch (err) {
615
+ console.log(` WARNING: Could not list applications: ${err instanceof Error ? err.message : String(err)}`);
616
+ }
617
+ console.log("");
618
+ // ── Step 6: Write project binding ──
619
+ const binding = {
620
+ session,
621
+ orgId: selectedOrg.id,
622
+ orgName: selectedOrg.name,
623
+ };
624
+ if (selectedOrg.workosOrgId) {
625
+ binding.workosOrgId = selectedOrg.workosOrgId;
626
+ }
627
+ if (boundApplicationId) {
628
+ binding.applicationId = boundApplicationId;
629
+ if (boundApplicationName)
630
+ binding.applicationName = boundApplicationName;
631
+ }
632
+ const zivisDir = path.join(process.cwd(), ".zivis");
633
+ if (!fs.existsSync(zivisDir)) {
634
+ fs.mkdirSync(zivisDir, { recursive: true });
635
+ }
636
+ const bindingPath = path.join(zivisDir, "project.json");
637
+ fs.writeFileSync(bindingPath, JSON.stringify(binding, null, 2) + "\n");
638
+ console.log("Step 6: Project binding");
639
+ console.log(` \u2713 Created: ${bindingPath}`);
640
+ console.log("");
641
+ console.log(" Contents:");
642
+ console.log(` session: ${binding.session}`);
643
+ console.log(` orgId: ${binding.orgId}`);
644
+ console.log(` orgName: ${binding.orgName}`);
645
+ if (binding.workosOrgId) {
646
+ console.log(` workosOrgId: ${binding.workosOrgId}`);
647
+ }
648
+ if (binding.applicationId) {
649
+ console.log(` applicationId: ${binding.applicationId}`);
650
+ if (binding.applicationName)
651
+ console.log(` applicationName: ${binding.applicationName}`);
652
+ }
653
+ console.log("");
654
+ // ── Step 7: Auto-configure IDEs ──
655
+ console.log("Step 7: IDE configuration");
656
+ console.log("");
657
+ // Always write VS Code + Claude Code CLI configs
658
+ try {
659
+ await setupVsCodeClaude(session);
660
+ await setupVsCodeCopilot(session);
661
+ }
662
+ catch (err) {
663
+ console.log(` WARNING: Failed to configure VS Code: ${err instanceof Error ? err.message : err}`);
664
+ }
665
+ try {
666
+ await setupClaudeCodeCli(session);
667
+ }
668
+ catch (err) {
669
+ console.log(` WARNING: Failed to configure Claude Code CLI: ${err instanceof Error ? err.message : err}`);
670
+ }
671
+ // Cursor: only if user explicitly uses it (setup-ide --for cursor)
672
+ console.log("");
673
+ console.log(" For Cursor, run: zivis mcp install --for cursor --session " + session);
674
+ console.log("");
675
+ // ── Done ──
676
+ console.log("Setup complete!");
677
+ console.log("");
678
+ console.log(" Commit .zivis/project.json to your repo for team-wide safety.");
679
+ console.log(" Restart your IDE for the MCP changes to take effect.");
680
+ }
681
+ finally {
682
+ rl.close();
683
+ }
684
+ }
685
+ async function handleServe(config) {
686
+ await startServer(config);
687
+ }
688
+ async function handleSetupIde(options) {
689
+ const ide = options["for"] || options["ide"];
690
+ const session = options["session"];
691
+ if (!ide) {
692
+ console.error("Please specify which IDE to setup:");
693
+ console.error(" zivis mcp install --for cursor");
694
+ console.error(" zivis mcp install --for vscode-copilot");
695
+ console.error(" zivis mcp install --for vscode-claude");
696
+ console.error(" zivis mcp install --for claude-code");
697
+ console.error("");
698
+ console.error("Options:");
699
+ console.error(" --session <name> Include ZIVIS_SESSION env var in config");
700
+ process.exit(1);
701
+ }
702
+ try {
703
+ switch (ide.toLowerCase()) {
704
+ case "cursor":
705
+ await setupCursor(session);
706
+ break;
707
+ case "vscode-copilot":
708
+ case "github-copilot":
709
+ await setupVsCodeCopilot(session);
710
+ break;
711
+ case "vscode-claude":
712
+ case "claude-code-vscode":
713
+ await setupVsCodeClaude(session);
714
+ break;
715
+ case "claude-code":
716
+ await setupClaudeCode(session);
717
+ break;
718
+ default:
719
+ console.error(`Unknown IDE: ${ide}`);
720
+ console.error("Supported: cursor, vscode-copilot, vscode-claude, claude-code");
721
+ process.exit(1);
722
+ }
723
+ }
724
+ catch (err) {
725
+ console.error("Setup failed:", err instanceof Error ? err.message : err);
726
+ process.exit(1);
727
+ }
728
+ }
729
+ /**
730
+ * Build server config with optional session env var
731
+ */
732
+ function buildServerConfig(session, extra) {
733
+ const config = {
734
+ ...extra,
735
+ command: "zivis-mcp",
736
+ args: ["serve"],
737
+ };
738
+ if (session) {
739
+ config.env = { ZIVIS_SESSION: session };
740
+ }
741
+ return config;
742
+ }
743
+ /**
744
+ * Cursor: ~/.cursor/mcp.json
745
+ */
746
+ async function setupCursor(session) {
747
+ const homeDir = process.env.HOME || "";
748
+ const configPath = path.join(homeDir, ".cursor", "mcp.json");
749
+ const serverConfig = buildServerConfig(session);
750
+ await updateMcpConfig(configPath, "zivis-security", serverConfig, "mcpServers");
751
+ console.log(`\u2713 Cursor MCP config updated: ${configPath}`);
752
+ if (session)
753
+ console.log(` Session: ${session}`);
754
+ console.log(" Restart Cursor for changes to take effect");
755
+ }
756
+ /**
757
+ * VS Code (GitHub Copilot): .vscode/mcp.json or ~/.vscode/mcp.json
758
+ */
759
+ async function setupVsCodeCopilot(session) {
760
+ const localConfigPath = path.join(process.cwd(), ".vscode", "mcp.json");
761
+ const globalConfigPath = path.join(process.env.HOME || "", ".vscode", "mcp.json");
762
+ const serverConfig = buildServerConfig(session, { type: "stdio" });
763
+ // Try local first, fall back to global
764
+ let configPath = localConfigPath;
765
+ const exists = fs.existsSync(localConfigPath);
766
+ if (!exists && !fs.existsSync(globalConfigPath)) {
767
+ // Neither exists, use local
768
+ configPath = localConfigPath;
769
+ }
770
+ else if (!exists) {
771
+ // Only global exists
772
+ configPath = globalConfigPath;
773
+ }
774
+ await updateMcpConfig(configPath, "zivis-security", serverConfig, "servers");
775
+ console.log(`\u2713 VS Code (GitHub Copilot) MCP config updated: ${configPath}`);
776
+ if (session)
777
+ console.log(` Session: ${session}`);
778
+ console.log(" Restart VS Code for changes to take effect");
779
+ }
780
+ /**
781
+ * VS Code (Claude Code): .vscode/mcp.json
782
+ */
783
+ async function setupVsCodeClaude(session) {
784
+ const configPath = path.join(process.cwd(), ".vscode", "mcp.json");
785
+ const serverConfig = buildServerConfig(session);
786
+ await updateMcpConfig(configPath, "zivis-security", serverConfig, "mcpServers");
787
+ console.log(`\u2713 VS Code (Claude Code) MCP config updated: ${configPath}`);
788
+ if (session)
789
+ console.log(` Session: ${session}`);
790
+ console.log(" Restart VS Code for changes to take effect");
791
+ }
792
+ /**
793
+ * Claude Code: Writes to stdout for user to copy to MCP settings
794
+ */
795
+ async function setupClaudeCode(session) {
796
+ const serverConfig = {
797
+ mcpServers: {
798
+ "zivis-security": buildServerConfig(session),
799
+ },
800
+ };
801
+ console.log("\n\u2713 Claude Code MCP configuration:");
802
+ console.log("");
803
+ console.log("Add the following to your Claude Code MCP settings:");
804
+ console.log("");
805
+ console.log(JSON.stringify(serverConfig, null, 2));
806
+ console.log("");
807
+ console.log("Then restart Claude Code for changes to take effect");
808
+ }
809
+ /**
810
+ * Claude Code CLI: Updates ~/.claude.json project-level mcpServers
811
+ * Claude Code CLI stores MCP configs per-project inside ~/.claude.json under
812
+ * projects["/path/to/project"].mcpServers
813
+ */
814
+ async function setupClaudeCodeCli(session) {
815
+ const homeDir = process.env.HOME || "";
816
+ const configPath = path.join(homeDir, ".claude.json");
817
+ const projectPath = process.cwd();
818
+ if (!fs.existsSync(configPath)) {
819
+ console.log(" Skipping Claude Code CLI (no ~/.claude.json found)");
820
+ return;
821
+ }
822
+ try {
823
+ const content = fs.readFileSync(configPath, "utf-8");
824
+ const config = JSON.parse(content);
825
+ // Ensure projects object exists
826
+ if (!config.projects) {
827
+ config.projects = {};
828
+ }
829
+ if (!config.projects[projectPath]) {
830
+ config.projects[projectPath] = {};
831
+ }
832
+ if (!config.projects[projectPath].mcpServers) {
833
+ config.projects[projectPath].mcpServers = {};
834
+ }
835
+ const serverConfig = {
836
+ type: "stdio",
837
+ command: "zivis-mcp",
838
+ args: ["serve"],
839
+ env: session ? { ZIVIS_SESSION: session } : {},
840
+ };
841
+ config.projects[projectPath].mcpServers["zivis-security"] = serverConfig;
842
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
843
+ console.log(`\u2713 Claude Code CLI MCP config updated: ~/.claude.json`);
844
+ if (session)
845
+ console.log(` Session: ${session}`);
846
+ console.log(" Restart Claude Code for changes to take effect");
847
+ }
848
+ catch (err) {
849
+ console.log(` WARNING: Failed to update ~/.claude.json: ${err instanceof Error ? err.message : err}`);
850
+ }
851
+ }
852
+ /**
853
+ * Update or create an MCP config file with proper JSON handling
854
+ */
855
+ async function updateMcpConfig(configPath, serverName, serverConfig, rootKey) {
856
+ // Ensure directory exists
857
+ const configDir = path.dirname(configPath);
858
+ if (!fs.existsSync(configDir)) {
859
+ fs.mkdirSync(configDir, { recursive: true });
860
+ }
861
+ let config = {};
862
+ // Read existing config if it exists
863
+ if (fs.existsSync(configPath)) {
864
+ try {
865
+ const content = fs.readFileSync(configPath, "utf-8");
866
+ config = JSON.parse(content);
867
+ }
868
+ catch {
869
+ console.warn(`Warning: Could not parse existing ${configPath}, will overwrite`);
870
+ }
871
+ }
872
+ // Ensure root key exists
873
+ if (!config[rootKey]) {
874
+ config[rootKey] = {};
875
+ }
876
+ // Add our server config
877
+ const servers = config[rootKey];
878
+ servers[serverName] = serverConfig;
879
+ // Write back to file
880
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
881
+ }
882
+ function handleHelp() {
883
+ console.log(`zivis-mcp \u2014 ZIVIS MCP Security Server
884
+
885
+ USAGE
886
+ zivis <command> [options]
887
+
888
+ COMMANDS
889
+ login Authenticate via browser (OAuth PKCE)
890
+ --session <name> Store credentials under a named session
891
+ --org <workos-org-id> Scope login to a specific organization
892
+ --yes Skip the pre-browser org confirmation prompt
893
+ --bind Auto-write .zivis/project.json (no prompt)
894
+ --no-bind Do not bind the current directory after login
895
+ configure Store an API key for authentication
896
+ --api-key <key> Provide key non-interactively
897
+ status Show current auth state and connection info
898
+ --session <name> Check status of a specific session
899
+ logout Clear stored credentials
900
+ --session <name> Clear only a specific session
901
+ sessions List all stored sessions
902
+ --remove <name> Remove a specific session
903
+ init Interactive setup: login, pick org, bind project
904
+ --session <name> Pre-set session name (skips prompt)
905
+ setup-ide Automatically configure IDE for MCP support
906
+ --for <ide> IDE to setup (see options below)
907
+ --session <name> Include session in generated config
908
+ serve Start MCP server (called by IDE, not directly)
909
+ help Show this help message
910
+ version Show version
911
+
912
+ MULTI-TENANT SESSIONS
913
+ Sessions allow multiple OAuth logins to coexist on the same machine.
914
+ Each project can use a different session via the ZIVIS_SESSION env var.
915
+
916
+ Example workflow:
917
+ zivis auth login # Default session (your admin account)
918
+ zivis auth login --session acme-corp # Customer session
919
+ zivis auth sessions # List all sessions
920
+
921
+ Then in your project's .mcp.json:
922
+ { "env": { "ZIVIS_SESSION": "acme-corp" } }
923
+
924
+ Safety: When ZIVIS_SESSION is set, credentials are scoped to that
925
+ session only. If the session has no credentials, the MCP server will
926
+ NOT fall back to your default session \u2014 preventing cross-tenant leaks.
927
+
928
+ PROJECT BINDING
929
+ Set up a project with org-scoped security (interactive wizard):
930
+ zivis auth init
931
+
932
+ This will:
933
+ 1. Log you in (if needed)
934
+ 2. Show your organizations and let you pick one
935
+ 3. Create an org-scoped session with a JWT locked to that org
936
+ 4. Write .zivis/project.json with org IDs for startup validation
937
+
938
+ When the MCP server starts, it:
939
+ - Refuses to start if ZIVIS_SESSION doesn't match
940
+ - Verifies the JWT's org_id matches the expected org (prevents cross-contamination)
941
+ - Scopes future logins to the correct org automatically
942
+
943
+ Commit .zivis/project.json to your repo for team-wide safety.
944
+
945
+ SETUP IDE OPTIONS
946
+ zivis mcp install --for cursor Setup Cursor (.cursor/mcp.json)
947
+ zivis mcp install --for vscode-copilot Setup VS Code + GitHub Copilot
948
+ zivis mcp install --for vscode-claude Setup VS Code + Claude Code
949
+ zivis mcp install --for claude-code Show Claude Code config (manual)
950
+
951
+ ENVIRONMENT VARIABLES
952
+ ZIVIS_SESSION Named session for multi-tenant isolation
953
+ ZIVIS_API_KEY API key (fallback when keychain is unavailable)
954
+ ZIVIS_API_URL API base URL (default: https://api.zivis.ai)
955
+ ZIVIS_OAUTH_CLIENT_ID OAuth client ID (optional, overrides default)
956
+ ZIVIS_OAUTH_API_URL OAuth API URL (default: https://auth.workos.com)
957
+ `);
958
+ }
959
+ function handleVersion() {
960
+ console.log(getVersion());
961
+ }
962
+ //# sourceMappingURL=cli.js.map