@corvio/admin-cli 0.1.0-beta.2
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/README.md +12 -0
- package/dist/cli.js +142 -0
- package/dist/core.js +324 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Corvio Platform Administrator CLI
|
|
2
|
+
|
|
3
|
+
Official read-only CLI for bounded Corvio platform investigations. It is separate from the Workspace CLI and accepts only a short-lived `cva_` credential.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install --global @corvio/admin-cli
|
|
7
|
+
export CORVIO_ADMIN_API_KEY='cva_...'
|
|
8
|
+
corvio-admin auth status
|
|
9
|
+
corvio-admin catalog --json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Every data query requires a purpose and returns an auditable receipt. This package contains no ordinary Workspace commands. See [the administrator CLI guide](https://corvio.ai/developers/admin-cli).
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ApiClient,
|
|
5
|
+
CliError,
|
|
6
|
+
EXIT,
|
|
7
|
+
listOption,
|
|
8
|
+
normalizeApiBase,
|
|
9
|
+
numberOption,
|
|
10
|
+
option,
|
|
11
|
+
readAdminCredential,
|
|
12
|
+
readTextFile,
|
|
13
|
+
runMain,
|
|
14
|
+
writeResult,
|
|
15
|
+
} from "./core.js";
|
|
16
|
+
|
|
17
|
+
const VERSION = "0.1.0-beta.2";
|
|
18
|
+
const DEFAULT_API_BASE = "https://api.corvio.ai/v1";
|
|
19
|
+
const USER_AGENT = `corvio-admin-cli/${VERSION}`;
|
|
20
|
+
|
|
21
|
+
const HELP = `Corvio Platform Administrator CLI ${VERSION}
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
corvio-admin auth status
|
|
25
|
+
corvio-admin capabilities
|
|
26
|
+
corvio-admin catalog
|
|
27
|
+
corvio-admin query|data query --file <request.json>
|
|
28
|
+
corvio-admin workspaces list --purpose <reason>
|
|
29
|
+
corvio-admin users list --purpose <reason>
|
|
30
|
+
corvio-admin runs list|content [id] --purpose <reason>
|
|
31
|
+
corvio-admin worker-jobs list --purpose <reason>
|
|
32
|
+
corvio-admin usage events|summary --purpose <reason>
|
|
33
|
+
corvio-admin receipts get <id>
|
|
34
|
+
|
|
35
|
+
Authentication:
|
|
36
|
+
Set CORVIO_ADMIN_API_KEY to a short-lived cva_ key.
|
|
37
|
+
|
|
38
|
+
This package contains no Workspace ask, search, docs, files, sync, or share commands.
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
function baseUrl(options) {
|
|
42
|
+
return normalizeApiBase(options["api-base"] || process.env.CORVIO_API_BASE, DEFAULT_API_BASE);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function client(options) {
|
|
46
|
+
const credential = readAdminCredential();
|
|
47
|
+
if (!credential) {
|
|
48
|
+
throw new CliError("Set CORVIO_ADMIN_API_KEY to a short-lived cva_ key.", {
|
|
49
|
+
code: "admin_auth_required",
|
|
50
|
+
exitCode: EXIT.AUTH,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return new ApiClient({ baseUrl: baseUrl(options), credential: credential.key, userAgent: USER_AGENT });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function listQuery(options) {
|
|
57
|
+
return {
|
|
58
|
+
purpose: option(options, "purpose", { required: true }),
|
|
59
|
+
workspace_id: options["workspace-id"],
|
|
60
|
+
status: options.status,
|
|
61
|
+
limit: numberOption(options, "limit", 100),
|
|
62
|
+
cursor: options.cursor,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function authStatus(options) {
|
|
67
|
+
const api = client(options);
|
|
68
|
+
const identity = await api.request("/me");
|
|
69
|
+
if (identity.api_key?.audience !== "platform_admin") {
|
|
70
|
+
throw new CliError("The configured key is not a platform administrator credential.", {
|
|
71
|
+
code: "admin_audience_required",
|
|
72
|
+
exitCode: EXIT.AUTH,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
writeResult({ authenticated: true, source: "environment", identity }, { json: options.json });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function capabilities(options) {
|
|
79
|
+
const api = client(options);
|
|
80
|
+
const [index, identity] = await Promise.all([api.request("/admin"), api.request("/me")]);
|
|
81
|
+
writeResult({ cli_version: VERSION, api: index, principal: identity }, { json: options.json });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function catalog(options) {
|
|
85
|
+
writeResult(await client(options).request("/admin/data/catalog"), { json: options.json });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function query(options) {
|
|
89
|
+
let body;
|
|
90
|
+
if (options.file) {
|
|
91
|
+
body = JSON.parse(await readTextFile(options.file));
|
|
92
|
+
if (options.purpose) body.purpose = options.purpose;
|
|
93
|
+
} else {
|
|
94
|
+
body = {
|
|
95
|
+
dataset: option(options, "dataset", { required: true }),
|
|
96
|
+
purpose: option(options, "purpose", { required: true }),
|
|
97
|
+
fields: listOption(options, "fields"),
|
|
98
|
+
group_by: listOption(options, "group-by"),
|
|
99
|
+
metrics: options.metrics ? JSON.parse(options.metrics) : [],
|
|
100
|
+
filters: options.filters ? JSON.parse(options.filters) : [],
|
|
101
|
+
order_by: options["order-by"] ? JSON.parse(options["order-by"]) : [],
|
|
102
|
+
limit: numberOption(options, "limit", 100),
|
|
103
|
+
cursor: options.cursor || null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
writeResult(await client(options).request("/admin/data/query", { method: "POST", body }), { json: options.json });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function listResource(options, resource) {
|
|
110
|
+
writeResult(await client(options).request(`/admin/${resource}`, { query: listQuery(options) }), { json: options.json });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main({ options, positionals }) {
|
|
114
|
+
const [command, action, id] = positionals;
|
|
115
|
+
if (options.version || command === "version") return writeResult(VERSION, { json: options.json });
|
|
116
|
+
if (options.help || !command || command === "help") return writeResult(HELP.trimEnd(), { json: false });
|
|
117
|
+
if (command === "auth" && action === "status") return authStatus(options);
|
|
118
|
+
if (command === "capabilities") return capabilities(options);
|
|
119
|
+
if (command === "catalog") return catalog(options);
|
|
120
|
+
if (command === "query") return query(options);
|
|
121
|
+
if (command === "data" && action === "query") return query(options);
|
|
122
|
+
if (command === "workspaces" && (action || "list") === "list") return listResource(options, "workspaces");
|
|
123
|
+
if (command === "users" && (action || "list") === "list") return listResource(options, "users");
|
|
124
|
+
if (command === "worker-jobs" && (action || "list") === "list") return listResource(options, "worker-jobs");
|
|
125
|
+
if (command === "runs" && action === "list") return listResource(options, "runs");
|
|
126
|
+
if (command === "runs" && action === "content") {
|
|
127
|
+
const runId = id || option(options, "id", { required: true });
|
|
128
|
+
const purpose = option(options, "purpose", { required: true });
|
|
129
|
+
return writeResult(await client(options).request(`/admin/runs/${runId}/content`, { query: { purpose } }), { json: options.json });
|
|
130
|
+
}
|
|
131
|
+
if (command === "usage" && action === "events") return listResource(options, "usage/events");
|
|
132
|
+
if (command === "usage" && action === "summary") {
|
|
133
|
+
return writeResult(await client(options).request("/admin/usage/summary", { query: listQuery(options) }), { json: options.json });
|
|
134
|
+
}
|
|
135
|
+
if (command === "receipts" && action === "get") {
|
|
136
|
+
const receiptId = id || option(options, "id", { required: true });
|
|
137
|
+
return writeResult(await client(options).request(`/admin/data/query-receipts/${receiptId}`), { json: options.json });
|
|
138
|
+
}
|
|
139
|
+
throw new CliError(`Unknown administrator command: ${positionals.join(" ")}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await runMain(main);
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir, platform } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
export const EXIT = Object.freeze({
|
|
8
|
+
OK: 0,
|
|
9
|
+
USAGE: 2,
|
|
10
|
+
AUTH: 3,
|
|
11
|
+
UNAVAILABLE: 4,
|
|
12
|
+
CONFLICT: 5,
|
|
13
|
+
RATE_LIMIT: 6,
|
|
14
|
+
REMOTE: 7,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export class CliError extends Error {
|
|
18
|
+
constructor(message, { code = "cli_error", exitCode = EXIT.USAGE, status = null, details = null } = {}) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "CliError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.exitCode = exitCode;
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.details = details;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function exitCodeForStatus(status) {
|
|
29
|
+
if (status === 401 || status === 403) return EXIT.AUTH;
|
|
30
|
+
if (status === 409) return EXIT.CONFLICT;
|
|
31
|
+
if (status === 429) return EXIT.RATE_LIMIT;
|
|
32
|
+
return EXIT.REMOTE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseArgv(argv) {
|
|
36
|
+
const options = Object.create(null);
|
|
37
|
+
const positionals = [];
|
|
38
|
+
const booleanOptions = new Set([
|
|
39
|
+
"json",
|
|
40
|
+
"no-input",
|
|
41
|
+
"yes",
|
|
42
|
+
"allow-actions",
|
|
43
|
+
"private-link",
|
|
44
|
+
"force",
|
|
45
|
+
"help",
|
|
46
|
+
"version",
|
|
47
|
+
]);
|
|
48
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
49
|
+
const value = argv[index];
|
|
50
|
+
if (!value.startsWith("--")) {
|
|
51
|
+
positionals.push(value);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const separator = value.indexOf("=");
|
|
55
|
+
const name = value.slice(2, separator === -1 ? undefined : separator);
|
|
56
|
+
if (booleanOptions.has(name)) {
|
|
57
|
+
options[name] = separator === -1 ? true : value.slice(separator + 1) !== "false";
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (separator !== -1) {
|
|
61
|
+
options[name] = value.slice(separator + 1);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const next = argv[index + 1];
|
|
65
|
+
if (next === undefined || next.startsWith("--")) {
|
|
66
|
+
throw new CliError(`Option --${name} requires a value.`, { code: "missing_option_value" });
|
|
67
|
+
}
|
|
68
|
+
options[name] = next;
|
|
69
|
+
index += 1;
|
|
70
|
+
}
|
|
71
|
+
return { options, positionals };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function option(options, name, { required = false, fallback = undefined } = {}) {
|
|
75
|
+
const value = options[name] ?? fallback;
|
|
76
|
+
if (required && (value === undefined || String(value).trim() === "")) {
|
|
77
|
+
throw new CliError(`Missing required option --${name}.`, { code: "missing_required_option" });
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function listOption(options, name, fallback = []) {
|
|
83
|
+
const value = options[name];
|
|
84
|
+
if (value === undefined) return fallback;
|
|
85
|
+
return String(value).split(",").map((item) => item.trim()).filter(Boolean);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function numberOption(options, name, fallback) {
|
|
89
|
+
const value = options[name];
|
|
90
|
+
if (value === undefined) return fallback;
|
|
91
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
92
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
93
|
+
throw new CliError(`Option --${name} must be a positive integer.`, { code: "invalid_number_option" });
|
|
94
|
+
}
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function normalizeApiBase(value, fallback) {
|
|
99
|
+
const raw = String(value || fallback).trim().replace(/\/+$/, "");
|
|
100
|
+
if (!/^https:\/\//i.test(raw) && !/^http:\/\/(127\.0\.0\.1|localhost)(:\d+)?/i.test(raw)) {
|
|
101
|
+
throw new CliError("Corvio API base must use HTTPS (localhost is allowed for development).", {
|
|
102
|
+
code: "insecure_api_base",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return raw.endsWith("/v1") ? raw : `${raw}/v1`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class ApiClient {
|
|
109
|
+
constructor({ baseUrl, credential = null, userAgent, fetchImpl = globalThis.fetch }) {
|
|
110
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
111
|
+
this.credential = credential;
|
|
112
|
+
this.userAgent = userAgent;
|
|
113
|
+
this.fetchImpl = fetchImpl;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async request(path, { method = "GET", body, query, headers = {}, auth = true } = {}) {
|
|
117
|
+
const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
|
|
118
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
119
|
+
if (value !== undefined && value !== null && value !== "") url.searchParams.set(key, String(value));
|
|
120
|
+
}
|
|
121
|
+
const requestHeaders = {
|
|
122
|
+
accept: "application/json",
|
|
123
|
+
"user-agent": this.userAgent,
|
|
124
|
+
...headers,
|
|
125
|
+
};
|
|
126
|
+
if (body !== undefined) requestHeaders["content-type"] = "application/json";
|
|
127
|
+
if (auth) {
|
|
128
|
+
if (!this.credential) {
|
|
129
|
+
throw new CliError("Corvio authentication is required.", { code: "auth_required", exitCode: EXIT.AUTH });
|
|
130
|
+
}
|
|
131
|
+
requestHeaders.authorization = `Bearer ${this.credential}`;
|
|
132
|
+
}
|
|
133
|
+
let response;
|
|
134
|
+
try {
|
|
135
|
+
response = await this.fetchImpl(url, {
|
|
136
|
+
method,
|
|
137
|
+
headers: requestHeaders,
|
|
138
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
139
|
+
});
|
|
140
|
+
} catch (cause) {
|
|
141
|
+
throw new CliError(`Unable to reach Corvio: ${cause instanceof Error ? cause.message : String(cause)}`, {
|
|
142
|
+
code: "network_error",
|
|
143
|
+
exitCode: EXIT.REMOTE,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const text = await response.text();
|
|
147
|
+
let payload = null;
|
|
148
|
+
if (text) {
|
|
149
|
+
try {
|
|
150
|
+
payload = JSON.parse(text);
|
|
151
|
+
} catch {
|
|
152
|
+
payload = { message: text.slice(0, 500) };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
const envelope = payload?.error || payload?.detail || payload || {};
|
|
157
|
+
throw new CliError(String(envelope.message || envelope.code || `Corvio request failed (${response.status}).`), {
|
|
158
|
+
code: String(envelope.code || `http_${response.status}`),
|
|
159
|
+
status: response.status,
|
|
160
|
+
exitCode: exitCodeForStatus(response.status),
|
|
161
|
+
details: envelope.details || null,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return payload;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function configRoot(env = process.env) {
|
|
169
|
+
if (env.CORVIO_CONFIG_DIR) return resolve(env.CORVIO_CONFIG_DIR);
|
|
170
|
+
if (env.XDG_CONFIG_HOME) return join(resolve(env.XDG_CONFIG_HOME), "corvio");
|
|
171
|
+
return join(homedir(), ".config", "corvio");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function workspaceCredentialPath(env = process.env) {
|
|
175
|
+
return join(configRoot(env), "credentials.json");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function readWorkspaceCredential(env = process.env) {
|
|
179
|
+
const environmentKey = String(env.CORVIO_API_KEY || "").trim();
|
|
180
|
+
if (environmentKey) {
|
|
181
|
+
if (!environmentKey.startsWith("cvk_")) {
|
|
182
|
+
throw new CliError("CORVIO_API_KEY must be a Workspace cvk_ key.", {
|
|
183
|
+
code: "invalid_workspace_key",
|
|
184
|
+
exitCode: EXIT.AUTH,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return { key: environmentKey, source: "environment", profile: null, metadata: null };
|
|
188
|
+
}
|
|
189
|
+
const path = workspaceCredentialPath(env);
|
|
190
|
+
let parsed;
|
|
191
|
+
try {
|
|
192
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
193
|
+
} catch (cause) {
|
|
194
|
+
if (cause?.code === "ENOENT") return null;
|
|
195
|
+
throw new CliError("Stored Corvio credentials are unreadable.", { code: "credential_store_invalid", exitCode: EXIT.AUTH });
|
|
196
|
+
}
|
|
197
|
+
const profileName = String(parsed.active_profile || "default");
|
|
198
|
+
const profile = parsed.profiles?.[profileName];
|
|
199
|
+
const key = String(profile?.api_key || "").trim();
|
|
200
|
+
if (!key.startsWith("cvk_")) {
|
|
201
|
+
throw new CliError("Stored Corvio credentials are invalid; run `corvio auth login` again.", {
|
|
202
|
+
code: "credential_store_invalid",
|
|
203
|
+
exitCode: EXIT.AUTH,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return { key, source: "device_link", profile: profileName, metadata: profile };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function storeWorkspaceCredential(payload, env = process.env) {
|
|
210
|
+
const path = workspaceCredentialPath(env);
|
|
211
|
+
const root = dirname(path);
|
|
212
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
213
|
+
await chmod(root, 0o700).catch(() => {});
|
|
214
|
+
const profileName = String(payload.workspace_id || "default");
|
|
215
|
+
const document = {
|
|
216
|
+
version: 1,
|
|
217
|
+
active_profile: profileName,
|
|
218
|
+
profiles: {
|
|
219
|
+
[profileName]: {
|
|
220
|
+
api_key: payload.secret,
|
|
221
|
+
key_id: payload.key_id,
|
|
222
|
+
workspace_id: payload.workspace_id,
|
|
223
|
+
workspace_name: payload.workspace_name,
|
|
224
|
+
scopes: payload.scopes || [],
|
|
225
|
+
created_at: payload.created_at || new Date().toISOString(),
|
|
226
|
+
source: "device_link",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
231
|
+
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
232
|
+
await chmod(temporary, 0o600);
|
|
233
|
+
await rename(temporary, path);
|
|
234
|
+
await chmod(path, 0o600);
|
|
235
|
+
return { path, profile: profileName };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function clearWorkspaceCredential(env = process.env) {
|
|
239
|
+
const path = workspaceCredentialPath(env);
|
|
240
|
+
await rm(path, { force: true });
|
|
241
|
+
return path;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function readAdminCredential(env = process.env) {
|
|
245
|
+
const key = String(env.CORVIO_ADMIN_API_KEY || "").trim();
|
|
246
|
+
if (!key) return null;
|
|
247
|
+
if (!key.startsWith("cva_")) {
|
|
248
|
+
throw new CliError("CORVIO_ADMIN_API_KEY must be a platform administrator cva_ key.", {
|
|
249
|
+
code: "invalid_admin_key",
|
|
250
|
+
exitCode: EXIT.AUTH,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return { key, source: "environment" };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function writeResult(value, { json = false, stdout = process.stdout } = {}) {
|
|
257
|
+
if (json || typeof value !== "string") {
|
|
258
|
+
stdout.write(`${JSON.stringify(value, null, json ? 2 : 0)}\n`);
|
|
259
|
+
} else {
|
|
260
|
+
stdout.write(`${value}\n`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function writeError(error, { json = false, stderr = process.stderr } = {}) {
|
|
265
|
+
const safe = {
|
|
266
|
+
error: {
|
|
267
|
+
code: error instanceof CliError ? error.code : "unexpected_error",
|
|
268
|
+
message: error instanceof Error ? error.message : String(error),
|
|
269
|
+
...(error instanceof CliError && error.status ? { status: error.status } : {}),
|
|
270
|
+
...(error instanceof CliError && error.details ? { details: error.details } : {}),
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
stderr.write(json ? `${JSON.stringify(safe)}\n` : `Error: ${safe.error.message}\n`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function runMain(main, argv = process.argv.slice(2)) {
|
|
277
|
+
let json = argv.includes("--json");
|
|
278
|
+
try {
|
|
279
|
+
const parsed = parseArgv(argv);
|
|
280
|
+
json = Boolean(parsed.options.json);
|
|
281
|
+
await main(parsed);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
writeError(error, { json });
|
|
284
|
+
process.exitCode = error instanceof CliError ? error.exitCode : 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function openBrowser(url) {
|
|
289
|
+
const target = String(url);
|
|
290
|
+
const command = platform() === "darwin" ? "open" : platform() === "win32" ? "cmd" : "xdg-open";
|
|
291
|
+
const args = platform() === "win32" ? ["/c", "start", "", target] : [target];
|
|
292
|
+
try {
|
|
293
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
294
|
+
child.unref();
|
|
295
|
+
return true;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function sleep(milliseconds) {
|
|
302
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function readTextFile(path) {
|
|
306
|
+
return readFile(resolve(String(path)), "utf8");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function writeTextFile(path, content) {
|
|
310
|
+
const target = resolve(String(path));
|
|
311
|
+
await mkdir(dirname(target), { recursive: true });
|
|
312
|
+
await writeFile(target, content, "utf8");
|
|
313
|
+
return target;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function requireConfirmation(options, action) {
|
|
317
|
+
if (!options.yes) {
|
|
318
|
+
throw new CliError(`${action} requires --yes.`, { code: "confirmation_required" });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function idempotencyKey(options) {
|
|
323
|
+
return String(options["idempotency-key"] || randomUUID());
|
|
324
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@corvio/admin-cli",
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
|
+
"description": "Official read-only Corvio platform administrator CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"corvio-admin": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"prepack": "node ../../scripts/build.mjs admin"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20.11"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"license": "UNLICENSED",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/NeoFlux-AI/corvio.git",
|
|
26
|
+
"directory": "developer-cli/packages/admin"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://corvio.ai/developers/admin-cli",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/NeoFlux-AI/corvio/issues"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"corvio",
|
|
34
|
+
"cli",
|
|
35
|
+
"admin",
|
|
36
|
+
"operations"
|
|
37
|
+
]
|
|
38
|
+
}
|