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

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 (180) 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 +33 -0
  6. package/dist/commands/assurance/index.js +149 -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 +249 -73
  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 +111 -0
  31. package/dist/commands/scan/index.js +3 -8
  32. package/dist/commands/sync/index.d.ts +2 -0
  33. package/dist/commands/sync/index.js +187 -0
  34. package/dist/commands/target/index.js +0 -3
  35. package/dist/commands/test/index.d.ts +2 -0
  36. package/dist/commands/test/index.js +115 -0
  37. package/dist/commands/tm/index.js +7 -10
  38. package/dist/index.js +24 -1
  39. package/dist/internal/application-binding.d.ts +8 -0
  40. package/dist/internal/application-binding.js +167 -0
  41. package/dist/internal/binding-token-mint.d.ts +33 -0
  42. package/dist/internal/binding-token-mint.js +174 -0
  43. package/dist/internal/cli-output.d.ts +16 -0
  44. package/dist/internal/cli-output.js +39 -0
  45. package/dist/internal/devx-run.d.ts +47 -0
  46. package/dist/internal/devx-run.js +36 -0
  47. package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
  48. package/dist/internal/extractor/adapters/go/index.js +0 -6
  49. package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
  50. package/dist/internal/extractor/adapters/go/manifest.js +19 -25
  51. package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
  52. package/dist/internal/extractor/adapters/go/parser.js +0 -5
  53. package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
  54. package/dist/internal/extractor/adapters/go/scanner.js +0 -43
  55. package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
  56. package/dist/internal/extractor/adapters/python/index.js +0 -4
  57. package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
  58. package/dist/internal/extractor/adapters/python/manifest.js +21 -18
  59. package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
  60. package/dist/internal/extractor/adapters/python/parser.js +0 -4
  61. package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
  62. package/dist/internal/extractor/adapters/python/scanner.js +1 -54
  63. package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
  64. package/dist/internal/extractor/adapters/typescript/index.js +1 -21
  65. package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
  66. package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
  67. package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
  68. package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
  69. package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
  70. package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
  71. package/dist/internal/extractor/graph-builder.d.ts +10 -0
  72. package/dist/internal/extractor/graph-builder.js +245 -0
  73. package/dist/internal/extractor/graph-types.d.ts +89 -0
  74. package/dist/internal/extractor/graph-types.js +1 -0
  75. package/dist/internal/extractor/index.d.ts +0 -14
  76. package/dist/internal/extractor/index.js +0 -13
  77. package/dist/internal/extractor/types.d.ts +0 -45
  78. package/dist/internal/extractor/types.js +0 -27
  79. package/dist/internal/extractor/walker.d.ts +0 -18
  80. package/dist/internal/extractor/walker.js +0 -18
  81. package/dist/internal/gate-evaluate.d.ts +50 -0
  82. package/dist/internal/gate-evaluate.js +111 -0
  83. package/dist/internal/gate-policy.d.ts +38 -0
  84. package/dist/internal/gate-policy.js +167 -0
  85. package/dist/internal/git-metadata.d.ts +8 -0
  86. package/dist/internal/git-metadata.js +38 -0
  87. package/dist/internal/git.d.ts +8 -0
  88. package/dist/internal/git.js +30 -0
  89. package/dist/internal/github-linkage-prompt.d.ts +7 -0
  90. package/dist/internal/github-linkage-prompt.js +95 -0
  91. package/dist/internal/ide-setup.d.ts +17 -7
  92. package/dist/internal/ide-setup.js +377 -46
  93. package/dist/internal/inventory-sync.d.ts +74 -0
  94. package/dist/internal/inventory-sync.js +189 -0
  95. package/dist/internal/org-picker.d.ts +11 -0
  96. package/dist/internal/org-picker.js +34 -0
  97. package/dist/internal/orgs.js +0 -1
  98. package/dist/internal/packs/cache.d.ts +8 -0
  99. package/dist/internal/packs/cache.js +60 -0
  100. package/dist/internal/packs/index.d.ts +10 -0
  101. package/dist/internal/packs/index.js +7 -0
  102. package/dist/internal/packs/integrity.d.ts +9 -0
  103. package/dist/internal/packs/integrity.js +24 -0
  104. package/dist/internal/packs/jcs.d.ts +2 -0
  105. package/dist/internal/packs/jcs.js +57 -0
  106. package/dist/internal/packs/local-paths.d.ts +4 -0
  107. package/dist/internal/packs/local-paths.js +26 -0
  108. package/dist/internal/packs/registry-client.d.ts +20 -0
  109. package/dist/internal/packs/registry-client.js +40 -0
  110. package/dist/internal/packs/resolve.d.ts +8 -0
  111. package/dist/internal/packs/resolve.js +89 -0
  112. package/dist/internal/packs/semver.d.ts +9 -0
  113. package/dist/internal/packs/semver.js +57 -0
  114. package/dist/internal/packs/signing.d.ts +5 -0
  115. package/dist/internal/packs/signing.js +49 -0
  116. package/dist/internal/packs/types.d.ts +70 -0
  117. package/dist/internal/packs/types.js +20 -0
  118. package/dist/internal/project-binding.d.ts +1 -0
  119. package/dist/internal/project-binding.js +14 -9
  120. package/dist/internal/run-workdir.d.ts +1 -0
  121. package/dist/internal/run-workdir.js +11 -0
  122. package/dist/internal/security-context.d.ts +62 -0
  123. package/dist/internal/security-context.js +50 -0
  124. package/dist/internal/sync-outbox.d.ts +40 -0
  125. package/dist/internal/sync-outbox.js +66 -0
  126. package/dist/internal/target-auth/api-config.d.ts +0 -17
  127. package/dist/internal/target-auth/api-config.js +0 -18
  128. package/dist/internal/target-auth/config.d.ts +0 -12
  129. package/dist/internal/target-auth/config.js +0 -13
  130. package/dist/internal/target-auth/index.d.ts +0 -18
  131. package/dist/internal/target-auth/index.js +0 -19
  132. package/dist/internal/target-auth/keychain.d.ts +0 -18
  133. package/dist/internal/target-auth/keychain.js +3 -23
  134. package/dist/internal/target-auth/types.d.ts +0 -26
  135. package/dist/internal/target-auth/types.js +0 -10
  136. package/dist/internal/target-auth/workos.d.ts +0 -15
  137. package/dist/internal/target-auth/workos.js +9 -18
  138. package/dist/internal/test-scope.d.ts +68 -0
  139. package/dist/internal/test-scope.js +69 -0
  140. package/dist/internal/zivis-local-state.d.ts +1 -0
  141. package/dist/internal/zivis-local-state.js +22 -0
  142. package/package.json +20 -5
  143. package/dist/commands/app/index.js.map +0 -1
  144. package/dist/commands/auth/index.js.map +0 -1
  145. package/dist/commands/auth/init.js.map +0 -1
  146. package/dist/commands/auth/login.js.map +0 -1
  147. package/dist/commands/auth/logout.js.map +0 -1
  148. package/dist/commands/auth/sessions.js.map +0 -1
  149. package/dist/commands/auth/status.js.map +0 -1
  150. package/dist/commands/credits/index.js.map +0 -1
  151. package/dist/commands/mcp/index.js.map +0 -1
  152. package/dist/commands/open/index.js.map +0 -1
  153. package/dist/commands/scan/index.js.map +0 -1
  154. package/dist/commands/target/index.js.map +0 -1
  155. package/dist/commands/tm/index.js.map +0 -1
  156. package/dist/index.js.map +0 -1
  157. package/dist/internal/extractor/adapters/go/index.js.map +0 -1
  158. package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
  159. package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
  160. package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
  161. package/dist/internal/extractor/adapters/python/index.js.map +0 -1
  162. package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
  163. package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
  164. package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
  165. package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
  166. package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
  167. package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
  168. package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
  169. package/dist/internal/extractor/index.js.map +0 -1
  170. package/dist/internal/extractor/types.js.map +0 -1
  171. package/dist/internal/extractor/walker.js.map +0 -1
  172. package/dist/internal/ide-setup.js.map +0 -1
  173. package/dist/internal/orgs.js.map +0 -1
  174. package/dist/internal/project-binding.js.map +0 -1
  175. package/dist/internal/target-auth/api-config.js.map +0 -1
  176. package/dist/internal/target-auth/config.js.map +0 -1
  177. package/dist/internal/target-auth/index.js.map +0 -1
  178. package/dist/internal/target-auth/keychain.js.map +0 -1
  179. package/dist/internal/target-auth/types.js.map +0 -1
  180. package/dist/internal/target-auth/workos.js.map +0 -1
@@ -0,0 +1,171 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import * as crypto from "node:crypto";
4
+ import { ApiClient, ApiError } from "@zivis/mcp/api-client";
5
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
6
+ import { requireApplicationId } from "@zivis/mcp/resolve-application-id";
7
+ import { getCurrentGitSha } from "../../internal/git.js";
8
+ import { parsePolicyFile, PolicyValidationError } from "../../internal/gate-policy.js";
9
+ import { evaluateGate } from "../../internal/gate-evaluate.js";
10
+ import { jsonEnvelope, printJson, EXIT, exitCodeForError, errorMessage } from "../../internal/cli-output.js";
11
+ const DEFAULT_POLICY_PATH = ".zivis/policy.yaml";
12
+ export function registerGateCommand(program) {
13
+ program
14
+ .command("gate [name]")
15
+ .description("evaluate a committed release/ship policy deterministically against current Zivis state")
16
+ .option("--file <path>", `policy file path (default: ${DEFAULT_POLICY_PATH})`)
17
+ .option("--app <id>", "Application id (defaults to the bound Application)")
18
+ .option("--model <zamId>", "ad-hoc form: assurance model key")
19
+ .option("--category <categoryId>", "ad-hoc form: category id to threshold")
20
+ .option("--min-score <0..1>", "ad-hoc form: minimum normalized score_raw")
21
+ .option("--min-coverage <0..1>", "ad-hoc form: minimum coverage")
22
+ .option("-s, --session <name>", "named session")
23
+ .option("--json", "emit JSON only on stdout")
24
+ .action(async (name, opts) => {
25
+ const json = !!opts.json;
26
+ try {
27
+ const { gateName, policy, policyHash } = await resolvePolicy(name, opts);
28
+ const config = opts.session
29
+ ? { ...resolveConfigFromBinding(process.cwd()), session: opts.session }
30
+ : resolveConfigFromBinding(process.cwd());
31
+ const client = new ApiClient(config);
32
+ const resolvedApp = requireApplicationId(opts.app, process.cwd());
33
+ if (!resolvedApp.ok) {
34
+ throw new PolicyValidationError(resolvedApp.message);
35
+ }
36
+ const applicationId = resolvedApp.id;
37
+ const [currentGitSha, statusResult] = await Promise.all([
38
+ getCurrentGitSha(process.cwd()),
39
+ fetchStatus(client, applicationId, policy),
40
+ ]);
41
+ let mark = null;
42
+ let markLookupFailed = false;
43
+ let markId = null;
44
+ let assessorVersion = null;
45
+ if (policy.provenance) {
46
+ try {
47
+ const query = new URLSearchParams({ applicationId, model: policy.model });
48
+ const raw = await client.get(`/api/zat/marks/latest?${query.toString()}`);
49
+ markId = raw.mark_id ?? null;
50
+ const methodology = raw.methodology;
51
+ assessorVersion = methodology?.assessor_version ?? null;
52
+ mark = {
53
+ basis: raw.framework?.basis,
54
+ expired: !!raw.expired,
55
+ revoked: !!raw.revoked,
56
+ signature: { verified: !!raw.signature?.verified },
57
+ };
58
+ }
59
+ catch (err) {
60
+ if (!(err instanceof ApiError && err.status === 404)) {
61
+ markLookupFailed = true;
62
+ }
63
+ }
64
+ }
65
+ const evaluation = evaluateGate({
66
+ policy,
67
+ status: statusResult,
68
+ mark,
69
+ markLookupFailed,
70
+ currentGitSha,
71
+ now: new Date(),
72
+ });
73
+ const output = jsonEnvelope("gate", {
74
+ gate: gateName,
75
+ result: evaluation.result,
76
+ application_id: applicationId,
77
+ git_sha: currentGitSha ?? null,
78
+ policy_hash: policyHash,
79
+ evaluated_at: new Date().toISOString(),
80
+ scoring_profile: policy.scoring_profile ?? null,
81
+ checks: evaluation.checks,
82
+ provenance: {
83
+ source: policy.source,
84
+ latest_mark_id: markId,
85
+ basis: mark?.basis ?? null,
86
+ assessor_version: assessorVersion,
87
+ last_evaluated_sha: statusResult?.lastEvaluatedGitSha ?? null,
88
+ },
89
+ });
90
+ if (json) {
91
+ printJson(output);
92
+ }
93
+ else {
94
+ printHuman(gateName, evaluation, output);
95
+ }
96
+ process.exit(evaluation.result === "pass" ? EXIT.OK : evaluation.result === "fail" ? EXIT.POLICY_FAIL : EXIT.INVALID_OR_INDETERMINATE);
97
+ }
98
+ catch (err) {
99
+ const code = err instanceof PolicyValidationError ? EXIT.INVALID_OR_INDETERMINATE : exitCodeForError(err);
100
+ const message = errorMessage(err);
101
+ if (json) {
102
+ printJson(jsonEnvelope("gate", { gate: name ?? null, result: "indeterminate", error: message }));
103
+ }
104
+ else {
105
+ console.error(`zivis gate: ${message}`);
106
+ }
107
+ process.exit(code);
108
+ }
109
+ });
110
+ }
111
+ async function resolvePolicy(name, opts) {
112
+ const isAdHoc = !!(opts.model && opts.category && opts.minScore !== undefined);
113
+ if (isAdHoc) {
114
+ const minScore = Number(opts.minScore);
115
+ if (Number.isNaN(minScore) || minScore < 0 || minScore > 1) {
116
+ throw new PolicyValidationError("--min-score must be a number between 0.0 and 1.0.");
117
+ }
118
+ const policy = {
119
+ source: "current",
120
+ model: opts.model,
121
+ assurance: { categories: [{ id: opts.category, min_score_raw: minScore }] },
122
+ };
123
+ if (opts.minCoverage !== undefined) {
124
+ const minCoverage = Number(opts.minCoverage);
125
+ if (Number.isNaN(minCoverage) || minCoverage < 0 || minCoverage > 1) {
126
+ throw new PolicyValidationError("--min-coverage must be a number between 0.0 and 1.0.");
127
+ }
128
+ policy.assurance.categories[0].min_coverage = minCoverage;
129
+ }
130
+ return { gateName: "ad-hoc", policy, policyHash: "sha256:" + crypto.createHash("sha256").update(JSON.stringify(policy)).digest("hex") };
131
+ }
132
+ if (!name) {
133
+ throw new PolicyValidationError("A gate name is required (e.g. `zivis gate ship`), or pass --model/--category/--min-score for the ad-hoc debugging form.");
134
+ }
135
+ const filePath = path.resolve(process.cwd(), opts.file ?? DEFAULT_POLICY_PATH);
136
+ let raw;
137
+ try {
138
+ raw = await fs.readFile(filePath, "utf8");
139
+ }
140
+ catch {
141
+ throw new PolicyValidationError(`Policy file not found: ${filePath}`);
142
+ }
143
+ const parsed = parsePolicyFile(raw);
144
+ const policy = parsed.gates[name];
145
+ if (!policy) {
146
+ const available = Object.keys(parsed.gates).join(", ") || "(none defined)";
147
+ throw new PolicyValidationError(`Gate '${name}' is not defined in ${filePath}. Available gates: ${available}`);
148
+ }
149
+ const policyHash = "sha256:" + crypto.createHash("sha256").update(raw).digest("hex");
150
+ return { gateName: name, policy, policyHash };
151
+ }
152
+ async function fetchStatus(client, applicationId, policy) {
153
+ const query = new URLSearchParams({ applicationId, model: policy.model });
154
+ try {
155
+ return await client.get(`/api/assurance/status?${query.toString()}`);
156
+ }
157
+ catch (err) {
158
+ if (err instanceof ApiError && err.status === 404)
159
+ return null;
160
+ throw err;
161
+ }
162
+ }
163
+ function printHuman(gateName, evaluation, _full) {
164
+ const symbol = evaluation.result === "pass" ? "✓" : evaluation.result === "fail" ? "✗" : "?";
165
+ console.log(`Gate '${gateName}': ${symbol} ${evaluation.result.toUpperCase()}`);
166
+ for (const c of evaluation.checks) {
167
+ const label = c.category_id ? `${c.type}[${c.category_id}]` : c.type;
168
+ const mark = c.result === "pass" ? "✓" : c.result === "fail" ? "✗" : "?";
169
+ console.log(` ${mark} ${label}: actual=${JSON.stringify(c.actual)} required=${JSON.stringify(c.required)}`);
170
+ }
171
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInspectCommands(program: Command): void;
@@ -0,0 +1,136 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { exec } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import { detectProjectBinding } from "@zivis/mcp/project-binding";
8
+ import { runExtractor } from "../../internal/extractor/index.js";
9
+ import { buildRepoGraphArtifact } from "../../internal/extractor/graph-builder.js";
10
+ const execAsync = promisify(exec);
11
+ function collect(value, previous) {
12
+ return previous.concat([value]);
13
+ }
14
+ export function registerInspectCommands(program) {
15
+ program
16
+ .command("inspect")
17
+ .description("build a local repo graph artifact (services, endpoints, external deps) — local-only, no network")
18
+ .option("--root <dir>", "repo root to inspect (default: cwd)")
19
+ .option("--out <file>", "write artifact JSON to a file (default: <user-cache>/repos/<repo-id>/inspect/runs/<run-id>.json; '-' for stdout)")
20
+ .option("--privacy-audit", "print exactly what would be sent without writing to cache or sending — never makes network calls")
21
+ .option("--lang <list>", "comma-separated subset of adapters: typescript,go,python")
22
+ .option("--ignore <pattern>", "extra ignore pattern (repeatable)", collect, [])
23
+ .option("--application <id>", "set provenance.application_id (default: from .zivis/project.json if present)")
24
+ .option("--repo-id <id>", "override repo identifier (default: hash of git remote, fallback to hash of root path)")
25
+ .option("--commit <sha>", "override commit SHA (default: from `git rev-parse HEAD`)")
26
+ .option("--branch <name>", "override branch (default: from `git rev-parse --abbrev-ref HEAD`)")
27
+ .action(async (opts) => {
28
+ const rootDir = path.resolve(opts.root ?? process.cwd());
29
+ const cliPkg = await readCliPackage();
30
+ const langs = opts.lang
31
+ ? String(opts.lang).split(",").map((s) => s.trim()).filter(Boolean)
32
+ : undefined;
33
+ const result = await runExtractor({
34
+ rootDir,
35
+ languages: langs,
36
+ extraIgnores: opts.ignore,
37
+ extractorVersion: cliPkg.version,
38
+ });
39
+ const gitMeta = await readGitMetadata(rootDir);
40
+ const applicationId = opts.application ?? detectProjectBinding(rootDir)?.binding.applicationId;
41
+ const repoId = opts.repoId ?? deriveRepoId(gitMeta.remoteUrl, rootDir);
42
+ const commitSha = opts.commit ?? gitMeta.commitSha;
43
+ const branch = opts.branch ?? gitMeta.branch;
44
+ const artifact = buildRepoGraphArtifact(result, {
45
+ applicationId,
46
+ repoId,
47
+ commitSha,
48
+ branch,
49
+ analysisMode: "local",
50
+ });
51
+ if (opts.privacyAudit) {
52
+ process.stdout.write(JSON.stringify(artifact, null, 2) + "\n");
53
+ printSummary(artifact, "PRIVACY AUDIT — nothing was written or sent");
54
+ return;
55
+ }
56
+ if (opts.out === "-") {
57
+ process.stdout.write(JSON.stringify(artifact, null, 2) + "\n");
58
+ printSummary(artifact);
59
+ return;
60
+ }
61
+ const outPath = opts.out
62
+ ? path.isAbsolute(opts.out)
63
+ ? opts.out
64
+ : path.join(rootDir, opts.out)
65
+ : defaultCacheOutPath(repoId, commitSha, artifact.extracted_at);
66
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
67
+ await fs.writeFile(outPath, JSON.stringify(artifact, null, 2) + "\n");
68
+ printSummary(artifact, `wrote ${outPath}`);
69
+ });
70
+ }
71
+ async function readCliPackage() {
72
+ const here = path.dirname(new URL(import.meta.url).pathname);
73
+ const pkgPath = path.join(here, "../../../package.json");
74
+ const raw = await fs.readFile(pkgPath, "utf8");
75
+ return JSON.parse(raw);
76
+ }
77
+ async function readGitMetadata(rootDir) {
78
+ const out = {};
79
+ out.remoteUrl = await tryGit(rootDir, "remote get-url origin");
80
+ out.commitSha = await tryGit(rootDir, "rev-parse HEAD");
81
+ out.branch = await tryGit(rootDir, "rev-parse --abbrev-ref HEAD");
82
+ return out;
83
+ }
84
+ async function tryGit(cwd, args) {
85
+ try {
86
+ const { stdout } = await execAsync(`git ${args}`, { cwd });
87
+ const value = stdout.trim();
88
+ return value || undefined;
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ function deriveRepoId(remoteUrl, rootDir) {
95
+ if (remoteUrl) {
96
+ return "remote-" + sha256Short(remoteUrl);
97
+ }
98
+ return "path-" + sha256Short(rootDir);
99
+ }
100
+ function sha256Short(value) {
101
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
102
+ }
103
+ function userCacheRoot() {
104
+ const home = os.homedir();
105
+ if (process.platform === "darwin") {
106
+ return path.join(home, "Library", "Caches", "zivis");
107
+ }
108
+ if (process.platform === "win32") {
109
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
110
+ return path.join(localAppData, "zivis", "Cache");
111
+ }
112
+ const xdg = process.env.XDG_CACHE_HOME;
113
+ return path.join(xdg ?? path.join(home, ".cache"), "zivis");
114
+ }
115
+ function defaultCacheOutPath(repoId, commitSha, extractedAt) {
116
+ const runId = formatRunId(commitSha, extractedAt);
117
+ return path.join(userCacheRoot(), "repos", repoId, "inspect", "runs", `${runId}.json`);
118
+ }
119
+ function formatRunId(commitSha, extractedAt) {
120
+ const compactTs = extractedAt.replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
121
+ if (!commitSha)
122
+ return `nogit-${compactTs}`;
123
+ return `${commitSha.slice(0, 7)}-${compactTs}`;
124
+ }
125
+ function printSummary(artifact, suffix) {
126
+ const services = artifact.nodes.filter((n) => n.type === "service").length;
127
+ const endpoints = artifact.nodes.filter((n) => n.type === "endpoint").length;
128
+ const deps = artifact.nodes.filter((n) => n.type === "external_dependency").length;
129
+ const exposes = artifact.edges.filter((e) => e.type === "exposes").length;
130
+ const dependsOn = artifact.edges.filter((e) => e.type === "depends_on").length;
131
+ const langs = artifact.scope.languages?.join(",") ?? "(none)";
132
+ process.stderr.write(`\n[zivis inspect] ${artifact.scope.files_scanned} files scanned in ${artifact.scope.duration_ms}ms (langs: ${langs})\n` +
133
+ ` ${artifact.nodes.length} nodes (${services} services, ${endpoints} endpoints, ${deps} external deps)\n` +
134
+ ` ${artifact.edges.length} edges (${exposes} exposes, ${dependsOn} depends_on)\n` +
135
+ (suffix ? ` ${suffix}\n` : ""));
136
+ }
@@ -1,6 +1,8 @@
1
+ import * as path from "node:path";
2
+ import * as readline from "node:readline/promises";
1
3
  import { startServer } from "@zivis/mcp/server";
2
4
  import { DEFAULT_CONFIG } from "@zivis/mcp/types";
3
- import { setupCursor, setupVsCodeCopilot, setupVsCodeClaude, setupClaudeCodeCli, setupCline, setupWindsurf, printClaudeCodeManual, } from "../../internal/ide-setup.js";
5
+ import { setupCursorWorkspace, setupVsCodeCopilot, setupVsCodeClaude, setupClaudeCodeCli, setupCline, setupWindsurf, printClaudeCodeManual, writeGuidance, removeGuidance, } from "../../internal/ide-setup.js";
4
6
  const KNOWN_CLIENTS = [
5
7
  "cursor",
6
8
  "claude-code",
@@ -10,50 +12,84 @@ const KNOWN_CLIENTS = [
10
12
  "vscode-copilot",
11
13
  "vscode-claude",
12
14
  ];
15
+ function toGuidanceClient(client) {
16
+ switch (client) {
17
+ case "cursor": return "cursor";
18
+ case "claude-code-cli": return "claude-code-cli";
19
+ case "vscode-copilot": return "vscode-copilot";
20
+ case "vscode-claude": return "vscode-claude";
21
+ case "cline": return "cline";
22
+ case "windsurf": return "windsurf";
23
+ default: return null;
24
+ }
25
+ }
26
+ async function promptYesNo(question) {
27
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
28
+ try {
29
+ const answer = (await rl.question(question)).trim().toLowerCase();
30
+ return answer !== "n" && answer !== "no";
31
+ }
32
+ finally {
33
+ rl.close();
34
+ }
35
+ }
13
36
  export function registerMcpCommands(program) {
14
37
  const mcp = program.command("mcp").description("MCP server for IDE integration");
15
38
  mcp
16
39
  .command("serve")
17
40
  .description("start the stdio MCP server (called by your IDE, not directly)")
18
- .action(async () => {
19
- await startServer(DEFAULT_CONFIG);
41
+ .option("-w, --workspace <path>", "workspace root for project binding detection (auto-set by IDE config, do not pass manually)")
42
+ .action(async (opts) => {
43
+ const config = { ...DEFAULT_CONFIG };
44
+ if (opts.workspace) {
45
+ config.workspacePath = path.resolve(opts.workspace);
46
+ }
47
+ await startServer(config);
20
48
  });
21
49
  mcp
22
50
  .command("install")
23
- .description("write MCP config for a supported IDE client")
51
+ .description("write MCP config for a supported IDE client (run from your project root)")
24
52
  .option("--client <name>", `target client (${KNOWN_CLIENTS.join(" | ")})`)
25
- .option("-s, --session <name>", "include ZIVIS_SESSION env var")
53
+ .option("--package <name>", "which ZIVIS MCP package to register: 'builder' (default, @zivis/mcp-builder, citizen-dev surface) or 'appsec' (@zivis/mcp, DevSecOps surface)", "builder")
54
+ .option("--with-guidance", "also write IDE guidance files so the AI knows when to suggest security checks (no prompt). Only applies to the appsec package; builder ships its own cadence block via zivis_setup.")
55
+ .option("--no-guidance", "skip writing IDE guidance files (no prompt)")
26
56
  .action(async (opts) => {
27
57
  const client = opts.client?.toLowerCase();
28
- const session = opts.session;
58
+ const pkgArg = opts.package?.toLowerCase() ?? "builder";
29
59
  if (!client) {
30
60
  console.error("Specify a client with --client <name>:");
31
61
  for (const c of KNOWN_CLIENTS)
32
62
  console.error(` zivis mcp install --client ${c}`);
33
63
  process.exit(1);
34
64
  }
65
+ if (pkgArg !== "appsec" && pkgArg !== "builder") {
66
+ console.error(`Unknown --package value: ${pkgArg}`);
67
+ console.error("Supported: appsec, builder");
68
+ process.exit(1);
69
+ }
70
+ const pkg = pkgArg;
35
71
  try {
36
72
  switch (client) {
37
73
  case "cursor":
38
- await setupCursor(session);
74
+ await setupCursorWorkspace(pkg);
39
75
  break;
40
76
  case "claude-code":
41
- printClaudeCodeManual(session);
77
+ printClaudeCodeManual(pkg);
42
78
  break;
43
79
  case "claude-code-cli":
44
- await setupClaudeCodeCli(session);
80
+ await setupClaudeCodeCli(pkg);
45
81
  break;
46
82
  case "cline":
47
- await setupCline(session);
83
+ await setupCline(pkg);
48
84
  break;
49
85
  case "windsurf":
50
- await setupWindsurf(session);
86
+ await setupWindsurf(pkg);
51
87
  break;
52
88
  case "vscode-copilot":
53
- await setupVsCodeCopilot(session);
89
+ await setupVsCodeCopilot(pkg);
54
90
  break;
55
91
  case "vscode-claude":
56
- await setupVsCodeClaude(session);
92
+ await setupVsCodeClaude(pkg);
57
93
  break;
58
94
  default:
59
95
  console.error(`Unknown client: ${client}`);
@@ -65,6 +101,69 @@ export function registerMcpCommands(program) {
65
101
  console.error("Install failed:", err instanceof Error ? err.message : err);
66
102
  process.exit(1);
67
103
  }
104
+ if (client === "claude-code")
105
+ return;
106
+ if (pkg === "builder")
107
+ return;
108
+ const guidanceClient = toGuidanceClient(client);
109
+ if (!guidanceClient)
110
+ return;
111
+ let shouldWrite;
112
+ if (opts.withGuidance) {
113
+ shouldWrite = true;
114
+ }
115
+ else if (opts.guidance === false) {
116
+ shouldWrite = false;
117
+ }
118
+ else {
119
+ console.log("");
120
+ shouldWrite = await promptYesNo("Write ZIVIS guidance files so your IDE knows when to suggest security checks?\n" +
121
+ "(Adds a rule/instruction file to the project. You can edit or remove it anytime.) [Y/n] ");
122
+ }
123
+ if (shouldWrite) {
124
+ try {
125
+ const files = writeGuidance({ cwd: process.cwd(), client: guidanceClient });
126
+ if (files.length > 0) {
127
+ console.log(`✓ Wrote guidance files:`);
128
+ for (const f of files)
129
+ console.log(` ${f}`);
130
+ }
131
+ }
132
+ catch (err) {
133
+ console.warn(` Warning: Could not write guidance files: ${err instanceof Error ? err.message : err}`);
134
+ }
135
+ }
136
+ });
137
+ mcp
138
+ .command("uninstall")
139
+ .description("remove ONLY the ZIVIS-managed guidance block/files for a client (run from your project root)")
140
+ .option("--client <name>", `target client (${KNOWN_CLIENTS.join(" | ")})`)
141
+ .action(async (opts) => {
142
+ const client = opts.client?.toLowerCase();
143
+ if (!client) {
144
+ console.error("Specify a client with --client <name>:");
145
+ for (const c of KNOWN_CLIENTS)
146
+ console.error(` zivis mcp uninstall --client ${c}`);
147
+ process.exit(1);
148
+ }
149
+ const guidanceClient = toGuidanceClient(client);
150
+ if (!guidanceClient) {
151
+ console.log(`Nothing to remove for "${client}" — no guidance files are written for this client.`);
152
+ return;
153
+ }
154
+ try {
155
+ const removed = removeGuidance({ cwd: process.cwd(), client: guidanceClient });
156
+ if (removed.length === 0) {
157
+ console.log("No ZIVIS-managed guidance found — nothing to remove.");
158
+ return;
159
+ }
160
+ console.log(`✓ Removed ZIVIS guidance:`);
161
+ for (const f of removed)
162
+ console.log(` ${f}`);
163
+ }
164
+ catch (err) {
165
+ console.error("Uninstall failed:", err instanceof Error ? err.message : err);
166
+ process.exit(1);
167
+ }
68
168
  });
69
169
  }
70
- //# sourceMappingURL=index.js.map
@@ -1,10 +1,2 @@
1
- /**
2
- * `zivis open` — Build and (by default) launch a vscode:// or cursor:// deep link
3
- * that opens a ZIVIS artifact in the ZIVIS Code IDE extension. Use `--ide cursor`
4
- * for Cursor. With --print, emit the URI to stdout for piping/sharing.
5
- *
6
- * Mirrors the URI shape emitted by the `zivis_open_in_ide` MCP tool and the
7
- * web app's buildIdeLink() helper. See docs/platform/integrations/IDE-DEEP-LINKS.md.
8
- */
9
1
  import type { Command } from "commander";
10
2
  export declare function registerOpenCommands(program: Command): void;
@@ -1,11 +1,3 @@
1
- /**
2
- * `zivis open` — Build and (by default) launch a vscode:// or cursor:// deep link
3
- * that opens a ZIVIS artifact in the ZIVIS Code IDE extension. Use `--ide cursor`
4
- * for Cursor. With --print, emit the URI to stdout for piping/sharing.
5
- *
6
- * Mirrors the URI shape emitted by the `zivis_open_in_ide` MCP tool and the
7
- * web app's buildIdeLink() helper. See docs/platform/integrations/IDE-DEEP-LINKS.md.
8
- */
9
1
  const KINDS = ["diagram", "document", "threat-model"];
10
2
  export function registerOpenCommands(program) {
11
3
  program
@@ -47,11 +39,8 @@ export function registerOpenCommands(program) {
47
39
  console.log(uri);
48
40
  return;
49
41
  }
50
- // Lazy-import `open` so --print stays dependency-free in environments
51
- // (e.g. headless CI) where launching a URL handler would fail or hang.
52
42
  const { default: open } = await import("open");
53
43
  await open(uri);
54
44
  console.log(uri);
55
45
  });
56
46
  }
57
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerRunCommands(program: Command): void;
@@ -0,0 +1,111 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { ApiClient } from "@zivis/mcp/api-client";
3
+ import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
4
+ import { DEVX_RUN_CONTRACT_VERSION, completeDevXRunRemote, cancelDevXRunRemote, isRetryableDevXRunError, } from "../../internal/devx-run.js";
5
+ import { writeDevXRunCompleteOutboxEntry } from "../../internal/sync-outbox.js";
6
+ import { jsonEnvelope, printJson, EXIT, errorMessage, failCommand } from "../../internal/cli-output.js";
7
+ async function readInput(inputArg) {
8
+ const raw = inputArg === "-" ? await readStdin() : await fs.readFile(inputArg, "utf-8");
9
+ try {
10
+ return JSON.parse(raw);
11
+ }
12
+ catch (err) {
13
+ throw new Error(`--input is not valid JSON: ${errorMessage(err)}`);
14
+ }
15
+ }
16
+ async function readStdin() {
17
+ let data = "";
18
+ process.stdin.setEncoding("utf-8");
19
+ for await (const chunk of process.stdin) {
20
+ data += chunk;
21
+ }
22
+ return data;
23
+ }
24
+ export function registerRunCommands(program) {
25
+ const run = program.command("run").description("resolve a DevX run started by a run producer (e.g. `zivis test`)");
26
+ run
27
+ .command("complete <runId>")
28
+ .description("complete a pending run with its result envelope")
29
+ .requiredOption("--input <path>", "path to a JSON result file, or '-' for stdin")
30
+ .option("--json", "emit JSON only on stdout")
31
+ .action(async (runId, opts) => {
32
+ const json = !!opts.json;
33
+ let result;
34
+ try {
35
+ result = await readInput(opts.input);
36
+ }
37
+ catch (err) {
38
+ failCommand("run complete", err, json);
39
+ }
40
+ const config = resolveConfigFromBinding(process.cwd());
41
+ const client = new ApiClient(config);
42
+ try {
43
+ const response = await completeDevXRunRemote(client, {
44
+ runId,
45
+ contractVersion: DEVX_RUN_CONTRACT_VERSION,
46
+ result,
47
+ });
48
+ const output = jsonEnvelope("run complete", {
49
+ run_id: runId,
50
+ outcome: response.outcome,
51
+ status: response.run.status,
52
+ completed_at: response.run.completedAt,
53
+ });
54
+ if (json) {
55
+ printJson(output);
56
+ }
57
+ else {
58
+ console.log(`zivis run complete: ${response.outcome} (run ${runId} is now ${response.run.status})`);
59
+ }
60
+ process.exit(EXIT.OK);
61
+ }
62
+ catch (err) {
63
+ if (isRetryableDevXRunError(err)) {
64
+ const message = errorMessage(err);
65
+ const outboxPath = writeDevXRunCompleteOutboxEntry(process.cwd(), {
66
+ runId,
67
+ contractVersion: DEVX_RUN_CONTRACT_VERSION,
68
+ result,
69
+ error: message,
70
+ });
71
+ if (json) {
72
+ printJson(jsonEnvelope("run complete", { run_id: runId, outcome: "queued", queued_at: outboxPath, error: message }));
73
+ }
74
+ else {
75
+ console.error(`zivis run complete: Cloud unreachable (${message})\nQueued for retry: ${outboxPath}\nRun \`zivis sync --flush\` once the issue is resolved.`);
76
+ }
77
+ process.exit(EXIT.CLOUD_UNAVAILABLE);
78
+ }
79
+ failCommand("run complete", err, json);
80
+ }
81
+ });
82
+ run
83
+ .command("cancel <runId>")
84
+ .description("cancel a pending run")
85
+ .option("--reason <text>", "optional human-readable cancellation reason")
86
+ .option("--json", "emit JSON only on stdout")
87
+ .action(async (runId, opts) => {
88
+ const json = !!opts.json;
89
+ const config = resolveConfigFromBinding(process.cwd());
90
+ const client = new ApiClient(config);
91
+ try {
92
+ const response = await cancelDevXRunRemote(client, { runId, reason: opts.reason ?? null });
93
+ const output = jsonEnvelope("run cancel", {
94
+ run_id: runId,
95
+ outcome: response.outcome,
96
+ status: response.run.status,
97
+ cancelled_at: response.run.cancelledAt,
98
+ });
99
+ if (json) {
100
+ printJson(output);
101
+ }
102
+ else {
103
+ console.log(`zivis run cancel: ${response.outcome} (run ${runId} is now ${response.run.status})`);
104
+ }
105
+ process.exit(EXIT.OK);
106
+ }
107
+ catch (err) {
108
+ failCommand("run cancel", err, json);
109
+ }
110
+ });
111
+ }