@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.
- package/LICENSE +201 -0
- package/README.md +31 -0
- package/dist/commands/app/index.d.ts +0 -8
- package/dist/commands/app/index.js +8 -18
- package/dist/commands/assurance/index.d.ts +35 -0
- package/dist/commands/assurance/index.js +154 -0
- package/dist/commands/auth/bind.d.ts +4 -0
- package/dist/commands/auth/bind.js +97 -0
- package/dist/commands/auth/index.js +21 -2
- package/dist/commands/auth/init.d.ts +2 -0
- package/dist/commands/auth/init.js +248 -96
- package/dist/commands/auth/login.d.ts +2 -0
- package/dist/commands/auth/login.js +136 -18
- package/dist/commands/auth/logout.js +19 -7
- package/dist/commands/auth/sessions.js +7 -4
- package/dist/commands/auth/status.js +33 -12
- package/dist/commands/auth/token.d.ts +4 -0
- package/dist/commands/auth/token.js +73 -0
- package/dist/commands/check/index.d.ts +2 -0
- package/dist/commands/check/index.js +463 -0
- package/dist/commands/credits/index.js +2 -3
- package/dist/commands/gate/index.d.ts +2 -0
- package/dist/commands/gate/index.js +171 -0
- package/dist/commands/inspect/index.d.ts +2 -0
- package/dist/commands/inspect/index.js +136 -0
- package/dist/commands/mcp/index.js +113 -14
- package/dist/commands/open/index.d.ts +0 -8
- package/dist/commands/open/index.js +0 -11
- package/dist/commands/run/index.d.ts +2 -0
- package/dist/commands/run/index.js +206 -0
- package/dist/commands/scan/index.js +3 -8
- package/dist/commands/share/index.d.ts +14 -0
- package/dist/commands/share/index.js +100 -0
- package/dist/commands/sync/index.d.ts +2 -0
- package/dist/commands/sync/index.js +193 -0
- package/dist/commands/target/index.js +0 -3
- package/dist/commands/test/index.d.ts +2 -0
- package/dist/commands/test/index.js +198 -0
- package/dist/commands/threatmodel/index.d.ts +2 -0
- package/dist/commands/threatmodel/index.js +167 -0
- package/dist/index.js +28 -3
- package/dist/internal/application-binding.d.ts +8 -0
- package/dist/internal/application-binding.js +167 -0
- package/dist/internal/binding-token-mint.d.ts +33 -0
- package/dist/internal/binding-token-mint.js +174 -0
- package/dist/internal/change-recommendation.d.ts +6 -0
- package/dist/internal/change-recommendation.js +113 -0
- package/dist/internal/cli-output.d.ts +16 -0
- package/dist/internal/cli-output.js +39 -0
- package/dist/internal/devx-run.d.ts +129 -0
- package/dist/internal/devx-run.js +39 -0
- package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
- package/dist/internal/extractor/adapters/go/index.js +0 -6
- package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
- package/dist/internal/extractor/adapters/go/manifest.js +19 -25
- package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
- package/dist/internal/extractor/adapters/go/parser.js +0 -5
- package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
- package/dist/internal/extractor/adapters/go/scanner.js +0 -43
- package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
- package/dist/internal/extractor/adapters/python/index.js +0 -4
- package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
- package/dist/internal/extractor/adapters/python/manifest.js +21 -18
- package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
- package/dist/internal/extractor/adapters/python/parser.js +0 -4
- package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
- package/dist/internal/extractor/adapters/python/scanner.js +1 -54
- package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
- package/dist/internal/extractor/adapters/typescript/index.js +1 -21
- package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
- package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
- package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
- package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
- package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
- package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
- package/dist/internal/extractor/graph-builder.d.ts +10 -0
- package/dist/internal/extractor/graph-builder.js +245 -0
- package/dist/internal/extractor/graph-types.d.ts +89 -0
- package/dist/internal/extractor/graph-types.js +1 -0
- package/dist/internal/extractor/index.d.ts +0 -14
- package/dist/internal/extractor/index.js +0 -13
- package/dist/internal/extractor/types.d.ts +0 -45
- package/dist/internal/extractor/types.js +0 -27
- package/dist/internal/extractor/walker.d.ts +0 -18
- package/dist/internal/extractor/walker.js +0 -18
- package/dist/internal/gate-evaluate.d.ts +50 -0
- package/dist/internal/gate-evaluate.js +111 -0
- package/dist/internal/gate-policy.d.ts +38 -0
- package/dist/internal/gate-policy.js +167 -0
- package/dist/internal/git-metadata.d.ts +8 -0
- package/dist/internal/git-metadata.js +38 -0
- package/dist/internal/git.d.ts +9 -0
- package/dist/internal/git.js +45 -0
- package/dist/internal/github-linkage-prompt.d.ts +7 -0
- package/dist/internal/github-linkage-prompt.js +95 -0
- package/dist/internal/ide-setup.d.ts +17 -7
- package/dist/internal/ide-setup.js +379 -46
- package/dist/internal/inventory-sync.d.ts +74 -0
- package/dist/internal/inventory-sync.js +189 -0
- package/dist/internal/org-picker.d.ts +11 -0
- package/dist/internal/org-picker.js +34 -0
- package/dist/internal/orgs.js +0 -1
- package/dist/internal/packs/cache.d.ts +8 -0
- package/dist/internal/packs/cache.js +60 -0
- package/dist/internal/packs/index.d.ts +10 -0
- package/dist/internal/packs/index.js +8 -0
- package/dist/internal/packs/integrity.d.ts +9 -0
- package/dist/internal/packs/integrity.js +24 -0
- package/dist/internal/packs/jcs.d.ts +2 -0
- package/dist/internal/packs/jcs.js +57 -0
- package/dist/internal/packs/local-paths.d.ts +4 -0
- package/dist/internal/packs/local-paths.js +26 -0
- package/dist/internal/packs/npm-registry-client.d.ts +14 -0
- package/dist/internal/packs/npm-registry-client.js +87 -0
- package/dist/internal/packs/registry-client.d.ts +9 -0
- package/dist/internal/packs/registry-client.js +25 -0
- package/dist/internal/packs/resolve.d.ts +8 -0
- package/dist/internal/packs/resolve.js +89 -0
- package/dist/internal/packs/semver.d.ts +9 -0
- package/dist/internal/packs/semver.js +57 -0
- package/dist/internal/packs/signing.d.ts +5 -0
- package/dist/internal/packs/signing.js +56 -0
- package/dist/internal/packs/types.d.ts +77 -0
- package/dist/internal/packs/types.js +20 -0
- package/dist/internal/project-binding.d.ts +1 -0
- package/dist/internal/project-binding.js +16 -11
- package/dist/internal/resolve-init-session.d.ts +8 -0
- package/dist/internal/resolve-init-session.js +36 -0
- package/dist/internal/run-workdir.d.ts +2 -0
- package/dist/internal/run-workdir.js +19 -0
- package/dist/internal/security-context.d.ts +63 -0
- package/dist/internal/security-context.js +50 -0
- package/dist/internal/sync-outbox.d.ts +40 -0
- package/dist/internal/sync-outbox.js +66 -0
- package/dist/internal/target-auth/api-config.d.ts +0 -17
- package/dist/internal/target-auth/api-config.js +0 -18
- package/dist/internal/target-auth/config.d.ts +0 -12
- package/dist/internal/target-auth/config.js +0 -13
- package/dist/internal/target-auth/index.d.ts +0 -18
- package/dist/internal/target-auth/index.js +0 -19
- package/dist/internal/target-auth/keychain.d.ts +0 -18
- package/dist/internal/target-auth/keychain.js +3 -23
- package/dist/internal/target-auth/types.d.ts +0 -26
- package/dist/internal/target-auth/types.js +0 -10
- package/dist/internal/target-auth/workos.d.ts +0 -15
- package/dist/internal/target-auth/workos.js +9 -18
- package/dist/internal/test-scope.d.ts +99 -0
- package/dist/internal/test-scope.js +101 -0
- package/dist/internal/threatmodel-run-state.d.ts +15 -0
- package/dist/internal/threatmodel-run-state.js +43 -0
- package/dist/internal/threatmodel-run.d.ts +85 -0
- package/dist/internal/threatmodel-run.js +159 -0
- package/dist/internal/trust-room-link.d.ts +10 -0
- package/dist/internal/trust-room-link.js +15 -0
- package/dist/internal/zivis-local-state.d.ts +1 -0
- package/dist/internal/zivis-local-state.js +22 -0
- package/package.json +21 -5
- package/dist/commands/app/index.js.map +0 -1
- package/dist/commands/auth/index.js.map +0 -1
- package/dist/commands/auth/init.js.map +0 -1
- package/dist/commands/auth/login.js.map +0 -1
- package/dist/commands/auth/logout.js.map +0 -1
- package/dist/commands/auth/sessions.js.map +0 -1
- package/dist/commands/auth/status.js.map +0 -1
- package/dist/commands/credits/index.js.map +0 -1
- package/dist/commands/mcp/index.js.map +0 -1
- package/dist/commands/open/index.js.map +0 -1
- package/dist/commands/scan/index.js.map +0 -1
- package/dist/commands/target/index.js.map +0 -1
- package/dist/commands/tm/index.d.ts +0 -2
- package/dist/commands/tm/index.js +0 -266
- package/dist/commands/tm/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/extractor/adapters/go/index.js.map +0 -1
- package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
- package/dist/internal/extractor/adapters/python/index.js.map +0 -1
- package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
- package/dist/internal/extractor/index.js.map +0 -1
- package/dist/internal/extractor/types.js.map +0 -1
- package/dist/internal/extractor/walker.js.map +0 -1
- package/dist/internal/ide-setup.js.map +0 -1
- package/dist/internal/orgs.js.map +0 -1
- package/dist/internal/project-binding.js.map +0 -1
- package/dist/internal/target-auth/api-config.js.map +0 -1
- package/dist/internal/target-auth/config.js.map +0 -1
- package/dist/internal/target-auth/index.js.map +0 -1
- package/dist/internal/target-auth/keychain.js.map +0 -1
- package/dist/internal/target-auth/types.js.map +0 -1
- package/dist/internal/target-auth/workos.js.map +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
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, reportDevXRunItems, 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
|
+
import { trustRoomJsonFields, trustRoomHumanLines } from "../../internal/trust-room-link.js";
|
|
8
|
+
async function readInput(inputArg) {
|
|
9
|
+
const raw = inputArg === "-" ? await readStdin() : await fs.readFile(inputArg, "utf-8");
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
throw new Error(`--input is not valid JSON: ${errorMessage(err)}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function readStdin() {
|
|
18
|
+
let data = "";
|
|
19
|
+
process.stdin.setEncoding("utf-8");
|
|
20
|
+
for await (const chunk of process.stdin) {
|
|
21
|
+
data += chunk;
|
|
22
|
+
}
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
export function registerRunCommands(program) {
|
|
26
|
+
const run = program.command("run").description("resolve a DevX run started by a run producer (e.g. `zivis test`)");
|
|
27
|
+
run
|
|
28
|
+
.command("complete <runId>")
|
|
29
|
+
.description("complete a pending run with its result envelope")
|
|
30
|
+
.requiredOption("--input <path>", "path to a JSON result file, or '-' for stdin")
|
|
31
|
+
.option("--json", "emit JSON only on stdout")
|
|
32
|
+
.action(async (runId, opts) => {
|
|
33
|
+
const json = !!opts.json;
|
|
34
|
+
let result;
|
|
35
|
+
try {
|
|
36
|
+
result = await readInput(opts.input);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
failCommand("run complete", err, json);
|
|
40
|
+
}
|
|
41
|
+
const config = resolveConfigFromBinding(process.cwd());
|
|
42
|
+
const client = new ApiClient(config);
|
|
43
|
+
try {
|
|
44
|
+
const response = await completeDevXRunRemote(client, {
|
|
45
|
+
runId,
|
|
46
|
+
contractVersion: DEVX_RUN_CONTRACT_VERSION,
|
|
47
|
+
result,
|
|
48
|
+
});
|
|
49
|
+
const reconciliation = response.reconciliation;
|
|
50
|
+
const output = jsonEnvelope("run complete", {
|
|
51
|
+
run_id: runId,
|
|
52
|
+
outcome: response.outcome,
|
|
53
|
+
status: response.run.status,
|
|
54
|
+
completed_at: response.run.completedAt,
|
|
55
|
+
...(reconciliation
|
|
56
|
+
? {
|
|
57
|
+
finding_ids: reconciliation.findings.map((f) => ({ id: f.id, action: f.action })),
|
|
58
|
+
retest_results: reconciliation.retests,
|
|
59
|
+
evidence_ids: reconciliation.evidence.map((e) => ({ id: e.id, action: e.action })),
|
|
60
|
+
coverage: reconciliation.coverage,
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
63
|
+
...trustRoomJsonFields(response),
|
|
64
|
+
});
|
|
65
|
+
if (json) {
|
|
66
|
+
printJson(output);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`zivis run complete: ${response.outcome} (run ${runId} is now ${response.run.status})`);
|
|
70
|
+
if (reconciliation) {
|
|
71
|
+
const created = reconciliation.findings.filter((f) => f.action === "created").length;
|
|
72
|
+
const updated = reconciliation.findings.filter((f) => f.action === "updated").length;
|
|
73
|
+
const retested = reconciliation.retests.filter((r) => r.action === "disposition_updated").length;
|
|
74
|
+
if (created)
|
|
75
|
+
console.log(` + ${created} new finding${created === 1 ? "" : "s"}`);
|
|
76
|
+
if (updated)
|
|
77
|
+
console.log(` ~ ${updated} finding${updated === 1 ? "" : "s"} updated`);
|
|
78
|
+
if (retested)
|
|
79
|
+
console.log(` ✓ ${retested} retest${retested === 1 ? "" : "s"} recorded`);
|
|
80
|
+
if (reconciliation.evidence.length)
|
|
81
|
+
console.log(` ${reconciliation.evidence.length} artifact(s) persisted`);
|
|
82
|
+
}
|
|
83
|
+
const trustRoomLines = trustRoomHumanLines(response);
|
|
84
|
+
if (trustRoomLines.length) {
|
|
85
|
+
console.log("");
|
|
86
|
+
for (const line of trustRoomLines)
|
|
87
|
+
console.log(line);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
process.exit(EXIT.OK);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (isRetryableDevXRunError(err)) {
|
|
94
|
+
const message = errorMessage(err);
|
|
95
|
+
const outboxPath = writeDevXRunCompleteOutboxEntry(process.cwd(), {
|
|
96
|
+
runId,
|
|
97
|
+
contractVersion: DEVX_RUN_CONTRACT_VERSION,
|
|
98
|
+
result,
|
|
99
|
+
error: message,
|
|
100
|
+
});
|
|
101
|
+
if (json) {
|
|
102
|
+
printJson(jsonEnvelope("run complete", { run_id: runId, outcome: "queued", queued_at: outboxPath, error: message }));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(`zivis run complete: Cloud unreachable (${message})\nQueued for retry: ${outboxPath}\nRun \`zivis sync --flush\` once the issue is resolved.`);
|
|
106
|
+
}
|
|
107
|
+
process.exit(EXIT.CLOUD_UNAVAILABLE);
|
|
108
|
+
}
|
|
109
|
+
failCommand("run complete", err, json);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
run
|
|
113
|
+
.command("report <runId>")
|
|
114
|
+
.description("post confirmed findings/evidence/observations against a pending run as you go, without completing it (ZIV-332)")
|
|
115
|
+
.requiredOption("--input <path>", "path to a JSON item-batch file ({ findings?, evidence?, observations? }), or '-' for stdin")
|
|
116
|
+
.option("--json", "emit JSON only on stdout")
|
|
117
|
+
.action(async (runId, opts) => {
|
|
118
|
+
const json = !!opts.json;
|
|
119
|
+
let items;
|
|
120
|
+
try {
|
|
121
|
+
items = await readInput(opts.input);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
failCommand("run report", err, json);
|
|
125
|
+
}
|
|
126
|
+
if (!items || typeof items !== "object" || Array.isArray(items)) {
|
|
127
|
+
failCommand("run report", new Error("--input must be a JSON object with findings[], evidence[], and/or observations[]"), json);
|
|
128
|
+
}
|
|
129
|
+
const config = resolveConfigFromBinding(process.cwd());
|
|
130
|
+
const client = new ApiClient(config);
|
|
131
|
+
try {
|
|
132
|
+
const response = await reportDevXRunItems(client, { runId, items: items });
|
|
133
|
+
const output = jsonEnvelope("run report", {
|
|
134
|
+
run_id: runId,
|
|
135
|
+
outcome: response.outcome,
|
|
136
|
+
findings: response.items.findings,
|
|
137
|
+
evidence: response.items.evidence,
|
|
138
|
+
observations: response.items.observations,
|
|
139
|
+
...(response.rejectedObservations?.length ? { rejected_observations: response.rejectedObservations } : {}),
|
|
140
|
+
run_totals: response.runTotals,
|
|
141
|
+
});
|
|
142
|
+
if (json) {
|
|
143
|
+
printJson(output);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const findings = response.items.findings;
|
|
147
|
+
const evidence = response.items.evidence;
|
|
148
|
+
const observations = response.items.observations;
|
|
149
|
+
const created = findings.filter((f) => f.action === "created").length;
|
|
150
|
+
const matched = findings.length - created;
|
|
151
|
+
const retest = findings.filter((f) => f.retestRecommendation !== null).length;
|
|
152
|
+
const withHistory = findings.filter((f) => f.dispositionHistory.length > 0).length;
|
|
153
|
+
const total = findings.length + evidence.length + observations.length;
|
|
154
|
+
console.log(`zivis run report: applied ${total} item${total === 1 ? "" : "s"} (run ${runId} stays pending)`);
|
|
155
|
+
if (created)
|
|
156
|
+
console.log(` + ${created} new finding${created === 1 ? "" : "s"}`);
|
|
157
|
+
if (matched)
|
|
158
|
+
console.log(` = ${matched} matched existing finding${matched === 1 ? "" : "s"}`);
|
|
159
|
+
if (retest)
|
|
160
|
+
console.log(` ↻ ${retest} retest recommendation${retest === 1 ? "" : "s"} — cite in retests[] on run complete`);
|
|
161
|
+
if (withHistory)
|
|
162
|
+
console.log(` ⚑ ${withHistory} finding${withHistory === 1 ? "" : "s"} with prior disposition history — surface to the human; never auto-apply`);
|
|
163
|
+
if (evidence.length)
|
|
164
|
+
console.log(` ${evidence.length} evidence item${evidence.length === 1 ? "" : "s"} persisted`);
|
|
165
|
+
if (observations.length)
|
|
166
|
+
console.log(` ${observations.length} observation${observations.length === 1 ? "" : "s"} recorded`);
|
|
167
|
+
if (response.rejectedObservations?.length)
|
|
168
|
+
console.log(` ! ${response.rejectedObservations.length} observation(s) rejected — see --json for reasons`);
|
|
169
|
+
console.log(` run totals so far: ${response.runTotals.findings} findings / ${response.runTotals.evidence} evidence / ${response.runTotals.observations} observations`);
|
|
170
|
+
}
|
|
171
|
+
process.exit(EXIT.OK);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
failCommand("run report", err, json);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
run
|
|
178
|
+
.command("cancel <runId>")
|
|
179
|
+
.description("cancel a pending run")
|
|
180
|
+
.option("--reason <text>", "optional human-readable cancellation reason")
|
|
181
|
+
.option("--json", "emit JSON only on stdout")
|
|
182
|
+
.action(async (runId, opts) => {
|
|
183
|
+
const json = !!opts.json;
|
|
184
|
+
const config = resolveConfigFromBinding(process.cwd());
|
|
185
|
+
const client = new ApiClient(config);
|
|
186
|
+
try {
|
|
187
|
+
const response = await cancelDevXRunRemote(client, { runId, reason: opts.reason ?? null });
|
|
188
|
+
const output = jsonEnvelope("run cancel", {
|
|
189
|
+
run_id: runId,
|
|
190
|
+
outcome: response.outcome,
|
|
191
|
+
status: response.run.status,
|
|
192
|
+
cancelled_at: response.run.cancelledAt,
|
|
193
|
+
});
|
|
194
|
+
if (json) {
|
|
195
|
+
printJson(output);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.log(`zivis run cancel: ${response.outcome} (run ${runId} is now ${response.run.status})`);
|
|
199
|
+
}
|
|
200
|
+
process.exit(EXIT.OK);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
failCommand("run cancel", err, json);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ApiClient } from "@zivis/mcp/api-client";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
|
|
3
3
|
const VALID_TARGET_TYPES = ["ai_chat", "web_app", "api"];
|
|
4
4
|
function buildClient(opts) {
|
|
5
|
-
const config = opts.session ? { ...
|
|
5
|
+
const config = opts.session ? { ...resolveConfigFromBinding(process.cwd()), session: opts.session } : resolveConfigFromBinding(process.cwd());
|
|
6
6
|
return new ApiClient(config);
|
|
7
7
|
}
|
|
8
8
|
export function registerScanCommands(program) {
|
|
@@ -118,11 +118,6 @@ export function registerScanCommands(program) {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Map customer-facing ApplicationType to the RT scan's targetType enum.
|
|
123
|
-
* The two enums differ historically: ApplicationType is product-shape,
|
|
124
|
-
* targetType is what the RT runner needs to know about the wire format.
|
|
125
|
-
*/
|
|
126
121
|
function appTypeToTargetType(appType) {
|
|
127
122
|
switch (appType) {
|
|
128
123
|
case "agent":
|
|
@@ -131,10 +126,10 @@ function appTypeToTargetType(appType) {
|
|
|
131
126
|
return "ai_chat";
|
|
132
127
|
case "rest_api":
|
|
133
128
|
return "api";
|
|
129
|
+
case "application":
|
|
134
130
|
case "ad_hoc_url":
|
|
135
131
|
return "web_app";
|
|
136
132
|
default:
|
|
137
133
|
return undefined;
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
export interface ShareResponse {
|
|
3
|
+
trustRoomId: string;
|
|
4
|
+
trustRoomName: string;
|
|
5
|
+
trustRoomUrl: string;
|
|
6
|
+
accessUrl: string;
|
|
7
|
+
email: string;
|
|
8
|
+
permission: string;
|
|
9
|
+
status: "shared" | "already_shared";
|
|
10
|
+
isNewUser: boolean;
|
|
11
|
+
emailSent: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function registerShareCommands(program: Command): void;
|
|
14
|
+
export default registerShareCommands;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { ApiClient } from "@zivis/mcp/api-client";
|
|
3
|
+
import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
|
|
4
|
+
import { requireApplicationId } from "@zivis/mcp/resolve-application-id";
|
|
5
|
+
import { jsonEnvelope, printJson, failCommand, EXIT } from "../../internal/cli-output.js";
|
|
6
|
+
const COMMAND = "share";
|
|
7
|
+
const PERMISSIONS = ["view", "comment", "edit", "admin"];
|
|
8
|
+
async function prompt(question) {
|
|
9
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
10
|
+
try {
|
|
11
|
+
return (await rl.question(question)).trim();
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
rl.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isValidEmail(value) {
|
|
18
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
19
|
+
}
|
|
20
|
+
export function registerShareCommands(program) {
|
|
21
|
+
program
|
|
22
|
+
.command("share")
|
|
23
|
+
.description("share this Application's current Assurance Room with someone by email")
|
|
24
|
+
.argument("[email]", "who to share with (prompted for when omitted)")
|
|
25
|
+
.option("-s, --session <name>", "named session for multi-tenant")
|
|
26
|
+
.option("--app <id>", "Application id (defaults to the bound Application)")
|
|
27
|
+
.option("--permission <level>", `access level: ${PERMISSIONS.join(" | ")}`, "view")
|
|
28
|
+
.option("--json", "emit machine-readable JSON")
|
|
29
|
+
.action(async (emailArg, opts) => {
|
|
30
|
+
const json = Boolean(opts.json);
|
|
31
|
+
try {
|
|
32
|
+
const permission = (opts.permission ?? "view").toLowerCase();
|
|
33
|
+
if (!PERMISSIONS.includes(permission)) {
|
|
34
|
+
throw new Error(`--permission must be one of: ${PERMISSIONS.join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
const resolvedApp = requireApplicationId(opts.app, process.cwd());
|
|
37
|
+
if (!resolvedApp.ok) {
|
|
38
|
+
if (json) {
|
|
39
|
+
printJson(jsonEnvelope(COMMAND, { result: "error", error: resolvedApp.message }));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(`zivis ${COMMAND}: ${resolvedApp.message}`);
|
|
43
|
+
}
|
|
44
|
+
process.exit(EXIT.INVALID_OR_INDETERMINATE);
|
|
45
|
+
}
|
|
46
|
+
let email = (emailArg ?? "").trim();
|
|
47
|
+
if (!email) {
|
|
48
|
+
if (json) {
|
|
49
|
+
throw new Error("An email argument is required with --json (no prompting).");
|
|
50
|
+
}
|
|
51
|
+
email = await prompt("Share this Assurance Room with (email): ");
|
|
52
|
+
}
|
|
53
|
+
if (!isValidEmail(email)) {
|
|
54
|
+
throw new Error(`'${email}' does not look like an email address.`);
|
|
55
|
+
}
|
|
56
|
+
const config = opts.session
|
|
57
|
+
? { ...resolveConfigFromBinding(process.cwd()), session: opts.session }
|
|
58
|
+
: resolveConfigFromBinding(process.cwd());
|
|
59
|
+
const client = new ApiClient(config);
|
|
60
|
+
const res = await client.post("/api/assurance/share", {
|
|
61
|
+
applicationId: resolvedApp.id,
|
|
62
|
+
email,
|
|
63
|
+
permission,
|
|
64
|
+
});
|
|
65
|
+
if (json) {
|
|
66
|
+
printJson(jsonEnvelope(COMMAND, {
|
|
67
|
+
result: "ok",
|
|
68
|
+
status: res.status,
|
|
69
|
+
email: res.email,
|
|
70
|
+
permission: res.permission,
|
|
71
|
+
is_new_user: res.isNewUser,
|
|
72
|
+
email_sent: res.emailSent,
|
|
73
|
+
trust_room_id: res.trustRoomId,
|
|
74
|
+
trust_room_url: res.trustRoomUrl,
|
|
75
|
+
access_url: res.accessUrl,
|
|
76
|
+
}));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (res.status === "already_shared") {
|
|
80
|
+
console.log(`${res.email} already has access to ${res.trustRoomName}.`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(`Shared ${res.trustRoomName} with ${res.email} (${res.permission}).`);
|
|
84
|
+
}
|
|
85
|
+
if (res.isNewUser) {
|
|
86
|
+
console.log(`${res.email} has no ZIVIS account yet — they'll create one when they open the link.`);
|
|
87
|
+
}
|
|
88
|
+
if (!res.emailSent) {
|
|
89
|
+
console.log("Access was granted, but the invitation email could not be sent.");
|
|
90
|
+
console.log("Pass this link on directly:");
|
|
91
|
+
}
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log(`Assurance Room: ${res.accessUrl}`);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
failCommand(COMMAND, err, json);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export default registerShareCommands;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ApiClient } from "@zivis/mcp/api-client";
|
|
4
|
+
import { detectProjectBinding, resolveConfigFromBinding } from "@zivis/mcp/project-binding";
|
|
5
|
+
import { readGitMetadata, deriveRepoFullNameFromRemote } from "../../internal/git-metadata.js";
|
|
6
|
+
import { validateInventorySyncInput, syncApplicationInventory, } from "../../internal/inventory-sync.js";
|
|
7
|
+
import { ensureZivisGitignore } from "../../internal/zivis-local-state.js";
|
|
8
|
+
import { writeOutboxEntry, listOutboxEntries, removeOutboxEntry, recordOutboxRetryFailure, } from "../../internal/sync-outbox.js";
|
|
9
|
+
import { completeDevXRunRemote } from "../../internal/devx-run.js";
|
|
10
|
+
export function registerSyncCommands(program) {
|
|
11
|
+
program
|
|
12
|
+
.command("sync")
|
|
13
|
+
.description("push a locally-derived endpoint/feature/technology-stack inventory to your bound ZIVIS Application " +
|
|
14
|
+
"(the coding agent derives the inventory; this command reconciles it — see --file)")
|
|
15
|
+
.option("--file <path>", "JSON file with { endpoints?, features?, technologyStack? } (use - for stdin)")
|
|
16
|
+
.option("--flush", "retry pending outbox entries from a previous failed sync, instead of reading --file")
|
|
17
|
+
.option("--cwd <path>", "repo root (default: cwd)")
|
|
18
|
+
.option("--source <source>", "provenance source: cli or agent", "agent")
|
|
19
|
+
.option("--format <fmt>", "output format: human or json", "human")
|
|
20
|
+
.option("--dry-run", "validate/list and print what would be sent — no network calls")
|
|
21
|
+
.action(async (opts) => {
|
|
22
|
+
const exitCode = opts.flush ? await runFlush(opts) : await runSync(opts);
|
|
23
|
+
process.exit(exitCode);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async function runSync(opts) {
|
|
27
|
+
const rootDir = opts.cwd ? path.resolve(opts.cwd) : process.cwd();
|
|
28
|
+
const format = opts.format === "json" ? "json" : "human";
|
|
29
|
+
const source = opts.source === "cli" ? "cli" : "agent";
|
|
30
|
+
if (!opts.file) {
|
|
31
|
+
return fail(format, "--file is required (or pass --flush to retry queued entries)");
|
|
32
|
+
}
|
|
33
|
+
if (opts.source && opts.source !== "cli" && opts.source !== "agent") {
|
|
34
|
+
return fail(format, `--source must be "cli" or "agent" (got: ${opts.source})`);
|
|
35
|
+
}
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = opts.file === "-" ? await readStdin() : await fs.readFile(path.resolve(rootDir, opts.file), "utf-8");
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return fail(format, `Could not read ${opts.file}: ${err instanceof Error ? err.message : err}`);
|
|
42
|
+
}
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(raw);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
return fail(format, `Invalid JSON: ${err instanceof Error ? err.message : err}`);
|
|
49
|
+
}
|
|
50
|
+
const validated = validateInventorySyncInput(parsed);
|
|
51
|
+
if (!validated.ok) {
|
|
52
|
+
return fail(format, `Invalid inventory input:\n - ${validated.errors.join("\n - ")}`);
|
|
53
|
+
}
|
|
54
|
+
const binding = detectProjectBinding(rootDir);
|
|
55
|
+
const applicationId = binding?.binding.applicationId;
|
|
56
|
+
if (!applicationId) {
|
|
57
|
+
return fail(format, "No Application bound for this repo. Run `zivis init` first (see .zivis/project.json).");
|
|
58
|
+
}
|
|
59
|
+
const gitMeta = await readGitMetadata(rootDir);
|
|
60
|
+
const repoFullName = deriveRepoFullNameFromRemote(gitMeta.remoteUrl);
|
|
61
|
+
const provenance = {
|
|
62
|
+
gitCommitSha: gitMeta.commitSha ?? null,
|
|
63
|
+
repoFullName: repoFullName ?? null,
|
|
64
|
+
defaultBranch: gitMeta.branch ?? null,
|
|
65
|
+
source,
|
|
66
|
+
};
|
|
67
|
+
if (opts.dryRun) {
|
|
68
|
+
process.stdout.write(JSON.stringify({ applicationId, provenance, input: validated.value }, null, 2) + "\n");
|
|
69
|
+
if (format === "human") {
|
|
70
|
+
process.stderr.write("\n[zivis sync] --dry-run: nothing was sent.\n");
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
ensureZivisGitignore(rootDir);
|
|
75
|
+
const config = resolveConfigFromBinding(rootDir);
|
|
76
|
+
const client = new ApiClient(config);
|
|
77
|
+
let summary;
|
|
78
|
+
try {
|
|
79
|
+
summary = await syncApplicationInventory({ client, applicationId, input: validated.value, provenance });
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
83
|
+
const outboxPath = writeOutboxEntry(rootDir, { applicationId, input: validated.value, provenance, error: message });
|
|
84
|
+
return fail(format, `Sync failed: ${message}\nQueued for retry: ${outboxPath}\nRun \`zivis sync --flush\` once the issue is resolved.`);
|
|
85
|
+
}
|
|
86
|
+
if (format === "json") {
|
|
87
|
+
process.stdout.write(JSON.stringify({ applicationId, summary }, null, 2) + "\n");
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
renderHumanSummary(summary);
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
async function runFlush(opts) {
|
|
94
|
+
const rootDir = opts.cwd ? path.resolve(opts.cwd) : process.cwd();
|
|
95
|
+
const format = opts.format === "json" ? "json" : "human";
|
|
96
|
+
const pending = listOutboxEntries(rootDir);
|
|
97
|
+
if (pending.length === 0) {
|
|
98
|
+
if (format === "json")
|
|
99
|
+
process.stdout.write(JSON.stringify({ pending: 0 }, null, 2) + "\n");
|
|
100
|
+
else
|
|
101
|
+
process.stdout.write("\nNo pending outbox entries.\n");
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
if (opts.dryRun) {
|
|
105
|
+
const preview = pending.map((p) => ({
|
|
106
|
+
file: p.filePath,
|
|
107
|
+
kind: p.entry.kind,
|
|
108
|
+
queuedAt: p.entry.queuedAt,
|
|
109
|
+
attempts: p.entry.attempts,
|
|
110
|
+
lastError: p.entry.lastError,
|
|
111
|
+
...(p.entry.kind === "devx-run-complete"
|
|
112
|
+
? { runId: p.entry.runId, result: p.entry.result }
|
|
113
|
+
: { applicationId: p.entry.applicationId, input: p.entry.input }),
|
|
114
|
+
}));
|
|
115
|
+
process.stdout.write(JSON.stringify(preview, null, 2) + "\n");
|
|
116
|
+
if (format === "human")
|
|
117
|
+
process.stderr.write(`\n[zivis sync --flush] --dry-run: ${pending.length} pending, nothing sent.\n`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
const config = resolveConfigFromBinding(rootDir);
|
|
121
|
+
const client = new ApiClient(config);
|
|
122
|
+
let succeeded = 0;
|
|
123
|
+
let failed = 0;
|
|
124
|
+
const trustRoomUrls = [];
|
|
125
|
+
for (const { filePath, entry } of pending) {
|
|
126
|
+
try {
|
|
127
|
+
if (entry.kind === "devx-run-complete") {
|
|
128
|
+
const completed = await completeDevXRunRemote(client, { runId: entry.runId, contractVersion: entry.contractVersion, result: entry.result });
|
|
129
|
+
if (completed.trustRoomUrl && !trustRoomUrls.includes(completed.trustRoomUrl)) {
|
|
130
|
+
trustRoomUrls.push(completed.trustRoomUrl);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await syncApplicationInventory({
|
|
135
|
+
client,
|
|
136
|
+
applicationId: entry.applicationId,
|
|
137
|
+
input: entry.input,
|
|
138
|
+
provenance: entry.provenance,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
removeOutboxEntry(filePath);
|
|
142
|
+
succeeded++;
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
recordOutboxRetryFailure(filePath, entry, err instanceof Error ? err.message : String(err));
|
|
146
|
+
failed++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (format === "json") {
|
|
150
|
+
process.stdout.write(JSON.stringify({ succeeded, failed, remaining: failed, ...(trustRoomUrls.length ? { trust_room_urls: trustRoomUrls } : {}) }, null, 2) + "\n");
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
process.stdout.write(`\nFlushed outbox: ${succeeded} succeeded, ${failed} still failing (left queued).\n`);
|
|
154
|
+
for (const url of trustRoomUrls)
|
|
155
|
+
process.stdout.write(`Trust Room: ${url}\n`);
|
|
156
|
+
}
|
|
157
|
+
return failed > 0 ? 1 : 0;
|
|
158
|
+
}
|
|
159
|
+
function renderHumanSummary(summary) {
|
|
160
|
+
let out = "\nZIVIS inventory sync:\n\n";
|
|
161
|
+
if (summary.technologyStack) {
|
|
162
|
+
out += ` Technology stack: ${summary.technologyStack.count} entries\n`;
|
|
163
|
+
}
|
|
164
|
+
if (summary.endpoints) {
|
|
165
|
+
const e = summary.endpoints;
|
|
166
|
+
out += ` Endpoints: +${e.created} new, ${e.updated} updated, ${e.deprecated} deprecated (${e.total} total reported)\n`;
|
|
167
|
+
}
|
|
168
|
+
if (summary.features) {
|
|
169
|
+
const f = summary.features;
|
|
170
|
+
out += ` Features: +${f.created} new, ${f.updated} updated, ${f.deprecated} deprecated\n`;
|
|
171
|
+
}
|
|
172
|
+
if (summary.repo) {
|
|
173
|
+
out += ` Repository: ${summary.repo.repoFullName} (${summary.repo.created ? "linked" : "revision updated"})\n`;
|
|
174
|
+
}
|
|
175
|
+
process.stdout.write(out + "\n");
|
|
176
|
+
}
|
|
177
|
+
function fail(format, message) {
|
|
178
|
+
if (format === "json") {
|
|
179
|
+
process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
process.stderr.write(`\n${message}\n`);
|
|
183
|
+
}
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
async function readStdin() {
|
|
187
|
+
let data = "";
|
|
188
|
+
process.stdin.setEncoding("utf-8");
|
|
189
|
+
for await (const chunk of process.stdin) {
|
|
190
|
+
data += chunk;
|
|
191
|
+
}
|
|
192
|
+
return data;
|
|
193
|
+
}
|
|
@@ -4,8 +4,6 @@ import { getStorageName } from "../../internal/target-auth/keychain.js";
|
|
|
4
4
|
import { decodeJwtPayload } from "../../internal/target-auth/workos.js";
|
|
5
5
|
const DEFAULT_PERSONA = "default";
|
|
6
6
|
function warnDeprecated() {
|
|
7
|
-
// stderr so it doesn't pollute stdout (CLI scripts pipe `zivis target token`
|
|
8
|
-
// straight into curl). One line, no color codes — keeps grep-friendly.
|
|
9
7
|
console.error("[zivis] WARNING: 'zivis target' is deprecated; use 'zivis app' instead.");
|
|
10
8
|
}
|
|
11
9
|
export function registerTargetCommands(program) {
|
|
@@ -143,4 +141,3 @@ export function registerTargetCommands(program) {
|
|
|
143
141
|
console.log(`✓ Cleared tokens for target ${targetId} (persona: ${opts.persona})`);
|
|
144
142
|
});
|
|
145
143
|
}
|
|
146
|
-
//# sourceMappingURL=index.js.map
|