@zivis/cli 0.1.0-alpha.5 → 0.1.0-alpha.52

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 (197) 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 +8 -18
  5. package/dist/commands/assurance/index.d.ts +35 -0
  6. package/dist/commands/assurance/index.js +154 -0
  7. package/dist/commands/auth/bind.d.ts +4 -0
  8. package/dist/commands/auth/bind.js +97 -0
  9. package/dist/commands/auth/index.js +21 -2
  10. package/dist/commands/auth/init.d.ts +2 -0
  11. package/dist/commands/auth/init.js +248 -96
  12. package/dist/commands/auth/login.d.ts +2 -0
  13. package/dist/commands/auth/login.js +136 -18
  14. package/dist/commands/auth/logout.js +19 -7
  15. package/dist/commands/auth/sessions.js +7 -4
  16. package/dist/commands/auth/status.js +33 -12
  17. package/dist/commands/auth/token.d.ts +4 -0
  18. package/dist/commands/auth/token.js +73 -0
  19. package/dist/commands/check/index.d.ts +2 -0
  20. package/dist/commands/check/index.js +463 -0
  21. package/dist/commands/credits/index.js +2 -3
  22. package/dist/commands/gate/index.d.ts +2 -0
  23. package/dist/commands/gate/index.js +171 -0
  24. package/dist/commands/inspect/index.d.ts +2 -0
  25. package/dist/commands/inspect/index.js +136 -0
  26. package/dist/commands/mcp/index.js +113 -14
  27. package/dist/commands/open/index.d.ts +0 -8
  28. package/dist/commands/open/index.js +0 -11
  29. package/dist/commands/run/index.d.ts +2 -0
  30. package/dist/commands/run/index.js +206 -0
  31. package/dist/commands/scan/index.js +3 -8
  32. package/dist/commands/share/index.d.ts +14 -0
  33. package/dist/commands/share/index.js +100 -0
  34. package/dist/commands/sync/index.d.ts +2 -0
  35. package/dist/commands/sync/index.js +193 -0
  36. package/dist/commands/target/index.js +0 -3
  37. package/dist/commands/test/index.d.ts +2 -0
  38. package/dist/commands/test/index.js +198 -0
  39. package/dist/commands/threatmodel/index.d.ts +2 -0
  40. package/dist/commands/threatmodel/index.js +167 -0
  41. package/dist/index.js +28 -3
  42. package/dist/internal/application-binding.d.ts +8 -0
  43. package/dist/internal/application-binding.js +167 -0
  44. package/dist/internal/binding-token-mint.d.ts +33 -0
  45. package/dist/internal/binding-token-mint.js +174 -0
  46. package/dist/internal/change-recommendation.d.ts +6 -0
  47. package/dist/internal/change-recommendation.js +113 -0
  48. package/dist/internal/cli-output.d.ts +16 -0
  49. package/dist/internal/cli-output.js +39 -0
  50. package/dist/internal/devx-run.d.ts +129 -0
  51. package/dist/internal/devx-run.js +39 -0
  52. package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
  53. package/dist/internal/extractor/adapters/go/index.js +0 -6
  54. package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
  55. package/dist/internal/extractor/adapters/go/manifest.js +19 -25
  56. package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
  57. package/dist/internal/extractor/adapters/go/parser.js +0 -5
  58. package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
  59. package/dist/internal/extractor/adapters/go/scanner.js +0 -43
  60. package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
  61. package/dist/internal/extractor/adapters/python/index.js +0 -4
  62. package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
  63. package/dist/internal/extractor/adapters/python/manifest.js +21 -18
  64. package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
  65. package/dist/internal/extractor/adapters/python/parser.js +0 -4
  66. package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
  67. package/dist/internal/extractor/adapters/python/scanner.js +1 -54
  68. package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
  69. package/dist/internal/extractor/adapters/typescript/index.js +1 -21
  70. package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
  71. package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
  72. package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
  73. package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
  74. package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
  75. package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
  76. package/dist/internal/extractor/graph-builder.d.ts +10 -0
  77. package/dist/internal/extractor/graph-builder.js +245 -0
  78. package/dist/internal/extractor/graph-types.d.ts +89 -0
  79. package/dist/internal/extractor/graph-types.js +1 -0
  80. package/dist/internal/extractor/index.d.ts +0 -14
  81. package/dist/internal/extractor/index.js +0 -13
  82. package/dist/internal/extractor/types.d.ts +0 -45
  83. package/dist/internal/extractor/types.js +0 -27
  84. package/dist/internal/extractor/walker.d.ts +0 -18
  85. package/dist/internal/extractor/walker.js +0 -18
  86. package/dist/internal/gate-evaluate.d.ts +50 -0
  87. package/dist/internal/gate-evaluate.js +111 -0
  88. package/dist/internal/gate-policy.d.ts +38 -0
  89. package/dist/internal/gate-policy.js +167 -0
  90. package/dist/internal/git-metadata.d.ts +8 -0
  91. package/dist/internal/git-metadata.js +38 -0
  92. package/dist/internal/git.d.ts +9 -0
  93. package/dist/internal/git.js +45 -0
  94. package/dist/internal/github-linkage-prompt.d.ts +7 -0
  95. package/dist/internal/github-linkage-prompt.js +95 -0
  96. package/dist/internal/ide-setup.d.ts +17 -7
  97. package/dist/internal/ide-setup.js +379 -46
  98. package/dist/internal/inventory-sync.d.ts +74 -0
  99. package/dist/internal/inventory-sync.js +189 -0
  100. package/dist/internal/org-picker.d.ts +11 -0
  101. package/dist/internal/org-picker.js +34 -0
  102. package/dist/internal/orgs.js +0 -1
  103. package/dist/internal/packs/cache.d.ts +8 -0
  104. package/dist/internal/packs/cache.js +60 -0
  105. package/dist/internal/packs/index.d.ts +10 -0
  106. package/dist/internal/packs/index.js +8 -0
  107. package/dist/internal/packs/integrity.d.ts +9 -0
  108. package/dist/internal/packs/integrity.js +24 -0
  109. package/dist/internal/packs/jcs.d.ts +2 -0
  110. package/dist/internal/packs/jcs.js +57 -0
  111. package/dist/internal/packs/local-paths.d.ts +4 -0
  112. package/dist/internal/packs/local-paths.js +26 -0
  113. package/dist/internal/packs/npm-registry-client.d.ts +14 -0
  114. package/dist/internal/packs/npm-registry-client.js +87 -0
  115. package/dist/internal/packs/registry-client.d.ts +9 -0
  116. package/dist/internal/packs/registry-client.js +25 -0
  117. package/dist/internal/packs/resolve.d.ts +8 -0
  118. package/dist/internal/packs/resolve.js +89 -0
  119. package/dist/internal/packs/semver.d.ts +9 -0
  120. package/dist/internal/packs/semver.js +57 -0
  121. package/dist/internal/packs/signing.d.ts +5 -0
  122. package/dist/internal/packs/signing.js +56 -0
  123. package/dist/internal/packs/types.d.ts +77 -0
  124. package/dist/internal/packs/types.js +20 -0
  125. package/dist/internal/project-binding.d.ts +1 -0
  126. package/dist/internal/project-binding.js +16 -11
  127. package/dist/internal/resolve-init-session.d.ts +8 -0
  128. package/dist/internal/resolve-init-session.js +36 -0
  129. package/dist/internal/run-workdir.d.ts +2 -0
  130. package/dist/internal/run-workdir.js +19 -0
  131. package/dist/internal/security-context.d.ts +63 -0
  132. package/dist/internal/security-context.js +50 -0
  133. package/dist/internal/sync-outbox.d.ts +40 -0
  134. package/dist/internal/sync-outbox.js +66 -0
  135. package/dist/internal/target-auth/api-config.d.ts +0 -17
  136. package/dist/internal/target-auth/api-config.js +0 -18
  137. package/dist/internal/target-auth/config.d.ts +0 -12
  138. package/dist/internal/target-auth/config.js +0 -13
  139. package/dist/internal/target-auth/index.d.ts +0 -18
  140. package/dist/internal/target-auth/index.js +0 -19
  141. package/dist/internal/target-auth/keychain.d.ts +0 -18
  142. package/dist/internal/target-auth/keychain.js +3 -23
  143. package/dist/internal/target-auth/types.d.ts +0 -26
  144. package/dist/internal/target-auth/types.js +0 -10
  145. package/dist/internal/target-auth/workos.d.ts +0 -15
  146. package/dist/internal/target-auth/workos.js +9 -18
  147. package/dist/internal/test-scope.d.ts +99 -0
  148. package/dist/internal/test-scope.js +101 -0
  149. package/dist/internal/threatmodel-run-state.d.ts +15 -0
  150. package/dist/internal/threatmodel-run-state.js +43 -0
  151. package/dist/internal/threatmodel-run.d.ts +85 -0
  152. package/dist/internal/threatmodel-run.js +159 -0
  153. package/dist/internal/trust-room-link.d.ts +10 -0
  154. package/dist/internal/trust-room-link.js +15 -0
  155. package/dist/internal/zivis-local-state.d.ts +1 -0
  156. package/dist/internal/zivis-local-state.js +22 -0
  157. package/package.json +21 -5
  158. package/dist/commands/app/index.js.map +0 -1
  159. package/dist/commands/auth/index.js.map +0 -1
  160. package/dist/commands/auth/init.js.map +0 -1
  161. package/dist/commands/auth/login.js.map +0 -1
  162. package/dist/commands/auth/logout.js.map +0 -1
  163. package/dist/commands/auth/sessions.js.map +0 -1
  164. package/dist/commands/auth/status.js.map +0 -1
  165. package/dist/commands/credits/index.js.map +0 -1
  166. package/dist/commands/mcp/index.js.map +0 -1
  167. package/dist/commands/open/index.js.map +0 -1
  168. package/dist/commands/scan/index.js.map +0 -1
  169. package/dist/commands/target/index.js.map +0 -1
  170. package/dist/commands/tm/index.d.ts +0 -2
  171. package/dist/commands/tm/index.js +0 -266
  172. package/dist/commands/tm/index.js.map +0 -1
  173. package/dist/index.js.map +0 -1
  174. package/dist/internal/extractor/adapters/go/index.js.map +0 -1
  175. package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
  176. package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
  177. package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
  178. package/dist/internal/extractor/adapters/python/index.js.map +0 -1
  179. package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
  180. package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
  181. package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
  182. package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
  183. package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
  184. package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
  185. package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
  186. package/dist/internal/extractor/index.js.map +0 -1
  187. package/dist/internal/extractor/types.js.map +0 -1
  188. package/dist/internal/extractor/walker.js.map +0 -1
  189. package/dist/internal/ide-setup.js.map +0 -1
  190. package/dist/internal/orgs.js.map +0 -1
  191. package/dist/internal/project-binding.js.map +0 -1
  192. package/dist/internal/target-auth/api-config.js.map +0 -1
  193. package/dist/internal/target-auth/config.js.map +0 -1
  194. package/dist/internal/target-auth/index.js.map +0 -1
  195. package/dist/internal/target-auth/keychain.js.map +0 -1
  196. package/dist/internal/target-auth/types.js.map +0 -1
  197. package/dist/internal/target-auth/workos.js.map +0 -1
@@ -1,4 +1,6 @@
1
1
  export interface InitCmdOpts {
2
2
  session?: string;
3
+ skipMintKey?: boolean;
4
+ application?: string;
3
5
  }
4
6
  export declare function initCommand(opts: InitCmdOpts): Promise<void>;
@@ -1,12 +1,23 @@
1
1
  import * as readline from "node:readline/promises";
2
- import { login, getStorageName, saveSessionEntry, loadTokens } from "@zivis/mcp/auth";
3
- import { DEFAULT_CONFIG } from "@zivis/mcp/types";
2
+ import { login, getStorageName, saveSessionEntry } from "@zivis/mcp/auth";
3
+ import { credentialStorageFromConfig, DEFAULT_CONFIG } from "@zivis/mcp/types";
4
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
4
5
  import { fetchUserOrgs, extractEmail } from "../../internal/orgs.js";
5
6
  import { ensureProjectBinding } from "../../internal/project-binding.js";
6
- /** Parse "received org_01XXX" out of the JWT mismatch error message. */
7
- function parseReceivedOrgId(msg) {
8
- const m = msg.match(/received (org_\w+)/);
9
- return m ? m[1] : null;
7
+ import { ensureApplicationBinding } from "../../internal/application-binding.js";
8
+ import { discardOauthAfterLogin, tryAutoMintBindingTokenAfterLogin, } from "../../internal/binding-token-mint.js";
9
+ import { decodeJwtPayload } from "@zivis/mcp/auth/jwt";
10
+ import { detectIdesInRepo, writeGuidance } from "../../internal/ide-setup.js";
11
+ import { buildLinkagePrompt } from "../../internal/github-linkage-prompt.js";
12
+ import { resolveExistingSessionAuth } from "../../internal/resolve-init-session.js";
13
+ async function maybePromptGitHubLinkage(config) {
14
+ try {
15
+ const msg = await buildLinkagePrompt({ config });
16
+ if (msg)
17
+ console.log(msg);
18
+ }
19
+ catch {
20
+ }
10
21
  }
11
22
  async function prompt(question) {
12
23
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
@@ -17,119 +28,260 @@ async function prompt(question) {
17
28
  rl.close();
18
29
  }
19
30
  }
31
+ async function offerGuidanceInstall(cwd) {
32
+ const detectedIdes = detectIdesInRepo(cwd);
33
+ const otherIdes = detectedIdes.filter((ide) => ide !== "claude-code-cli");
34
+ const targets = ["claude-code-cli", ...otherIdes];
35
+ const ideLabels = {
36
+ cursor: "Cursor",
37
+ "claude-code-cli": "Claude Code (CLAUDE.md) / Codex (AGENTS.md)",
38
+ "vscode-copilot": "VS Code (Copilot)",
39
+ "vscode-claude": "VS Code (Claude)",
40
+ cline: "Cline",
41
+ windsurf: "Windsurf",
42
+ };
43
+ const ideNames = targets.map((ide) => ideLabels[ide] ?? ide).join(", ");
44
+ console.log(`\nSet up AI agent guidance for: ${ideNames}`);
45
+ const ans = await prompt("Add ZIVIS guidance so your AI agent knows to run `zivis test` / `zivis threatmodel` when asked?\n" +
46
+ "(Adds a small managed block — your own content is preserved, remove anytime with `zivis mcp uninstall`.) [Y/n] ");
47
+ if (ans.toLowerCase() === "n" || ans.toLowerCase() === "no") {
48
+ console.log(" Skipped. Run 'zivis mcp install --client <ide> --with-guidance' anytime to add later.");
49
+ return;
50
+ }
51
+ const written = [];
52
+ for (const ide of targets) {
53
+ try {
54
+ const files = writeGuidance({ cwd, client: ide });
55
+ written.push(...files);
56
+ }
57
+ catch (err) {
58
+ console.warn(` Warning: Could not write guidance for ${ide}: ${err instanceof Error ? err.message : err}`);
59
+ }
60
+ }
61
+ if (written.length > 0) {
62
+ console.log(`✓ Wrote guidance files:`);
63
+ for (const f of written)
64
+ console.log(` ${f}`);
65
+ }
66
+ }
20
67
  export async function initCommand(opts) {
21
- const config = DEFAULT_CONFIG;
68
+ const config = {
69
+ ...DEFAULT_CONFIG,
70
+ ...resolveConfigFromBinding(process.cwd()),
71
+ };
22
72
  const session = opts.session || "default";
23
- // Reuse a stored token if one exists — avoids an extra browser open
24
- let bootstrapToken = null;
25
- const stored = await loadTokens(session);
26
- if (stored) {
27
- bootstrapToken = stored.access_token;
28
- const email = extractEmail(stored.id_token);
29
- console.log(`Using existing session (${email}).`);
30
- console.log(`To force re-login first: zivis auth logout && zivis auth init\n`);
73
+ const envLabel = config.apiBaseUrl.includes("staging") ? "staging" : "production";
74
+ console.log(`Environment: ${envLabel} (${config.apiBaseUrl})`);
75
+ console.log("");
76
+ let tokens;
77
+ let existingOrgId;
78
+ let existingEmail;
79
+ const existing = await resolveExistingSessionAuth(config, session, process.cwd());
80
+ if (existing) {
81
+ console.log("Existing session found — reusing stored credentials.");
82
+ console.log("(Run `zivis auth logout` first to switch accounts.)\n");
83
+ tokens = { access_token: existing.accessToken, id_token: existing.idToken };
84
+ existingOrgId = existing.organizationId;
85
+ existingEmail = existing.email;
31
86
  }
32
87
  else {
33
- console.log("Step 1/2: Authenticate (browser will open)...");
88
+ console.log("Opening browser for authentication...");
89
+ console.log("Log into the org you want to bind this project to.\n");
34
90
  try {
35
- const tokens = await login(config, session, undefined, { skipConfirm: true });
36
- bootstrapToken = tokens.access_token;
37
- const email = extractEmail(tokens.id_token);
38
- console.log(`✓ Authenticated as ${email}`);
91
+ tokens = await login(config, session, undefined, { skipConfirm: true });
39
92
  }
40
93
  catch (err) {
41
94
  console.error("Login failed:", err instanceof Error ? err.message : err);
42
95
  process.exit(1);
43
96
  }
44
97
  }
45
- // Fetch the user's orgs
46
- let orgs;
47
- try {
48
- orgs = await fetchUserOrgs(config, bootstrapToken);
49
- }
50
- catch (err) {
51
- console.error("Failed to fetch organizations:", err instanceof Error ? err.message : err);
52
- process.exit(1);
53
- }
54
- if (orgs.length === 0) {
55
- console.error("No organizations found for this account. Contact support.");
56
- process.exit(1);
57
- }
58
- let selectedOrg = orgs[0];
59
- if (orgs.length > 1) {
60
- console.log("Your organizations:");
61
- orgs.forEach((org, i) => console.log(` ${i + 1}. ${org.name}`));
62
- const answer = await prompt(`\nSelect org [1-${orgs.length}]: `);
63
- const idx = parseInt(answer, 10) - 1;
64
- if (isNaN(idx) || idx < 0 || idx >= orgs.length) {
65
- console.error("Invalid selection.");
66
- process.exit(1);
98
+ const email = existingEmail ?? extractEmail(tokens.id_token);
99
+ const claims = decodeJwtPayload(tokens.access_token);
100
+ const jwtOrgId = existingOrgId ?? claims?.org_id;
101
+ if (jwtOrgId) {
102
+ let orgName;
103
+ try {
104
+ const orgs = await fetchUserOrgs(config, tokens.access_token);
105
+ orgName = orgs.find((o) => o.workosOrgId === jwtOrgId)?.name;
106
+ if (!orgName && orgs.length > 0) {
107
+ console.log(`\n ⚠ WorkOS org ${jwtOrgId} is not registered in ZIVIS.`);
108
+ console.log(` Your ZIVIS orgs: ${orgs.map((o) => `${o.name} (${o.workosOrgId})`).join(", ")}`);
109
+ console.log(` Try logging into ${config.apiBaseUrl} in your browser first, then re-run init.\n`);
110
+ }
111
+ }
112
+ catch (e) {
113
+ console.error(` (note: org name lookup failed — ${e instanceof Error ? e.message : e})`);
114
+ }
115
+ const orgLabel = orgName ?? `unknown org (${jwtOrgId})`;
116
+ console.log(`\nAuthenticated as: ${email}`);
117
+ console.log(`Org: ${orgLabel}`);
118
+ console.log(`Environment: ${envLabel} (${config.apiBaseUrl})\n`);
119
+ const confirm = await prompt(`Bind this project to "${orgLabel}"? [Y/n] `);
120
+ if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
121
+ console.log("");
122
+ const retry = await prompt("Retry login in a private/incognito window? [Y/n] ");
123
+ if (retry.toLowerCase() !== "n" && retry.toLowerCase() !== "no") {
124
+ console.log("\nOpening a private/incognito window — log into the correct org...\n");
125
+ let retryTokens;
126
+ try {
127
+ retryTokens = await login(config, session, undefined, { skipConfirm: true, incognito: true });
128
+ }
129
+ catch (err) {
130
+ console.error("Login failed:", err instanceof Error ? err.message : err);
131
+ process.exit(1);
132
+ }
133
+ tokens = retryTokens;
134
+ const retryClaims = decodeJwtPayload(retryTokens.access_token);
135
+ const retryOrgId = retryClaims?.org_id;
136
+ if (!retryOrgId) {
137
+ console.error("JWT returned no org scope. Try `zivis auth logout` then `zivis auth init` again.");
138
+ process.exit(1);
139
+ }
140
+ let retryOrgName;
141
+ try {
142
+ const retryOrgs = await fetchUserOrgs(config, retryTokens.access_token);
143
+ retryOrgName = retryOrgs.find((o) => o.workosOrgId === retryOrgId)?.name;
144
+ }
145
+ catch (e) {
146
+ console.error(` (note: org name lookup failed — ${e instanceof Error ? e.message : e})`);
147
+ }
148
+ const retryLabel = retryOrgName ?? retryOrgId;
149
+ const retryEmail = extractEmail(retryTokens.id_token);
150
+ console.log(`\nAuthenticated as: ${retryEmail}`);
151
+ console.log(`Org: ${retryLabel}\n`);
152
+ const retryConfirm = await prompt(`Bind this project to "${retryLabel}"? [Y/n] `);
153
+ if (retryConfirm.toLowerCase() === "n" || retryConfirm.toLowerCase() === "no") {
154
+ console.log("Aborted.");
155
+ process.exit(0);
156
+ }
157
+ const storageName2 = getStorageName();
158
+ await saveSessionEntry(session, retryEmail, credentialStorageFromConfig(config));
159
+ console.log(`\n✓ Logged in as ${retryEmail} → ${retryLabel}`);
160
+ console.log(`✓ Credentials stored in ${storageName2}`);
161
+ await ensureProjectBinding({
162
+ config, session, accessToken: retryTokens.access_token,
163
+ organizationId: retryOrgId, cwd: process.cwd(), autoBind: true, skipBind: false, force: true,
164
+ });
165
+ await ensureApplicationBinding({ config, accessToken: retryTokens.access_token, cwd: process.cwd(), explicitApplication: opts.application });
166
+ await tryAutoMintBindingTokenAfterLogin({
167
+ cwd: process.cwd(),
168
+ sessionName: session,
169
+ accessToken: retryTokens.access_token,
170
+ skip: opts.skipMintKey === true,
171
+ warnOnFailure: true,
172
+ });
173
+ await discardOauthAfterLogin({ session, config });
174
+ await offerGuidanceInstall(process.cwd());
175
+ await maybePromptGitHubLinkage(config);
176
+ process.exit(0);
177
+ }
178
+ console.log("\nAborted. Switch orgs in your browser then run `zivis auth logout && zivis auth init`.");
179
+ process.exit(0);
67
180
  }
68
- selectedOrg = orgs[idx];
181
+ const storageName = getStorageName();
182
+ await saveSessionEntry(session, email, credentialStorageFromConfig(config));
183
+ console.log(`\n✓ Logged in as ${email} → ${orgLabel}`);
184
+ console.log(`✓ Credentials stored in ${storageName}`);
185
+ await ensureProjectBinding({
186
+ config,
187
+ session,
188
+ accessToken: tokens.access_token,
189
+ organizationId: jwtOrgId,
190
+ cwd: process.cwd(),
191
+ autoBind: true,
192
+ skipBind: false,
193
+ force: true,
194
+ });
195
+ await ensureApplicationBinding({ config, accessToken: tokens.access_token, cwd: process.cwd(), explicitApplication: opts.application });
196
+ await tryAutoMintBindingTokenAfterLogin({
197
+ cwd: process.cwd(),
198
+ sessionName: session,
199
+ accessToken: tokens.access_token,
200
+ skip: opts.skipMintKey === true,
201
+ warnOnFailure: true,
202
+ });
203
+ await discardOauthAfterLogin({ session, config });
204
+ await offerGuidanceInstall(process.cwd());
205
+ await maybePromptGitHubLinkage(config);
69
206
  }
70
207
  else {
71
- console.log(`One org found: ${selectedOrg.name}`);
72
- }
73
- if (!selectedOrg.workosOrgId) {
74
- console.error(`Org "${selectedOrg.name}" has no WorkOS organization ID — cannot scope login.`);
75
- process.exit(1);
76
- }
77
- // Attempt org-scoped login, with graceful handling for browser-session mismatches
78
- let scopedTokens;
79
- let finalOrgId = selectedOrg.workosOrgId;
80
- let finalOrgName = selectedOrg.name;
81
- for (let attempt = 0; attempt < 2; attempt++) {
82
- console.log(`\n${stored ? "Step 1/1" : "Step 2/2"}: Logging in to ${finalOrgName} (browser will open)...`);
208
+ console.log("\nYour account isn't scoped to a specific org yet.");
209
+ console.log("Fetching your organizations...\n");
210
+ let orgs;
83
211
  try {
84
- scopedTokens = await login(config, session, finalOrgId, {
85
- orgName: finalOrgName,
86
- skipConfirm: true,
87
- });
88
- break; // success
212
+ orgs = await fetchUserOrgs(config, tokens.access_token);
89
213
  }
90
214
  catch (err) {
91
- const msg = err instanceof Error ? err.message : String(err);
92
- const receivedOrgId = parseReceivedOrgId(msg);
93
- if (!receivedOrgId) {
94
- console.error("Login failed:", msg);
215
+ console.error("Failed to fetch organizations:", err instanceof Error ? err.message : err);
216
+ process.exit(1);
217
+ }
218
+ if (orgs.length === 0) {
219
+ console.error("No organizations found. Contact support.");
220
+ process.exit(1);
221
+ }
222
+ if (orgs.length === 1) {
223
+ console.log(`One org found: ${orgs[0].name}. Re-authenticating scoped to that org...`);
224
+ }
225
+ else {
226
+ console.log("Your organizations:");
227
+ orgs.forEach((org, i) => console.log(` ${i + 1}. ${org.name}`));
228
+ const answer = await prompt(`\nSelect org to bind this project to [1-${orgs.length}]: `);
229
+ const idx = parseInt(answer, 10) - 1;
230
+ if (isNaN(idx) || idx < 0 || idx >= orgs.length) {
231
+ console.error("Invalid selection.");
95
232
  process.exit(1);
96
233
  }
97
- // Browser authenticated to a different org than selected
98
- const receivedOrg = orgs.find((o) => o.workosOrgId === receivedOrgId);
99
- const receivedName = receivedOrg?.name ?? receivedOrgId;
100
- console.error(`\n⚠ Browser authenticated to "${receivedName}" instead of "${finalOrgName}".`);
101
- console.error(` This happens when your browser's active session is for a different org.\n`);
102
- if (attempt >= 1) {
103
- console.error("Still mismatched after retry. Run `zivis auth init` again after switching orgs in your browser.");
234
+ const selected = orgs[idx];
235
+ if (!selected.workosOrgId) {
236
+ console.error(`Org "${selected.name}" has no WorkOS org ID.`);
104
237
  process.exit(1);
105
238
  }
106
- const choice = await prompt(`Use "${receivedName}" instead? [Y/n] `);
107
- if (choice.toLowerCase() === "n" || choice.toLowerCase() === "no") {
108
- console.log("Aborted. Log into the correct org in your browser then run `zivis auth init` again.");
109
- process.exit(0);
239
+ console.log(`\nRe-opening browser scoped to ${selected.name}...`);
240
+ console.log("(Tip: if your browser logs into the wrong org, use option 2 to get a URL for a private/incognito window)\n");
241
+ try {
242
+ const scopedTokens = await login(config, session, selected.workosOrgId, {
243
+ orgName: selected.name,
244
+ skipConfirm: true,
245
+ });
246
+ const storageName2 = getStorageName();
247
+ await saveSessionEntry(session, email, credentialStorageFromConfig(config));
248
+ console.log(`\n✓ Logged in as ${email} → ${selected.name}`);
249
+ console.log(`✓ Credentials stored in ${storageName2}`);
250
+ await ensureProjectBinding({
251
+ config,
252
+ session,
253
+ accessToken: scopedTokens.access_token,
254
+ organizationId: selected.workosOrgId,
255
+ cwd: process.cwd(),
256
+ autoBind: true,
257
+ skipBind: false,
258
+ force: true,
259
+ });
260
+ await ensureApplicationBinding({ config, accessToken: scopedTokens.access_token, cwd: process.cwd(), explicitApplication: opts.application });
261
+ await tryAutoMintBindingTokenAfterLogin({
262
+ cwd: process.cwd(),
263
+ sessionName: session,
264
+ accessToken: scopedTokens.access_token,
265
+ skip: opts.skipMintKey === true,
266
+ warnOnFailure: true,
267
+ });
268
+ await discardOauthAfterLogin({ session, config });
269
+ await offerGuidanceInstall(process.cwd());
270
+ }
271
+ catch (err) {
272
+ const msg = err instanceof Error ? err.message : String(err);
273
+ const m = msg.match(/received (org_\w+)/);
274
+ if (m) {
275
+ console.error(`\n⚠ Browser authenticated to a different org than selected.`);
276
+ console.error(` Your browser has an active session for a different org.\n`);
277
+ console.error(` To fix: run \`zivis auth init\` again and paste the URL into a private/incognito window.`);
278
+ }
279
+ else {
280
+ console.error("Login failed:", msg);
281
+ }
282
+ process.exit(1);
110
283
  }
111
- // Retry scoped to what the browser actually has
112
- finalOrgId = receivedOrgId;
113
- finalOrgName = receivedName;
114
284
  }
115
285
  }
116
- if (!scopedTokens) {
117
- process.exit(1);
118
- }
119
- const email = extractEmail(scopedTokens.id_token);
120
- const storageName = getStorageName();
121
- await saveSessionEntry(session, email);
122
- console.log(`\n✓ Logged in as ${email} → ${finalOrgName}`);
123
- console.log(`✓ Credentials stored in ${storageName}`);
124
- await ensureProjectBinding({
125
- config,
126
- session,
127
- accessToken: scopedTokens.access_token,
128
- organizationId: finalOrgId,
129
- cwd: process.cwd(),
130
- autoBind: true,
131
- skipBind: false,
132
- });
133
286
  process.exit(0);
134
287
  }
135
- //# sourceMappingURL=init.js.map
@@ -4,5 +4,7 @@ export interface LoginCmdOpts {
4
4
  yes?: boolean;
5
5
  bind?: boolean;
6
6
  noBind?: boolean;
7
+ rebind?: boolean;
8
+ skipMintKey?: boolean;
7
9
  }
8
10
  export declare function loginCommand(opts: LoginCmdOpts): Promise<void>;
@@ -1,60 +1,161 @@
1
+ import * as readline from "node:readline/promises";
1
2
  import { login } from "@zivis/mcp/auth";
2
3
  import { getStorageName } from "@zivis/mcp/auth";
3
4
  import { saveSessionEntry } from "@zivis/mcp/auth";
4
- import { detectProjectBinding } from "@zivis/mcp/project-binding";
5
- import { DEFAULT_CONFIG } from "@zivis/mcp/types";
5
+ import { detectProjectBinding, resolveConfigFromBinding } from "@zivis/mcp/project-binding";
6
+ import { credentialStorageFromConfig, DEFAULT_CONFIG } from "@zivis/mcp/types";
6
7
  import { extractEmail } from "../../internal/orgs.js";
8
+ import { pickOrg } from "../../internal/org-picker.js";
7
9
  import { ensureProjectBinding } from "../../internal/project-binding.js";
8
- import { setupVsCodeClaude, setupVsCodeCopilot, setupClaudeCodeCli, } from "../../internal/ide-setup.js";
10
+ import { discardOauthAfterLogin, tryAutoMintBindingTokenAfterLogin, } from "../../internal/binding-token-mint.js";
11
+ import { setupCursorWorkspace, setupVsCodeClaude, setupVsCodeCopilot, setupClaudeCodeCli, } from "../../internal/ide-setup.js";
12
+ import { buildLinkagePrompt } from "../../internal/github-linkage-prompt.js";
13
+ async function confirm(question) {
14
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
15
+ try {
16
+ const answer = (await rl.question(question)).trim().toLowerCase();
17
+ return answer !== "n" && answer !== "no";
18
+ }
19
+ finally {
20
+ rl.close();
21
+ }
22
+ }
9
23
  export async function loginCommand(opts) {
10
- const config = DEFAULT_CONFIG;
24
+ const config = {
25
+ ...DEFAULT_CONFIG,
26
+ ...resolveConfigFromBinding(process.cwd()),
27
+ };
11
28
  const detected = detectProjectBinding(process.cwd());
12
29
  const session = opts.session || detected?.binding.session || config.session;
13
- let organizationId = opts.org;
14
- if (!organizationId && detected?.binding.workosOrgId) {
30
+ const sessionName = session || "default";
31
+ let organizationId;
32
+ let orgName;
33
+ let writeMode = "no-write";
34
+ if (opts.org) {
35
+ organizationId = opts.org;
36
+ orgName = detected?.binding.orgName && detected.binding.workosOrgId === opts.org
37
+ ? detected.binding.orgName
38
+ : undefined;
39
+ if (opts.rebind) {
40
+ writeMode = "replace";
41
+ }
42
+ }
43
+ else if (opts.rebind) {
44
+ const picked = await runIdentityThenPicker(config, sessionName, {
45
+ currentOrgId: detected?.binding.workosOrgId ?? undefined,
46
+ header: detected?.binding.workosOrgId
47
+ ? "Switch this project to which org?"
48
+ : "Your organizations:",
49
+ });
50
+ if (detected?.binding.workosOrgId && detected.binding.workosOrgId === picked.workosOrgId) {
51
+ console.log(`Project is already bound to ${picked.name}. Nothing to change.`);
52
+ process.exit(0);
53
+ }
54
+ if (detected?.binding.workosOrgId) {
55
+ const fromLabel = detected.binding.orgName
56
+ ? `${detected.binding.orgName} (${detected.binding.workosOrgId})`
57
+ : detected.binding.workosOrgId;
58
+ const toLabel = `${picked.name} (${picked.workosOrgId})`;
59
+ if (!opts.yes) {
60
+ const ok = await confirm(`Change project binding from "${fromLabel}" to "${toLabel}"? [Y/n] `);
61
+ if (!ok) {
62
+ console.log("Aborted.");
63
+ process.exit(0);
64
+ }
65
+ }
66
+ writeMode = "replace";
67
+ }
68
+ else {
69
+ writeMode = "create";
70
+ }
71
+ organizationId = picked.workosOrgId;
72
+ orgName = picked.name;
73
+ }
74
+ else if (detected?.binding.workosOrgId) {
15
75
  organizationId = detected.binding.workosOrgId;
16
- console.log(`Using org from project binding: ${organizationId}${detected.binding.orgName ? ` (${detected.binding.orgName})` : ""}`);
76
+ orgName = detected.binding.orgName;
77
+ console.log(`Using org from project binding: ${organizationId}${orgName ? ` (${orgName})` : ""}`);
78
+ }
79
+ else {
80
+ const picked = await runIdentityThenPicker(config, sessionName, {
81
+ header: "Your organizations:",
82
+ });
83
+ organizationId = picked.workosOrgId;
84
+ orgName = picked.name;
85
+ writeMode = "create";
86
+ }
87
+ if (!organizationId) {
88
+ console.error("Internal error: no organization resolved for login.");
89
+ process.exit(1);
17
90
  }
18
91
  if (detected?.binding.session && !opts.session) {
19
92
  console.log(`Using session from project binding: ${session}`);
20
93
  }
21
94
  try {
22
95
  const tokens = await login(config, session, organizationId, {
23
- orgName: detected?.binding.orgName,
24
- skipConfirm: opts.yes === true,
96
+ orgName,
97
+ skipConfirm: opts.yes === true || writeMode !== "no-write",
25
98
  });
26
99
  const storageName = getStorageName();
27
100
  const email = extractEmail(tokens.id_token);
28
- const sessionName = session || "default";
29
- await saveSessionEntry(sessionName, email);
101
+ await saveSessionEntry(sessionName, email, credentialStorageFromConfig(config));
30
102
  const sessionLabel = session ? ` (session: ${session})` : "";
31
103
  console.log(`✓ Logged in as ${email}${sessionLabel}`);
32
104
  console.log(`✓ Credentials stored in ${storageName}`);
33
- if (organizationId) {
34
- console.log(`✓ JWT scoped to organization: ${organizationId}`);
105
+ console.log(`✓ JWT scoped to organization: ${organizationId}${orgName ? ` (${orgName})` : ""}`);
106
+ try {
107
+ await setupCursorWorkspace();
108
+ }
109
+ catch (err) {
110
+ console.log(` WARNING: Failed to update .cursor/mcp.json: ${err instanceof Error ? err.message : err}`);
35
111
  }
36
112
  try {
37
- await setupVsCodeClaude(session);
38
- await setupVsCodeCopilot(session);
113
+ await setupVsCodeClaude();
114
+ await setupVsCodeCopilot();
39
115
  }
40
116
  catch (err) {
41
117
  console.log(` WARNING: Failed to update .vscode/mcp.json: ${err instanceof Error ? err.message : err}`);
42
118
  }
43
119
  try {
44
- await setupClaudeCodeCli(session);
120
+ await setupClaudeCodeCli();
45
121
  }
46
122
  catch (err) {
47
123
  console.log(` WARNING: Failed to update Claude Code CLI config: ${err instanceof Error ? err.message : err}`);
48
124
  }
125
+ if (writeMode === "create" && !opts.yes && !opts.noBind) {
126
+ const orgLabel = orgName ? `${orgName} (${organizationId})` : organizationId;
127
+ const ok = await confirm(`Bind this project to ${orgLabel}? [Y/n] `);
128
+ if (!ok) {
129
+ console.log("Login succeeded but project was not bound.");
130
+ await discardOauthAfterLogin({ session, config });
131
+ process.exit(0);
132
+ }
133
+ }
49
134
  await ensureProjectBinding({
50
135
  config,
51
136
  session: sessionName,
52
137
  accessToken: tokens.access_token,
53
138
  organizationId,
54
139
  cwd: process.cwd(),
55
- autoBind: opts.bind === true,
140
+ autoBind: opts.bind === true || writeMode !== "no-write",
56
141
  skipBind: opts.noBind === true,
142
+ force: writeMode === "replace",
57
143
  });
144
+ await tryAutoMintBindingTokenAfterLogin({
145
+ cwd: process.cwd(),
146
+ sessionName,
147
+ accessToken: tokens.access_token,
148
+ skip: opts.skipMintKey === true,
149
+ warnOnFailure: true,
150
+ });
151
+ await discardOauthAfterLogin({ session, config });
152
+ try {
153
+ const linkagePrompt = await buildLinkagePrompt({ config });
154
+ if (linkagePrompt)
155
+ console.log(linkagePrompt);
156
+ }
157
+ catch {
158
+ }
58
159
  process.exit(0);
59
160
  }
60
161
  catch (err) {
@@ -62,4 +163,21 @@ export async function loginCommand(opts) {
62
163
  process.exit(1);
63
164
  }
64
165
  }
65
- //# sourceMappingURL=login.js.map
166
+ async function runIdentityThenPicker(config, session, pickOpts) {
167
+ console.log("Opening browser to identify your account (no org scope yet)...");
168
+ let identity;
169
+ try {
170
+ identity = await login(config, session, undefined, { skipConfirm: true });
171
+ }
172
+ catch (err) {
173
+ console.error("Login failed:", err instanceof Error ? err.message : err);
174
+ process.exit(1);
175
+ }
176
+ try {
177
+ return await pickOrg(config, identity.access_token, pickOpts);
178
+ }
179
+ catch (err) {
180
+ console.error(err instanceof Error ? err.message : err);
181
+ process.exit(1);
182
+ }
183
+ }
@@ -1,14 +1,26 @@
1
- import { keychainClearAll, keychainClearSession } from "@zivis/mcp/auth";
1
+ import { keychainClearAll, keychainClearSession, keychainDelete, keychainRead } from "@zivis/mcp/auth";
2
2
  import { removeSessionEntry } from "@zivis/mcp/auth";
3
+ import { detectProjectBinding, resolveConfigFromBinding } from "@zivis/mcp/project-binding";
4
+ import { credentialStorageFromConfig } from "@zivis/mcp/types";
3
5
  export async function logoutCommand(opts) {
4
- if (opts.session) {
5
- await keychainClearSession(opts.session);
6
- await removeSessionEntry(opts.session);
7
- console.log(`✓ Session "${opts.session}" credentials cleared`);
6
+ const config = resolveConfigFromBinding(process.cwd());
7
+ const st = credentialStorageFromConfig(config);
8
+ const sessionName = opts.session ??
9
+ detectProjectBinding(process.cwd())?.binding.session;
10
+ if (sessionName) {
11
+ await keychainClearSession(sessionName, st);
12
+ const apiKeyAccount = `apikey.${sessionName}`;
13
+ const hadApiKey = !!(await keychainRead(apiKeyAccount, st).catch(() => null));
14
+ if (hadApiKey) {
15
+ await keychainDelete(apiKeyAccount, st);
16
+ console.log(`✓ Local Project Binding Token cleared for session "${sessionName}"`);
17
+ console.log(` Note: To revoke the key server-side, visit Settings → API Tokens in the ZIVIS UI.`);
18
+ }
19
+ await removeSessionEntry(sessionName, st);
20
+ console.log(`✓ Session "${sessionName}" credentials cleared`);
8
21
  return;
9
22
  }
10
- await keychainClearAll();
23
+ await keychainClearAll(st);
11
24
  console.log("✓ Default credentials cleared");
12
25
  console.log(" Note: Named sessions are not affected. Use 'zivis auth sessions' to list them.");
13
26
  }
14
- //# sourceMappingURL=logout.js.map
@@ -1,12 +1,16 @@
1
1
  import { listSessions, removeSessionEntry, keychainClearSession } from "@zivis/mcp/auth";
2
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
3
+ import { credentialStorageFromConfig } from "@zivis/mcp/types";
2
4
  export async function sessionsCommand(opts) {
5
+ const config = resolveConfigFromBinding(process.cwd());
6
+ const st = credentialStorageFromConfig(config);
3
7
  if (opts.remove) {
4
- await keychainClearSession(opts.remove);
5
- await removeSessionEntry(opts.remove);
8
+ await keychainClearSession(opts.remove, st);
9
+ await removeSessionEntry(opts.remove, st);
6
10
  console.log(`✓ Session "${opts.remove}" removed`);
7
11
  return;
8
12
  }
9
- const sessions = await listSessions();
13
+ const sessions = await listSessions(st);
10
14
  const activeSession = process.env.ZIVIS_SESSION;
11
15
  if (sessions.length === 0) {
12
16
  console.log("No sessions stored.");
@@ -27,4 +31,3 @@ export async function sessionsCommand(opts) {
27
31
  console.log("To use a session in a project, add to .mcp.json:");
28
32
  console.log(' "env": { "ZIVIS_SESSION": "<session-name>" }');
29
33
  }
30
- //# sourceMappingURL=sessions.js.map