@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
@@ -0,0 +1,198 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { ApiClient } from "@zivis/mcp/api-client";
5
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
6
+ import { requireApplicationId } from "@zivis/mcp/resolve-application-id";
7
+ import { renderSecurityContext } from "../../internal/security-context.js";
8
+ import { startTestRun, listTestScopes, tryBuildChangeRecommendation, } from "../../internal/test-scope.js";
9
+ import { LocalPackRegistryClient, registryDirOverride } from "../../internal/packs/registry-client.js";
10
+ import { NpmPackRegistryClient } from "../../internal/packs/npm-registry-client.js";
11
+ import { PackUnavailableError, PackIntegrityError, UnknownScopeError } from "../../internal/packs/types.js";
12
+ import { jsonEnvelope, printJson, EXIT, exitCodeForError, errorMessage, failCommand } from "../../internal/cli-output.js";
13
+ class UsageError extends Error {
14
+ }
15
+ function buildRegistryClient() {
16
+ const override = registryDirOverride();
17
+ if (override)
18
+ return new LocalPackRegistryClient(override);
19
+ return new NpmPackRegistryClient();
20
+ }
21
+ function exitCodeForTestError(err) {
22
+ if (err instanceof UnknownScopeError || err instanceof PackIntegrityError)
23
+ return EXIT.INVALID_OR_INDETERMINATE;
24
+ if (err instanceof PackUnavailableError)
25
+ return EXIT.CLOUD_UNAVAILABLE;
26
+ return exitCodeForError(err);
27
+ }
28
+ function getCliVersion() {
29
+ try {
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../../package.json"), "utf-8"));
32
+ return pkg.version;
33
+ }
34
+ catch {
35
+ return "0.0.0";
36
+ }
37
+ }
38
+ export function registerTestCommand(program) {
39
+ program
40
+ .command("test [scope]")
41
+ .description("run a Zivis-guided AppSec review — change-aware by default once a prior run exists, full/adaptive on the first run or with --full, or targeted at a stable scope")
42
+ .option("--list", "list available scopes from the verified AppSec methodology pack")
43
+ .option("--app <id>", "Application id (defaults to the bound Application)")
44
+ .option("--full", "skip the change-aware recommendation and always run a full adaptive review")
45
+ .option("--json", "emit JSON only on stdout")
46
+ .action(async (scope, opts) => {
47
+ const json = !!opts.json;
48
+ const cwd = process.cwd();
49
+ const config = resolveConfigFromBinding(cwd);
50
+ const apiClient = new ApiClient(config);
51
+ const registryClient = buildRegistryClient();
52
+ const cliVersion = getCliVersion();
53
+ const deps = { apiClient, registryClient };
54
+ if (opts.list) {
55
+ try {
56
+ const listing = await listTestScopes(registryClient, cliVersion);
57
+ if (json) {
58
+ printJson(jsonEnvelope("test", { list: true, methodology: { pack: listing.pack, version: listing.version }, scopes: listing.scopes }));
59
+ }
60
+ else {
61
+ printListHuman(listing);
62
+ }
63
+ process.exit(EXIT.OK);
64
+ }
65
+ catch (err) {
66
+ failCommand("test", err, json);
67
+ }
68
+ return;
69
+ }
70
+ try {
71
+ const resolvedApp = requireApplicationId(opts.app, cwd);
72
+ if (!resolvedApp.ok) {
73
+ throw new UsageError(resolvedApp.message);
74
+ }
75
+ let precomputedContext;
76
+ if (!scope && !opts.full) {
77
+ const changeOutcome = await tryBuildChangeRecommendation(deps, { applicationId: resolvedApp.id, cwd, cliVersion });
78
+ if (changeOutcome.kind === "recommendation" || changeOutcome.kind === "up-to-date" || changeOutcome.kind === "no-scope-match") {
79
+ if (json) {
80
+ printJson(jsonEnvelope("test", changeOutcomeToJson(changeOutcome)));
81
+ }
82
+ else {
83
+ printChangeOutcomeHuman(changeOutcome);
84
+ }
85
+ process.exit(EXIT.OK);
86
+ }
87
+ precomputedContext = changeOutcome.contextOutcome;
88
+ if (!json && changeOutcome.kind === "sha-not-found") {
89
+ console.error(`[zivis] Last-tested commit ${shortSha(changeOutcome.sha)} not found locally (shallow clone, force-push, or unrelated history?) — running a full adaptive review.`);
90
+ }
91
+ }
92
+ const { envelope, resolvedScope, contextOutcome } = await startTestRun(deps, {
93
+ applicationId: resolvedApp.id,
94
+ scopeId: scope,
95
+ cwd,
96
+ cliVersion,
97
+ precomputedContext,
98
+ });
99
+ const output = jsonEnvelope("test", { ...envelope });
100
+ if (json) {
101
+ printJson(output);
102
+ }
103
+ else {
104
+ printRunHuman(envelope, resolvedScope, contextOutcome);
105
+ }
106
+ process.exit(EXIT.OK);
107
+ }
108
+ catch (err) {
109
+ const code = err instanceof UsageError ? EXIT.INVALID_OR_INDETERMINATE : exitCodeForTestError(err);
110
+ const message = errorMessage(err);
111
+ if (json) {
112
+ printJson(jsonEnvelope("test", { result: "error", scope: scope ?? null, error: message }));
113
+ }
114
+ else {
115
+ console.error(`zivis test: ${message}`);
116
+ if (err instanceof UnknownScopeError) {
117
+ console.error(`Run \`zivis test --list\` for descriptions.`);
118
+ }
119
+ }
120
+ process.exit(code);
121
+ }
122
+ });
123
+ }
124
+ function printListHuman(listing) {
125
+ const lines = [`ZIVIS AppSec methodology — ${listing.pack} v${listing.version}`, ""];
126
+ const idWidth = Math.max(...listing.scopes.map((s) => s.id.length), "scope".length);
127
+ for (const s of listing.scopes) {
128
+ lines.push(` ${s.id.padEnd(idWidth)} ${s.label} — ${s.description}`);
129
+ }
130
+ lines.push("", "Run `zivis test <scope>` for a focused review, or `zivis test` for a full adaptive review.");
131
+ console.log(lines.join("\n"));
132
+ }
133
+ function printRunHuman(envelope, resolvedScope, contextOutcome) {
134
+ console.log(`zivis test: started run ${envelope.run_id}${resolvedScope ? ` (scope: ${resolvedScope.id})` : " (full adaptive review)"}`);
135
+ console.log(renderSecurityContext(contextOutcome, "human"));
136
+ console.log(`\nMethodology: ${envelope.methodology.path}`);
137
+ if (envelope.methodology.scope) {
138
+ console.log(`Focused scope guidance: ${envelope.methodology.scope.path}`);
139
+ }
140
+ console.log(`\nWhen finished: ${envelope.result_contract.complete_command}`);
141
+ }
142
+ function shortSha(sha) {
143
+ return sha.slice(0, 8);
144
+ }
145
+ function joinWithAnd(items) {
146
+ if (items.length <= 1)
147
+ return items[0] ?? "";
148
+ if (items.length === 2)
149
+ return `${items[0]} and ${items[1]}`;
150
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
151
+ }
152
+ function filesLabel(count) {
153
+ return `${count} file${count === 1 ? "" : "s"}`;
154
+ }
155
+ function printChangeOutcomeHuman(outcome) {
156
+ switch (outcome.kind) {
157
+ case "up-to-date":
158
+ console.log(`zivis test: no files changed since last-tested commit ${shortSha(outcome.sha)}. Nothing to recommend.`);
159
+ console.log(`Run \`zivis test --full\` for a full adaptive review anyway, or \`zivis test <scope>\` for a specific one.`);
160
+ return;
161
+ case "no-scope-match":
162
+ console.log(`${filesLabel(outcome.changedFileCount)} changed since ${shortSha(outcome.sha)}, but none map to a known methodology scope.`);
163
+ console.log(`Run \`zivis test --full\` for a full adaptive review, \`zivis test <scope>\` for a specific one, or \`zivis test --list\` to see all scopes.`);
164
+ return;
165
+ case "recommendation": {
166
+ const { sha, changedFileCount, scopes, testIds } = outcome;
167
+ console.log(`${filesLabel(changedFileCount)} changed since ${shortSha(sha)}.`);
168
+ console.log(`Changes touch ${joinWithAnd(scopes.map((s) => s.label))}.`);
169
+ console.log("");
170
+ console.log("Recommended:");
171
+ for (const s of scopes)
172
+ console.log(` zivis test ${s.id}`);
173
+ if (testIds.length > 0) {
174
+ console.log("");
175
+ console.log(`${testIds.join(", ")} ${testIds.length === 1 ? "is" : "are"} especially relevant.`);
176
+ }
177
+ console.log("");
178
+ console.log(`Run \`zivis test --full\` for a full adaptive review instead.`);
179
+ return;
180
+ }
181
+ }
182
+ }
183
+ function changeOutcomeToJson(outcome) {
184
+ switch (outcome.kind) {
185
+ case "up-to-date":
186
+ return { result: "up_to_date", since: outcome.sha };
187
+ case "no-scope-match":
188
+ return { result: "no_scope_match", since: outcome.sha, changed_file_count: outcome.changedFileCount };
189
+ case "recommendation":
190
+ return {
191
+ result: "recommendation",
192
+ since: outcome.sha,
193
+ changed_file_count: outcome.changedFileCount,
194
+ scopes: outcome.scopes,
195
+ test_ids: outcome.testIds,
196
+ };
197
+ }
198
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerThreatmodelCommand(program: Command): void;
@@ -0,0 +1,167 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as fsSync from "node:fs";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { ApiClient } from "@zivis/mcp/api-client";
6
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
7
+ import { requireApplicationId } from "@zivis/mcp/resolve-application-id";
8
+ import { renderSecurityContext } from "../../internal/security-context.js";
9
+ import { startThreatModelRun, syncThreatModel, } from "../../internal/threatmodel-run.js";
10
+ import { LocalPackRegistryClient, registryDirOverride } from "../../internal/packs/registry-client.js";
11
+ import { NpmPackRegistryClient } from "../../internal/packs/npm-registry-client.js";
12
+ import { PackUnavailableError, PackIntegrityError } from "../../internal/packs/types.js";
13
+ import { jsonEnvelope, printJson, EXIT, exitCodeForError, errorMessage, failCommand } from "../../internal/cli-output.js";
14
+ class UsageError extends Error {
15
+ }
16
+ function buildRegistryClient() {
17
+ const override = registryDirOverride();
18
+ if (override)
19
+ return new LocalPackRegistryClient(override);
20
+ return new NpmPackRegistryClient();
21
+ }
22
+ function exitCodeForThreatModelError(err) {
23
+ if (err instanceof PackIntegrityError)
24
+ return EXIT.INVALID_OR_INDETERMINATE;
25
+ if (err instanceof PackUnavailableError)
26
+ return EXIT.CLOUD_UNAVAILABLE;
27
+ return exitCodeForError(err);
28
+ }
29
+ async function readMarkdownInput(inputArg) {
30
+ if (inputArg === "-")
31
+ return readStdin();
32
+ const content = await fs.readFile(inputArg, "utf-8");
33
+ if (content.trim().length === 0) {
34
+ throw new UsageError(`--input file "${inputArg}" is empty.`);
35
+ }
36
+ return content;
37
+ }
38
+ async function readStdin() {
39
+ let data = "";
40
+ process.stdin.setEncoding("utf-8");
41
+ for await (const chunk of process.stdin) {
42
+ data += chunk;
43
+ }
44
+ if (data.trim().length === 0) {
45
+ throw new UsageError("--input - read no content from stdin.");
46
+ }
47
+ return data;
48
+ }
49
+ export function registerThreatmodelCommand(program) {
50
+ const threatmodel = program
51
+ .command("threatmodel")
52
+ .description("start (or continue) a Zivis-guided living threat model for the bound Application")
53
+ .option("--app <id>", "Application id (defaults to the bound Application)")
54
+ .option("--json", "emit JSON only on stdout")
55
+ .action(async (opts) => {
56
+ const json = !!opts.json;
57
+ const cwd = process.cwd();
58
+ const config = resolveConfigFromBinding(cwd);
59
+ const apiClient = new ApiClient(config);
60
+ const registryClient = buildRegistryClient();
61
+ const cliVersion = getCliVersion();
62
+ try {
63
+ const resolvedApp = requireApplicationId(opts.app, cwd);
64
+ if (!resolvedApp.ok) {
65
+ throw new UsageError(resolvedApp.message);
66
+ }
67
+ const { envelope, contextOutcome } = await startThreatModelRun({ apiClient, registryClient }, { applicationId: resolvedApp.id, cwd, cliVersion });
68
+ const output = jsonEnvelope("threatmodel", { ...envelope });
69
+ if (json) {
70
+ printJson(output);
71
+ }
72
+ else {
73
+ printStartHuman(envelope, contextOutcome);
74
+ }
75
+ process.exit(EXIT.OK);
76
+ }
77
+ catch (err) {
78
+ const code = err instanceof UsageError ? EXIT.INVALID_OR_INDETERMINATE : exitCodeForThreatModelError(err);
79
+ const message = errorMessage(err);
80
+ if (json) {
81
+ printJson(jsonEnvelope("threatmodel", { result: "error", error: message }));
82
+ }
83
+ else {
84
+ console.error(`zivis threatmodel: ${message}`);
85
+ }
86
+ process.exit(code);
87
+ }
88
+ });
89
+ threatmodel
90
+ .command("sync")
91
+ .description("persist the finished Markdown threat model as a new Artifact revision and complete the run")
92
+ .requiredOption("--input <path>", "path to the finished Markdown threat model, or '-' for stdin")
93
+ .option("--run <runId>", "override the auto-resolved active threatmodel run")
94
+ .option("--change-note <text>", "optional human-readable summary of what changed")
95
+ .option("--app <id>", "Application id (defaults to the bound Application)")
96
+ .option("--json", "emit JSON only on stdout")
97
+ .action(async (opts) => {
98
+ const json = !!opts.json;
99
+ const cwd = process.cwd();
100
+ let content;
101
+ try {
102
+ content = await readMarkdownInput(opts.input);
103
+ }
104
+ catch (err) {
105
+ failCommand("threatmodel sync", err, json);
106
+ }
107
+ const config = resolveConfigFromBinding(cwd);
108
+ const apiClient = new ApiClient(config);
109
+ const registryClient = buildRegistryClient();
110
+ try {
111
+ const resolvedApp = requireApplicationId(opts.app, cwd);
112
+ if (!resolvedApp.ok) {
113
+ throw new UsageError(resolvedApp.message);
114
+ }
115
+ const result = await syncThreatModel({ apiClient, registryClient }, { applicationId: resolvedApp.id, runId: opts.run, cwd, content, changeNote: opts.changeNote });
116
+ const output = jsonEnvelope("threatmodel sync", {
117
+ run_id: result.runId,
118
+ run_outcome: result.runOutcome,
119
+ evidence_id: result.evidenceId,
120
+ version: result.version,
121
+ unchanged: result.unchanged,
122
+ });
123
+ if (json) {
124
+ printJson(output);
125
+ }
126
+ else {
127
+ console.log(result.unchanged
128
+ ? `zivis threatmodel sync: no change (content identical to revision ${result.version}); run ${result.runId} ${result.runOutcome}.`
129
+ : `zivis threatmodel sync: created revision ${result.version} (evidence ${result.evidenceId}); run ${result.runId} ${result.runOutcome}.`);
130
+ }
131
+ process.exit(EXIT.OK);
132
+ }
133
+ catch (err) {
134
+ const code = err instanceof UsageError ? EXIT.INVALID_OR_INDETERMINATE : exitCodeForThreatModelError(err);
135
+ const message = errorMessage(err);
136
+ if (json) {
137
+ printJson(jsonEnvelope("threatmodel sync", { result: "error", error: message }));
138
+ }
139
+ else {
140
+ console.error(`zivis threatmodel sync: ${message}`);
141
+ }
142
+ process.exit(code);
143
+ }
144
+ });
145
+ }
146
+ function getCliVersion() {
147
+ try {
148
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
149
+ const pkg = JSON.parse(fsSync.readFileSync(path.join(__dirname, "../../../package.json"), "utf-8"));
150
+ return pkg.version;
151
+ }
152
+ catch {
153
+ return "0.0.0";
154
+ }
155
+ }
156
+ function printStartHuman(envelope, contextOutcome) {
157
+ console.log(`zivis threatmodel: started run ${envelope.run_id}`);
158
+ console.log(renderSecurityContext(contextOutcome, "human"));
159
+ console.log(`\nMethodology: ${envelope.methodology.path}`);
160
+ if (envelope.prior_model?.exists) {
161
+ console.log(`Prior model: ${envelope.prior_model.path} (revision ${envelope.prior_model.version}, last modeled at ${envelope.prior_model.git_commit_sha ?? "unknown"})`);
162
+ }
163
+ else {
164
+ console.log("Prior model: none on record — this will be the first threat model for this Application.");
165
+ }
166
+ console.log(`\nWhen finished: ${envelope.result_contract.complete_command}`);
167
+ }
package/dist/index.js CHANGED
@@ -4,13 +4,22 @@ 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
- import { registerTmCommands } from "./commands/tm/index.js";
10
10
  import { registerScanCommands } from "./commands/scan/index.js";
11
+ import { registerCheckCommands } from "./commands/check/index.js";
12
+ import { registerInspectCommands } from "./commands/inspect/index.js";
11
13
  import { registerMcpCommands } from "./commands/mcp/index.js";
12
14
  import { registerCreditsCommands } from "./commands/credits/index.js";
13
15
  import { registerOpenCommands } from "./commands/open/index.js";
16
+ import { registerSyncCommands } from "./commands/sync/index.js";
17
+ import { registerAssuranceCommands } from "./commands/assurance/index.js";
18
+ import { registerShareCommands } from "./commands/share/index.js";
19
+ import { registerGateCommand } from "./commands/gate/index.js";
20
+ import { registerRunCommands } from "./commands/run/index.js";
21
+ import { registerTestCommand } from "./commands/test/index.js";
22
+ import { registerThreatmodelCommand } from "./commands/threatmodel/index.js";
14
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
24
  function getVersion() {
16
25
  try {
@@ -26,16 +35,32 @@ program
26
35
  .name("zivis")
27
36
  .description("ZIVIS CLI — threat modeling, scans, and MCP server")
28
37
  .version(getVersion(), "-v, --version", "show version");
38
+ program
39
+ .command("init")
40
+ .description("interactive setup: authenticate, pick org, and bind this repo (alias for `zivis auth init`)")
41
+ .option("-s, --session <name>", "named session for multi-tenant")
42
+ .option("--skip-mint-key", "skip MCP API key mint after successful OAuth")
43
+ .option("--application <name-or-id>", "select an existing Application by id or exact name (fails if not found — never creates one)")
44
+ .action(async (opts) => {
45
+ await authInitCommand(opts);
46
+ });
29
47
  registerAuthCommands(program);
48
+ registerCheckCommands(program);
30
49
  registerAppCommands(program);
31
50
  registerTargetCommands(program);
32
- registerTmCommands(program);
33
51
  registerScanCommands(program);
52
+ registerInspectCommands(program);
34
53
  registerMcpCommands(program);
35
54
  registerCreditsCommands(program);
36
55
  registerOpenCommands(program);
56
+ registerSyncCommands(program);
57
+ registerAssuranceCommands(program);
58
+ registerShareCommands(program);
59
+ registerGateCommand(program);
60
+ registerRunCommands(program);
61
+ registerTestCommand(program);
62
+ registerThreatmodelCommand(program);
37
63
  program.parseAsync(process.argv).catch((err) => {
38
64
  console.error(err instanceof Error ? err.message : err);
39
65
  process.exit(1);
40
66
  });
41
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,8 @@
1
+ import type { ZivisConfig } from "@zivis/mcp/types";
2
+ export interface EnsureApplicationBindingOpts {
3
+ config: ZivisConfig;
4
+ accessToken: string;
5
+ cwd: string;
6
+ explicitApplication?: string;
7
+ }
8
+ export declare function ensureApplicationBinding(opts: EnsureApplicationBindingOpts): Promise<void>;
@@ -0,0 +1,167 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as readline from "node:readline/promises";
4
+ import * as child_process from "node:child_process";
5
+ import { detectProjectBinding } from "@zivis/mcp/project-binding";
6
+ async function apiFetch(config, accessToken, method, path, body) {
7
+ const response = await fetch(`${config.apiBaseUrl}${path}`, {
8
+ method,
9
+ headers: {
10
+ Authorization: `Bearer ${accessToken}`,
11
+ "Content-Type": "application/json",
12
+ "User-Agent": "@zivis/cli",
13
+ },
14
+ ...(body ? { body: JSON.stringify(body) } : {}),
15
+ });
16
+ if (!response.ok) {
17
+ const errorBody = (await response.json().catch(() => ({})));
18
+ throw new Error(errorBody.message || errorBody.error || `HTTP ${response.status}`);
19
+ }
20
+ return (await response.json());
21
+ }
22
+ async function fetchApplications(config, accessToken) {
23
+ const result = await apiFetch(config, accessToken, "GET", "/api/rt/applications?limit=100");
24
+ return result.applications;
25
+ }
26
+ async function createApplication(config, accessToken, name) {
27
+ const result = await apiFetch(config, accessToken, "POST", "/api/rt/applications", { name, placeholder: true });
28
+ return result.application;
29
+ }
30
+ function defaultApplicationName(cwd) {
31
+ try {
32
+ const remote = child_process
33
+ .execSync("git remote get-url origin 2>/dev/null", { cwd, encoding: "utf8" })
34
+ .trim();
35
+ const match = remote.match(/\/([^/]+?)(\.git)?$/);
36
+ if (match?.[1])
37
+ return match[1];
38
+ }
39
+ catch {
40
+ }
41
+ return path.basename(cwd);
42
+ }
43
+ async function prompt(question) {
44
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
45
+ try {
46
+ return (await rl.question(question)).trim();
47
+ }
48
+ finally {
49
+ rl.close();
50
+ }
51
+ }
52
+ async function promptForApplicationName(defaultName) {
53
+ const answer = await prompt(`Application name [${defaultName}]: `);
54
+ return answer || defaultName;
55
+ }
56
+ const NON_INTERACTIVE_HELP = "Pass --application <name-or-id> to select an existing one non-interactively " +
57
+ "(it will not create one), or set applicationId manually in .zivis/project.json.";
58
+ export async function ensureApplicationBinding(opts) {
59
+ const { config, accessToken, cwd, explicitApplication } = opts;
60
+ const detected = detectProjectBinding(cwd);
61
+ if (!detected)
62
+ return;
63
+ if (detected.binding.applicationId) {
64
+ console.log(`✓ Application binding verified: ${detected.binding.applicationName ?? detected.binding.applicationId}`);
65
+ return;
66
+ }
67
+ let apps;
68
+ try {
69
+ apps = await fetchApplications(config, accessToken);
70
+ }
71
+ catch (err) {
72
+ console.log(` WARNING: Could not list applications: ${err instanceof Error ? err.message : err}`);
73
+ console.log(" Skipping application binding — set applicationId manually in .zivis/project.json.");
74
+ return;
75
+ }
76
+ const interactive = process.stderr.isTTY === true && process.stdin.isTTY === true;
77
+ let selected;
78
+ if (explicitApplication) {
79
+ const byId = apps.find((a) => a.id === explicitApplication);
80
+ const byName = byId
81
+ ? []
82
+ : apps.filter((a) => a.name.toLowerCase() === explicitApplication.toLowerCase());
83
+ if (byId) {
84
+ selected = byId;
85
+ console.log(`\nUsing existing application: ${selected.name}`);
86
+ }
87
+ else if (byName.length > 1) {
88
+ console.log(`\n AMBIGUOUS: ${byName.length} applications named "${explicitApplication}" found — ` +
89
+ `refusing to guess which one you mean.`);
90
+ for (const a of byName)
91
+ console.log(` ${a.id} ${a.name}`);
92
+ console.log(" Use the Application ID instead of the name, or run `zivis init` interactively.");
93
+ return;
94
+ }
95
+ else if (byName.length === 1) {
96
+ selected = byName[0];
97
+ console.log(`\nUsing existing application: ${selected.name}`);
98
+ }
99
+ else {
100
+ console.log(`\n NOT FOUND: no application matches id or name "${explicitApplication}".`);
101
+ console.log(" --application selects an EXISTING application — it does not create one.");
102
+ console.log(" Check the id/name (`zivis app list`), or run `zivis init` interactively to create a new one.");
103
+ return;
104
+ }
105
+ }
106
+ else if (apps.length === 0) {
107
+ if (!interactive) {
108
+ console.log("\n WARNING: No applications found and no TTY to prompt — skipping application binding.");
109
+ console.log(` ${NON_INTERACTIVE_HELP}`);
110
+ return;
111
+ }
112
+ console.log("\nNo applications found in this org — let's create one.");
113
+ const name = await promptForApplicationName(defaultApplicationName(cwd));
114
+ try {
115
+ selected = await createApplication(config, accessToken, name);
116
+ }
117
+ catch (err) {
118
+ console.log(` WARNING: Could not create application: ${err instanceof Error ? err.message : err}`);
119
+ return;
120
+ }
121
+ console.log(`✓ Created application ${selected.name} (${selected.id})`);
122
+ }
123
+ else if (!interactive) {
124
+ console.log(`\n WARNING: ${apps.length} application(s) found and no TTY to prompt — skipping application binding.`);
125
+ console.log(" Never auto-selecting the sole application without explicit confirmation.");
126
+ console.log(` ${NON_INTERACTIVE_HELP}`);
127
+ return;
128
+ }
129
+ else {
130
+ console.log("\nWhich application are you working on?");
131
+ apps.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
132
+ console.log(` ${apps.length + 1}. Create a new application`);
133
+ const answer = await prompt(`\n> `);
134
+ const idx = parseInt(answer, 10) - 1;
135
+ if (idx === apps.length) {
136
+ const name = await promptForApplicationName(defaultApplicationName(cwd));
137
+ try {
138
+ selected = await createApplication(config, accessToken, name);
139
+ }
140
+ catch (err) {
141
+ console.log(` WARNING: Could not create application: ${err instanceof Error ? err.message : err}`);
142
+ return;
143
+ }
144
+ console.log(`✓ Created application ${selected.name} (${selected.id})`);
145
+ }
146
+ else if (!Number.isNaN(idx) && idx >= 0 && idx < apps.length) {
147
+ selected = apps[idx];
148
+ }
149
+ else {
150
+ console.log(" Invalid selection — skipping application binding. Set applicationId manually in .zivis/project.json.");
151
+ return;
152
+ }
153
+ }
154
+ if (!selected)
155
+ return;
156
+ const current = detectProjectBinding(cwd);
157
+ if (!current)
158
+ return;
159
+ const updated = {
160
+ ...current.binding,
161
+ applicationId: selected.id,
162
+ applicationName: selected.name,
163
+ };
164
+ fs.writeFileSync(current.filePath, JSON.stringify(updated, null, 2) + "\n");
165
+ console.log(`✓ Repo bound to application: ${selected.name}`);
166
+ console.log(` File: ${current.filePath}`);
167
+ }
@@ -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>;