@verentis/cli 0.2.0

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/dist/index.js ADDED
@@ -0,0 +1,2585 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { mkdir, stat, writeFile, readFile, readdir, mkdtemp, rm, glob } from 'fs/promises';
4
+ import { tmpdir, homedir } from 'os';
5
+ import { resolve, basename, dirname, posix, join, sep, relative } from 'path';
6
+ import { spawn } from 'child_process';
7
+ import { generateKeyPairSync, createHash, randomUUID, randomBytes, createCipheriv, createPublicKey, verify, diffieHellman, hkdfSync, sign, createPrivateKey } from 'crypto';
8
+ import { parse } from 'yaml';
9
+ import { createWriteStream, createReadStream } from 'fs';
10
+ import { Readable } from 'stream';
11
+ import { pipeline } from 'stream/promises';
12
+ import * as tar from 'tar';
13
+
14
+ var configDir = () => process.env.VERENTIS_CONFIG_DIR ?? join(homedir(), ".verentis");
15
+ var configPath = () => join(configDir(), "config.json");
16
+ var keysDir = () => join(configDir(), "keys");
17
+ async function loadConfig() {
18
+ try {
19
+ return JSON.parse(await readFile(configPath(), "utf8"));
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+ async function saveConfig(config) {
25
+ await mkdir(configDir(), { recursive: true, mode: 448 });
26
+ await writeFile(configPath(), JSON.stringify(config, null, 2) + "\n", { mode: 384 });
27
+ }
28
+ function requireApiUrl(config) {
29
+ const url = process.env.VERENTIS_API_URL ?? config.apiUrl;
30
+ if (!url) {
31
+ throw new Error("No API URL configured. Run `verentis login --api-url <url>` first (or set VERENTIS_API_URL).");
32
+ }
33
+ return url.replace(/\/+$/, "");
34
+ }
35
+
36
+ // src/jwt.ts
37
+ function decodeJwtPayload(token) {
38
+ const parts = token.split(".");
39
+ if (parts.length !== 3) return null;
40
+ try {
41
+ const json = Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
42
+ return JSON.parse(json);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function isExpired(token, skewSeconds = 60) {
48
+ const payload = decodeJwtPayload(token);
49
+ if (!payload?.exp) return false;
50
+ return payload.exp * 1e3 <= Date.now() + skewSeconds * 1e3;
51
+ }
52
+
53
+ // src/http.ts
54
+ var ApiError = class extends Error {
55
+ constructor(status, message, body) {
56
+ super(message);
57
+ this.status = status;
58
+ this.body = body;
59
+ }
60
+ status;
61
+ body;
62
+ };
63
+ var AUDIENCE = "verentis";
64
+ async function createApiClient(overrides) {
65
+ const config = { ...await loadConfig(), ...overrides };
66
+ const apiUrl = requireApiUrl(config);
67
+ const tokenCache = /* @__PURE__ */ new Map();
68
+ async function credentialFor(scopes, workspaceId) {
69
+ const cacheKey = `${workspaceId ?? ""}:${[...scopes].sort().join(" ")}`;
70
+ const cached = tokenCache.get(cacheKey);
71
+ if (cached && !isExpired(cached)) return cached;
72
+ const token = await mintAccessToken({ scopes, workspaceId });
73
+ tokenCache.set(cacheKey, token);
74
+ return token;
75
+ }
76
+ async function mintAccessToken(options) {
77
+ const apiKey = process.env.VERENTIS_API_KEY ?? config.apiKey;
78
+ const identityToken = process.env.VERENTIS_TOKEN ?? config.identityToken;
79
+ const credential = apiKey ?? identityToken;
80
+ if (!credential) {
81
+ throw new Error("Not logged in. Run `verentis login` (or pass --api-key / --token).");
82
+ }
83
+ if (!apiKey && identityToken && isExpired(identityToken)) {
84
+ throw new ApiError(401, "Your identity token has expired \u2014 run `verentis login` again.");
85
+ }
86
+ const res = await fetch(`${apiUrl}/v1/tokens/access`, {
87
+ method: "POST",
88
+ headers: {
89
+ "Content-Type": "application/json",
90
+ Authorization: `Bearer ${credential}`
91
+ },
92
+ body: JSON.stringify({
93
+ audience: AUDIENCE,
94
+ scopes: options.scopes,
95
+ ...options.workspaceId ? { workspaceId: options.workspaceId } : {},
96
+ ...options.accountId ? { accountId: options.accountId } : {},
97
+ ...options.resourceId ? { resourceId: options.resourceId, resourceKind: options.resourceKind ?? "Node" } : {}
98
+ })
99
+ });
100
+ if (!res.ok) {
101
+ const text = await res.text().catch(() => "");
102
+ throw new ApiError(res.status, `Access-token exchange failed (${res.status}). ${hintFor(res.status)}${text ? `
103
+ ${truncate(text)}` : ""}`);
104
+ }
105
+ const data = await res.json();
106
+ if (!data.access_token) throw new Error("Access-token response did not include access_token.");
107
+ return data.access_token;
108
+ }
109
+ async function requestRaw(method, path, options = {}) {
110
+ const token = await credentialFor(options.scopes ?? [], options.tokenWorkspaceId);
111
+ const url = new URL(`${apiUrl}${path}`);
112
+ for (const [key, value] of Object.entries(options.query ?? {})) {
113
+ if (value !== void 0) url.searchParams.set(key, String(value));
114
+ }
115
+ const headers = { Authorization: `Bearer ${token}` };
116
+ let body;
117
+ if (options.rawBody !== void 0) {
118
+ body = options.rawBody;
119
+ headers["Content-Type"] = options.contentType ?? "application/octet-stream";
120
+ } else if (options.body !== void 0) {
121
+ body = JSON.stringify(options.body);
122
+ headers["Content-Type"] = "application/json";
123
+ }
124
+ for (const [key, value] of Object.entries(options.headers ?? {})) headers[key] = value;
125
+ const res = await fetch(url, { method, headers, body, ...options.rawBody !== void 0 ? { duplex: "half" } : {} });
126
+ if (!res.ok) {
127
+ const text = await res.text().catch(() => "");
128
+ throw new ApiError(res.status, `${method} ${path} failed (${res.status}). ${hintFor(res.status)}${formatErrorBody(text)}`);
129
+ }
130
+ return res;
131
+ }
132
+ async function request(method, path, options = {}) {
133
+ const res = await requestRaw(method, path, options);
134
+ if (res.status === 204) return void 0;
135
+ const text = await res.text();
136
+ if (!text) return void 0;
137
+ try {
138
+ return JSON.parse(text);
139
+ } catch {
140
+ return text;
141
+ }
142
+ }
143
+ return { apiUrl, request, requestRaw, mintAccessToken };
144
+ }
145
+ function hintFor(status) {
146
+ switch (status) {
147
+ case 401:
148
+ return "Your credential is invalid or expired \u2014 run `verentis login` again.";
149
+ case 403:
150
+ return "You lack the required scope or ownership for this operation.";
151
+ case 404:
152
+ return "Not found (or not visible to you).";
153
+ default:
154
+ return "";
155
+ }
156
+ }
157
+ function truncate(text, max = 400) {
158
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
159
+ }
160
+ function formatErrorBody(text) {
161
+ if (!text) return "";
162
+ try {
163
+ const problem = JSON.parse(text);
164
+ const lines = [];
165
+ if (problem.title) lines.push(problem.title);
166
+ if (problem.detail && problem.detail !== problem.title) lines.push(problem.detail);
167
+ for (const [field, messages] of Object.entries(problem.errors ?? {})) {
168
+ for (const message of messages) lines.push(` ${field}: ${message}`);
169
+ }
170
+ if (lines.length > 0) return `
171
+ ${truncate(lines.join("\n"), 800)}`;
172
+ } catch {
173
+ }
174
+ return `
175
+ ${truncate(text)}`;
176
+ }
177
+ var SCOPES = {
178
+ publisherRead: "marketplace.publisher.read",
179
+ publisherCreate: "marketplace.publisher.create",
180
+ publisherUpdate: "marketplace.publisher.update",
181
+ packageRead: "marketplace.package.read",
182
+ packageReadAll: "marketplace.package.read-all",
183
+ packageCreate: "marketplace.package.create",
184
+ packagePublish: "marketplace.package.publish",
185
+ installCreate: "marketplace.install.create",
186
+ installDelete: "marketplace.install.delete"
187
+ };
188
+
189
+ // src/api/scopes.ts
190
+ var SECURITY = {
191
+ apiKeyCreate: "security.api-key.create",
192
+ apiKeyRead: "security.api-key.read",
193
+ apiKeyReadAll: "security.api-key.read-all",
194
+ apiKeyUpdate: "security.api-key.update",
195
+ grantsCreate: "security.grants.create",
196
+ userRead: "security.user.read",
197
+ userReadAll: "security.user.read-all",
198
+ userUpdate: "security.user.update"};
199
+ var ACCOUNT = {
200
+ invitationAccept: "account.invitation.accept",
201
+ invitationCreate: "account.invitation.create",
202
+ invitationDelete: "account.invitation.delete",
203
+ invitationRead: "account.invitation.read",
204
+ invitationUpdate: "account.invitation.update"
205
+ };
206
+ var WORKSPACE = {
207
+ create: "account.workspace.create",
208
+ read: "account.workspace.read",
209
+ readAll: "account.workspace.read-all",
210
+ update: "account.workspace.update",
211
+ urlsCreate: "account.workspace.urls.create",
212
+ urlsDelete: "account.workspace.urls.delete",
213
+ urlsRead: "account.workspace.urls.read",
214
+ invitationAccept: "account.workspace.invitation.accept",
215
+ invitationCreate: "account.workspace.invitation.create",
216
+ invitationDelete: "account.workspace.invitation.delete",
217
+ invitationRead: "account.workspace.invitation.read",
218
+ invitationUpdate: "account.workspace.invitation.update"
219
+ };
220
+ var SETTINGS = {
221
+ delete: "fabric.setting.delete",
222
+ read: "fabric.setting.read",
223
+ readAll: "fabric.setting.read-all",
224
+ update: "fabric.setting.update"
225
+ };
226
+ var NODE = {
227
+ branchRead: "node.branch.read",
228
+ branchUpdate: "node.branch.update",
229
+ contentCreate: "node.content.create",
230
+ contentRead: "node.content.read",
231
+ contentUpdate: "node.content.update",
232
+ fileDelete: "node.file.delete",
233
+ fileRead: "node.file.read",
234
+ journalCreate: "node.journal.create",
235
+ journalUpdate: "node.journal.update",
236
+ nodeCreate: "node.node.create",
237
+ nodeUpdate: "node.node.update"
238
+ };
239
+ var EXECUTION = {
240
+ run: "execution.execution.run"
241
+ };
242
+ var MARKETPLACE = {
243
+ installRead: "marketplace.install.read",
244
+ packageDelete: "marketplace.package.delete",
245
+ packageRead: "marketplace.package.read",
246
+ packageUpdate: "marketplace.package.update",
247
+ packageVisibilityManage: "marketplace.package.visibility.manage",
248
+ statsRead: "marketplace.stats.read"};
249
+ var ENGINE_DEFAULT_PERMISSIONS = [
250
+ NODE.fileRead,
251
+ NODE.contentRead,
252
+ NODE.branchRead,
253
+ NODE.nodeCreate,
254
+ NODE.nodeUpdate,
255
+ NODE.contentCreate,
256
+ NODE.contentUpdate,
257
+ NODE.journalCreate,
258
+ NODE.journalUpdate,
259
+ NODE.branchUpdate,
260
+ NODE.fileDelete,
261
+ EXECUTION.run
262
+ ];
263
+
264
+ // src/api/catalog.ts
265
+ var e = (endpoint) => endpoint;
266
+ var security = {
267
+ listUsers: e({ method: "GET", path: "/v1/users", scopes: [SECURITY.userReadAll] }),
268
+ getUser: e({ method: "GET", path: "/v1/users/{userId}", scopes: [SECURITY.userRead] }),
269
+ getUserRoles: e({ method: "GET", path: "/v1/users/{userId}/roles", scopes: [SECURITY.userRead] }),
270
+ getUserGrants: e({ method: "GET", path: "/v1/users/{userId}/grants", scopes: [SECURITY.userRead] }),
271
+ activateUser: e({ method: "PATCH", path: "/v1/users/{userId}/activate", scopes: [SECURITY.userUpdate] }),
272
+ suspendUser: e({ method: "PATCH", path: "/v1/users/{userId}/suspend", scopes: [SECURITY.userUpdate] }),
273
+ grantUserPermission: e({ method: "POST", path: "/v1/users/{userId}/grants", scopes: [SECURITY.grantsCreate] }),
274
+ createApiKey: e({ method: "POST", path: "/v1/api-keys", scopes: [SECURITY.apiKeyCreate] }),
275
+ listApiKeys: e({ method: "GET", path: "/v1/api-keys", scopes: [SECURITY.apiKeyReadAll] }),
276
+ getApiKey: e({ method: "GET", path: "/v1/api-keys/{apiKeyId}", scopes: [SECURITY.apiKeyRead] }),
277
+ updateApiKey: e({ method: "PATCH", path: "/v1/api-keys/{apiKeyId}", scopes: [SECURITY.apiKeyUpdate] }),
278
+ revokeApiKey: e({ method: "PATCH", path: "/v1/api-keys/{apiKeyId}/revoke", scopes: [SECURITY.apiKeyUpdate] }),
279
+ regenerateApiKey: e({ method: "POST", path: "/v1/api-keys/{apiKeyId}/regenerate", scopes: [SECURITY.apiKeyUpdate] }),
280
+ addApiKeyGrant: e({ method: "POST", path: "/v1/api-keys/{apiKeyId}/grants", scopes: [SECURITY.apiKeyUpdate] }),
281
+ removeApiKeyGrant: e({ method: "DELETE", path: "/v1/api-keys/{apiKeyId}/grants/{permissionKey}", scopes: [SECURITY.apiKeyUpdate] })};
282
+ var account = {
283
+ get: e({ method: "GET", path: "/v1/accounts/{accountId}", scopes: ["account.account.read"] }),
284
+ update: e({ method: "PUT", path: "/v1/accounts/{accountId}", scopes: ["account.account.update"] }),
285
+ listInvitations: e({ method: "GET", path: "/v1/accounts/{accountId}/invitations", scopes: [ACCOUNT.invitationRead], paged: true }),
286
+ createInvitation: e({ method: "POST", path: "/v1/accounts/{accountId}/invitations", scopes: [ACCOUNT.invitationCreate] }),
287
+ resendInvitation: e({ method: "POST", path: "/v1/accounts/{accountId}/invitations/{invitationId}/resend", scopes: [ACCOUNT.invitationUpdate] }),
288
+ revokeInvitation: e({ method: "DELETE", path: "/v1/accounts/{accountId}/invitations/{invitationId}", scopes: [ACCOUNT.invitationDelete] }),
289
+ acceptInvitation: e({ method: "POST", path: "/v1/account-invitations/accept", scopes: [ACCOUNT.invitationAccept] })
290
+ };
291
+ var workspace = {
292
+ list: e({ method: "GET", path: "/v1/workspaces", scopes: [WORKSPACE.readAll], paged: true }),
293
+ get: e({ method: "GET", path: "/v1/workspaces/{workspaceId}", scopes: [WORKSPACE.read] }),
294
+ createMine: e({ method: "POST", path: "/v1/workspaces", scopes: [WORKSPACE.create] }),
295
+ update: e({ method: "PUT", path: "/v1/workspaces/{workspaceId}", scopes: [WORKSPACE.update] }),
296
+ listDomains: e({ method: "GET", path: "/v1/workspaces/{workspaceId}/domains", scopes: [WORKSPACE.urlsRead] }),
297
+ addDomain: e({ method: "POST", path: "/v1/workspaces/{workspaceId}/domains", scopes: [WORKSPACE.urlsCreate] }),
298
+ removeDomain: e({ method: "DELETE", path: "/v1/workspaces/{workspaceId}/domains/{domainId}", scopes: [WORKSPACE.urlsDelete] }),
299
+ listInvitations: e({ method: "GET", path: "/v1/workspaces/{workspaceId}/invitations", scopes: [WORKSPACE.invitationRead], paged: true }),
300
+ createInvitation: e({ method: "POST", path: "/v1/workspaces/{workspaceId}/invitations", scopes: [WORKSPACE.invitationCreate] }),
301
+ resendInvitation: e({ method: "POST", path: "/v1/workspaces/{workspaceId}/invitations/{invitationId}/resend", scopes: [WORKSPACE.invitationUpdate] }),
302
+ revokeInvitation: e({ method: "DELETE", path: "/v1/workspaces/{workspaceId}/invitations/{invitationId}", scopes: [WORKSPACE.invitationDelete] }),
303
+ acceptInvitation: e({ method: "POST", path: "/v1/workspace-invitations/accept", scopes: [WORKSPACE.invitationAccept] })
304
+ };
305
+ var settings = {
306
+ list: e({ method: "GET", path: "/v1/settings", scopes: [SETTINGS.readAll], paged: true }),
307
+ get: e({ method: "GET", path: "/v1/settings/{key}", scopes: [SETTINGS.read] }),
308
+ upsert: e({ method: "PUT", path: "/v1/settings/{key}", scopes: [SETTINGS.update] }),
309
+ delete: e({ method: "DELETE", path: "/v1/settings/{key}", scopes: [SETTINGS.delete] })
310
+ };
311
+ var files = {
312
+ get: e({ method: "GET", path: "/v1/files/{path}", scopes: [NODE.fileRead], workspaceBound: true }),
313
+ raw: e({ method: "GET", path: "/v1/files/raw/{path}", scopes: [NODE.fileRead], workspaceBound: true }),
314
+ upload: e({
315
+ method: "POST",
316
+ path: "/v1/files/{path}",
317
+ scopes: [NODE.nodeCreate, NODE.fileRead, NODE.nodeUpdate, NODE.journalCreate],
318
+ workspaceBound: true
319
+ }),
320
+ update: e({ method: "PUT", path: "/v1/files/{path}", scopes: [NODE.nodeUpdate, NODE.fileRead, NODE.journalCreate], workspaceBound: true }),
321
+ delete: e({ method: "DELETE", path: "/v1/files/{path}", scopes: [NODE.fileDelete], workspaceBound: true })
322
+ };
323
+ var execution = {
324
+ request: e({ method: "POST", path: "/v1/executions", scopes: [EXECUTION.run, ...ENGINE_DEFAULT_PERMISSIONS], workspaceBound: true }),
325
+ list: e({ method: "GET", path: "/v1/executions", scopes: [EXECUTION.run], workspaceBound: true, paged: true }),
326
+ get: e({ method: "GET", path: "/v1/executions/{id}", scopes: [EXECUTION.run], workspaceBound: true }),
327
+ cancel: e({ method: "POST", path: "/v1/executions/{id}/cancel", scopes: [EXECUTION.run], workspaceBound: true }),
328
+ logs: e({ method: "GET", path: "/v1/executions/{id}/logs", scopes: [EXECUTION.run], workspaceBound: true }),
329
+ logsStream: e({ method: "GET", path: "/v1/executions/{id}/logs/stream", scopes: [EXECUTION.run], workspaceBound: true }),
330
+ listEngines: e({ method: "GET", path: "/v1/engines", scopes: [EXECUTION.run], paged: true }),
331
+ getEngine: e({ method: "GET", path: "/v1/engines/{id}", scopes: [EXECUTION.run] }),
332
+ resolveEngine: e({ method: "GET", path: "/v1/engines/resolve", scopes: [EXECUTION.run] }),
333
+ syncEngines: e({ method: "POST", path: "/v1/engines/sync", scopes: [EXECUTION.run], workspaceBound: true })
334
+ };
335
+ var marketplace = {
336
+ getPackage: e({ method: "GET", path: "/v1/packages/{id}", scopes: [MARKETPLACE.packageRead] }),
337
+ updatePackage: e({ method: "PUT", path: "/v1/packages/{id}", scopes: [MARKETPLACE.packageUpdate] }),
338
+ archivePackage: e({ method: "DELETE", path: "/v1/packages/{id}", scopes: [MARKETPLACE.packageDelete] }),
339
+ deprecatePackage: e({ method: "POST", path: "/v1/packages/{id}/deprecate", scopes: [MARKETPLACE.packageUpdate] }),
340
+ undeprecatePackage: e({ method: "DELETE", path: "/v1/packages/{id}/deprecate", scopes: [MARKETPLACE.packageUpdate] }),
341
+ setPackageVisibility: e({ method: "PUT", path: "/v1/packages/{id}/visibility", scopes: [MARKETPLACE.packageVisibilityManage] }),
342
+ listPublisherPackages: e({ method: "GET", path: "/v1/publishers/{publisherId}/packages", scopes: [MARKETPLACE.packageRead], paged: true }),
343
+ listVersions: e({ method: "GET", path: "/v1/packages/{packageId}/versions", scopes: [MARKETPLACE.packageRead], paged: true }),
344
+ listVisibilityGrants: e({ method: "GET", path: "/v1/packages/{packageId}/visibility-grants", scopes: [MARKETPLACE.packageVisibilityManage] }),
345
+ grantVisibility: e({ method: "POST", path: "/v1/packages/{packageId}/visibility-grants", scopes: [MARKETPLACE.packageVisibilityManage] }),
346
+ revokeVisibility: e({ method: "DELETE", path: "/v1/packages/{packageId}/visibility-grants/{grantId}", scopes: [MARKETPLACE.packageVisibilityManage] }),
347
+ getPackageStats: e({ method: "GET", path: "/v1/packages/{packageId}/stats", scopes: [MARKETPLACE.statsRead] }),
348
+ listInstallations: e({ method: "GET", path: "/v1/installations", scopes: [MARKETPLACE.installRead], paged: true })};
349
+
350
+ // src/api/client.ts
351
+ function fillPath(template, params = {}) {
352
+ return template.replace(/\{(\w+)\}/g, (_, name) => {
353
+ const value = params[name];
354
+ if (value === void 0) throw new Error(`Missing path parameter '${name}' for ${template}`);
355
+ return String(value).split("/").map((segment) => encodeURIComponent(segment)).join("/");
356
+ });
357
+ }
358
+ function toRequestOptions(endpoint, options) {
359
+ if (endpoint.workspaceBound && !options.workspaceId) {
360
+ throw new Error("This operation requires a workspace. Pass --workspace <id> or set one with `verentis use workspace <id>`.");
361
+ }
362
+ return {
363
+ scopes: [.../* @__PURE__ */ new Set([...endpoint.scopes, ...options.extraScopes ?? []])],
364
+ tokenWorkspaceId: endpoint.workspaceBound ? options.workspaceId : void 0,
365
+ query: options.query,
366
+ body: options.body,
367
+ rawBody: options.rawBody,
368
+ contentType: options.contentType
369
+ };
370
+ }
371
+ function unwrap(payload) {
372
+ if (payload !== null && typeof payload === "object" && !Array.isArray(payload)) {
373
+ const keys = Object.keys(payload);
374
+ if (keys.length === 1 && keys[0] === "value") return payload.value;
375
+ }
376
+ return payload;
377
+ }
378
+ async function call(api, endpoint, options = {}) {
379
+ const result = await api.request(endpoint.method, fillPath(endpoint.path, options.params), toRequestOptions(endpoint, options));
380
+ return unwrap(result);
381
+ }
382
+ async function callRaw(api, endpoint, options = {}) {
383
+ return api.requestRaw(endpoint.method, fillPath(endpoint.path, options.params), toRequestOptions(endpoint, options));
384
+ }
385
+ var pageItems = (page) => {
386
+ if (!page) return [];
387
+ if (Array.isArray(page)) return page;
388
+ return page.data ?? page.items ?? [];
389
+ };
390
+ function pagingQuery(flags) {
391
+ return {
392
+ PageNo: flags.page ? Number(flags.page) : 1,
393
+ PageSize: flags.pageSize ? Number(flags.pageSize) : 50,
394
+ OrderBy: flags.orderBy
395
+ };
396
+ }
397
+
398
+ // src/context.ts
399
+ async function resolveWorkspaceId(flags, required = true) {
400
+ const config = await loadConfig();
401
+ const workspaceId = flags.workspace ?? process.env.VERENTIS_WORKSPACE_ID ?? config.defaultWorkspaceId;
402
+ if (!workspaceId && required) {
403
+ throw new Error("No workspace selected. Pass --workspace <id> or set a default with `verentis use workspace <id>`.");
404
+ }
405
+ return workspaceId;
406
+ }
407
+ async function resolveAccountId(flags, required = true) {
408
+ const config = await loadConfig();
409
+ let accountId = flags.account ?? process.env.VERENTIS_ACCOUNT_ID ?? config.defaultAccountId;
410
+ if (!accountId) {
411
+ const identityToken = process.env.VERENTIS_TOKEN ?? config.identityToken;
412
+ if (identityToken) accountId = decodeJwtPayload(identityToken)?.account_id;
413
+ }
414
+ if (!accountId && required) {
415
+ throw new Error("No account selected. Pass --account <id> or set a default with `verentis use account <id>`.");
416
+ }
417
+ return accountId;
418
+ }
419
+ async function managementScope(flags) {
420
+ const accountId = await resolveAccountId(flags);
421
+ const workspaceId = await resolveWorkspaceId(flags, false);
422
+ if (flags.node && !workspaceId) throw new Error("--node requires --workspace.");
423
+ const scopeKind = flags.node ? "Node" : workspaceId ? "Workspace" : "Account";
424
+ const scopeId = flags.node ?? workspaceId ?? accountId;
425
+ return {
426
+ scopeKind,
427
+ scopeId,
428
+ accountId,
429
+ ...workspaceId ? { workspaceId } : {},
430
+ ...flags.node ? { nodeId: flags.node } : {}
431
+ };
432
+ }
433
+
434
+ // src/output.ts
435
+ function printJson(value) {
436
+ console.log(JSON.stringify(value, null, 2));
437
+ }
438
+ function printTable(rows, columns) {
439
+ if (rows.length === 0) {
440
+ console.log("(none)");
441
+ return;
442
+ }
443
+ const keys = Object.keys(columns);
444
+ const cells = rows.map((row) => keys.map((key) => formatCell(row[key])));
445
+ const headers = keys.map((key) => columns[key]);
446
+ const widths = headers.map((header, i) => Math.max(header.length, ...cells.map((row) => row[i].length)));
447
+ const line = (parts) => parts.map((part, i) => part.padEnd(widths[i])).join(" ").trimEnd();
448
+ console.log(line(headers));
449
+ console.log(line(widths.map((w) => "-".repeat(w))));
450
+ for (const row of cells) console.log(line(row));
451
+ }
452
+ function formatCell(value) {
453
+ if (value === null || value === void 0) return "";
454
+ if (value instanceof Date) return value.toISOString();
455
+ if (typeof value === "object") return JSON.stringify(value);
456
+ return String(value);
457
+ }
458
+ function output(rows, columns, options, jsonValue) {
459
+ if (options.json) {
460
+ printJson(jsonValue ?? rows);
461
+ return;
462
+ }
463
+ printTable(rows, columns);
464
+ }
465
+ function outputRecord(record, options) {
466
+ if (options.json) {
467
+ printJson(record);
468
+ return;
469
+ }
470
+ const width = Math.max(...Object.keys(record).map((k) => k.length));
471
+ for (const [key, value] of Object.entries(record)) {
472
+ console.log(`${key.padEnd(width)} ${formatCell(value)}`);
473
+ }
474
+ }
475
+
476
+ // src/commands/account.ts
477
+ function registerAccount(program2) {
478
+ const accountCmd = program2.command("account").description("Manage Verentis accounts");
479
+ accountCmd.command("get").description("Show account details").option("--account <accountId>", "account id (default: configured/derived account)").option("--json", "output JSON").action(async (options) => {
480
+ const accountId = await resolveAccountId(options);
481
+ const api = await createApiClient();
482
+ const result = await call(api, account.get, { params: { accountId } });
483
+ outputRecord(result, options);
484
+ });
485
+ accountCmd.command("update").description("Update account details").requiredOption("--name <name>", "account name").option("--description <description>").option("--account <accountId>", "account id").action(async (options) => {
486
+ const accountId = await resolveAccountId(options);
487
+ const api = await createApiClient();
488
+ await call(api, account.update, {
489
+ params: { accountId },
490
+ body: { accountId, name: options.name, description: options.description ?? null }
491
+ });
492
+ console.log(`Account ${accountId} updated.`);
493
+ });
494
+ const inviteCmd = accountCmd.command("invite").description("Manage account invitations");
495
+ inviteCmd.command("list").description("List invitations for the account").option("--account <accountId>", "account id").option("--json", "output JSON").action(async (options) => {
496
+ const accountId = await resolveAccountId(options);
497
+ const api = await createApiClient();
498
+ const page = await call(api, account.listInvitations, { params: { accountId } });
499
+ const items = pageItems(page);
500
+ output(items, { emailAddress: "Email", roleKey: "Role", status: "Status", id: "Id" }, options, page);
501
+ });
502
+ inviteCmd.command("create <email>").description("Invite a user to the account").option("--role <roleKey>", "role to assign on acceptance", "member").option("--account <accountId>", "account id").action(async (email, options) => {
503
+ const accountId = await resolveAccountId(options);
504
+ const api = await createApiClient();
505
+ const id = await call(api, account.createInvitation, {
506
+ params: { accountId },
507
+ body: { accountId, emailAddress: email, roleKey: options.role }
508
+ });
509
+ console.log(`Invitation ${id ?? ""} sent to ${email} (role ${options.role}).`.replace(" ", " "));
510
+ });
511
+ inviteCmd.command("resend <invitationId>").description("Resend an invitation email").option("--account <accountId>", "account id").action(async (invitationId, options) => {
512
+ const accountId = await resolveAccountId(options);
513
+ const api = await createApiClient();
514
+ await call(api, account.resendInvitation, { params: { accountId, invitationId }, body: { accountId, invitationId } });
515
+ console.log(`Invitation ${invitationId} resent.`);
516
+ });
517
+ inviteCmd.command("revoke <invitationId>").description("Revoke an invitation").option("--account <accountId>", "account id").action(async (invitationId, options) => {
518
+ const accountId = await resolveAccountId(options);
519
+ const api = await createApiClient();
520
+ await call(api, account.revokeInvitation, { params: { accountId, invitationId } });
521
+ console.log(`Invitation ${invitationId} revoked.`);
522
+ });
523
+ inviteCmd.command("accept <code>").description("Accept an account invitation code as the current user").action(async (code) => {
524
+ const api = await createApiClient();
525
+ await call(api, account.acceptInvitation, { body: { code } });
526
+ console.log("Invitation accepted.");
527
+ });
528
+ }
529
+ function registerApi(program2) {
530
+ program2.command("api <method> <path>").description("Call any platform API endpoint with automatic authentication (e.g. `verentis api GET /v1/workspaces`)").option("--scope <scope...>", "scopes to request on the access token (repeatable)").option("--workspace <workspaceId>", "bind the access token to a workspace").option("--body <json>", "JSON request body (use @file.json to read from a file, or - for stdin)").option("--query <key=value...>", "query string parameters (repeatable)").option("--content-type <type>", "content type for --body when not JSON").option("--raw", "print the raw response body without JSON pretty-printing").action(async (method, path, options) => {
531
+ const api = await createApiClient();
532
+ const query = {};
533
+ for (const pair of options.query ?? []) {
534
+ const idx = pair.indexOf("=");
535
+ if (idx < 0) throw new Error(`Invalid --query '${pair}' (expected key=value).`);
536
+ query[pair.slice(0, idx)] = pair.slice(idx + 1);
537
+ }
538
+ let body;
539
+ let rawBody;
540
+ if (options.body !== void 0) {
541
+ let text2 = options.body;
542
+ if (text2 === "-") text2 = await readStdin();
543
+ else if (text2.startsWith("@")) text2 = await readFile(text2.slice(1), "utf8");
544
+ if (options.contentType && !options.contentType.includes("json")) rawBody = text2;
545
+ else body = JSON.parse(text2);
546
+ }
547
+ const res = await api.requestRaw(method.toUpperCase(), path, {
548
+ scopes: options.scope ?? [],
549
+ tokenWorkspaceId: options.workspace,
550
+ body,
551
+ rawBody,
552
+ contentType: options.contentType,
553
+ query
554
+ });
555
+ const text = await res.text();
556
+ if (!text) {
557
+ console.error(`${res.status} ${res.statusText}`);
558
+ return;
559
+ }
560
+ if (options.raw) {
561
+ process.stdout.write(text);
562
+ return;
563
+ }
564
+ try {
565
+ printJson(JSON.parse(text));
566
+ } catch {
567
+ process.stdout.write(text);
568
+ }
569
+ });
570
+ }
571
+ async function readStdin() {
572
+ const chunks = [];
573
+ for await (const chunk of process.stdin) chunks.push(chunk);
574
+ return Buffer.concat(chunks).toString("utf8");
575
+ }
576
+
577
+ // src/commands/apikey.ts
578
+ async function currentUserId() {
579
+ const config = await loadConfig();
580
+ const token = process.env.VERENTIS_TOKEN ?? config.identityToken;
581
+ return token ? decodeJwtPayload(token)?.sub : void 0;
582
+ }
583
+ function registerApiKey(program2) {
584
+ const keyCmd = program2.command("apikey").description("Manage Verentis API keys (vrt_...)");
585
+ keyCmd.command("create <name>").description("Create an API key in the selected scope (prints the key once \u2014 store it safely)").option("--account <accountId>").option("--workspace <workspaceId>", "create a workspace-scoped key").option("--node <nodeId>", "confine the key to a node subtree (requires --workspace)").option("--principal <principalId>", "principal the key acts as (default: you)").option("--principal-type <type>", "User | Role | Service", "User").option("--expires <iso8601>", "expiry timestamp").option("--grant <scope...>", "permission scopes to grant the key (repeatable)").option("--json", "output JSON").action(async (name, options) => {
586
+ const api = await createApiClient();
587
+ const scope = await managementScope(options);
588
+ const principalId = options.principal ?? await currentUserId();
589
+ if (!principalId) throw new Error("Cannot determine the key principal. Pass --principal <userId> (API-key logins have no user context).");
590
+ const result = await call(api, security.createApiKey, {
591
+ body: {
592
+ ...scope,
593
+ name,
594
+ principalType: options.principalType,
595
+ principalId,
596
+ expiresAt: options.expires ?? null
597
+ }
598
+ });
599
+ for (const grantScope of options.grant ?? []) {
600
+ await call(api, security.addApiKeyGrant, {
601
+ params: { apiKeyId: result.id },
602
+ body: { ...scope, apiKeyId: result.id, permissionKey: grantScope, effect: "Allow" }
603
+ });
604
+ }
605
+ if (options.json) {
606
+ outputRecord(result, options);
607
+ return;
608
+ }
609
+ console.log(`API key '${name}' created (${result.id}).`);
610
+ console.log(`
611
+ ${result.rawKeyValue}
612
+ `);
613
+ console.log("This value is shown ONCE. Store it safely (e.g. `verentis login --api-key ...` on the target machine).");
614
+ if (options.grant?.length) console.log(`Granted: ${options.grant.join(", ")}`);
615
+ });
616
+ keyCmd.command("list").description("List API keys in the selected scope").option("--account <accountId>").option("--workspace <workspaceId>").option("--search <text>").option("--revoked", "include only revoked keys").option("--json", "output JSON").action(async (options) => {
617
+ const api = await createApiClient();
618
+ const scope = await managementScope(options);
619
+ const result = await call(api, security.listApiKeys, {
620
+ query: { ...scope, search: options.search, isRevoked: options.revoked }
621
+ });
622
+ output(result ?? [], { name: "Name", keyPrefix: "Prefix", isRevoked: "Revoked", expiresAt: "Expires", id: "Id" }, options, result);
623
+ });
624
+ keyCmd.command("get <apiKeyId>").description("Show API key details (metadata only \u2014 the secret is never retrievable)").option("--account <accountId>").option("--workspace <workspaceId>").option("--json", "output JSON").action(async (apiKeyId, options) => {
625
+ const api = await createApiClient();
626
+ const scope = await managementScope(options);
627
+ const result = await call(api, security.getApiKey, { params: { apiKeyId }, query: scope });
628
+ outputRecord(result, options);
629
+ });
630
+ keyCmd.command("update <apiKeyId>").description("Update API key metadata").requiredOption("--name <name>", "new key name").option("--expires <iso8601>", "new expiry timestamp").option("--account <accountId>").option("--workspace <workspaceId>").action(async (apiKeyId, options) => {
631
+ const api = await createApiClient();
632
+ const scope = await managementScope(options);
633
+ await call(api, security.updateApiKey, {
634
+ params: { apiKeyId },
635
+ body: { ...scope, apiKeyId, name: options.name, expiresAt: options.expires ?? null }
636
+ });
637
+ console.log(`API key ${apiKeyId} updated.`);
638
+ });
639
+ keyCmd.command("revoke <apiKeyId>").description("Revoke an API key permanently").option("--account <accountId>").option("--workspace <workspaceId>").action(async (apiKeyId, options) => {
640
+ const api = await createApiClient();
641
+ const scope = await managementScope(options);
642
+ await call(api, security.revokeApiKey, { params: { apiKeyId }, body: { ...scope, apiKeyId } });
643
+ console.log(`API key ${apiKeyId} revoked.`);
644
+ });
645
+ keyCmd.command("regenerate <apiKeyId>").description("Regenerate the secret for an API key (prints the new value once)").option("--account <accountId>").option("--workspace <workspaceId>").option("--json", "output JSON").action(async (apiKeyId, options) => {
646
+ const api = await createApiClient();
647
+ const scope = await managementScope(options);
648
+ const result = await call(api, security.regenerateApiKey, { params: { apiKeyId }, body: { ...scope, apiKeyId } });
649
+ if (options.json) {
650
+ outputRecord(result, options);
651
+ return;
652
+ }
653
+ console.log(`API key ${apiKeyId} regenerated:`);
654
+ console.log(`
655
+ ${result.rawKeyValue}
656
+ `);
657
+ console.log("This value is shown ONCE. The previous secret no longer works.");
658
+ });
659
+ const grantCmd = keyCmd.command("grant").description("Manage permission grants on an API key");
660
+ grantCmd.command("add <apiKeyId> <scope>").description("Grant a permission scope to an API key (e.g. node.file.read)").option("--deny", "add as a Deny grant instead of Allow").option("--account <accountId>").option("--workspace <workspaceId>").action(async (apiKeyId, scopeName, options) => {
661
+ const api = await createApiClient();
662
+ const scope = await managementScope(options);
663
+ await call(api, security.addApiKeyGrant, {
664
+ params: { apiKeyId },
665
+ body: { ...scope, apiKeyId, permissionKey: scopeName, effect: options.deny ? "Deny" : "Allow" }
666
+ });
667
+ console.log(`Granted ${scopeName} to API key ${apiKeyId}${options.deny ? " (Deny)" : ""}.`);
668
+ });
669
+ grantCmd.command("remove <apiKeyId> <scope>").description("Remove a permission grant from an API key").option("--account <accountId>").option("--workspace <workspaceId>").action(async (apiKeyId, scopeName, options) => {
670
+ const api = await createApiClient();
671
+ const scope = await managementScope(options);
672
+ await call(api, security.removeApiKeyGrant, { params: { apiKeyId, permissionKey: scopeName }, query: scope });
673
+ console.log(`Removed ${scopeName} from API key ${apiKeyId}.`);
674
+ });
675
+ }
676
+ function buildLocalContext(input) {
677
+ const payload = decodeJwtPayload(input.accessToken);
678
+ const expiresAt = payload?.exp ? new Date(payload.exp * 1e3).toISOString() : void 0;
679
+ const filePath = input.filePath ? input.filePath.startsWith("/") ? input.filePath : `/${input.filePath}` : void 0;
680
+ return {
681
+ version: 1,
682
+ execution: {
683
+ id: input.executionId ?? `local-${randomUUID()}`,
684
+ mode: input.mode ?? "request-response",
685
+ ...input.tool ? { tool: input.tool } : {},
686
+ trigger: "user",
687
+ timeout: input.timeoutSeconds ?? 300
688
+ },
689
+ workspace: { id: input.workspaceId, ...input.workspaceName ? { name: input.workspaceName } : {} },
690
+ ...filePath ? {
691
+ file: {
692
+ path: filePath,
693
+ name: filePath.split("/").pop() ?? filePath,
694
+ ...input.fileMimeType ? { mimeType: input.fileMimeType } : {},
695
+ branch: input.branch ?? "main"
696
+ }
697
+ } : {},
698
+ input: {
699
+ arguments: input.args ?? [],
700
+ environment: input.env ?? {},
701
+ ...input.parameters ? { parameters: input.parameters } : {}
702
+ },
703
+ platform: {
704
+ apiUrl: input.apiUrl,
705
+ token: { accessToken: input.accessToken, ...expiresAt ? { expiresAt } : {}, scopes: input.scopes }
706
+ }
707
+ };
708
+ }
709
+ function contextEnv(context, contextPath) {
710
+ return {
711
+ VERENTIS_CONTEXT_PATH: contextPath,
712
+ VERENTIS_EXECUTION_ID: context.execution.id,
713
+ VERENTIS_EXECUTION_MODE: context.execution.mode,
714
+ ...context.execution.tool ? { VERENTIS_EXECUTION_TOOL: context.execution.tool } : {},
715
+ VERENTIS_API_URL: context.platform.apiUrl,
716
+ VERENTIS_ACCESS_TOKEN: context.platform.token.accessToken,
717
+ VERENTIS_WORKSPACE_ID: context.workspace.id,
718
+ ...context.file ? {
719
+ VERENTIS_FILE_PATH: context.file.path,
720
+ ...context.file.mimeType ? { VERENTIS_FILE_MIME_TYPE: context.file.mimeType } : {},
721
+ VERENTIS_FILE_BRANCH: context.file.branch
722
+ } : {},
723
+ VERENTIS_TIMEOUT: String(context.execution.timeout)
724
+ };
725
+ }
726
+ async function readManifestDocument(path) {
727
+ const text = await readFile(path, "utf8");
728
+ const doc = parse(text);
729
+ if (!doc || typeof doc !== "object") throw new Error(`${path} is not a YAML manifest.`);
730
+ return doc;
731
+ }
732
+ function manifestPermissions(manifest) {
733
+ const permissions = manifest.spec?.permissions;
734
+ if (!Array.isArray(permissions)) return [];
735
+ return permissions.filter((p) => typeof p === "string");
736
+ }
737
+ function validateManifest(manifest) {
738
+ const issues = [];
739
+ const error = (message) => issues.push({ level: "error", message });
740
+ const warning = (message) => issues.push({ level: "warning", message });
741
+ const kind = manifest.kind;
742
+ if (!kind) {
743
+ error("missing 'kind' (ExecutionEngine | Application | Bundle)");
744
+ return issues;
745
+ }
746
+ if (!["ExecutionEngine", "Application", "Bundle"].includes(kind)) {
747
+ error(`unknown kind '${kind}' (expected ExecutionEngine, Application or Bundle)`);
748
+ }
749
+ if (!manifest["api-version"]) warning("missing 'api-version' (expected verentis.io/v1)");
750
+ const meta = manifest.metadata ?? {};
751
+ for (const field of ["name", "display-name", "version"]) {
752
+ if (!meta[field]) error(`metadata.${field} is required`);
753
+ }
754
+ const spec = manifest.spec ?? {};
755
+ if (kind === "ExecutionEngine") {
756
+ const runtimes = spec.runtimes ?? {};
757
+ if (Object.keys(runtimes).length === 0) {
758
+ error("spec.runtimes must declare at least one runtime (docker and/or wasm)");
759
+ }
760
+ const docker = runtimes["docker"];
761
+ if (docker && !docker.image) error("spec.runtimes.docker.image is required");
762
+ const wasm = runtimes["wasm"];
763
+ if (wasm && !wasm.module) error("spec.runtimes.wasm.module is required");
764
+ if (!spec["file-types"]?.length) warning("spec.file-types is empty \u2014 the engine will never be auto-resolved for a file");
765
+ for (const fileType of spec["file-types"] ?? []) {
766
+ if (!fileType.pattern) error("every spec.file-types[] entry needs a 'pattern' (MIME type)");
767
+ }
768
+ for (const tool of spec.tools ?? []) {
769
+ if (!tool.name) error("every spec.tools[] entry needs a 'name'");
770
+ }
771
+ if (!spec["execution-modes"]?.length) warning("spec.execution-modes is empty (expected e.g. request-response)");
772
+ if (!manifestPermissions(manifest).length) warning("spec.permissions is empty \u2014 the run token will carry no VFS scopes");
773
+ }
774
+ if (kind === "Application") {
775
+ if (!spec.entry && !spec["entry-points"]?.length) {
776
+ error("applications need spec.entry or spec.entry-points[]");
777
+ }
778
+ }
779
+ const packaging = manifest.packaging ?? {};
780
+ if (!packaging.publisher) warning("packaging.publisher is not set \u2014 `verentis pack`/`publish` will refuse the package");
781
+ return issues;
782
+ }
783
+ var KIND_MAP = {
784
+ application: "Application",
785
+ executionengine: "ExecutionEngine",
786
+ bundle: "Bundle"
787
+ };
788
+ function normalizeKind(kind) {
789
+ const mapped = KIND_MAP[kind.replace(/[^a-z]/gi, "").toLowerCase()];
790
+ if (!mapped) throw new Error(`Unsupported manifest kind '${kind}' \u2014 expected Application, ExecutionEngine or Bundle.`);
791
+ return mapped;
792
+ }
793
+ async function loadManifest(path) {
794
+ const text = await readFile(path, "utf8");
795
+ const manifest = parse(text);
796
+ if (!manifest || typeof manifest !== "object") throw new Error(`Failed to parse manifest at ${path}.`);
797
+ if (!manifest.kind) throw new Error(`Manifest at ${path} is missing 'kind'.`);
798
+ if (!manifest.metadata?.name) throw new Error(`Manifest at ${path} is missing 'metadata.name'.`);
799
+ if (!manifest.metadata?.version) throw new Error(`Manifest at ${path} is missing 'metadata.version'.`);
800
+ normalizeKind(manifest.kind);
801
+ return manifest;
802
+ }
803
+ async function findManifest(dir) {
804
+ const explicit = join(dir, "manifest.yaml");
805
+ try {
806
+ if ((await stat(explicit)).isFile()) return explicit;
807
+ } catch {
808
+ }
809
+ const entries = await readdir(dir);
810
+ const candidates = entries.filter((name) => /\.(app|engine|bundle)\.ya?ml$/.test(name));
811
+ if (candidates.length === 1) return join(dir, candidates[0]);
812
+ if (candidates.length === 0) {
813
+ throw new Error(`No manifest found in ${dir}. Expected manifest.yaml or *.app.yaml / *.engine.yaml / *.bundle.yaml.`);
814
+ }
815
+ throw new Error(`Multiple manifests found in ${dir} (${candidates.join(", ")}). Pass --manifest to disambiguate.`);
816
+ }
817
+ var NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
818
+ function validatePackageName(name, label) {
819
+ if (!NAME_PATTERN.test(name)) {
820
+ throw new Error(`Invalid ${label} '${name}' \u2014 use lowercase letters, digits and hyphens (no leading/trailing hyphen).`);
821
+ }
822
+ }
823
+
824
+ // src/commands/dev.ts
825
+ async function resolveDevScopes(flags) {
826
+ if (flags.scopes?.length) return flags.scopes;
827
+ const manifestPath = flags.manifest ?? await findManifest(process.cwd()).catch(() => void 0);
828
+ if (manifestPath) {
829
+ const manifest = await readManifestDocument(manifestPath);
830
+ const permissions = manifestPermissions(manifest);
831
+ if (permissions.length > 0) {
832
+ console.error(`Using ${permissions.length} scope(s) from ${manifestPath}`);
833
+ return permissions;
834
+ }
835
+ }
836
+ return [...ENGINE_DEFAULT_PERMISSIONS];
837
+ }
838
+ async function mintDevToken(api, workspaceId, scopes, boundary, branch = "main") {
839
+ let resourceId;
840
+ if (boundary) {
841
+ const node = await call(api, files.get, {
842
+ params: { path: boundary.replace(/^\/+/, "").replace(/\/+$/, "") },
843
+ workspaceId,
844
+ query: { branch }
845
+ });
846
+ if (!node.nodeId) throw new Error(`Could not resolve a node id for boundary path '${boundary}'.`);
847
+ resourceId = node.nodeId;
848
+ }
849
+ return api.mintAccessToken({
850
+ scopes,
851
+ workspaceId,
852
+ ...resourceId ? { resourceId, resourceKind: "Node" } : {}
853
+ });
854
+ }
855
+ async function buildContextFromFlags(api, options) {
856
+ const workspaceId = await resolveWorkspaceId(options);
857
+ const scopes = await resolveDevScopes(options);
858
+ const token = await mintDevToken(api, workspaceId, scopes, options.boundary, options.branch);
859
+ const env = {};
860
+ for (const pair of options.env ?? []) {
861
+ const idx = pair.indexOf("=");
862
+ if (idx < 0) throw new Error(`Invalid --env '${pair}' (expected KEY=value).`);
863
+ env[pair.slice(0, idx)] = pair.slice(idx + 1);
864
+ }
865
+ let fileMimeType;
866
+ if (options.file) {
867
+ const remote = await call(api, files.get, {
868
+ params: { path: options.file.replace(/^\/+/, "") },
869
+ workspaceId,
870
+ query: { branch: options.branch }
871
+ }).catch(() => void 0);
872
+ fileMimeType = remote?.mimeType;
873
+ }
874
+ return buildLocalContext({
875
+ apiUrl: api.apiUrl,
876
+ accessToken: token,
877
+ scopes,
878
+ workspaceId,
879
+ filePath: options.file,
880
+ fileMimeType,
881
+ branch: options.branch,
882
+ tool: options.tool,
883
+ mode: options.mode,
884
+ timeoutSeconds: options.timeout ? Number(options.timeout) : void 0,
885
+ args: options.arg,
886
+ env,
887
+ parameters: options.params ? JSON.parse(options.params) : void 0
888
+ });
889
+ }
890
+ function registerDev(program2) {
891
+ const devCmd = program2.command("dev").description("Local development loop for engine/app scripts (real workspace access from your machine)");
892
+ devCmd.command("token").description("Mint a workspace-bound access token like a platform run receives").option("--workspace <workspaceId>").option("--scopes <scope...>", "explicit scopes (default: manifest spec.permissions, else the engine default set)").option("--manifest <path>", "engine/app manifest to read spec.permissions from").option("--boundary <path>", "confine the token to this workspace directory subtree (like platform runs)").option("--branch <branch>", "workspace branch", "main").option("--json", "output JSON with token metadata").action(async (options) => {
893
+ const workspaceId = await resolveWorkspaceId(options);
894
+ const api = await createApiClient();
895
+ const scopes = await resolveDevScopes(options);
896
+ const token = await mintDevToken(api, workspaceId, scopes, options.boundary, options.branch);
897
+ if (options.json) {
898
+ const payload = decodeJwtPayload(token);
899
+ printJson({
900
+ access_token: token,
901
+ scopes,
902
+ workspaceId,
903
+ expiresAt: payload?.exp ? new Date(payload.exp * 1e3).toISOString() : void 0
904
+ });
905
+ return;
906
+ }
907
+ console.log(token);
908
+ });
909
+ devCmd.command("context").description("Write a real context.json (the platform's engine contract) for local runs").option("--workspace <workspaceId>").option("--file <path>", "workspace file path the run targets (engine.context.file)").option("--tool <tool>", "engine tool name (e.g. execute)", "execute").option("--mode <mode>", "execution mode", "request-response").option("--timeout <seconds>", "execution timeout").option("--arg <value...>", "command-line arguments (repeatable)").option("--env <KEY=value...>", "environment entries for engine.context.input.environment (repeatable)").option("--params <json>", "tool parameters JSON").option("--scopes <scope...>", "explicit token scopes").option("--manifest <path>", "manifest to read spec.permissions from").option("--boundary <path>", "confine the token to this directory subtree").option("--branch <branch>", "workspace branch", "main").option("--out <path>", "output path", ".verentis/context.json").action(async (options) => {
910
+ const api = await createApiClient();
911
+ const context = await buildContextFromFlags(api, options);
912
+ const outPath = resolve(options.out ?? ".verentis/context.json");
913
+ await mkdir(dirname(outPath), { recursive: true });
914
+ await writeFile(outPath, JSON.stringify(context, null, 2) + "\n", { mode: 384 });
915
+ console.log(`Wrote ${outPath} (token expires ${context.platform.token.expiresAt ?? "unknown"}).`);
916
+ console.log("\nRun your script with the SDK pointed at it:");
917
+ console.log(` export VERENTIS_CONTEXT_PATH=${outPath}`);
918
+ console.log(" python your_script.py");
919
+ });
920
+ devCmd.command("run <script>").description("Run a local script against the REAL workspace (mint token \u2192 context.json \u2192 spawn)").option("--workspace <workspaceId>").option("--file <path>", "workspace file path the run pretends to target (default: /<script name>)").option("--tool <tool>", "engine tool name", "execute").option("--mode <mode>", "execution mode", "request-response").option("--timeout <seconds>", "execution timeout").option("--arg <value...>", "arguments passed to the script (repeatable)").option("--env <KEY=value...>", "extra environment variables (repeatable)").option("--params <json>", "tool parameters JSON").option("--scopes <scope...>", "explicit token scopes").option("--manifest <path>", "manifest to read spec.permissions from").option("--boundary <path>", "confine the token to this directory subtree").option("--branch <branch>", "workspace branch", "main").option("--python <interpreter>", "python interpreter", "python3").option("--debug [port]", "wait for a debugger: wraps with debugpy --listen <port> --wait-for-client").option("--keep-context", "keep the generated .verentis/context.json after the run").action(async (script, options) => {
921
+ const api = await createApiClient();
922
+ const scriptPath = resolve(script);
923
+ const effective = { ...options, file: options.file ?? `/${script.replace(/^\.\//, "")}` };
924
+ const context = await buildContextFromFlags(api, effective);
925
+ const contextPath = resolve(".verentis/context.json");
926
+ await mkdir(dirname(contextPath), { recursive: true });
927
+ await writeFile(contextPath, JSON.stringify(context, null, 2) + "\n", { mode: 384 });
928
+ const env = {
929
+ ...process.env,
930
+ ...context.input.environment,
931
+ ...contextEnv(context, contextPath)
932
+ };
933
+ const pythonArgs = [];
934
+ if (options.debug !== void 0) {
935
+ const port = typeof options.debug === "string" ? options.debug : "5678";
936
+ pythonArgs.push("-m", "debugpy", "--listen", port, "--wait-for-client");
937
+ console.error(`Waiting for a debugger to attach on port ${port} (VS Code: "Python Debugger: Attach")\u2026`);
938
+ }
939
+ pythonArgs.push(scriptPath, ...context.input.arguments ?? []);
940
+ console.error(`\u2192 ${options.python} ${pythonArgs.join(" ")}`);
941
+ console.error(` workspace ${context.workspace.id} \xB7 branch ${options.branch} \xB7 token expires ${context.platform.token.expiresAt ?? "unknown"}
942
+ `);
943
+ const child = spawn(options.python, pythonArgs, { env, stdio: "inherit" });
944
+ const exitCode = await new Promise((resolveExit) => {
945
+ child.on("close", (code) => resolveExit(code ?? 1));
946
+ child.on("error", (error) => {
947
+ console.error(`Failed to start ${options.python}: ${error.message}`);
948
+ if (options.debug !== void 0) console.error("Is debugpy installed? pip install debugpy");
949
+ resolveExit(1);
950
+ });
951
+ });
952
+ process.exitCode = exitCode;
953
+ });
954
+ devCmd.command("vscode").description("Scaffold .vscode/launch.json with attach + launch configs for the dev loop").option("--port <port>", "debugpy port", "5678").action(async (options) => {
955
+ const launch = {
956
+ version: "0.2.0",
957
+ configurations: [
958
+ {
959
+ name: "Verentis: attach to dev run",
960
+ type: "debugpy",
961
+ request: "attach",
962
+ connect: { host: "localhost", port: Number(options.port) },
963
+ justMyCode: true
964
+ },
965
+ {
966
+ name: "Verentis: launch current file with context",
967
+ type: "debugpy",
968
+ request: "launch",
969
+ program: "${file}",
970
+ console: "integratedTerminal",
971
+ justMyCode: true,
972
+ env: { VERENTIS_CONTEXT_PATH: "${workspaceFolder}/.verentis/context.json" }
973
+ }
974
+ ]
975
+ };
976
+ const outPath = resolve(".vscode/launch.json");
977
+ await mkdir(dirname(outPath), { recursive: true });
978
+ await writeFile(outPath, JSON.stringify(launch, null, 2) + "\n");
979
+ console.log(`Wrote ${outPath}.`);
980
+ console.log("Workflow:");
981
+ console.log(" 1. verentis dev run script.py --debug # waits for the debugger");
982
+ console.log(' 2. F5 \u2192 "Verentis: attach to dev run" # breakpoints hit your local code');
983
+ console.log(' or: verentis dev context && F5 \u2192 "launch current file with context"');
984
+ });
985
+ }
986
+
987
+ // src/commands/exec.ts
988
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["Completed", "Failed", "Cancelled", "TimedOut"]);
989
+ function registerExec(program2) {
990
+ const execCmd = program2.command("exec").description("Run and inspect executions");
991
+ execCmd.command("run <filePath>").description("Execute a workspace file with its engine (e.g. `verentis exec run /scripts/transform.py`)").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").option("--engine <engineId>", "explicit engine id (default: resolved from the file type)").option("--tool <tool>", "engine tool to invoke (default: the engine default, e.g. execute)").option("--mode <mode>", "request-response | long-running").option("--arg <value...>", "command-line arguments passed to the script (repeatable)").option("--params <json>", "tool parameters JSON (engine.context.input.parameters)").option("--follow", "wait for completion and stream logs").option("--json", "output the execution record as JSON").action(async (filePath, options) => {
992
+ const workspaceId = await resolveWorkspaceId(options);
993
+ const api = await createApiClient();
994
+ if (options.params) JSON.parse(options.params);
995
+ const result = await call(api, execution.request, {
996
+ workspaceId,
997
+ body: {
998
+ workspaceId,
999
+ engineId: options.engine ?? null,
1000
+ filePath: filePath.startsWith("/") ? filePath : `/${filePath}`,
1001
+ branch: options.branch,
1002
+ tool: options.tool ?? null,
1003
+ mode: options.mode ?? null,
1004
+ arguments: options.arg ?? [],
1005
+ parameters: options.params ?? null,
1006
+ clientRuntimeCapable: false
1007
+ }
1008
+ });
1009
+ if (options.json && !options.follow) {
1010
+ printJson(result);
1011
+ return;
1012
+ }
1013
+ console.log(`Execution ${result.id} \u2192 ${result.status} (engine ${result.engineName ?? result.engineId})`);
1014
+ if (options.follow) {
1015
+ await followExecution(api, workspaceId, result.id, options.json);
1016
+ } else if (!TERMINAL_STATUSES.has(result.status)) {
1017
+ console.log(`Follow with: verentis exec logs ${result.id} --follow`);
1018
+ } else {
1019
+ printResult(result);
1020
+ }
1021
+ });
1022
+ execCmd.command("list").description("List executions in a workspace").option("--workspace <workspaceId>").option("--file <filePath>", "filter by workspace file path").option("--page <n>").option("--page-size <n>").option("--order-by <field>").option("--json", "output JSON").action(async (options) => {
1023
+ const workspaceId = await resolveWorkspaceId(options);
1024
+ const api = await createApiClient();
1025
+ const page = await call(api, execution.list, {
1026
+ workspaceId,
1027
+ query: { workspaceId, filePath: options.file, ...pagingQuery(options) }
1028
+ });
1029
+ output(
1030
+ pageItems(page).map((exec) => ({
1031
+ id: exec.id,
1032
+ status: exec.status,
1033
+ engine: exec.engineName ?? exec.engineId,
1034
+ file: exec.filePath ?? "",
1035
+ requestedAt: exec.requestedAt ?? ""
1036
+ })),
1037
+ { id: "Id", status: "Status", engine: "Engine", file: "File", requestedAt: "Requested" },
1038
+ options,
1039
+ page
1040
+ );
1041
+ });
1042
+ execCmd.command("get <executionId>").description("Show execution details (status, result, artifacts)").option("--workspace <workspaceId>").option("--json", "output JSON").action(async (executionId, options) => {
1043
+ const workspaceId = await resolveWorkspaceId(options);
1044
+ const api = await createApiClient();
1045
+ const result = await call(api, execution.get, {
1046
+ params: { id: executionId },
1047
+ workspaceId,
1048
+ query: { workspaceId }
1049
+ });
1050
+ if (options.json) {
1051
+ printJson(result);
1052
+ return;
1053
+ }
1054
+ const { result: execResult, ...rest } = result;
1055
+ outputRecord(rest, options);
1056
+ printResult(result);
1057
+ });
1058
+ execCmd.command("cancel <executionId>").description("Cancel a running execution").option("--workspace <workspaceId>").action(async (executionId, options) => {
1059
+ const workspaceId = await resolveWorkspaceId(options);
1060
+ const api = await createApiClient();
1061
+ const result = await call(api, execution.cancel, {
1062
+ params: { id: executionId },
1063
+ workspaceId,
1064
+ body: { id: executionId, workspaceId }
1065
+ });
1066
+ console.log(`Execution ${executionId} \u2192 ${result?.status ?? "Cancelled"}.`);
1067
+ });
1068
+ execCmd.command("logs <executionId>").description("Fetch (or stream with --follow) the logs of an execution").option("--workspace <workspaceId>").option("--follow", "stream live logs via SSE until the execution ends").action(async (executionId, options) => {
1069
+ const workspaceId = await resolveWorkspaceId(options);
1070
+ const api = await createApiClient();
1071
+ if (options.follow) {
1072
+ await streamLogs(api, workspaceId, executionId);
1073
+ return;
1074
+ }
1075
+ const logs = await call(api, execution.logs, {
1076
+ params: { id: executionId },
1077
+ workspaceId,
1078
+ query: { workspaceId }
1079
+ });
1080
+ if (logs.stdout) process.stdout.write(logs.stdout.endsWith("\n") ? logs.stdout : `${logs.stdout}
1081
+ `);
1082
+ if (logs.stderr) process.stderr.write(logs.stderr.endsWith("\n") ? logs.stderr : `${logs.stderr}
1083
+ `);
1084
+ if (logs.truncated) console.error("(logs truncated \u2014 the full log exceeded the inline limit)");
1085
+ if (logs.running) console.error("(execution still running \u2014 use --follow to stream)");
1086
+ });
1087
+ const engineCmd = program2.command("engine").description("Inspect execution engines");
1088
+ engineCmd.command("list").description("List registered execution engines").option("--json", "output JSON").action(async (options) => {
1089
+ const api = await createApiClient();
1090
+ const page = await call(api, execution.listEngines, {
1091
+ query: { PageNo: 1, PageSize: 100 }
1092
+ });
1093
+ output(pageItems(page), { name: "Name", displayName: "Display", version: "Version", id: "Id" }, options, page);
1094
+ });
1095
+ engineCmd.command("get <engineId>").description("Show engine details (runtimes, tools, permissions)").action(async (engineId) => {
1096
+ const api = await createApiClient();
1097
+ const result = await call(api, execution.getEngine, { params: { id: engineId } });
1098
+ printJson(result);
1099
+ });
1100
+ engineCmd.command("resolve <fileTypeOrPath>").description("Resolve which engine handles a MIME type or file path").option("--workspace <workspaceId>").action(async (fileTypeOrPath, options) => {
1101
+ const api = await createApiClient();
1102
+ const workspaceId = await resolveWorkspaceId(options, false);
1103
+ const query = fileTypeOrPath.includes("/") && !fileTypeOrPath.includes(".") ? { fileType: fileTypeOrPath } : fileTypeOrPath.includes(".") && !fileTypeOrPath.includes("/") ? { fileType: fileTypeOrPath } : { filePath: fileTypeOrPath };
1104
+ const result = await call(api, execution.resolveEngine, {
1105
+ query: { ...query, ...workspaceId ? { workspaceId } : {} }
1106
+ });
1107
+ printJson(result);
1108
+ });
1109
+ engineCmd.command("sync").description("Re-scan the workspace for engine manifests and sync registrations").option("--workspace <workspaceId>").action(async (options) => {
1110
+ const workspaceId = await resolveWorkspaceId(options);
1111
+ const api = await createApiClient();
1112
+ const result = await call(api, execution.syncEngines, { workspaceId, body: { workspaceId } });
1113
+ printJson(result);
1114
+ });
1115
+ }
1116
+ function printResult(exec) {
1117
+ if (!exec.result) return;
1118
+ if (exec.result.error) console.error(`Error: ${exec.result.error}`);
1119
+ if (exec.result.data !== void 0 && exec.result.data !== null) {
1120
+ console.log("Result:");
1121
+ printJson(exec.result.data);
1122
+ }
1123
+ for (const artifact of exec.result.artifacts ?? []) console.log(`Artifact: ${artifact}`);
1124
+ }
1125
+ async function streamLogs(api, workspaceId, executionId) {
1126
+ const res = await callRaw(api, execution.logsStream, {
1127
+ params: { id: executionId },
1128
+ workspaceId,
1129
+ query: { workspaceId }
1130
+ });
1131
+ if (!res.body) return;
1132
+ const decoder = new TextDecoder();
1133
+ let buffer = "";
1134
+ let currentEvent = "message";
1135
+ const reader = res.body.getReader();
1136
+ for (; ; ) {
1137
+ const { done, value } = await reader.read();
1138
+ if (done) break;
1139
+ buffer += decoder.decode(value, { stream: true });
1140
+ let newlineIdx;
1141
+ while ((newlineIdx = buffer.indexOf("\n")) >= 0) {
1142
+ const line = buffer.slice(0, newlineIdx).replace(/\r$/, "");
1143
+ buffer = buffer.slice(newlineIdx + 1);
1144
+ if (line.startsWith("event: ")) {
1145
+ currentEvent = line.slice(7).trim();
1146
+ } else if (line.startsWith("data: ")) {
1147
+ if (currentEvent === "end") return;
1148
+ console.log(line.slice(6));
1149
+ } else if (line === "") {
1150
+ currentEvent = "message";
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+ async function followExecution(api, workspaceId, executionId, json) {
1156
+ await streamLogs(api, workspaceId, executionId);
1157
+ for (let attempt = 0; attempt < 30; attempt++) {
1158
+ const exec = await call(api, execution.get, {
1159
+ params: { id: executionId },
1160
+ workspaceId,
1161
+ query: { workspaceId }
1162
+ });
1163
+ if (TERMINAL_STATUSES.has(exec.status)) {
1164
+ if (json) printJson(exec);
1165
+ else {
1166
+ console.log(`Status: ${exec.status}`);
1167
+ printResult(exec);
1168
+ }
1169
+ if (exec.status !== "Completed") process.exitCode = 1;
1170
+ return;
1171
+ }
1172
+ await new Promise((resolve8) => setTimeout(resolve8, 1e3));
1173
+ }
1174
+ console.error("Timed out waiting for a terminal status \u2014 check `verentis exec get`.");
1175
+ process.exitCode = 1;
1176
+ }
1177
+ var normalizeRemote = (path) => {
1178
+ const trimmed = path.replace(/^\/+/, "").replace(/\/+$/, "");
1179
+ return trimmed;
1180
+ };
1181
+ async function uploadWithHeaders(api, workspaceId, remotePath, localPath, options) {
1182
+ const stream = Readable.toWeb(createReadStream(localPath));
1183
+ const res = await api.requestRaw("POST", fillPath(files.upload.path, { path: normalizeRemote(remotePath) }), {
1184
+ scopes: [...files.upload.scopes],
1185
+ tokenWorkspaceId: workspaceId,
1186
+ rawBody: stream,
1187
+ contentType: options.contentType ?? "application/octet-stream",
1188
+ headers: {
1189
+ "X-Branch": options.branch,
1190
+ "Content-Disposition": `attachment; filename="${basename(localPath)}"`,
1191
+ ...options.autoExtract ? { "X-Auto-Extract": "true" } : {}
1192
+ }
1193
+ });
1194
+ return await res.json();
1195
+ }
1196
+ function registerFiles(program2) {
1197
+ const filesCmd = program2.command("files").description("Work with workspace files (the VFS)");
1198
+ filesCmd.command("ls [path]").description("List a directory in the workspace").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").option("--json", "output JSON").action(async (path, options) => {
1199
+ const workspaceId = await resolveWorkspaceId(options);
1200
+ const api = await createApiClient();
1201
+ const result = await call(api, files.get, {
1202
+ params: { path: normalizeRemote(path ?? "/") },
1203
+ workspaceId,
1204
+ query: { branch: options.branch, PageNo: 1, PageSize: 100 }
1205
+ });
1206
+ const entries = pageItems(result.children);
1207
+ if (entries.length === 0 && result.name) {
1208
+ outputRecord(result, options);
1209
+ return;
1210
+ }
1211
+ output(
1212
+ entries.map((entry) => ({ ...entry, size: entry.size ?? "" })),
1213
+ { name: "Name", mimeType: "Type", size: "Size", path: "Path" },
1214
+ options,
1215
+ result
1216
+ );
1217
+ });
1218
+ filesCmd.command("stat <path>").description("Show file metadata").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").option("--json", "output JSON").action(async (path, options) => {
1219
+ const workspaceId = await resolveWorkspaceId(options);
1220
+ const api = await createApiClient();
1221
+ const result = await call(api, files.get, {
1222
+ params: { path: normalizeRemote(path) },
1223
+ workspaceId,
1224
+ query: { branch: options.branch }
1225
+ });
1226
+ const { children, ...rest } = result;
1227
+ outputRecord(rest, options);
1228
+ });
1229
+ filesCmd.command("cat <path>").description("Print raw file content to stdout").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").action(async (path, options) => {
1230
+ const workspaceId = await resolveWorkspaceId(options);
1231
+ const api = await createApiClient();
1232
+ const res = await callRaw(api, files.raw, {
1233
+ params: { path: normalizeRemote(path) },
1234
+ workspaceId,
1235
+ query: { branch: options.branch }
1236
+ });
1237
+ if (!res.body) return;
1238
+ await pipeline(Readable.fromWeb(res.body), process.stdout, { end: false });
1239
+ });
1240
+ filesCmd.command("download <remotePath> [localPath]").description("Download a file (or directory as an archive with --format)").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").option("--format <format>", "archive format for directories: Zip | TarGz").action(async (remotePath, localPath, options) => {
1241
+ const workspaceId = await resolveWorkspaceId(options);
1242
+ const api = await createApiClient();
1243
+ const res = await callRaw(api, files.raw, {
1244
+ params: { path: normalizeRemote(remotePath) },
1245
+ workspaceId,
1246
+ query: { branch: options.branch, format: options.format }
1247
+ });
1248
+ const suffix = options.format === "Zip" ? ".zip" : options.format === "TarGz" ? ".tar.gz" : "";
1249
+ const out = resolve(localPath ?? `${basename(normalizeRemote(remotePath)) || "download"}${suffix}`);
1250
+ if (!res.body) throw new Error("Empty response.");
1251
+ await mkdir(dirname(out), { recursive: true });
1252
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(out));
1253
+ console.log(`Downloaded ${remotePath} \u2192 ${out}`);
1254
+ });
1255
+ filesCmd.command("upload <localPath> [remotePath]").description("Upload a file or directory (directories upload file-by-file)").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").option("--content-type <type>", "content type for a single-file upload").option("--extract", "archive uploads: extract server-side into the target directory (X-Auto-Extract)").action(async (localPath, remotePath, options) => {
1256
+ const workspaceId = await resolveWorkspaceId(options);
1257
+ const api = await createApiClient();
1258
+ const local = resolve(localPath);
1259
+ const info = await stat(local);
1260
+ if (info.isDirectory()) {
1261
+ const remoteBase = normalizeRemote(remotePath ?? basename(local));
1262
+ const uploaded = await uploadDirectory(api, workspaceId, local, remoteBase, options.branch);
1263
+ console.log(`Uploaded ${uploaded} file(s) to /${remoteBase}.`);
1264
+ return;
1265
+ }
1266
+ const remote = remotePath && remotePath.endsWith("/") ? posix.join(normalizeRemote(remotePath), basename(local)) : normalizeRemote(remotePath ?? basename(local));
1267
+ const result = await uploadWithHeaders(api, workspaceId, remote, local, {
1268
+ branch: options.branch,
1269
+ contentType: options.contentType,
1270
+ autoExtract: options.extract
1271
+ });
1272
+ console.log(`Uploaded ${localPath} \u2192 /${result.path ?? remote}`);
1273
+ });
1274
+ filesCmd.command("rm <path>").description("Delete a file or directory").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").option("--recursive", "delete directory contents recursively").action(async (path, options) => {
1275
+ const workspaceId = await resolveWorkspaceId(options);
1276
+ const api = await createApiClient();
1277
+ await api.requestRaw("DELETE", fillPath(files.delete.path, { path: normalizeRemote(path) }), {
1278
+ scopes: [...files.delete.scopes],
1279
+ tokenWorkspaceId: workspaceId,
1280
+ query: { branch: options.branch },
1281
+ headers: options.recursive ? { "X-Delete-Mode": "Deep" } : {}
1282
+ });
1283
+ console.log(`Deleted /${normalizeRemote(path)}.`);
1284
+ });
1285
+ filesCmd.command("mv <fromPath> <toPath>").description("Move or rename a file").option("--workspace <workspaceId>").option("--branch <branch>", "workspace branch", "main").action(async (fromPath, toPath, options) => {
1286
+ const workspaceId = await resolveWorkspaceId(options);
1287
+ const api = await createApiClient();
1288
+ await call(api, files.update, {
1289
+ params: { path: normalizeRemote(fromPath) },
1290
+ workspaceId,
1291
+ body: { branch: options.branch, path: normalizeRemote(fromPath), toPath: normalizeRemote(toPath) }
1292
+ });
1293
+ console.log(`Moved /${normalizeRemote(fromPath)} \u2192 /${normalizeRemote(toPath)}.`);
1294
+ });
1295
+ }
1296
+ async function uploadDirectory(api, workspaceId, localDir, remoteBase, branch) {
1297
+ let count = 0;
1298
+ const entries = await readdir(localDir, { withFileTypes: true });
1299
+ for (const entry of entries) {
1300
+ if (entry.name.startsWith(".git") || entry.name === "node_modules" || entry.name === "__pycache__") continue;
1301
+ const localEntry = join(localDir, entry.name);
1302
+ const remoteEntry = posix.join(remoteBase, entry.name);
1303
+ if (entry.isDirectory()) {
1304
+ count += await uploadDirectory(api, workspaceId, localEntry, remoteEntry, branch);
1305
+ } else if (entry.isFile()) {
1306
+ await uploadWithHeaders(api, workspaceId, remoteEntry, localEntry, { branch });
1307
+ console.log(` /${remoteEntry}`);
1308
+ count += 1;
1309
+ }
1310
+ }
1311
+ return count;
1312
+ }
1313
+ var TEMPLATES = {
1314
+ application: (name) => `api-version: verentis.io/v1
1315
+ kind: Application
1316
+
1317
+ metadata:
1318
+ name: ${name}
1319
+ display-name: ${titleCase(name)}
1320
+ description: ""
1321
+ icon: layout-dashboard
1322
+ version: 0.1.0
1323
+ author: ""
1324
+
1325
+ spec:
1326
+ entry: https://example.com # hosted app origin
1327
+ scope: global
1328
+
1329
+ entry-points: []
1330
+ capabilities: []
1331
+ permissions: []
1332
+
1333
+ sandbox:
1334
+ allow-scripts: true
1335
+ allow-same-origin: true
1336
+ allow-popups: false
1337
+ allow-forms: true
1338
+
1339
+ packaging:
1340
+ publisher: "" # your publisher name
1341
+ files: [] # globs relative to this manifest, e.g. ["schemas/**/*.json"]
1342
+ encryption: none
1343
+ `,
1344
+ engine: (name) => `api-version: verentis.io/v1
1345
+ kind: ExecutionEngine
1346
+
1347
+ metadata:
1348
+ name: ${name}
1349
+ display-name: ${titleCase(name)}
1350
+ description: ""
1351
+ icon: lucide:terminal
1352
+ version: 0.1.0
1353
+ author: ""
1354
+
1355
+ spec:
1356
+ runtimes: {}
1357
+ file-types: []
1358
+ execution-modes:
1359
+ - request-response
1360
+
1361
+ packaging:
1362
+ publisher: "" # your publisher name
1363
+ files: [] # e.g. ["dist/runner.js"] \u2014 reference as ./runner.js in spec
1364
+ encryption: none
1365
+ `,
1366
+ bundle: (name) => `api-version: verentis.io/v1
1367
+ kind: Bundle
1368
+
1369
+ metadata:
1370
+ name: ${name}
1371
+ display-name: ${titleCase(name)}
1372
+ description: ""
1373
+ version: 0.1.0
1374
+ author: ""
1375
+
1376
+ packaging:
1377
+ publisher: "" # your publisher name
1378
+ files:
1379
+ - "content/**/*" # everything under content/ is installed at the target path
1380
+ bundle:
1381
+ default-path: /data/${name}
1382
+ encryption: none
1383
+ `
1384
+ };
1385
+ function registerInit(program2) {
1386
+ program2.command("init [dir]").description("Scaffold a manifest.yaml for a new package").option("--kind <kind>", "application | engine | bundle", "application").option("--name <name>", "package name (defaults to the directory name)").action(async (dir, options) => {
1387
+ const template = TEMPLATES[options.kind.toLowerCase()];
1388
+ if (!template) throw new Error(`Unknown kind '${options.kind}' \u2014 use application, engine or bundle.`);
1389
+ const targetDir = resolve(dir ?? ".");
1390
+ await mkdir(targetDir, { recursive: true });
1391
+ const name = (options.name ?? targetDir.split("/").pop() ?? "my-package").toLowerCase().replace(/[^a-z0-9-]/g, "-");
1392
+ const manifestPath = join(targetDir, "manifest.yaml");
1393
+ if (await exists(manifestPath)) throw new Error(`${manifestPath} already exists.`);
1394
+ await writeFile(manifestPath, template(name));
1395
+ console.log(`Created ${manifestPath} (kind: ${options.kind}).`);
1396
+ console.log("Fill in packaging.publisher and packaging.files, then run `verentis pack`.");
1397
+ });
1398
+ }
1399
+ async function exists(path) {
1400
+ try {
1401
+ await stat(path);
1402
+ return true;
1403
+ } catch {
1404
+ return false;
1405
+ }
1406
+ }
1407
+ function titleCase(value) {
1408
+ return value.replace(/(^|[-_])(\w)/g, (_, __, c) => ` ${c.toUpperCase()}`).trim();
1409
+ }
1410
+ function computeKid(publicKeyBase64) {
1411
+ return createHash("sha256").update(Buffer.from(publicKeyBase64, "base64")).digest("hex").slice(0, 16);
1412
+ }
1413
+ function generateSigningKey() {
1414
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
1415
+ const rawPublic = rawPublicKeyBytes(publicKey);
1416
+ const publicKeyBase64 = rawPublic.toString("base64");
1417
+ return {
1418
+ kid: computeKid(publicKeyBase64),
1419
+ algorithm: "Ed25519",
1420
+ publicKey: publicKeyBase64,
1421
+ privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
1422
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1423
+ };
1424
+ }
1425
+ function signEd25519(privateKeyPem, message) {
1426
+ return sign(null, message, createPrivateKey(privateKeyPem));
1427
+ }
1428
+ function verifyEd25519(publicKeyBase64, message, signature) {
1429
+ const jwk = { kty: "OKP", crv: "Ed25519", x: Buffer.from(publicKeyBase64, "base64").toString("base64url") };
1430
+ const key = createPublicKey({ key: jwk, format: "jwk" });
1431
+ return verify(null, message, key, signature);
1432
+ }
1433
+ function rawPublicKeyBytes(publicKey) {
1434
+ const jwk = publicKey.export({ format: "jwk" });
1435
+ if (!jwk.x) throw new Error("Failed to export Ed25519 public key.");
1436
+ return Buffer.from(jwk.x, "base64url");
1437
+ }
1438
+ function keyPath(name) {
1439
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name)) {
1440
+ throw new Error(`Invalid key name '${name}' \u2014 use lowercase letters, digits and hyphens.`);
1441
+ }
1442
+ return join(keysDir(), `${name}.json`);
1443
+ }
1444
+ async function saveKey(name, key) {
1445
+ await mkdir(keysDir(), { recursive: true, mode: 448 });
1446
+ const path = keyPath(name);
1447
+ await writeFile(path, JSON.stringify(key, null, 2) + "\n", { mode: 384, flag: "wx" });
1448
+ return path;
1449
+ }
1450
+ async function loadKey(name) {
1451
+ try {
1452
+ return JSON.parse(await readFile(keyPath(name), "utf8"));
1453
+ } catch (error) {
1454
+ if (error.code === "ENOENT") {
1455
+ throw new Error(`Signing key '${name}' not found. Generate one with \`verentis keygen --name ${name}\`.`);
1456
+ }
1457
+ throw error;
1458
+ }
1459
+ }
1460
+ var pageItems2 = (page) => page.data ?? page.items ?? [];
1461
+ async function getMyPublisher(api) {
1462
+ const result = await api.request("GET", "/v1/publishers/me", {
1463
+ scopes: [SCOPES.publisherRead]
1464
+ });
1465
+ return result ?? null;
1466
+ }
1467
+ async function requireMyPublisher(api) {
1468
+ const publisher = await getMyPublisher(api);
1469
+ if (!publisher) {
1470
+ throw new Error("No publisher profile for this account. Create one with `verentis publisher create --name <name> --display-name <display>`.");
1471
+ }
1472
+ return publisher;
1473
+ }
1474
+ async function findPackageByName(api, publisherId, name) {
1475
+ const page = await api.request("GET", `/v1/publishers/${publisherId}/packages`, {
1476
+ scopes: [SCOPES.packageRead],
1477
+ query: { PageNo: 1, PageSize: 100 }
1478
+ });
1479
+ return pageItems2(page).find((pkg) => pkg.name === name) ?? null;
1480
+ }
1481
+ async function createPackage(api, input) {
1482
+ return api.request("POST", "/v1/packages", {
1483
+ scopes: [SCOPES.packageCreate],
1484
+ body: {
1485
+ publisherId: input.publisherId,
1486
+ name: input.name,
1487
+ displayName: input.displayName,
1488
+ kind: input.kind,
1489
+ description: input.description ?? null,
1490
+ icon: null,
1491
+ categories: [],
1492
+ visibility: input.visibility ?? "Private"
1493
+ }
1494
+ });
1495
+ }
1496
+ async function uploadVersion(api, packageId, vpkgPath) {
1497
+ (await stat(vpkgPath)).size;
1498
+ const stream = Readable.toWeb(createReadStream(vpkgPath));
1499
+ const res = await api.requestRaw("POST", `/v1/packages/${packageId}/versions`, {
1500
+ scopes: [SCOPES.packagePublish],
1501
+ rawBody: stream,
1502
+ contentType: "application/vnd.verentis.package+tar+gzip"
1503
+ });
1504
+ return await res.json();
1505
+ }
1506
+ async function yankVersion(api, packageId, version) {
1507
+ await api.request("POST", `/v1/packages/${packageId}/versions/${encodeURIComponent(version)}/yank`, {
1508
+ scopes: [SCOPES.packagePublish]
1509
+ });
1510
+ }
1511
+ async function downloadTarball(api, packageId, version, workspaceId) {
1512
+ return api.requestRaw("GET", `/v1/packages/${packageId}/versions/${encodeURIComponent(version)}/tarball`, {
1513
+ scopes: [SCOPES.packageRead],
1514
+ query: { workspaceId }
1515
+ });
1516
+ }
1517
+ async function resolvePackageRef(api, ref) {
1518
+ const [publisherName, packageName] = ref.includes("/") ? ref.split("/", 2) : [void 0, ref];
1519
+ if (!publisherName) {
1520
+ const me = await requireMyPublisher(api);
1521
+ const pkg2 = await findPackageByName(api, me.id, packageName);
1522
+ if (!pkg2) throw new Error(`Package '${packageName}' not found under your publisher '${me.name}'.`);
1523
+ return { package: pkg2, publisherName: me.name };
1524
+ }
1525
+ const page = await api.request("GET", "/v1/packages", {
1526
+ scopes: [SCOPES.packageReadAll],
1527
+ query: { SearchText: packageName, PageNo: 1, PageSize: 100 }
1528
+ });
1529
+ const pkg = pageItems2(page).find((p) => p.name === packageName && p.publisherName === publisherName);
1530
+ if (!pkg) throw new Error(`Package '${publisherName}/${packageName}' not found (or not visible to you).`);
1531
+ return { package: pkg, publisherName };
1532
+ }
1533
+
1534
+ // src/commands/keygen.ts
1535
+ function registerKeygen(program2) {
1536
+ program2.command("keygen").description("Generate an Ed25519 signing key and register its public half with your publisher profile").option("--name <name>", "key name (stored at ~/.verentis/keys/<name>.json)", "default").option("--no-register", "skip registering the public key with the marketplace").option("--set-default", "use this key as the default for `verentis pack`", true).action(async (options) => {
1537
+ const key = generateSigningKey();
1538
+ const path = await saveKey(options.name, key);
1539
+ console.log(`Generated Ed25519 key '${options.name}' (kid ${key.kid})`);
1540
+ console.log(` private key: ${path} \u2014 keep this safe; it never leaves your machine.`);
1541
+ if (options.setDefault) {
1542
+ const config = await loadConfig();
1543
+ config.defaultKey = options.name;
1544
+ await saveConfig(config);
1545
+ }
1546
+ if (options.register) {
1547
+ const api = await createApiClient();
1548
+ const publisher = await requireMyPublisher(api);
1549
+ await api.request("POST", `/v1/publishers/${publisher.id}/keys`, {
1550
+ scopes: [SCOPES.publisherUpdate],
1551
+ body: { publisherId: publisher.id, algorithm: "Ed25519", publicKey: key.publicKey }
1552
+ });
1553
+ console.log(`Registered public key with publisher '${publisher.name}'.`);
1554
+ } else {
1555
+ console.log("Public key NOT registered (use `verentis key register` later).");
1556
+ }
1557
+ });
1558
+ const keyCmd = program2.command("key").description("Manage signing keys");
1559
+ keyCmd.command("register").description("Register a local key's public half with your publisher profile").option("--name <name>", "key name", "default").action(async (options) => {
1560
+ const key = await loadKey(options.name);
1561
+ const api = await createApiClient();
1562
+ const publisher = await requireMyPublisher(api);
1563
+ await api.request("POST", `/v1/publishers/${publisher.id}/keys`, {
1564
+ scopes: [SCOPES.publisherUpdate],
1565
+ body: { publisherId: publisher.id, algorithm: "Ed25519", publicKey: key.publicKey }
1566
+ });
1567
+ console.log(`Registered key '${options.name}' (kid ${key.kid}) with publisher '${publisher.name}'.`);
1568
+ });
1569
+ keyCmd.command("revoke <kid>").description("Revoke a registered signing key").action(async (kid) => {
1570
+ const api = await createApiClient();
1571
+ const publisher = await requireMyPublisher(api);
1572
+ await api.request("DELETE", `/v1/publishers/${publisher.id}/keys/${encodeURIComponent(kid)}`, {
1573
+ scopes: [SCOPES.publisherUpdate]
1574
+ });
1575
+ console.log(`Revoked key ${kid}.`);
1576
+ });
1577
+ }
1578
+
1579
+ // src/auth/deviceFlow.ts
1580
+ var CLI_CLIENT_ID = "Verentis.Cli";
1581
+ async function startDeviceAuthorization(apiUrl) {
1582
+ const res = await fetch(`${apiUrl}/connect/device/authorize`, {
1583
+ method: "POST",
1584
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1585
+ body: new URLSearchParams({ client_id: CLI_CLIENT_ID })
1586
+ });
1587
+ if (!res.ok) {
1588
+ const text = await res.text().catch(() => "");
1589
+ throw new Error(`Could not start device sign-in (${res.status}).${text ? ` ${text.slice(0, 300)}` : ""}`);
1590
+ }
1591
+ return await res.json();
1592
+ }
1593
+ async function pollForIdentityToken(apiUrl, authorization, onPoll) {
1594
+ let intervalMs = Math.max(authorization.interval, 1) * 1e3;
1595
+ const deadline = Date.now() + authorization.expires_in * 1e3;
1596
+ while (Date.now() < deadline) {
1597
+ await sleep(intervalMs);
1598
+ onPoll?.();
1599
+ const res = await fetch(`${apiUrl}/connect/token`, {
1600
+ method: "POST",
1601
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1602
+ body: new URLSearchParams({
1603
+ grant_type: "device_code",
1604
+ device_code: authorization.device_code,
1605
+ client_id: CLI_CLIENT_ID
1606
+ })
1607
+ });
1608
+ let payload;
1609
+ try {
1610
+ payload = await res.json();
1611
+ } catch {
1612
+ payload = void 0;
1613
+ }
1614
+ if (res.ok && payload?.id_token) return payload.id_token;
1615
+ switch (payload?.error) {
1616
+ case "authorization_pending":
1617
+ continue;
1618
+ case "slow_down":
1619
+ intervalMs += 5e3;
1620
+ continue;
1621
+ case "access_denied":
1622
+ throw new Error("Sign-in was denied in the browser.");
1623
+ case "expired_token":
1624
+ throw new Error("The device code expired before it was approved. Run `verentis login` again.");
1625
+ default:
1626
+ throw new Error(`Device sign-in failed (${res.status}).${payload?.error ? ` ${payload.error}` : ""}`);
1627
+ }
1628
+ }
1629
+ throw new Error("The device code expired before it was approved. Run `verentis login` again.");
1630
+ }
1631
+ var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
1632
+
1633
+ // src/commands/login.ts
1634
+ function registerLogin(program2) {
1635
+ program2.command("login").description("Sign in to Verentis (browser device flow by default; --api-key/--token for non-interactive use)").option("--api-url <url>", "Verentis API gateway URL").option("--api-key <key>", "Verentis API key (vrt_...)").option("--token <token>", "Verentis identity token (exchanged per request for scoped access tokens)").option("--sealing-key <key>", "the platform's base64 X25519 sealing PUBLIC key (used by `pack --encrypt`)").action(async (options) => {
1636
+ const config = await loadConfig();
1637
+ if (options.apiUrl) config.apiUrl = options.apiUrl;
1638
+ if (options.sealingKey) config.sealingPublicKey = options.sealingKey;
1639
+ if (!config.apiUrl) throw new Error("An API URL is required. Pass --api-url <url>.");
1640
+ if (options.apiKey) {
1641
+ if (!options.apiKey.startsWith("vrt_")) throw new Error('API keys start with "vrt_".');
1642
+ config.apiKey = options.apiKey;
1643
+ delete config.identityToken;
1644
+ await saveConfig(config);
1645
+ console.log(`Configuration saved (API key @ ${config.apiUrl}).`);
1646
+ return;
1647
+ }
1648
+ if (options.token) {
1649
+ config.identityToken = options.token;
1650
+ delete config.apiKey;
1651
+ await saveConfig(config);
1652
+ console.log(`Configuration saved (identity token @ ${config.apiUrl}).`);
1653
+ return;
1654
+ }
1655
+ const apiUrl = config.apiUrl.replace(/\/+$/, "");
1656
+ const authorization = await startDeviceAuthorization(apiUrl);
1657
+ console.log("To sign in, open this URL in a browser:");
1658
+ console.log(`
1659
+ ${authorization.verification_uri_complete ?? authorization.verification_uri}
1660
+ `);
1661
+ console.log(`and confirm the code: ${authorization.user_code}`);
1662
+ console.log("\nWaiting for approval\u2026");
1663
+ const identityToken = await pollForIdentityToken(apiUrl, authorization, () => process.stderr.write("."));
1664
+ process.stderr.write("\n");
1665
+ config.identityToken = identityToken;
1666
+ delete config.apiKey;
1667
+ await saveConfig(config);
1668
+ const claims = decodeJwtPayload(identityToken);
1669
+ console.log(`Signed in as ${claims?.name ?? claims?.email ?? claims?.sub ?? "unknown"} @ ${config.apiUrl}.`);
1670
+ if (!claims?.account_id) {
1671
+ console.log("Note: no unambiguous account context \u2014 admin commands may need --account <id>.");
1672
+ }
1673
+ });
1674
+ program2.command("logout").description("Remove stored credentials").action(async () => {
1675
+ const config = await loadConfig();
1676
+ delete config.apiKey;
1677
+ delete config.identityToken;
1678
+ await saveConfig(config);
1679
+ console.log("Credentials removed.");
1680
+ });
1681
+ program2.command("whoami").description("Show the current identity, account and publisher profile").action(async () => {
1682
+ const config = await loadConfig();
1683
+ const identityToken = process.env.VERENTIS_TOKEN ?? config.identityToken;
1684
+ if (identityToken) {
1685
+ const claims = decodeJwtPayload(identityToken);
1686
+ if (claims) {
1687
+ const expiry = claims.exp ? new Date(claims.exp * 1e3) : void 0;
1688
+ const expired = expiry && expiry.getTime() <= Date.now();
1689
+ console.log(`User: ${claims.name ?? claims.email ?? claims.sub ?? "(unknown)"}${claims.sub ? ` (${claims.sub})` : ""}`);
1690
+ if (claims.account_id) console.log(`Account: ${claims.account_id}`);
1691
+ if (expiry) console.log(`Token ${expired ? "EXPIRED" : "expires"}: ${expiry.toISOString()}`);
1692
+ }
1693
+ } else if (process.env.VERENTIS_API_KEY ?? config.apiKey) {
1694
+ console.log("Credential: API key (identity claims not available)");
1695
+ }
1696
+ if (config.defaultWorkspaceId) console.log(`Workspace: ${config.defaultWorkspaceId} (default)`);
1697
+ const api = await createApiClient();
1698
+ const publisher = await api.request(
1699
+ "GET",
1700
+ "/v1/publishers/me",
1701
+ { scopes: [SCOPES.publisherRead] }
1702
+ ).catch(() => null);
1703
+ if (!publisher) {
1704
+ console.log('Publisher: none \u2014 create one with `verentis publisher create --name <name> --display-name "<display name>"`');
1705
+ return;
1706
+ }
1707
+ console.log(`Publisher: ${publisher.name} (${publisher.displayName})${publisher.isVerified ? " \u2713 verified" : ""}`);
1708
+ for (const key of publisher.signingKeys ?? []) {
1709
+ console.log(` key ${key.kid} [${key.status}]`);
1710
+ }
1711
+ });
1712
+ }
1713
+
1714
+ // src/crypto/dsse.ts
1715
+ var DSSE_PAYLOAD_TYPE = "application/vnd.verentis.package.digests+json";
1716
+ function preAuthenticationEncoding(payloadType, payload) {
1717
+ const typeBytes = Buffer.from(payloadType, "utf8");
1718
+ const header = Buffer.from(
1719
+ `DSSEv1 ${typeBytes.length} ${payloadType} ${payload.length} `,
1720
+ "utf8"
1721
+ );
1722
+ return Buffer.concat([header, payload]);
1723
+ }
1724
+ function signEnvelope(payload, key) {
1725
+ const payloadBytes = Buffer.from(payload, "utf8");
1726
+ const pae = preAuthenticationEncoding(DSSE_PAYLOAD_TYPE, payloadBytes);
1727
+ const signature = signEd25519(key.privateKeyPem, pae);
1728
+ return {
1729
+ payload: payloadBytes.toString("base64"),
1730
+ payloadType: DSSE_PAYLOAD_TYPE,
1731
+ signatures: [{ keyid: key.kid, sig: signature.toString("base64") }]
1732
+ };
1733
+ }
1734
+ function verifyEnvelope(envelope, expectedPayload, publicKeysByKid) {
1735
+ if (envelope.payloadType !== DSSE_PAYLOAD_TYPE) return null;
1736
+ const payloadBytes = Buffer.from(envelope.payload, "base64");
1737
+ if (payloadBytes.toString("utf8") !== expectedPayload) return null;
1738
+ const pae = preAuthenticationEncoding(envelope.payloadType, payloadBytes);
1739
+ for (const signature of envelope.signatures) {
1740
+ for (const [kid, publicKey] of Object.entries(publicKeysByKid)) {
1741
+ if (signature.keyid && signature.keyid !== kid) continue;
1742
+ try {
1743
+ if (verifyEd25519(publicKey, pae, Buffer.from(signature.sig, "base64"))) return kid;
1744
+ } catch {
1745
+ }
1746
+ }
1747
+ }
1748
+ return null;
1749
+ }
1750
+ var WRAP_ALGORITHM = "x25519-hkdf-sha256-aes256gcm";
1751
+ var CONTENT_ALGORITHM = "aes-256-gcm";
1752
+ var WRAP_INFO = "verentis-vpkg-wrap-v1";
1753
+ function generateSealingKeyPair() {
1754
+ const { publicKey, privateKey } = generateKeyPairSync("x25519");
1755
+ return {
1756
+ publicKey: rawX25519Public(publicKey).toString("base64"),
1757
+ privateKey: rawX25519Private(privateKey).toString("base64")
1758
+ };
1759
+ }
1760
+ function generateContentKey() {
1761
+ return randomBytes(32);
1762
+ }
1763
+ function encryptBytes(key, plaintext) {
1764
+ const nonce = randomBytes(12);
1765
+ const cipher = createCipheriv(CONTENT_ALGORITHM, key, nonce);
1766
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1767
+ return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()]);
1768
+ }
1769
+ function wrapContentKey(contentKey, recipientId, recipientPublicKeyBase64) {
1770
+ const recipientPublic = Buffer.from(recipientPublicKeyBase64, "base64");
1771
+ const ephemeral = generateKeyPairSync("x25519");
1772
+ const ephemeralPublic = rawX25519Public(ephemeral.publicKey);
1773
+ const kek = deriveWrapKey(ephemeral.privateKey, publicKeyFromRaw(recipientPublic), ephemeralPublic, recipientPublic);
1774
+ return {
1775
+ id: recipientId,
1776
+ publicKey: recipientPublicKeyBase64,
1777
+ ephemeralPublicKey: ephemeralPublic.toString("base64"),
1778
+ wrappedKey: encryptBytes(kek, contentKey).toString("base64")
1779
+ };
1780
+ }
1781
+ function deriveWrapKey(privateKey, publicKey, ephemeralPublic, recipientPublic) {
1782
+ const shared = diffieHellman({ privateKey, publicKey });
1783
+ const salt = Buffer.concat([ephemeralPublic, recipientPublic]);
1784
+ return Buffer.from(hkdfSync("sha256", shared, salt, WRAP_INFO, 32));
1785
+ }
1786
+ function rawX25519Public(key) {
1787
+ const jwk = key.export({ format: "jwk" });
1788
+ if (!jwk.x) throw new Error("Failed to export X25519 public key.");
1789
+ return Buffer.from(jwk.x, "base64url");
1790
+ }
1791
+ function rawX25519Private(key) {
1792
+ const jwk = key.export({ format: "jwk" });
1793
+ if (!jwk.d) throw new Error("Failed to export X25519 private key.");
1794
+ return Buffer.from(jwk.d, "base64url");
1795
+ }
1796
+ function publicKeyFromRaw(raw) {
1797
+ return createPublicKey({ key: { kty: "OKP", crv: "X25519", x: raw.toString("base64url") }, format: "jwk" });
1798
+ }
1799
+ async function pack(options) {
1800
+ const manifestPath = resolve(options.manifestPath);
1801
+ const projectDir = dirname(manifestPath);
1802
+ const manifest = await loadManifest(manifestPath);
1803
+ const kind = normalizeKind(manifest.kind);
1804
+ const name = (options.name ?? manifest.packaging?.name ?? manifest.metadata.name).toLowerCase();
1805
+ const publisher = options.publisher.toLowerCase();
1806
+ validatePackageName(name, "package name");
1807
+ validatePackageName(publisher, "publisher name");
1808
+ const version = manifest.metadata.version;
1809
+ const payloadFiles = await resolvePayloadFiles(projectDir, manifestPath, manifest);
1810
+ const staging = await mkdtemp(join(tmpdir(), "vpkg-"));
1811
+ try {
1812
+ const manifestText = await readFile(manifestPath, "utf8");
1813
+ await writeFile(join(staging, "manifest.yaml"), manifestText);
1814
+ const encrypt = (options.encryptFor?.length ?? 0) > 0;
1815
+ const contentKey = encrypt ? generateContentKey() : null;
1816
+ const digests = {
1817
+ "manifest.yaml": sha256Hex(Buffer.from(manifestText, "utf8"))
1818
+ };
1819
+ for (const file of payloadFiles) {
1820
+ const sourcePath = join(projectDir, file);
1821
+ const entryName = `files/${toEntryPath(file)}`;
1822
+ const targetPath = join(staging, "files", file);
1823
+ await mkdir(dirname(targetPath), { recursive: true });
1824
+ const bytes = contentKey ? encryptBytes(contentKey, await readFile(sourcePath)) : await readFile(sourcePath);
1825
+ await writeFile(targetPath, bytes);
1826
+ digests[entryName] = sha256Hex(bytes);
1827
+ }
1828
+ await mkdir(join(staging, ".verentis", "signatures"), { recursive: true });
1829
+ if (contentKey) {
1830
+ const encryption = {
1831
+ algorithm: CONTENT_ALGORITHM,
1832
+ keyWrapping: WRAP_ALGORITHM,
1833
+ recipients: options.encryptFor.map((r) => wrapContentKey(contentKey, r.id, r.publicKey))
1834
+ };
1835
+ await writeFile(join(staging, ".verentis", "encryption.json"), JSON.stringify(encryption, null, 2));
1836
+ }
1837
+ const digestsJson = JSON.stringify({ algorithm: "sha256", entries: digests }, null, 2);
1838
+ await writeFile(join(staging, ".verentis", "digests.json"), digestsJson);
1839
+ const packageYaml = [
1840
+ `name: ${name}`,
1841
+ `publisher: ${publisher}`,
1842
+ `version: ${version}`,
1843
+ `kind: ${kind}`,
1844
+ ...manifest.metadata["display-name"] ? [`display-name: ${JSON.stringify(manifest.metadata["display-name"])}`] : [],
1845
+ ""
1846
+ ].join("\n");
1847
+ await writeFile(join(staging, ".verentis", "package.yaml"), packageYaml);
1848
+ let signed = false;
1849
+ if (options.signingKey) {
1850
+ const envelope = signEnvelope(digestsJson, options.signingKey);
1851
+ await writeFile(join(staging, ".verentis", "signatures", "developer.dsse.json"), JSON.stringify(envelope, null, 2));
1852
+ signed = true;
1853
+ }
1854
+ const outDir = resolve(options.outDir ?? projectDir);
1855
+ await mkdir(outDir, { recursive: true });
1856
+ const outFile = join(outDir, `${name}-${version}.vpkg`);
1857
+ const entries = [
1858
+ "manifest.yaml",
1859
+ ".verentis/package.yaml",
1860
+ ".verentis/digests.json",
1861
+ ...contentKey ? [".verentis/encryption.json"] : [],
1862
+ ...signed ? [".verentis/signatures/developer.dsse.json"] : [],
1863
+ ...payloadFiles.map((file) => `files/${toEntryPath(file)}`)
1864
+ ];
1865
+ await tar.create({ gzip: true, file: outFile, cwd: staging, portable: true }, entries);
1866
+ return {
1867
+ outFile,
1868
+ name,
1869
+ publisher,
1870
+ version,
1871
+ kind,
1872
+ files: payloadFiles.map(toEntryPath),
1873
+ treeDigest: sha256Hex(Buffer.from(digestsJson, "utf8")),
1874
+ signed,
1875
+ encrypted: encrypt
1876
+ };
1877
+ } finally {
1878
+ await rm(staging, { recursive: true, force: true });
1879
+ }
1880
+ }
1881
+ async function resolvePayloadFiles(projectDir, manifestPath, manifest) {
1882
+ const includes = manifest.packaging?.files ?? defaultIncludes(manifest);
1883
+ const excludes = manifest.packaging?.exclude ?? [];
1884
+ const matched = /* @__PURE__ */ new Set();
1885
+ for (const pattern of includes) {
1886
+ for await (const entry of glob(pattern, { cwd: projectDir, exclude: excludes })) {
1887
+ const abs = join(projectDir, entry);
1888
+ if (!(await stat(abs)).isFile()) continue;
1889
+ if (abs === manifestPath) continue;
1890
+ const rel = relative(projectDir, abs);
1891
+ if (rel.startsWith("..")) throw new Error(`Packaging glob '${pattern}' matched '${rel}' outside the project directory.`);
1892
+ matched.add(rel);
1893
+ }
1894
+ }
1895
+ return [...matched].sort();
1896
+ }
1897
+ function defaultIncludes(manifest) {
1898
+ const sources = /* @__PURE__ */ new Set();
1899
+ collectSchemaSources(manifest, sources);
1900
+ if (sources.size === 0) return [];
1901
+ return [...sources];
1902
+ }
1903
+ function collectSchemaSources(node, sources) {
1904
+ if (Array.isArray(node)) {
1905
+ for (const item of node) collectSchemaSources(item, sources);
1906
+ return;
1907
+ }
1908
+ if (node && typeof node === "object") {
1909
+ for (const [key, value] of Object.entries(node)) {
1910
+ if (key === "source" && typeof value === "string" && value.startsWith("./")) {
1911
+ sources.add(value.slice(2));
1912
+ } else {
1913
+ collectSchemaSources(value, sources);
1914
+ }
1915
+ }
1916
+ }
1917
+ }
1918
+ function toEntryPath(file) {
1919
+ return file.split(sep).join("/");
1920
+ }
1921
+ function sha256Hex(data) {
1922
+ return createHash("sha256").update(data).digest("hex");
1923
+ }
1924
+ async function readVpkg(vpkgPath) {
1925
+ const staging = await mkdtemp(join(tmpdir(), "vpkg-read-"));
1926
+ try {
1927
+ await tar.extract({ file: vpkgPath, cwd: staging });
1928
+ const manifestYaml = await readFile(join(staging, "manifest.yaml"), "utf8").catch(() => {
1929
+ throw new Error("Invalid package: missing manifest.yaml.");
1930
+ });
1931
+ const packageYaml = await readFile(join(staging, ".verentis", "package.yaml"), "utf8").catch(() => {
1932
+ throw new Error("Invalid package: missing .verentis/package.yaml.");
1933
+ });
1934
+ const digestsJson = await readFile(join(staging, ".verentis", "digests.json"), "utf8").catch(() => {
1935
+ throw new Error("Invalid package: missing .verentis/digests.json.");
1936
+ });
1937
+ const metadata = parse(packageYaml);
1938
+ const declared = JSON.parse(digestsJson);
1939
+ const files2 = [];
1940
+ for await (const entry of glob("files/**/*", { cwd: staging })) {
1941
+ const abs = join(staging, entry);
1942
+ const info = await readFile(abs).catch(() => null);
1943
+ if (info === null) continue;
1944
+ files2.push(entry.split(sep).join("/"));
1945
+ }
1946
+ files2.sort();
1947
+ const digestErrors = [];
1948
+ const entries = declared.entries ?? {};
1949
+ const manifestDigest = createHash("sha256").update(manifestYaml, "utf8").digest("hex");
1950
+ if (entries["manifest.yaml"] && entries["manifest.yaml"].toLowerCase() !== manifestDigest) {
1951
+ digestErrors.push("Digest mismatch for manifest.yaml.");
1952
+ }
1953
+ for (const file of files2) {
1954
+ const declaredDigest = entries[file];
1955
+ if (!declaredDigest) {
1956
+ digestErrors.push(`Entry '${file}' is not covered by digests.json.`);
1957
+ continue;
1958
+ }
1959
+ const actual = createHash("sha256").update(await readFile(join(staging, file))).digest("hex");
1960
+ if (actual !== declaredDigest.toLowerCase()) digestErrors.push(`Digest mismatch for '${file}'.`);
1961
+ }
1962
+ for (const declaredPath of Object.keys(entries)) {
1963
+ if (declaredPath !== "manifest.yaml" && !files2.includes(declaredPath)) {
1964
+ digestErrors.push(`Digest declared for missing entry '${declaredPath}'.`);
1965
+ }
1966
+ }
1967
+ const developerSignature = await readJsonIfExists(join(staging, ".verentis", "signatures", "developer.dsse.json"));
1968
+ const marketplaceSignature = await readJsonIfExists(join(staging, ".verentis", "signatures", "marketplace.dsse.json"));
1969
+ const encryption = await readJsonIfExists(join(staging, ".verentis", "encryption.json"));
1970
+ return {
1971
+ metadata,
1972
+ manifestYaml,
1973
+ digestsJson,
1974
+ treeDigest: createHash("sha256").update(digestsJson, "utf8").digest("hex"),
1975
+ developerSignature: developerSignature ?? void 0,
1976
+ marketplaceSignature: marketplaceSignature ?? void 0,
1977
+ encryption: encryption ?? void 0,
1978
+ files: files2.map((f) => f.replace(/^files\//, "")),
1979
+ digestErrors
1980
+ };
1981
+ } finally {
1982
+ await rm(staging, { recursive: true, force: true });
1983
+ }
1984
+ }
1985
+ async function readJsonIfExists(path) {
1986
+ try {
1987
+ return JSON.parse(await readFile(path, "utf8"));
1988
+ } catch {
1989
+ return null;
1990
+ }
1991
+ }
1992
+
1993
+ // src/commands/pack.ts
1994
+ function registerPack(program2) {
1995
+ program2.command("pack [dir]").description("Build a signed .vpkg from a project directory").option("--manifest <path>", "manifest path (default: auto-detected in the project directory)").option("--publisher <name>", "publisher name (default: packaging.publisher in the manifest)").option("--name <name>", "override the package name").option("--out <dir>", "output directory (default: the project directory)").option("--key <name>", "signing key name (default: the configured default key)").option("--no-sign", "produce an unsigned package").option("--encrypt", "seal the payload (AES-256-GCM); requires at least one recipient").option("--recipient <id=base64key...>", "additional X25519 recipient (repeatable)", collectRecipients, []).action(async (dir, options) => {
1996
+ const projectDir = resolve(dir ?? ".");
1997
+ const manifestPath = options.manifest ? resolve(options.manifest) : await findManifest(projectDir);
1998
+ const manifest = await loadManifest(manifestPath);
1999
+ const publisher = options.publisher ?? manifest.packaging?.publisher;
2000
+ if (!publisher) {
2001
+ throw new Error("No publisher: set packaging.publisher in the manifest or pass --publisher.");
2002
+ }
2003
+ const config = await loadConfig();
2004
+ let signingKey;
2005
+ if (options.sign) {
2006
+ const keyName = options.key ?? config.defaultKey;
2007
+ if (keyName) {
2008
+ signingKey = await loadKey(keyName);
2009
+ } else {
2010
+ console.warn("Warning: no signing key configured \u2014 producing an UNSIGNED package. Run `verentis keygen` to create one.");
2011
+ }
2012
+ }
2013
+ let encryptFor;
2014
+ if (options.encrypt || manifest.packaging?.encryption === "required") {
2015
+ encryptFor = [...options.recipient];
2016
+ const platformKey = process.env.VERENTIS_SEALING_KEY ?? config.sealingPublicKey;
2017
+ if (platformKey && !encryptFor.some((r) => r.id === "platform")) {
2018
+ encryptFor.unshift({ id: "platform", publicKey: platformKey });
2019
+ }
2020
+ if (encryptFor.length === 0) {
2021
+ throw new Error(
2022
+ "Encryption requires at least one recipient. Configure the platform sealing key (`verentis login --sealing-key <base64>` / VERENTIS_SEALING_KEY) or pass --recipient id=key."
2023
+ );
2024
+ }
2025
+ }
2026
+ const result = await pack({
2027
+ manifestPath,
2028
+ publisher,
2029
+ name: options.name,
2030
+ outDir: options.out,
2031
+ signingKey,
2032
+ encryptFor
2033
+ });
2034
+ console.log(`Packed ${result.publisher}/${result.name}@${result.version} (${result.kind})`);
2035
+ console.log(` ${result.outFile}`);
2036
+ console.log(` ${result.files.length} payload file(s), tree digest ${result.treeDigest.slice(0, 16)}\u2026, ${result.signed ? "signed" : "UNSIGNED"}${result.encrypted ? ", ENCRYPTED" : ""}`);
2037
+ });
2038
+ program2.command("seal-keygen").description("Generate an X25519 sealing keypair (for platform escrow or custom recipients)").action(() => {
2039
+ const pair = generateSealingKeyPair();
2040
+ console.log(`public: ${pair.publicKey}`);
2041
+ console.log(`private: ${pair.privateKey}`);
2042
+ console.log("Distribute the PUBLIC key to packers; keep the private key in the consumer (e.g. Node Packaging:SealingPrivateKey).");
2043
+ });
2044
+ program2.command("verify <vpkg>").description("Verify the digests (and, when possible, the developer signature) of a .vpkg").option("--key <name>", "verify the developer signature against a local key").action(async (vpkgPath, options) => {
2045
+ const contents = await readVpkg(resolve(vpkgPath));
2046
+ console.log(`${contents.metadata.publisher}/${contents.metadata.name}@${contents.metadata.version} (${contents.metadata.kind})`);
2047
+ console.log(` files: ${contents.files.length}, tree digest ${contents.treeDigest.slice(0, 16)}\u2026`);
2048
+ if (contents.encryption) {
2049
+ console.log(` \u{1F512} encrypted (${contents.encryption.algorithm}); recipients: ${contents.encryption.recipients.map((r) => r.id).join(", ")}`);
2050
+ }
2051
+ if (contents.digestErrors.length > 0) {
2052
+ for (const error of contents.digestErrors) console.error(` \u2717 ${error}`);
2053
+ process.exitCode = 1;
2054
+ } else {
2055
+ console.log(" \u2713 digests verified");
2056
+ }
2057
+ if (contents.developerSignature) {
2058
+ if (options.key) {
2059
+ const key = await loadKey(options.key);
2060
+ const kid = verifyEnvelope(contents.developerSignature, contents.digestsJson, { [key.kid]: key.publicKey });
2061
+ if (kid) {
2062
+ console.log(` \u2713 developer signature verified (kid ${kid})`);
2063
+ } else {
2064
+ console.error(" \u2717 developer signature does NOT verify with that key");
2065
+ process.exitCode = 1;
2066
+ }
2067
+ } else {
2068
+ console.log(` \u2022 developer signature present (keyid ${contents.developerSignature.signatures[0]?.keyid ?? "?"}) \u2014 pass --key to verify locally`);
2069
+ }
2070
+ } else {
2071
+ console.log(" \u2022 unsigned");
2072
+ }
2073
+ if (contents.marketplaceSignature) console.log(" \u2713 marketplace countersignature present");
2074
+ });
2075
+ }
2076
+ function collectRecipients(value, previous) {
2077
+ const eq = value.indexOf("=");
2078
+ if (eq <= 0) throw new Error(`Invalid --recipient '${value}' \u2014 expected id=base64PublicKey.`);
2079
+ return [...previous, { id: value.slice(0, eq), publicKey: value.slice(eq + 1) }];
2080
+ }
2081
+
2082
+ // src/commands/package.ts
2083
+ function registerPackage(program2) {
2084
+ const pkgCmd = program2.command("package").description("Manage marketplace packages");
2085
+ pkgCmd.command("list").description("List your publisher's packages").option("--json", "output JSON").action(async (options) => {
2086
+ const api = await createApiClient();
2087
+ const publisher = await requireMyPublisher(api);
2088
+ const page = await call(api, marketplace.listPublisherPackages, {
2089
+ params: { publisherId: publisher.id },
2090
+ query: { PageNo: 1, PageSize: 100 }
2091
+ });
2092
+ output(pageItems(page), { name: "Name", kind: "Kind", visibility: "Visibility", latestPublishedVersion: "Latest", id: "Id" }, options, page);
2093
+ });
2094
+ pkgCmd.command("info <ref>").description("Show package details (ref: 'name' or 'publisher/name')").option("--json", "output JSON").action(async (ref, options) => {
2095
+ const api = await createApiClient();
2096
+ const { package: pkg } = await resolvePackageRef(api, ref);
2097
+ const detail = await call(api, marketplace.getPackage, { params: { id: pkg.id } });
2098
+ outputRecord(detail, options);
2099
+ });
2100
+ pkgCmd.command("versions <ref>").description("List versions of a package").option("--json", "output JSON").action(async (ref, options) => {
2101
+ const api = await createApiClient();
2102
+ const { package: pkg } = await resolvePackageRef(api, ref);
2103
+ const page = await call(api, marketplace.listVersions, {
2104
+ params: { packageId: pkg.id },
2105
+ query: { PageNo: 1, PageSize: 100 }
2106
+ });
2107
+ output(pageItems(page), { version: "Version", status: "Status", sizeBytes: "Size", hasMarketplaceSignature: "Countersigned", id: "Id" }, options, page);
2108
+ });
2109
+ pkgCmd.command("update <ref>").description("Update package metadata").option("--display-name <displayName>").option("--description <description>").option("--icon <icon>").option("--categories <category...>", "category list (replaces existing)").action(async (ref, options) => {
2110
+ const api = await createApiClient();
2111
+ const { package: pkg } = await resolvePackageRef(api, ref);
2112
+ const current = await call(api, marketplace.getPackage, { params: { id: pkg.id } });
2113
+ await call(api, marketplace.updatePackage, {
2114
+ params: { id: pkg.id },
2115
+ body: {
2116
+ id: pkg.id,
2117
+ displayName: options.displayName ?? current.displayName,
2118
+ description: options.description ?? current.description ?? null,
2119
+ icon: options.icon ?? current.icon ?? null,
2120
+ categories: options.categories ?? current.categories ?? []
2121
+ }
2122
+ });
2123
+ console.log(`Package ${ref} updated.`);
2124
+ });
2125
+ pkgCmd.command("deprecate <ref>").description("Mark a package as deprecated (optionally with a successor)").option("--message <message>", "deprecation message shown in the catalog").option("--replacement <packageId>", "suggested replacement package id").action(async (ref, options) => {
2126
+ const api = await createApiClient();
2127
+ const { package: pkg } = await resolvePackageRef(api, ref);
2128
+ await call(api, marketplace.deprecatePackage, {
2129
+ params: { id: pkg.id },
2130
+ body: { id: pkg.id, message: options.message ?? null, replacementPackageId: options.replacement ?? null }
2131
+ });
2132
+ console.log(`Package ${ref} deprecated.`);
2133
+ });
2134
+ pkgCmd.command("undeprecate <ref>").description("Remove the deprecation flag from a package").action(async (ref) => {
2135
+ const api = await createApiClient();
2136
+ const { package: pkg } = await resolvePackageRef(api, ref);
2137
+ await call(api, marketplace.undeprecatePackage, { params: { id: pkg.id } });
2138
+ console.log(`Package ${ref} restored.`);
2139
+ });
2140
+ pkgCmd.command("archive <ref>").description("Archive (soft-delete) a package \u2014 it disappears from the catalog").action(async (ref) => {
2141
+ const api = await createApiClient();
2142
+ const { package: pkg } = await resolvePackageRef(api, ref);
2143
+ await call(api, marketplace.archivePackage, { params: { id: pkg.id } });
2144
+ console.log(`Package ${ref} archived.`);
2145
+ });
2146
+ pkgCmd.command("stats <ref>").description("Show download/install statistics for a package").action(async (ref) => {
2147
+ const api = await createApiClient();
2148
+ const { package: pkg } = await resolvePackageRef(api, ref);
2149
+ const stats = await call(api, marketplace.getPackageStats, { params: { packageId: pkg.id } });
2150
+ printJson(stats);
2151
+ });
2152
+ const visibilityCmd = pkgCmd.command("visibility").description("Manage private-package visibility");
2153
+ visibilityCmd.command("set <ref> <visibility>").description("Set catalog visibility: Public | Private").action(async (ref, visibility) => {
2154
+ const api = await createApiClient();
2155
+ const { package: pkg } = await resolvePackageRef(api, ref);
2156
+ await call(api, marketplace.setPackageVisibility, { params: { id: pkg.id }, body: { id: pkg.id, visibility } });
2157
+ console.log(`Package ${ref} visibility \u2192 ${visibility}.`);
2158
+ });
2159
+ visibilityCmd.command("grants <ref>").description("List visibility grants of a private package").option("--json", "output JSON").action(async (ref, options) => {
2160
+ const api = await createApiClient();
2161
+ const { package: pkg } = await resolvePackageRef(api, ref);
2162
+ const grants = await call(api, marketplace.listVisibilityGrants, { params: { packageId: pkg.id } });
2163
+ output(grants ?? [], { granteeType: "Type", granteeId: "Grantee", id: "GrantId" }, options, grants);
2164
+ });
2165
+ visibilityCmd.command("grant <ref> <granteeId>").description("Grant a workspace/account visibility of a private package").option("--type <granteeType>", "Workspace | Account", "Workspace").action(async (ref, granteeId, options) => {
2166
+ const api = await createApiClient();
2167
+ const { package: pkg } = await resolvePackageRef(api, ref);
2168
+ await call(api, marketplace.grantVisibility, {
2169
+ params: { packageId: pkg.id },
2170
+ body: { packageId: pkg.id, granteeType: options.type, granteeId }
2171
+ });
2172
+ console.log(`Visibility of ${ref} granted to ${options.type} ${granteeId}.`);
2173
+ });
2174
+ visibilityCmd.command("revoke <ref> <grantId>").description("Revoke a visibility grant").action(async (ref, grantId) => {
2175
+ const api = await createApiClient();
2176
+ const { package: pkg } = await resolvePackageRef(api, ref);
2177
+ await call(api, marketplace.revokeVisibility, { params: { packageId: pkg.id, grantId } });
2178
+ console.log(`Visibility grant ${grantId} revoked.`);
2179
+ });
2180
+ program2.command("installs").description("List package installations in a workspace").option("--workspace <workspaceId>").option("--page <n>").option("--page-size <n>").option("--json", "output JSON").action(async (options) => {
2181
+ const api = await createApiClient();
2182
+ const page = await call(api, marketplace.listInstallations, {
2183
+ query: { workspaceId: options.workspace, ...pagingQuery(options) }
2184
+ });
2185
+ output(pageItems(page), { packageName: "Package", version: "Version", status: "Status", installPath: "Path", id: "Id" }, options, page);
2186
+ });
2187
+ }
2188
+
2189
+ // src/commands/publisher.ts
2190
+ function registerPublisher(program2) {
2191
+ const publisherCmd = program2.command("publisher").description("Manage your publisher profile");
2192
+ publisherCmd.command("create").description("Onboard your account as a marketplace publisher").requiredOption("--name <name>", "URL-safe publisher name (e.g. acme)").requiredOption("--display-name <displayName>", "human-readable publisher name").option("--description <description>").option("--website <url>").option("--email <email>").action(async (options) => {
2193
+ const api = await createApiClient();
2194
+ const existing = await getMyPublisher(api);
2195
+ if (existing) {
2196
+ console.log(`This account already has publisher '${existing.name}'.`);
2197
+ return;
2198
+ }
2199
+ const id = await api.request("POST", "/v1/publishers", {
2200
+ scopes: [SCOPES.publisherCreate],
2201
+ body: {
2202
+ name: options.name,
2203
+ displayName: options.displayName,
2204
+ description: options.description ?? null,
2205
+ websiteUrl: options.website ?? null,
2206
+ contactEmail: options.email ?? null
2207
+ }
2208
+ });
2209
+ console.log(`Publisher '${options.name}' created (${id}).`);
2210
+ console.log("Next: generate and register a signing key with `verentis keygen`.");
2211
+ });
2212
+ }
2213
+ function registerRegistry(program2) {
2214
+ program2.command("publish <vpkg>").description("Upload a packed .vpkg to the marketplace registry (creates the package on first publish)").option("--visibility <visibility>", "catalog visibility for a newly created package: Public | Private", "Private").action(async (vpkgPath, options) => {
2215
+ const contents = await readVpkg(resolve(vpkgPath));
2216
+ if (contents.digestErrors.length > 0) {
2217
+ throw new Error(`Refusing to publish: package failed digest verification:
2218
+ ${contents.digestErrors.join("\n ")}`);
2219
+ }
2220
+ const api = await createApiClient();
2221
+ const publisher = await requireMyPublisher(api);
2222
+ if (publisher.name !== contents.metadata.publisher) {
2223
+ throw new Error(`Package declares publisher '${contents.metadata.publisher}' but you are '${publisher.name}'.`);
2224
+ }
2225
+ let pkg = await findPackageByName(api, publisher.id, contents.metadata.name);
2226
+ if (!pkg) {
2227
+ const visibility = options.visibility === "Public" ? "Public" : "Private";
2228
+ const id = await createPackage(api, {
2229
+ publisherId: publisher.id,
2230
+ name: contents.metadata.name,
2231
+ displayName: contents.metadata["display-name"] ?? contents.metadata.name,
2232
+ kind: contents.metadata.kind,
2233
+ visibility
2234
+ });
2235
+ console.log(`Created package ${publisher.name}/${contents.metadata.name} (${visibility.toLowerCase()}).`);
2236
+ pkg = { id };
2237
+ }
2238
+ const version = await uploadVersion(api, pkg.id, resolve(vpkgPath));
2239
+ console.log(`Published ${publisher.name}/${contents.metadata.name}@${version.version} \u2192 ${version.status}`);
2240
+ if (version.status === "Submitted") {
2241
+ console.log("The version is queued for moderation; it becomes installable once approved and published.");
2242
+ }
2243
+ });
2244
+ program2.command("yank <ref> <version>").description("Withdraw a published version from the catalog (ref: 'name' or 'publisher/name')").action(async (ref, version) => {
2245
+ const api = await createApiClient();
2246
+ const { package: pkg } = await resolvePackageRef(api, ref);
2247
+ await yankVersion(api, pkg.id, version);
2248
+ console.log(`Yanked ${ref}@${version}.`);
2249
+ });
2250
+ program2.command("download <ref> [version]").description("Download a package tarball (defaults to the latest published version)").option("--out <path>", "output file path").option("--workspace <workspaceId>", "workspace context for visibility checks").action(async (ref, version, options) => {
2251
+ const api = await createApiClient();
2252
+ const { package: pkg } = await resolvePackageRef(api, ref);
2253
+ const effectiveVersion = version ?? pkg.latestPublishedVersion ?? void 0;
2254
+ if (!effectiveVersion) throw new Error(`Package '${ref}' has no published version.`);
2255
+ const res = await downloadTarball(api, pkg.id, effectiveVersion, options.workspace);
2256
+ const outPath = resolve(options.out ?? `${pkg.name}-${effectiveVersion}.vpkg`);
2257
+ if (!res.body) throw new Error("Empty download response.");
2258
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(outPath));
2259
+ console.log(`Downloaded ${ref}@${effectiveVersion} \u2192 ${outPath}`);
2260
+ });
2261
+ program2.command("search [text]").description("Search the marketplace catalog").option("--kind <kind>", "Application | ExecutionEngine | Bundle").action(async (text, options) => {
2262
+ const api = await createApiClient();
2263
+ const page = await api.request("GET", "/v1/packages", {
2264
+ scopes: [SCOPES.packageReadAll],
2265
+ query: { SearchText: text, Kind: options.kind, PageNo: 1, PageSize: 50 }
2266
+ });
2267
+ const items = page.data ?? page.items ?? [];
2268
+ if (items.length === 0) {
2269
+ console.log("No packages found.");
2270
+ return;
2271
+ }
2272
+ for (const pkg of items) {
2273
+ const version = pkg.latestPublishedVersion ? `@${pkg.latestPublishedVersion}` : " (no published version)";
2274
+ console.log(`${pkg.publisherName}/${pkg.name}${version} \u2014 ${pkg.displayName} [${pkg.kind}, ${pkg.visibility.toLowerCase()}]`);
2275
+ }
2276
+ });
2277
+ program2.command("install <ref> [version]").description("Install a package into a workspace").option("--workspace <workspaceId>", "target workspace (default: configured default workspace)").option("--path <path>", "install path override (bundles)").option("--branch <branch>", "workspace branch", "main").option("--sealed", "install as a sealed tarball instead of extracting").action(async (ref, version, options) => {
2278
+ const config = await loadConfig();
2279
+ const workspaceId = options.workspace ?? config.defaultWorkspaceId;
2280
+ if (!workspaceId) throw new Error("No workspace: pass --workspace <id> or set defaultWorkspaceId in ~/.verentis/config.json.");
2281
+ const api = await createApiClient();
2282
+ const { package: pkg } = await resolvePackageRef(api, ref);
2283
+ const installationId = await api.request("POST", "/v1/installations", {
2284
+ // Marketplace forwards this token to Node for the extraction, so it needs
2285
+ // the node scope and must be bound to the target workspace.
2286
+ scopes: [SCOPES.installCreate, "node.node.create"],
2287
+ tokenWorkspaceId: workspaceId,
2288
+ body: {
2289
+ workspaceId,
2290
+ packageId: pkg.id,
2291
+ version: version ?? null,
2292
+ installPath: options.path ?? null,
2293
+ branch: options.branch,
2294
+ installMode: options.sealed ? "Sealed" : "Extracted"
2295
+ }
2296
+ });
2297
+ console.log(`Installation ${installationId} started for ${ref} in workspace ${workspaceId}.`);
2298
+ });
2299
+ program2.command("uninstall <installationId>").description("Uninstall a package from a workspace").requiredOption("--workspace <workspaceId>", "the workspace the installation belongs to").action(async (installationId, options) => {
2300
+ const api = await createApiClient();
2301
+ await api.request("DELETE", `/v1/installations/${installationId}`, {
2302
+ scopes: [SCOPES.installDelete, "node.file.delete"],
2303
+ tokenWorkspaceId: options.workspace,
2304
+ body: { id: installationId, workspaceId: options.workspace }
2305
+ });
2306
+ console.log(`Uninstalled ${installationId}.`);
2307
+ });
2308
+ }
2309
+
2310
+ // src/commands/settings.ts
2311
+ async function settingScope(flags) {
2312
+ const workspaceId = await resolveWorkspaceId(flags, false);
2313
+ if (flags.workspace || !flags.account && workspaceId) {
2314
+ if (!workspaceId) throw new Error("No workspace selected.");
2315
+ return { scopeKind: "Workspace", parentId: workspaceId };
2316
+ }
2317
+ const accountId = await resolveAccountId(flags);
2318
+ return { scopeKind: "Account", parentId: accountId };
2319
+ }
2320
+ function registerSettings(program2) {
2321
+ const settingsCmd = program2.command("settings").description("Manage platform settings (account or workspace scope)");
2322
+ settingsCmd.command("list").description("List settings in the selected scope").option("--account <accountId>", "account scope").option("--workspace <workspaceId>", "workspace scope").option("--secrets", "include secret values").action(async (options) => {
2323
+ const api = await createApiClient();
2324
+ const scope = await settingScope(options);
2325
+ const result = await call(api, settings.list, {
2326
+ query: { ...scope, includeSecretValues: options.secrets ?? false }
2327
+ });
2328
+ printJson(result);
2329
+ });
2330
+ settingsCmd.command("get <key>").description("Read one setting").option("--account <accountId>").option("--workspace <workspaceId>").option("--secrets", "include secret values").action(async (key, options) => {
2331
+ const api = await createApiClient();
2332
+ const scope = await settingScope(options);
2333
+ const result = await call(api, settings.get, {
2334
+ params: { key },
2335
+ query: { ...scope, includeSecretValues: options.secrets ?? false }
2336
+ });
2337
+ printJson(result);
2338
+ });
2339
+ settingsCmd.command("set <key> <value>").description("Create or update a setting (value parsed as JSON when possible)").option("--account <accountId>").option("--workspace <workspaceId>").action(async (key, value, options) => {
2340
+ const api = await createApiClient();
2341
+ const scope = await settingScope(options);
2342
+ let parsed = value;
2343
+ try {
2344
+ parsed = JSON.parse(value);
2345
+ } catch {
2346
+ }
2347
+ await call(api, settings.upsert, {
2348
+ params: { key },
2349
+ query: scope,
2350
+ body: { scopeKind: scope.scopeKind, parentId: scope.parentId, key, value: parsed }
2351
+ });
2352
+ console.log(`Setting ${key} saved (${scope.scopeKind.toLowerCase()} scope).`);
2353
+ });
2354
+ settingsCmd.command("delete <key>").description("Delete a setting").option("--account <accountId>").option("--workspace <workspaceId>").action(async (key, options) => {
2355
+ const api = await createApiClient();
2356
+ const scope = await settingScope(options);
2357
+ await call(api, settings.delete, { params: { key }, query: scope });
2358
+ console.log(`Setting ${key} deleted.`);
2359
+ });
2360
+ }
2361
+
2362
+ // src/commands/use.ts
2363
+ function registerUse(program2) {
2364
+ const useCmd = program2.command("use").description("Set default context for subsequent commands");
2365
+ useCmd.command("workspace <workspaceId>").description("Set the default workspace (files, exec, install, dev commands)").action(async (workspaceId) => {
2366
+ const config = await loadConfig();
2367
+ config.defaultWorkspaceId = workspaceId;
2368
+ await saveConfig(config);
2369
+ console.log(`Default workspace set to ${workspaceId}.`);
2370
+ });
2371
+ useCmd.command("account <accountId>").description("Set the default account (admin commands)").action(async (accountId) => {
2372
+ const config = await loadConfig();
2373
+ config.defaultAccountId = accountId;
2374
+ await saveConfig(config);
2375
+ console.log(`Default account set to ${accountId}.`);
2376
+ });
2377
+ program2.command("context").description("Show the current CLI context (API URL, credential kind, defaults)").option("--json", "output JSON").action(async (options) => {
2378
+ const config = await loadConfig();
2379
+ outputRecord(
2380
+ {
2381
+ apiUrl: process.env.VERENTIS_API_URL ?? config.apiUrl ?? "(not set)",
2382
+ credential: process.env.VERENTIS_API_KEY ?? config.apiKey ? "api key" : process.env.VERENTIS_TOKEN ?? config.identityToken ? "identity token" : "(none)",
2383
+ defaultWorkspaceId: config.defaultWorkspaceId ?? "(not set)",
2384
+ defaultAccountId: config.defaultAccountId ?? "(not set)",
2385
+ defaultKey: config.defaultKey ?? "(not set)"
2386
+ },
2387
+ options
2388
+ );
2389
+ });
2390
+ }
2391
+
2392
+ // src/commands/user.ts
2393
+ function registerUser(program2) {
2394
+ const userCmd = program2.command("user").description("Manage users in an account/workspace scope");
2395
+ userCmd.command("list").description("List users visible in the selected scope").option("--account <accountId>").option("--workspace <workspaceId>").option("--search <text>").option("--active <bool>", "filter by active state (true/false)").option("--json", "output JSON").action(async (options) => {
2396
+ const api = await createApiClient();
2397
+ const scope = await managementScope(options);
2398
+ const result = await call(api, security.listUsers, {
2399
+ query: { ...scope, search: options.search, isActive: options.active }
2400
+ });
2401
+ output(result ?? [], { displayName: "Name", email: "Email", isActive: "Active", userId: "UserId", id: "Id" }, options, result);
2402
+ });
2403
+ userCmd.command("get <userId>").description("Show user details").option("--account <accountId>").option("--workspace <workspaceId>").option("--json", "output JSON").action(async (userId, options) => {
2404
+ const api = await createApiClient();
2405
+ const scope = await managementScope(options);
2406
+ const result = await call(api, security.getUser, { params: { userId }, query: scope });
2407
+ outputRecord(result, options);
2408
+ });
2409
+ userCmd.command("activate <userId>").description("Activate a suspended user in the scope").option("--account <accountId>").option("--workspace <workspaceId>").action(async (userId, options) => {
2410
+ const api = await createApiClient();
2411
+ const scope = await managementScope(options);
2412
+ await call(api, security.activateUser, { params: { userId }, body: { ...scope, userId } });
2413
+ console.log(`User ${userId} activated.`);
2414
+ });
2415
+ userCmd.command("suspend <userId>").description("Suspend a user in the scope").option("--account <accountId>").option("--workspace <workspaceId>").action(async (userId, options) => {
2416
+ const api = await createApiClient();
2417
+ const scope = await managementScope(options);
2418
+ await call(api, security.suspendUser, { params: { userId }, body: { ...scope, userId } });
2419
+ console.log(`User ${userId} suspended.`);
2420
+ });
2421
+ userCmd.command("roles <userId>").description("List a user's role memberships in the scope").option("--account <accountId>").option("--workspace <workspaceId>").option("--json", "output JSON").action(async (userId, options) => {
2422
+ const api = await createApiClient();
2423
+ const scope = await managementScope(options);
2424
+ const result = await call(api, security.getUserRoles, { params: { userId }, query: scope });
2425
+ output(result ?? [], { roleName: "Role", roleKey: "Key", membershipId: "MembershipId" }, options, result);
2426
+ });
2427
+ const grantsCmd = userCmd.command("grants").description("Manage direct permission grants for a user");
2428
+ grantsCmd.command("list <userId>").description("List direct grants for a user").option("--account <accountId>").option("--workspace <workspaceId>").option("--json", "output JSON").action(async (userId, options) => {
2429
+ const api = await createApiClient();
2430
+ const scope = await managementScope(options);
2431
+ const result = await call(api, security.getUserGrants, { params: { userId }, query: scope });
2432
+ output(result ?? [], { scope: "Scope", resourceKind: "ResourceKind", resourceId: "ResourceId", effect: "Effect", id: "GrantId" }, options, result);
2433
+ });
2434
+ grantsCmd.command("add <userId> <scope>").description("Grant a permission scope to a user (e.g. node.file.read)").requiredOption("--resource-id <id>", "resource the grant applies to (account/workspace/node id)").requiredOption("--resource-kind <kind>", "Account | Workspace | Node").option("--expires <iso8601>", "expiry timestamp").option("--account <accountId>").option("--workspace <workspaceId>").action(async (userId, scopeName, options) => {
2435
+ const api = await createApiClient();
2436
+ const scope = await managementScope(options);
2437
+ await call(api, security.grantUserPermission, {
2438
+ params: { userId },
2439
+ body: {
2440
+ ...scope,
2441
+ userId,
2442
+ resourceId: options.resourceId,
2443
+ resourceKind: options.resourceKind,
2444
+ scope: scopeName,
2445
+ expiresAt: options.expires ?? null
2446
+ }
2447
+ });
2448
+ console.log(`Granted ${scopeName} on ${options.resourceKind} ${options.resourceId} to user ${userId}.`);
2449
+ });
2450
+ }
2451
+ function registerValidate(program2) {
2452
+ program2.command("validate [pathOrDir]").description("Validate an engine/app/bundle manifest (structure + registration-time checks)").action(async (pathOrDir) => {
2453
+ const target = resolve(pathOrDir ?? ".");
2454
+ const info = await stat(target).catch(() => null);
2455
+ const manifestPath = info?.isDirectory() ? await findManifest(target) : target;
2456
+ if (!info) throw new Error(`${target} does not exist.`);
2457
+ const manifest = await readManifestDocument(manifestPath);
2458
+ const issues = validateManifest(manifest);
2459
+ const errors = issues.filter((issue) => issue.level === "error");
2460
+ const warnings = issues.filter((issue) => issue.level === "warning");
2461
+ console.log(`${manifestPath} \u2014 kind ${manifest.kind ?? "(unknown)"}, ${manifest.metadata?.name ?? "?"}@${manifest.metadata?.version ?? "?"}`);
2462
+ for (const issue of errors) console.error(` error: ${issue.message}`);
2463
+ for (const issue of warnings) console.warn(` warning: ${issue.message}`);
2464
+ if (errors.length === 0) {
2465
+ console.log(warnings.length > 0 ? `OK with ${warnings.length} warning(s).` : "OK.");
2466
+ } else {
2467
+ console.error(`${errors.length} error(s).`);
2468
+ process.exitCode = 1;
2469
+ }
2470
+ });
2471
+ }
2472
+
2473
+ // src/commands/workspace.ts
2474
+ function registerWorkspace(program2) {
2475
+ const wsCmd = program2.command("workspace").description("Manage workspaces");
2476
+ wsCmd.command("list").description("List workspaces you can access").option("--page <n>").option("--page-size <n>").option("--order-by <field>").option("--json", "output JSON").action(async (options) => {
2477
+ const api = await createApiClient();
2478
+ const page = await call(api, workspace.list, { query: pagingQuery(options) });
2479
+ output(pageItems(page), { name: "Name", slug: "Slug", id: "Id" }, options, page);
2480
+ });
2481
+ wsCmd.command("get [workspaceId]").description("Show workspace details (default: configured default workspace)").option("--json", "output JSON").action(async (workspaceId, options) => {
2482
+ const config = await loadConfig();
2483
+ const id = workspaceId ?? config.defaultWorkspaceId;
2484
+ if (!id) throw new Error("No workspace: pass an id or set one with `verentis use workspace <id>`.");
2485
+ const api = await createApiClient();
2486
+ const result = await call(api, workspace.get, { params: { workspaceId: id } });
2487
+ outputRecord(result, options);
2488
+ });
2489
+ wsCmd.command("create <name>").description("Create a workspace owned by you").option("--use", "set the new workspace as the default").action(async (name, options) => {
2490
+ const api = await createApiClient();
2491
+ const id = await call(api, workspace.createMine, { body: { name } });
2492
+ console.log(`Workspace '${name}' created (${id}).`);
2493
+ if (options.use) {
2494
+ const config = await loadConfig();
2495
+ config.defaultWorkspaceId = id;
2496
+ await saveConfig(config);
2497
+ console.log("Set as default workspace.");
2498
+ }
2499
+ });
2500
+ wsCmd.command("update <workspaceId>").description("Update workspace details").requiredOption("--name <name>", "workspace name").option("--description <description>").action(async (workspaceId, options) => {
2501
+ const api = await createApiClient();
2502
+ await call(api, workspace.update, {
2503
+ params: { workspaceId },
2504
+ body: { workspaceId, name: options.name, description: options.description ?? null }
2505
+ });
2506
+ console.log(`Workspace ${workspaceId} updated.`);
2507
+ });
2508
+ const domainCmd = wsCmd.command("domain").description("Manage workspace domains");
2509
+ domainCmd.command("list <workspaceId>").description("List domains attached to a workspace").option("--json", "output JSON").action(async (workspaceId, options) => {
2510
+ const api = await createApiClient();
2511
+ const result = await call(api, workspace.listDomains, { params: { workspaceId } });
2512
+ const items = pageItems(result);
2513
+ output(items, { host: "Host", status: "Status", id: "Id" }, options, result);
2514
+ });
2515
+ domainCmd.command("add <workspaceId> <host>").description("Attach a domain to a workspace").action(async (workspaceId, host) => {
2516
+ const api = await createApiClient();
2517
+ const id = await call(api, workspace.addDomain, { params: { workspaceId }, body: { workspaceId, host } });
2518
+ console.log(`Domain ${host} added${id ? ` (${id})` : ""}.`);
2519
+ });
2520
+ domainCmd.command("remove <workspaceId> <domainId>").description("Remove a domain from a workspace").action(async (workspaceId, domainId) => {
2521
+ const api = await createApiClient();
2522
+ await call(api, workspace.removeDomain, { params: { workspaceId, domainId } });
2523
+ console.log(`Domain ${domainId} removed.`);
2524
+ });
2525
+ const inviteCmd = wsCmd.command("invite").description("Manage workspace invitations");
2526
+ inviteCmd.command("list <workspaceId>").description("List invitations for a workspace").option("--json", "output JSON").action(async (workspaceId, options) => {
2527
+ const api = await createApiClient();
2528
+ const page = await call(api, workspace.listInvitations, { params: { workspaceId } });
2529
+ output(pageItems(page), { emailAddress: "Email", roleKey: "Role", status: "Status", id: "Id" }, options, page);
2530
+ });
2531
+ inviteCmd.command("create <workspaceId> <email>").description("Invite a user to a workspace").option("--role <roleKey>", "role to assign on acceptance", "member").action(async (workspaceId, email, options) => {
2532
+ const api = await createApiClient();
2533
+ const id = await call(api, workspace.createInvitation, {
2534
+ params: { workspaceId },
2535
+ body: { workspaceId, emailAddress: email, roleKey: options.role }
2536
+ });
2537
+ console.log(`Invitation${id ? ` ${id}` : ""} sent to ${email} (role ${options.role}).`);
2538
+ });
2539
+ inviteCmd.command("resend <workspaceId> <invitationId>").description("Resend a workspace invitation").action(async (workspaceId, invitationId) => {
2540
+ const api = await createApiClient();
2541
+ await call(api, workspace.resendInvitation, { params: { workspaceId, invitationId }, body: { workspaceId, invitationId } });
2542
+ console.log(`Invitation ${invitationId} resent.`);
2543
+ });
2544
+ inviteCmd.command("revoke <workspaceId> <invitationId>").description("Revoke a workspace invitation").action(async (workspaceId, invitationId) => {
2545
+ const api = await createApiClient();
2546
+ await call(api, workspace.revokeInvitation, { params: { workspaceId, invitationId } });
2547
+ console.log(`Invitation ${invitationId} revoked.`);
2548
+ });
2549
+ inviteCmd.command("accept <code>").description("Accept a workspace invitation code as the current user").action(async (code) => {
2550
+ const api = await createApiClient();
2551
+ await call(api, workspace.acceptInvitation, { body: { code } });
2552
+ console.log("Invitation accepted.");
2553
+ });
2554
+ }
2555
+
2556
+ // src/index.ts
2557
+ var program = new Command().name("verentis").description("Verentis CLI \u2014 admin, marketplace publishing, and the engine/app developer loop").version("0.2.0");
2558
+ registerLogin(program);
2559
+ registerUse(program);
2560
+ registerAccount(program);
2561
+ registerWorkspace(program);
2562
+ registerUser(program);
2563
+ registerApiKey(program);
2564
+ registerSettings(program);
2565
+ registerFiles(program);
2566
+ registerExec(program);
2567
+ registerDev(program);
2568
+ registerValidate(program);
2569
+ registerPublisher(program);
2570
+ registerKeygen(program);
2571
+ registerInit(program);
2572
+ registerPack(program);
2573
+ registerRegistry(program);
2574
+ registerPackage(program);
2575
+ registerApi(program);
2576
+ program.parseAsync().catch((error) => {
2577
+ if (error instanceof ApiError || error instanceof Error) {
2578
+ console.error(`Error: ${error.message}`);
2579
+ } else {
2580
+ console.error(error);
2581
+ }
2582
+ process.exitCode = 1;
2583
+ });
2584
+ //# sourceMappingURL=index.js.map
2585
+ //# sourceMappingURL=index.js.map