@zivis/cli 0.1.0-alpha.40 → 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 (66) hide show
  1. package/dist/commands/app/index.js +4 -2
  2. package/dist/commands/assurance/index.d.ts +33 -0
  3. package/dist/commands/assurance/index.js +149 -0
  4. package/dist/commands/auth/index.js +1 -0
  5. package/dist/commands/auth/init.d.ts +1 -0
  6. package/dist/commands/auth/init.js +12 -8
  7. package/dist/commands/gate/index.d.ts +2 -0
  8. package/dist/commands/gate/index.js +171 -0
  9. package/dist/commands/mcp/index.js +33 -1
  10. package/dist/commands/run/index.d.ts +2 -0
  11. package/dist/commands/run/index.js +111 -0
  12. package/dist/commands/sync/index.d.ts +2 -0
  13. package/dist/commands/sync/index.js +187 -0
  14. package/dist/commands/test/index.d.ts +2 -0
  15. package/dist/commands/test/index.js +115 -0
  16. package/dist/commands/tm/index.js +4 -0
  17. package/dist/index.js +11 -0
  18. package/dist/internal/application-binding.d.ts +8 -0
  19. package/dist/internal/application-binding.js +167 -0
  20. package/dist/internal/cli-output.d.ts +16 -0
  21. package/dist/internal/cli-output.js +39 -0
  22. package/dist/internal/devx-run.d.ts +47 -0
  23. package/dist/internal/devx-run.js +36 -0
  24. package/dist/internal/gate-evaluate.d.ts +50 -0
  25. package/dist/internal/gate-evaluate.js +111 -0
  26. package/dist/internal/gate-policy.d.ts +38 -0
  27. package/dist/internal/gate-policy.js +167 -0
  28. package/dist/internal/git-metadata.d.ts +8 -0
  29. package/dist/internal/git-metadata.js +38 -0
  30. package/dist/internal/git.d.ts +8 -0
  31. package/dist/internal/git.js +30 -0
  32. package/dist/internal/ide-setup.d.ts +1 -0
  33. package/dist/internal/ide-setup.js +76 -19
  34. package/dist/internal/inventory-sync.d.ts +74 -0
  35. package/dist/internal/inventory-sync.js +189 -0
  36. package/dist/internal/packs/cache.d.ts +8 -0
  37. package/dist/internal/packs/cache.js +60 -0
  38. package/dist/internal/packs/index.d.ts +10 -0
  39. package/dist/internal/packs/index.js +7 -0
  40. package/dist/internal/packs/integrity.d.ts +9 -0
  41. package/dist/internal/packs/integrity.js +24 -0
  42. package/dist/internal/packs/jcs.d.ts +2 -0
  43. package/dist/internal/packs/jcs.js +57 -0
  44. package/dist/internal/packs/local-paths.d.ts +4 -0
  45. package/dist/internal/packs/local-paths.js +26 -0
  46. package/dist/internal/packs/registry-client.d.ts +20 -0
  47. package/dist/internal/packs/registry-client.js +40 -0
  48. package/dist/internal/packs/resolve.d.ts +8 -0
  49. package/dist/internal/packs/resolve.js +89 -0
  50. package/dist/internal/packs/semver.d.ts +9 -0
  51. package/dist/internal/packs/semver.js +57 -0
  52. package/dist/internal/packs/signing.d.ts +5 -0
  53. package/dist/internal/packs/signing.js +49 -0
  54. package/dist/internal/packs/types.d.ts +70 -0
  55. package/dist/internal/packs/types.js +20 -0
  56. package/dist/internal/run-workdir.d.ts +1 -0
  57. package/dist/internal/run-workdir.js +11 -0
  58. package/dist/internal/security-context.d.ts +62 -0
  59. package/dist/internal/security-context.js +50 -0
  60. package/dist/internal/sync-outbox.d.ts +40 -0
  61. package/dist/internal/sync-outbox.js +66 -0
  62. package/dist/internal/test-scope.d.ts +68 -0
  63. package/dist/internal/test-scope.js +69 -0
  64. package/dist/internal/zivis-local-state.d.ts +1 -0
  65. package/dist/internal/zivis-local-state.js +22 -0
  66. package/package.json +3 -2
@@ -0,0 +1,187 @@
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
+ for (const { filePath, entry } of pending) {
125
+ try {
126
+ if (entry.kind === "devx-run-complete") {
127
+ await completeDevXRunRemote(client, { runId: entry.runId, contractVersion: entry.contractVersion, result: entry.result });
128
+ }
129
+ else {
130
+ await syncApplicationInventory({
131
+ client,
132
+ applicationId: entry.applicationId,
133
+ input: entry.input,
134
+ provenance: entry.provenance,
135
+ });
136
+ }
137
+ removeOutboxEntry(filePath);
138
+ succeeded++;
139
+ }
140
+ catch (err) {
141
+ recordOutboxRetryFailure(filePath, entry, err instanceof Error ? err.message : String(err));
142
+ failed++;
143
+ }
144
+ }
145
+ if (format === "json") {
146
+ process.stdout.write(JSON.stringify({ succeeded, failed, remaining: failed }, null, 2) + "\n");
147
+ }
148
+ else {
149
+ process.stdout.write(`\nFlushed outbox: ${succeeded} succeeded, ${failed} still failing (left queued).\n`);
150
+ }
151
+ return failed > 0 ? 1 : 0;
152
+ }
153
+ function renderHumanSummary(summary) {
154
+ let out = "\nZIVIS inventory sync:\n\n";
155
+ if (summary.technologyStack) {
156
+ out += ` Technology stack: ${summary.technologyStack.count} entries\n`;
157
+ }
158
+ if (summary.endpoints) {
159
+ const e = summary.endpoints;
160
+ out += ` Endpoints: +${e.created} new, ${e.updated} updated, ${e.deprecated} deprecated (${e.total} total reported)\n`;
161
+ }
162
+ if (summary.features) {
163
+ const f = summary.features;
164
+ out += ` Features: +${f.created} new, ${f.updated} updated, ${f.deprecated} deprecated\n`;
165
+ }
166
+ if (summary.repo) {
167
+ out += ` Repository: ${summary.repo.repoFullName} (${summary.repo.created ? "linked" : "revision updated"})\n`;
168
+ }
169
+ process.stdout.write(out + "\n");
170
+ }
171
+ function fail(format, message) {
172
+ if (format === "json") {
173
+ process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
174
+ }
175
+ else {
176
+ process.stderr.write(`\n${message}\n`);
177
+ }
178
+ return 1;
179
+ }
180
+ async function readStdin() {
181
+ let data = "";
182
+ process.stdin.setEncoding("utf-8");
183
+ for await (const chunk of process.stdin) {
184
+ data += chunk;
185
+ }
186
+ return data;
187
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerTestCommand(program: Command): void;
@@ -0,0 +1,115 @@
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 } from "../../internal/test-scope.js";
9
+ import { HttpPackRegistryClient, LocalPackRegistryClient, registryDirOverride } from "../../internal/packs/registry-client.js";
10
+ import { PackUnavailableError, PackIntegrityError, UnknownScopeError } from "../../internal/packs/types.js";
11
+ import { jsonEnvelope, printJson, EXIT, exitCodeForError, errorMessage, failCommand } from "../../internal/cli-output.js";
12
+ class UsageError extends Error {
13
+ }
14
+ function buildRegistryClient(apiClient) {
15
+ const override = registryDirOverride();
16
+ if (override)
17
+ return new LocalPackRegistryClient(override);
18
+ return new HttpPackRegistryClient(apiClient);
19
+ }
20
+ function exitCodeForTestError(err) {
21
+ if (err instanceof UnknownScopeError || err instanceof PackIntegrityError)
22
+ return EXIT.INVALID_OR_INDETERMINATE;
23
+ if (err instanceof PackUnavailableError)
24
+ return EXIT.CLOUD_UNAVAILABLE;
25
+ return exitCodeForError(err);
26
+ }
27
+ function getCliVersion() {
28
+ try {
29
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
30
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../../package.json"), "utf-8"));
31
+ return pkg.version;
32
+ }
33
+ catch {
34
+ return "0.0.0";
35
+ }
36
+ }
37
+ export function registerTestCommand(program) {
38
+ program
39
+ .command("test [scope]")
40
+ .description("run a Zivis-guided AppSec review — full/adaptive by default, or targeted at a stable scope")
41
+ .option("--list", "list available scopes from the verified AppSec methodology pack")
42
+ .option("--app <id>", "Application id (defaults to the bound Application)")
43
+ .option("--json", "emit JSON only on stdout")
44
+ .action(async (scope, opts) => {
45
+ const json = !!opts.json;
46
+ const cwd = process.cwd();
47
+ const config = resolveConfigFromBinding(cwd);
48
+ const apiClient = new ApiClient(config);
49
+ const registryClient = buildRegistryClient(apiClient);
50
+ const cliVersion = getCliVersion();
51
+ if (opts.list) {
52
+ try {
53
+ const listing = await listTestScopes(registryClient, cliVersion);
54
+ if (json) {
55
+ printJson(jsonEnvelope("test", { list: true, methodology: { pack: listing.pack, version: listing.version }, scopes: listing.scopes }));
56
+ }
57
+ else {
58
+ printListHuman(listing);
59
+ }
60
+ process.exit(EXIT.OK);
61
+ }
62
+ catch (err) {
63
+ failCommand("test", err, json);
64
+ }
65
+ return;
66
+ }
67
+ try {
68
+ const resolvedApp = requireApplicationId(opts.app, cwd);
69
+ if (!resolvedApp.ok) {
70
+ throw new UsageError(resolvedApp.message);
71
+ }
72
+ const { envelope, resolvedScope, contextOutcome } = await startTestRun({ apiClient, registryClient }, { applicationId: resolvedApp.id, scopeId: scope, cwd, cliVersion });
73
+ const output = jsonEnvelope("test", { ...envelope });
74
+ if (json) {
75
+ printJson(output);
76
+ }
77
+ else {
78
+ printRunHuman(envelope, resolvedScope, contextOutcome);
79
+ }
80
+ process.exit(EXIT.OK);
81
+ }
82
+ catch (err) {
83
+ const code = err instanceof UsageError ? EXIT.INVALID_OR_INDETERMINATE : exitCodeForTestError(err);
84
+ const message = errorMessage(err);
85
+ if (json) {
86
+ printJson(jsonEnvelope("test", { result: "error", scope: scope ?? null, error: message }));
87
+ }
88
+ else {
89
+ console.error(`zivis test: ${message}`);
90
+ if (err instanceof UnknownScopeError) {
91
+ console.error(`Run \`zivis test --list\` for descriptions.`);
92
+ }
93
+ }
94
+ process.exit(code);
95
+ }
96
+ });
97
+ }
98
+ function printListHuman(listing) {
99
+ const lines = [`ZIVIS AppSec methodology — ${listing.pack} v${listing.version}`, ""];
100
+ const idWidth = Math.max(...listing.scopes.map((s) => s.id.length), "scope".length);
101
+ for (const s of listing.scopes) {
102
+ lines.push(` ${s.id.padEnd(idWidth)} ${s.label} — ${s.description}`);
103
+ }
104
+ lines.push("", "Run `zivis test <scope>` for a focused review, or `zivis test` for a full adaptive review.");
105
+ console.log(lines.join("\n"));
106
+ }
107
+ function printRunHuman(envelope, resolvedScope, contextOutcome) {
108
+ console.log(`zivis test: started run ${envelope.run_id}${resolvedScope ? ` (scope: ${resolvedScope.id})` : " (full adaptive review)"}`);
109
+ console.log(renderSecurityContext(contextOutcome, "human"));
110
+ console.log(`\nMethodology: ${envelope.methodology.path}`);
111
+ if (envelope.methodology.scope) {
112
+ console.log(`Focused scope guidance: ${envelope.methodology.scope.path}`);
113
+ }
114
+ console.log(`\nWhen finished: ${envelope.result_contract.complete_command}`);
115
+ }
@@ -4,6 +4,7 @@ import { ApiClient } from "@zivis/mcp/api-client";
4
4
  import { resolveConfigFromBinding } from "@zivis/mcp/project-binding";
5
5
  import { getEphemeralCreds } from "../../internal/target-auth/index.js";
6
6
  import { runExtractor } from "../../internal/extractor/index.js";
7
+ import { ensureZivisGitignore } from "../../internal/zivis-local-state.js";
7
8
  function buildClient(opts) {
8
9
  const config = opts.session
9
10
  ? { ...resolveConfigFromBinding(process.cwd()), session: opts.session }
@@ -132,6 +133,9 @@ export function registerTmCommands(program) {
132
133
  return;
133
134
  }
134
135
  const absOut = path.isAbsolute(target) ? target : path.join(rootDir, target);
136
+ if (absOut.startsWith(path.join(rootDir, ".zivis") + path.sep)) {
137
+ ensureZivisGitignore(rootDir);
138
+ }
135
139
  await fs.mkdir(path.dirname(absOut), { recursive: true });
136
140
  await fs.writeFile(absOut, JSON.stringify(result, null, 2) + "\n", "utf8");
137
141
  console.log(`✓ Wrote ${absOut}`);
package/dist/index.js CHANGED
@@ -14,6 +14,11 @@ import { registerInspectCommands } from "./commands/inspect/index.js";
14
14
  import { registerMcpCommands } from "./commands/mcp/index.js";
15
15
  import { registerCreditsCommands } from "./commands/credits/index.js";
16
16
  import { registerOpenCommands } from "./commands/open/index.js";
17
+ import { registerSyncCommands } from "./commands/sync/index.js";
18
+ import { registerAssuranceCommands } from "./commands/assurance/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";
17
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
23
  function getVersion() {
19
24
  try {
@@ -34,6 +39,7 @@ program
34
39
  .description("interactive setup: authenticate, pick org, and bind this repo (alias for `zivis auth init`)")
35
40
  .option("-s, --session <name>", "named session for multi-tenant")
36
41
  .option("--skip-mint-key", "skip MCP API key mint after successful OAuth")
42
+ .option("--application <name-or-id>", "select an existing Application by id or exact name (fails if not found — never creates one)")
37
43
  .action(async (opts) => {
38
44
  await authInitCommand(opts);
39
45
  });
@@ -47,6 +53,11 @@ registerInspectCommands(program);
47
53
  registerMcpCommands(program);
48
54
  registerCreditsCommands(program);
49
55
  registerOpenCommands(program);
56
+ registerSyncCommands(program);
57
+ registerAssuranceCommands(program);
58
+ registerGateCommand(program);
59
+ registerRunCommands(program);
60
+ registerTestCommand(program);
50
61
  program.parseAsync(process.argv).catch((err) => {
51
62
  console.error(err instanceof Error ? err.message : err);
52
63
  process.exit(1);
@@ -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,16 @@
1
+ export declare const SCHEMA_VERSION = "1";
2
+ export declare const EXIT: {
3
+ readonly OK: 0;
4
+ readonly POLICY_FAIL: 1;
5
+ readonly INVALID_OR_INDETERMINATE: 2;
6
+ readonly AUTH_FAILURE: 3;
7
+ readonly CLOUD_UNAVAILABLE: 4;
8
+ };
9
+ export declare function jsonEnvelope<T extends Record<string, unknown>>(command: string, data: T): {
10
+ schema_version: string;
11
+ command: string;
12
+ } & T;
13
+ export declare function printJson(value: unknown): void;
14
+ export declare function exitCodeForError(err: unknown): number;
15
+ export declare function errorMessage(err: unknown): string;
16
+ export declare function failCommand(command: string, err: unknown, json: boolean): never;
@@ -0,0 +1,39 @@
1
+ import { ApiError } from "@zivis/mcp/api-client";
2
+ export const SCHEMA_VERSION = "1";
3
+ export const EXIT = {
4
+ OK: 0,
5
+ POLICY_FAIL: 1,
6
+ INVALID_OR_INDETERMINATE: 2,
7
+ AUTH_FAILURE: 3,
8
+ CLOUD_UNAVAILABLE: 4,
9
+ };
10
+ export function jsonEnvelope(command, data) {
11
+ return { schema_version: SCHEMA_VERSION, command, ...data };
12
+ }
13
+ export function printJson(value) {
14
+ process.stdout.write(JSON.stringify(value, null, 2) + "\n");
15
+ }
16
+ export function exitCodeForError(err) {
17
+ if (err instanceof ApiError) {
18
+ if (err.code === "NETWORK_ERROR")
19
+ return EXIT.CLOUD_UNAVAILABLE;
20
+ if (err.code === "AUTH_REQUIRED" || err.status === 401 || err.status === 403)
21
+ return EXIT.AUTH_FAILURE;
22
+ return EXIT.INVALID_OR_INDETERMINATE;
23
+ }
24
+ return EXIT.INVALID_OR_INDETERMINATE;
25
+ }
26
+ export function errorMessage(err) {
27
+ return err instanceof Error ? err.message : String(err);
28
+ }
29
+ export function failCommand(command, err, json) {
30
+ const code = exitCodeForError(err);
31
+ const message = errorMessage(err);
32
+ if (json) {
33
+ printJson(jsonEnvelope(command, { result: "error", error: message }));
34
+ }
35
+ else {
36
+ console.error(`zivis ${command}: ${message}`);
37
+ }
38
+ process.exit(code);
39
+ }
@@ -0,0 +1,47 @@
1
+ export declare const DEVX_RUN_CONTRACT_VERSION = "1";
2
+ export interface DevXRunApi {
3
+ post<T>(path: string, body: unknown): Promise<T>;
4
+ }
5
+ export interface DevXRunPackProvenance {
6
+ packId: string;
7
+ packType: string;
8
+ version: string;
9
+ }
10
+ export interface StartDevXRunParams {
11
+ applicationId: string;
12
+ runType: string;
13
+ requestedIntent?: unknown;
14
+ pack?: DevXRunPackProvenance;
15
+ cwd?: string;
16
+ }
17
+ export interface StartedDevXRun {
18
+ runId: string;
19
+ contractVersion: string;
20
+ applicationId: string;
21
+ startedAt: string;
22
+ }
23
+ export declare function startDevXRun(client: DevXRunApi, params: StartDevXRunParams): Promise<StartedDevXRun>;
24
+ export interface DevXRunWire {
25
+ id: string;
26
+ applicationId: string;
27
+ status: "pending" | "completed" | "cancelled";
28
+ contractVersion: string;
29
+ startedAt: string;
30
+ completedAt: string | null;
31
+ cancelledAt: string | null;
32
+ cancelReason: string | null;
33
+ }
34
+ export interface DevXRunOutcome {
35
+ outcome: "completed" | "cancelled" | "idempotent-replay";
36
+ run: DevXRunWire;
37
+ }
38
+ export declare function isRetryableDevXRunError(err: unknown): boolean;
39
+ export declare function completeDevXRunRemote(client: DevXRunApi, params: {
40
+ runId: string;
41
+ contractVersion: string;
42
+ result: unknown;
43
+ }): Promise<DevXRunOutcome>;
44
+ export declare function cancelDevXRunRemote(client: DevXRunApi, params: {
45
+ runId: string;
46
+ reason?: string | null;
47
+ }): Promise<DevXRunOutcome>;
@@ -0,0 +1,36 @@
1
+ import { ApiError } from "@zivis/mcp/api-client";
2
+ import { readGitMetadata } from "./git.js";
3
+ export const DEVX_RUN_CONTRACT_VERSION = "1";
4
+ export async function startDevXRun(client, params) {
5
+ const git = await readGitMetadata(params.cwd ?? process.cwd());
6
+ const response = await client.post(`/api/rt/applications/${params.applicationId}/runs`, {
7
+ runType: params.runType,
8
+ contractVersion: DEVX_RUN_CONTRACT_VERSION,
9
+ gitSha: git.commitSha ?? null,
10
+ gitDirty: git.dirty ?? null,
11
+ packId: params.pack?.packId ?? null,
12
+ packType: params.pack?.packType ?? null,
13
+ packVersion: params.pack?.version ?? null,
14
+ requestedIntent: params.requestedIntent,
15
+ });
16
+ return {
17
+ runId: response.id,
18
+ contractVersion: response.contractVersion,
19
+ applicationId: response.applicationId,
20
+ startedAt: response.startedAt,
21
+ };
22
+ }
23
+ export function isRetryableDevXRunError(err) {
24
+ return err instanceof ApiError && (err.code === "NETWORK_ERROR" || err.status >= 500);
25
+ }
26
+ export async function completeDevXRunRemote(client, params) {
27
+ return client.post(`/api/devx-runs/${params.runId}/complete`, {
28
+ contractVersion: params.contractVersion,
29
+ result: params.result,
30
+ });
31
+ }
32
+ export async function cancelDevXRunRemote(client, params) {
33
+ return client.post(`/api/devx-runs/${params.runId}/cancel`, {
34
+ reason: params.reason ?? null,
35
+ });
36
+ }