@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.
- 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 +33 -0
- package/dist/commands/assurance/index.js +149 -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 +249 -73
- 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 +111 -0
- package/dist/commands/scan/index.js +3 -8
- package/dist/commands/sync/index.d.ts +2 -0
- package/dist/commands/sync/index.js +187 -0
- package/dist/commands/target/index.js +0 -3
- package/dist/commands/test/index.d.ts +2 -0
- package/dist/commands/test/index.js +115 -0
- package/dist/commands/tm/index.js +7 -10
- package/dist/index.js +24 -1
- 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/cli-output.d.ts +16 -0
- package/dist/internal/cli-output.js +39 -0
- package/dist/internal/devx-run.d.ts +47 -0
- package/dist/internal/devx-run.js +36 -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 +8 -0
- package/dist/internal/git.js +30 -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 +377 -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 +7 -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/registry-client.d.ts +20 -0
- package/dist/internal/packs/registry-client.js +40 -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 +49 -0
- package/dist/internal/packs/types.d.ts +70 -0
- package/dist/internal/packs/types.js +20 -0
- package/dist/internal/project-binding.d.ts +1 -0
- package/dist/internal/project-binding.js +14 -9
- package/dist/internal/run-workdir.d.ts +1 -0
- package/dist/internal/run-workdir.js +11 -0
- package/dist/internal/security-context.d.ts +62 -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 +68 -0
- package/dist/internal/test-scope.js +69 -0
- package/dist/internal/zivis-local-state.d.ts +1 -0
- package/dist/internal/zivis-local-state.js +22 -0
- package/package.json +20 -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.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
|
@@ -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,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
|
+
}
|
|
@@ -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
|
|
@@ -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
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { ApiClient } from "@zivis/mcp/api-client";
|
|
4
|
-
import {
|
|
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
|
-
:
|
|
10
|
+
? { ...resolveConfigFromBinding(process.cwd()), session: opts.session }
|
|
11
|
+
: resolveConfigFromBinding(process.cwd());
|
|
11
12
|
return new ApiClient(config);
|
|
12
13
|
}
|
|
13
14
|
function collect(value, previous) {
|
|
14
15
|
return previous.concat([value]);
|
|
15
16
|
}
|
|
16
17
|
function defaultOutPath(extractedAt) {
|
|
17
|
-
// .zivis/extracted/<iso-no-colons>.json — matches the doc's example.
|
|
18
18
|
const ts = extractedAt.replace(/[:.]/g, "-");
|
|
19
19
|
return `.zivis/extracted/${ts}.json`;
|
|
20
20
|
}
|
|
@@ -80,9 +80,6 @@ export function registerTmCommands(program) {
|
|
|
80
80
|
process.exit(1);
|
|
81
81
|
});
|
|
82
82
|
const parsed = JSON.parse(raw);
|
|
83
|
-
// Only fields the PATCH endpoint accepts. tm pull dumps the full
|
|
84
|
-
// record; tm push only sends the editable subset, so a round-trip
|
|
85
|
-
// edit cycle Just Works.
|
|
86
83
|
const payload = {};
|
|
87
84
|
for (const k of ["name", "description", "status", "campaignId"]) {
|
|
88
85
|
if (parsed[k] !== undefined)
|
|
@@ -123,8 +120,6 @@ export function registerTmCommands(program) {
|
|
|
123
120
|
extractorVersion: cliPkg.version,
|
|
124
121
|
});
|
|
125
122
|
if (opts.privacyAudit) {
|
|
126
|
-
// Prints the entire object that WOULD be sent to ZIVIS via --apply.
|
|
127
|
-
// Never makes a network call; the extractor itself is local-only.
|
|
128
123
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
129
124
|
process.stderr.write(`\n[zivis] Privacy audit: ${result.scope.filesScanned} files scanned, ` +
|
|
130
125
|
`${result.components.length} component(s), ${result.externalServices.length} external service(s), ` +
|
|
@@ -138,6 +133,9 @@ export function registerTmCommands(program) {
|
|
|
138
133
|
return;
|
|
139
134
|
}
|
|
140
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
|
+
}
|
|
141
139
|
await fs.mkdir(path.dirname(absOut), { recursive: true });
|
|
142
140
|
await fs.writeFile(absOut, JSON.stringify(result, null, 2) + "\n", "utf8");
|
|
143
141
|
console.log(`✓ Wrote ${absOut}`);
|
|
@@ -263,4 +261,3 @@ export function registerTmCommands(program) {
|
|
|
263
261
|
}
|
|
264
262
|
});
|
|
265
263
|
}
|
|
266
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,21 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { registerAuthCommands } from "./commands/auth/index.js";
|
|
7
|
+
import { initCommand as authInitCommand } from "./commands/auth/init.js";
|
|
7
8
|
import { registerAppCommands } from "./commands/app/index.js";
|
|
8
9
|
import { registerTargetCommands } from "./commands/target/index.js";
|
|
9
10
|
import { registerTmCommands } from "./commands/tm/index.js";
|
|
10
11
|
import { registerScanCommands } from "./commands/scan/index.js";
|
|
12
|
+
import { registerCheckCommands } from "./commands/check/index.js";
|
|
13
|
+
import { registerInspectCommands } from "./commands/inspect/index.js";
|
|
11
14
|
import { registerMcpCommands } from "./commands/mcp/index.js";
|
|
12
15
|
import { registerCreditsCommands } from "./commands/credits/index.js";
|
|
13
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";
|
|
14
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
23
|
function getVersion() {
|
|
16
24
|
try {
|
|
@@ -26,16 +34,31 @@ program
|
|
|
26
34
|
.name("zivis")
|
|
27
35
|
.description("ZIVIS CLI — threat modeling, scans, and MCP server")
|
|
28
36
|
.version(getVersion(), "-v, --version", "show version");
|
|
37
|
+
program
|
|
38
|
+
.command("init")
|
|
39
|
+
.description("interactive setup: authenticate, pick org, and bind this repo (alias for `zivis auth init`)")
|
|
40
|
+
.option("-s, --session <name>", "named session for multi-tenant")
|
|
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)")
|
|
43
|
+
.action(async (opts) => {
|
|
44
|
+
await authInitCommand(opts);
|
|
45
|
+
});
|
|
29
46
|
registerAuthCommands(program);
|
|
47
|
+
registerCheckCommands(program);
|
|
30
48
|
registerAppCommands(program);
|
|
31
49
|
registerTargetCommands(program);
|
|
32
50
|
registerTmCommands(program);
|
|
33
51
|
registerScanCommands(program);
|
|
52
|
+
registerInspectCommands(program);
|
|
34
53
|
registerMcpCommands(program);
|
|
35
54
|
registerCreditsCommands(program);
|
|
36
55
|
registerOpenCommands(program);
|
|
56
|
+
registerSyncCommands(program);
|
|
57
|
+
registerAssuranceCommands(program);
|
|
58
|
+
registerGateCommand(program);
|
|
59
|
+
registerRunCommands(program);
|
|
60
|
+
registerTestCommand(program);
|
|
37
61
|
program.parseAsync(process.argv).catch((err) => {
|
|
38
62
|
console.error(err instanceof Error ? err.message : err);
|
|
39
63
|
process.exit(1);
|
|
40
64
|
});
|
|
41
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ZivisConfig } from "@zivis/mcp/types";
|
|
2
|
+
export interface EnsureApplicationBindingOpts {
|
|
3
|
+
config: ZivisConfig;
|
|
4
|
+
accessToken: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
explicitApplication?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function ensureApplicationBinding(opts: EnsureApplicationBindingOpts): Promise<void>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as readline from "node:readline/promises";
|
|
4
|
+
import * as child_process from "node:child_process";
|
|
5
|
+
import { detectProjectBinding } from "@zivis/mcp/project-binding";
|
|
6
|
+
async function apiFetch(config, accessToken, method, path, body) {
|
|
7
|
+
const response = await fetch(`${config.apiBaseUrl}${path}`, {
|
|
8
|
+
method,
|
|
9
|
+
headers: {
|
|
10
|
+
Authorization: `Bearer ${accessToken}`,
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
"User-Agent": "@zivis/cli",
|
|
13
|
+
},
|
|
14
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const errorBody = (await response.json().catch(() => ({})));
|
|
18
|
+
throw new Error(errorBody.message || errorBody.error || `HTTP ${response.status}`);
|
|
19
|
+
}
|
|
20
|
+
return (await response.json());
|
|
21
|
+
}
|
|
22
|
+
async function fetchApplications(config, accessToken) {
|
|
23
|
+
const result = await apiFetch(config, accessToken, "GET", "/api/rt/applications?limit=100");
|
|
24
|
+
return result.applications;
|
|
25
|
+
}
|
|
26
|
+
async function createApplication(config, accessToken, name) {
|
|
27
|
+
const result = await apiFetch(config, accessToken, "POST", "/api/rt/applications", { name, placeholder: true });
|
|
28
|
+
return result.application;
|
|
29
|
+
}
|
|
30
|
+
function defaultApplicationName(cwd) {
|
|
31
|
+
try {
|
|
32
|
+
const remote = child_process
|
|
33
|
+
.execSync("git remote get-url origin 2>/dev/null", { cwd, encoding: "utf8" })
|
|
34
|
+
.trim();
|
|
35
|
+
const match = remote.match(/\/([^/]+?)(\.git)?$/);
|
|
36
|
+
if (match?.[1])
|
|
37
|
+
return match[1];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
}
|
|
41
|
+
return path.basename(cwd);
|
|
42
|
+
}
|
|
43
|
+
async function prompt(question) {
|
|
44
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
45
|
+
try {
|
|
46
|
+
return (await rl.question(question)).trim();
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
rl.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function promptForApplicationName(defaultName) {
|
|
53
|
+
const answer = await prompt(`Application name [${defaultName}]: `);
|
|
54
|
+
return answer || defaultName;
|
|
55
|
+
}
|
|
56
|
+
const NON_INTERACTIVE_HELP = "Pass --application <name-or-id> to select an existing one non-interactively " +
|
|
57
|
+
"(it will not create one), or set applicationId manually in .zivis/project.json.";
|
|
58
|
+
export async function ensureApplicationBinding(opts) {
|
|
59
|
+
const { config, accessToken, cwd, explicitApplication } = opts;
|
|
60
|
+
const detected = detectProjectBinding(cwd);
|
|
61
|
+
if (!detected)
|
|
62
|
+
return;
|
|
63
|
+
if (detected.binding.applicationId) {
|
|
64
|
+
console.log(`✓ Application binding verified: ${detected.binding.applicationName ?? detected.binding.applicationId}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let apps;
|
|
68
|
+
try {
|
|
69
|
+
apps = await fetchApplications(config, accessToken);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.log(` WARNING: Could not list applications: ${err instanceof Error ? err.message : err}`);
|
|
73
|
+
console.log(" Skipping application binding — set applicationId manually in .zivis/project.json.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const interactive = process.stderr.isTTY === true && process.stdin.isTTY === true;
|
|
77
|
+
let selected;
|
|
78
|
+
if (explicitApplication) {
|
|
79
|
+
const byId = apps.find((a) => a.id === explicitApplication);
|
|
80
|
+
const byName = byId
|
|
81
|
+
? []
|
|
82
|
+
: apps.filter((a) => a.name.toLowerCase() === explicitApplication.toLowerCase());
|
|
83
|
+
if (byId) {
|
|
84
|
+
selected = byId;
|
|
85
|
+
console.log(`\nUsing existing application: ${selected.name}`);
|
|
86
|
+
}
|
|
87
|
+
else if (byName.length > 1) {
|
|
88
|
+
console.log(`\n AMBIGUOUS: ${byName.length} applications named "${explicitApplication}" found — ` +
|
|
89
|
+
`refusing to guess which one you mean.`);
|
|
90
|
+
for (const a of byName)
|
|
91
|
+
console.log(` ${a.id} ${a.name}`);
|
|
92
|
+
console.log(" Use the Application ID instead of the name, or run `zivis init` interactively.");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
else if (byName.length === 1) {
|
|
96
|
+
selected = byName[0];
|
|
97
|
+
console.log(`\nUsing existing application: ${selected.name}`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(`\n NOT FOUND: no application matches id or name "${explicitApplication}".`);
|
|
101
|
+
console.log(" --application selects an EXISTING application — it does not create one.");
|
|
102
|
+
console.log(" Check the id/name (`zivis app list`), or run `zivis init` interactively to create a new one.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (apps.length === 0) {
|
|
107
|
+
if (!interactive) {
|
|
108
|
+
console.log("\n WARNING: No applications found and no TTY to prompt — skipping application binding.");
|
|
109
|
+
console.log(` ${NON_INTERACTIVE_HELP}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log("\nNo applications found in this org — let's create one.");
|
|
113
|
+
const name = await promptForApplicationName(defaultApplicationName(cwd));
|
|
114
|
+
try {
|
|
115
|
+
selected = await createApplication(config, accessToken, name);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.log(` WARNING: Could not create application: ${err instanceof Error ? err.message : err}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
console.log(`✓ Created application ${selected.name} (${selected.id})`);
|
|
122
|
+
}
|
|
123
|
+
else if (!interactive) {
|
|
124
|
+
console.log(`\n WARNING: ${apps.length} application(s) found and no TTY to prompt — skipping application binding.`);
|
|
125
|
+
console.log(" Never auto-selecting the sole application without explicit confirmation.");
|
|
126
|
+
console.log(` ${NON_INTERACTIVE_HELP}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log("\nWhich application are you working on?");
|
|
131
|
+
apps.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
|
|
132
|
+
console.log(` ${apps.length + 1}. Create a new application`);
|
|
133
|
+
const answer = await prompt(`\n> `);
|
|
134
|
+
const idx = parseInt(answer, 10) - 1;
|
|
135
|
+
if (idx === apps.length) {
|
|
136
|
+
const name = await promptForApplicationName(defaultApplicationName(cwd));
|
|
137
|
+
try {
|
|
138
|
+
selected = await createApplication(config, accessToken, name);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
console.log(` WARNING: Could not create application: ${err instanceof Error ? err.message : err}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
console.log(`✓ Created application ${selected.name} (${selected.id})`);
|
|
145
|
+
}
|
|
146
|
+
else if (!Number.isNaN(idx) && idx >= 0 && idx < apps.length) {
|
|
147
|
+
selected = apps[idx];
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(" Invalid selection — skipping application binding. Set applicationId manually in .zivis/project.json.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!selected)
|
|
155
|
+
return;
|
|
156
|
+
const current = detectProjectBinding(cwd);
|
|
157
|
+
if (!current)
|
|
158
|
+
return;
|
|
159
|
+
const updated = {
|
|
160
|
+
...current.binding,
|
|
161
|
+
applicationId: selected.id,
|
|
162
|
+
applicationName: selected.name,
|
|
163
|
+
};
|
|
164
|
+
fs.writeFileSync(current.filePath, JSON.stringify(updated, null, 2) + "\n");
|
|
165
|
+
console.log(`✓ Repo bound to application: ${selected.name}`);
|
|
166
|
+
console.log(` File: ${current.filePath}`);
|
|
167
|
+
}
|