@alva-ai/toolkit 0.1.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/cli.js ADDED
@@ -0,0 +1,1469 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/error.ts
4
+ var AlvaError = class extends Error {
5
+ code;
6
+ status;
7
+ constructor(code, message, status) {
8
+ super(message);
9
+ this.name = "AlvaError";
10
+ this.code = code;
11
+ this.status = status;
12
+ }
13
+ };
14
+
15
+ // src/resources/fs.ts
16
+ var FsResource = class {
17
+ constructor(client) {
18
+ this.client = client;
19
+ }
20
+ client;
21
+ /** Returns `ArrayBuffer` for binary files, or parsed JSON for time-series virtual paths. */
22
+ async read(params) {
23
+ return this.client._request("GET", "/api/v1/fs/read", {
24
+ query: { path: params.path, offset: params.offset, size: params.size }
25
+ });
26
+ }
27
+ /** Write file using JSON body (Mode 2). For text content. */
28
+ async write(params) {
29
+ this.client._requireAuth();
30
+ return this.client._request("POST", "/api/v1/fs/write", {
31
+ body: {
32
+ path: params.path,
33
+ data: params.data,
34
+ mkdir_parents: params.mkdir_parents
35
+ }
36
+ });
37
+ }
38
+ /** Write file using raw body (Mode 1). Supports binary data. Path and options are query params. */
39
+ async rawWrite(params) {
40
+ this.client._requireAuth();
41
+ return this.client._request("POST", "/api/v1/fs/write", {
42
+ query: {
43
+ path: params.path,
44
+ mkdir_parents: params.mkdir_parents
45
+ },
46
+ rawBody: params.body
47
+ });
48
+ }
49
+ async stat(params) {
50
+ this.client._requireAuth();
51
+ return this.client._request("GET", "/api/v1/fs/stat", {
52
+ query: { path: params.path }
53
+ });
54
+ }
55
+ async readdir(params) {
56
+ this.client._requireAuth();
57
+ return this.client._request("GET", "/api/v1/fs/readdir", {
58
+ query: { path: params.path, recursive: params.recursive }
59
+ });
60
+ }
61
+ async mkdir(params) {
62
+ this.client._requireAuth();
63
+ await this.client._request("POST", "/api/v1/fs/mkdir", {
64
+ body: { path: params.path }
65
+ });
66
+ }
67
+ async remove(params) {
68
+ this.client._requireAuth();
69
+ await this.client._request("DELETE", "/api/v1/fs/remove", {
70
+ query: { path: params.path, recursive: params.recursive }
71
+ });
72
+ }
73
+ async rename(params) {
74
+ this.client._requireAuth();
75
+ await this.client._request("POST", "/api/v1/fs/rename", {
76
+ body: { old_path: params.old_path, new_path: params.new_path }
77
+ });
78
+ }
79
+ async copy(params) {
80
+ this.client._requireAuth();
81
+ await this.client._request("POST", "/api/v1/fs/copy", {
82
+ body: { src_path: params.src_path, dst_path: params.dst_path }
83
+ });
84
+ }
85
+ async symlink(params) {
86
+ this.client._requireAuth();
87
+ await this.client._request("POST", "/api/v1/fs/symlink", {
88
+ body: {
89
+ target_path: params.target_path,
90
+ link_path: params.link_path
91
+ }
92
+ });
93
+ }
94
+ async readlink(params) {
95
+ this.client._requireAuth();
96
+ return this.client._request("GET", "/api/v1/fs/readlink", {
97
+ query: { path: params.path }
98
+ });
99
+ }
100
+ async chmod(params) {
101
+ this.client._requireAuth();
102
+ await this.client._request("POST", "/api/v1/fs/chmod", {
103
+ body: { path: params.path, mode: params.mode }
104
+ });
105
+ }
106
+ async grant(params) {
107
+ this.client._requireAuth();
108
+ await this.client._request("POST", "/api/v1/fs/grant", {
109
+ body: {
110
+ path: params.path,
111
+ subject: params.subject,
112
+ permission: params.permission
113
+ }
114
+ });
115
+ }
116
+ async revoke(params) {
117
+ this.client._requireAuth();
118
+ await this.client._request("POST", "/api/v1/fs/revoke", {
119
+ body: {
120
+ path: params.path,
121
+ subject: params.subject,
122
+ permission: params.permission
123
+ }
124
+ });
125
+ }
126
+ };
127
+
128
+ // src/resources/run.ts
129
+ var RunResource = class {
130
+ constructor(client) {
131
+ this.client = client;
132
+ }
133
+ client;
134
+ async execute(params) {
135
+ this.client._requireAuth();
136
+ return this.client._request("POST", "/api/v1/run", {
137
+ body: {
138
+ code: params.code,
139
+ entry_path: params.entry_path,
140
+ working_dir: params.working_dir,
141
+ args: params.args
142
+ }
143
+ });
144
+ }
145
+ };
146
+
147
+ // src/resources/deploy.ts
148
+ var DeployResource = class {
149
+ constructor(client) {
150
+ this.client = client;
151
+ }
152
+ client;
153
+ async create(params) {
154
+ this.client._requireAuth();
155
+ return this.client._request("POST", "/api/v1/deploy/cronjob", {
156
+ body: {
157
+ name: params.name,
158
+ path: params.path,
159
+ cron_expression: params.cron_expression,
160
+ args: params.args,
161
+ push_notify: params.push_notify
162
+ }
163
+ });
164
+ }
165
+ async list(params) {
166
+ this.client._requireAuth();
167
+ return this.client._request("GET", "/api/v1/deploy/cronjobs", {
168
+ query: { limit: params?.limit, cursor: params?.cursor }
169
+ });
170
+ }
171
+ async get(params) {
172
+ this.client._requireAuth();
173
+ return this.client._request(
174
+ "GET",
175
+ `/api/v1/deploy/cronjob/${params.id}`
176
+ );
177
+ }
178
+ async update(params) {
179
+ this.client._requireAuth();
180
+ const { id, ...body } = params;
181
+ return this.client._request("PATCH", `/api/v1/deploy/cronjob/${id}`, {
182
+ body
183
+ });
184
+ }
185
+ async delete(params) {
186
+ this.client._requireAuth();
187
+ await this.client._request("DELETE", `/api/v1/deploy/cronjob/${params.id}`);
188
+ }
189
+ async pause(params) {
190
+ this.client._requireAuth();
191
+ await this.client._request(
192
+ "POST",
193
+ `/api/v1/deploy/cronjob/${params.id}/pause`
194
+ );
195
+ }
196
+ async resume(params) {
197
+ this.client._requireAuth();
198
+ await this.client._request(
199
+ "POST",
200
+ `/api/v1/deploy/cronjob/${params.id}/resume`
201
+ );
202
+ }
203
+ };
204
+
205
+ // src/resources/release.ts
206
+ var ReleaseResource = class {
207
+ constructor(client) {
208
+ this.client = client;
209
+ }
210
+ client;
211
+ async feed(params) {
212
+ this.client._requireAuth();
213
+ return this.client._request("POST", "/api/v1/release/feed", {
214
+ body: {
215
+ name: params.name,
216
+ version: params.version,
217
+ cronjob_id: params.cronjob_id,
218
+ view_json: params.view_json,
219
+ description: params.description
220
+ }
221
+ });
222
+ }
223
+ async playbookDraft(params) {
224
+ this.client._requireAuth();
225
+ return this.client._request("POST", "/api/v1/draft/playbook", {
226
+ body: {
227
+ name: params.name,
228
+ display_name: params.display_name,
229
+ description: params.description,
230
+ feeds: params.feeds,
231
+ trading_symbols: params.trading_symbols
232
+ }
233
+ });
234
+ }
235
+ async playbook(params) {
236
+ this.client._requireAuth();
237
+ return this.client._request("POST", "/api/v1/release/playbook", {
238
+ body: {
239
+ name: params.name,
240
+ version: params.version,
241
+ feeds: params.feeds,
242
+ changelog: params.changelog
243
+ }
244
+ });
245
+ }
246
+ };
247
+
248
+ // src/resources/secrets.ts
249
+ var SecretsResource = class {
250
+ constructor(client) {
251
+ this.client = client;
252
+ }
253
+ client;
254
+ async create(params) {
255
+ this.client._requireAuth();
256
+ await this.client._request("POST", "/api/v1/secrets", {
257
+ body: { name: params.name, value: params.value }
258
+ });
259
+ }
260
+ async list() {
261
+ this.client._requireAuth();
262
+ return this.client._request("GET", "/api/v1/secrets");
263
+ }
264
+ async get(params) {
265
+ this.client._requireAuth();
266
+ const encoded = encodeURIComponent(params.name);
267
+ return this.client._request(
268
+ "GET",
269
+ `/api/v1/secrets/${encoded}`
270
+ );
271
+ }
272
+ async update(params) {
273
+ this.client._requireAuth();
274
+ const encoded = encodeURIComponent(params.name);
275
+ await this.client._request("PUT", `/api/v1/secrets/${encoded}`, {
276
+ body: { value: params.value }
277
+ });
278
+ }
279
+ async delete(params) {
280
+ this.client._requireAuth();
281
+ const encoded = encodeURIComponent(params.name);
282
+ await this.client._request("DELETE", `/api/v1/secrets/${encoded}`);
283
+ }
284
+ };
285
+
286
+ // src/resources/sdkDocs.ts
287
+ var SdkDocsResource = class {
288
+ constructor(client) {
289
+ this.client = client;
290
+ }
291
+ client;
292
+ async doc(params) {
293
+ this.client._requireAuth();
294
+ return this.client._request("GET", "/api/v1/sdk/doc", {
295
+ query: { name: params.name }
296
+ });
297
+ }
298
+ async partitions() {
299
+ this.client._requireAuth();
300
+ return this.client._request(
301
+ "GET",
302
+ "/api/v1/sdk/partitions"
303
+ );
304
+ }
305
+ async partitionSummary(params) {
306
+ this.client._requireAuth();
307
+ const encoded = encodeURIComponent(params.partition);
308
+ return this.client._request(
309
+ "GET",
310
+ `/api/v1/sdk/partitions/${encoded}/summary`
311
+ );
312
+ }
313
+ };
314
+
315
+ // src/resources/comments.ts
316
+ var CommentsResource = class {
317
+ constructor(client) {
318
+ this.client = client;
319
+ }
320
+ client;
321
+ async create(params) {
322
+ this.client._requireAuth();
323
+ return this.client._request("POST", "/api/v1/playbook/comment", {
324
+ body: {
325
+ username: params.username,
326
+ name: params.name,
327
+ content: params.content,
328
+ parent_id: params.parent_id
329
+ }
330
+ });
331
+ }
332
+ async pin(params) {
333
+ this.client._requireAuth();
334
+ return this.client._request("POST", "/api/v1/playbook/comment/pin", {
335
+ body: { comment_id: params.comment_id }
336
+ });
337
+ }
338
+ async unpin(params) {
339
+ this.client._requireAuth();
340
+ return this.client._request("POST", "/api/v1/playbook/comment/unpin", {
341
+ body: { comment_id: params.comment_id }
342
+ });
343
+ }
344
+ };
345
+
346
+ // src/resources/remix.ts
347
+ var RemixResource = class {
348
+ constructor(client) {
349
+ this.client = client;
350
+ }
351
+ client;
352
+ async save(params) {
353
+ this.client._requireAuth();
354
+ await this.client._request("POST", "/api/v1/remix", {
355
+ body: {
356
+ child: params.child,
357
+ parents: params.parents
358
+ }
359
+ });
360
+ }
361
+ };
362
+
363
+ // src/resources/screenshot.ts
364
+ var ScreenshotResource = class {
365
+ constructor(client) {
366
+ this.client = client;
367
+ }
368
+ client;
369
+ async capture(params) {
370
+ this.client._requireAuth();
371
+ return this.client._request("GET", "/api/v1/screenshot", {
372
+ query: {
373
+ url: params.url,
374
+ selector: params.selector,
375
+ xpath: params.xpath
376
+ }
377
+ });
378
+ }
379
+ };
380
+
381
+ // src/resources/user.ts
382
+ var UserResource = class {
383
+ constructor(client) {
384
+ this.client = client;
385
+ }
386
+ client;
387
+ async me() {
388
+ this.client._requireAuth();
389
+ return this.client._request("GET", "/api/v1/me");
390
+ }
391
+ };
392
+
393
+ // src/client.ts
394
+ var DEFAULT_BASE_URL = "https://api-llm.prd.alva.ai";
395
+ var AlvaClient = class {
396
+ baseUrl;
397
+ apiKey;
398
+ _fs;
399
+ _run;
400
+ _deploy;
401
+ _release;
402
+ _secrets;
403
+ _sdk;
404
+ _comments;
405
+ _remix;
406
+ _screenshot;
407
+ _user;
408
+ constructor(config) {
409
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
410
+ this.apiKey = config.apiKey;
411
+ }
412
+ get fs() {
413
+ return this._fs ??= new FsResource(this);
414
+ }
415
+ get run() {
416
+ return this._run ??= new RunResource(this);
417
+ }
418
+ get deploy() {
419
+ return this._deploy ??= new DeployResource(this);
420
+ }
421
+ get release() {
422
+ return this._release ??= new ReleaseResource(this);
423
+ }
424
+ get secrets() {
425
+ return this._secrets ??= new SecretsResource(this);
426
+ }
427
+ get sdk() {
428
+ return this._sdk ??= new SdkDocsResource(this);
429
+ }
430
+ get comments() {
431
+ return this._comments ??= new CommentsResource(this);
432
+ }
433
+ get remix() {
434
+ return this._remix ??= new RemixResource(this);
435
+ }
436
+ get screenshot() {
437
+ return this._screenshot ??= new ScreenshotResource(this);
438
+ }
439
+ get user() {
440
+ return this._user ??= new UserResource(this);
441
+ }
442
+ _requireAuth() {
443
+ if (!this.apiKey) {
444
+ throw new AlvaError(
445
+ "UNAUTHENTICATED",
446
+ "API key is required for this operation. Pass apiKey in the constructor.",
447
+ 401
448
+ );
449
+ }
450
+ }
451
+ async _request(method, path, options) {
452
+ let url = `${this.baseUrl}${path}`;
453
+ if (options?.query) {
454
+ const params = new URLSearchParams();
455
+ for (const [key, value] of Object.entries(options.query)) {
456
+ if (value !== void 0 && value !== null) {
457
+ params.set(key, String(value));
458
+ }
459
+ }
460
+ const qs = params.toString();
461
+ if (qs) {
462
+ url += `?${qs}`;
463
+ }
464
+ }
465
+ const headers = {};
466
+ if (this.apiKey) {
467
+ headers["X-Alva-Api-Key"] = this.apiKey;
468
+ }
469
+ let fetchBody;
470
+ if (options?.rawBody !== void 0) {
471
+ headers["Content-Type"] = "application/octet-stream";
472
+ fetchBody = options.rawBody;
473
+ } else if (options?.body !== void 0) {
474
+ headers["Content-Type"] = "application/json";
475
+ fetchBody = JSON.stringify(options.body);
476
+ }
477
+ let response;
478
+ try {
479
+ response = await fetch(url, {
480
+ method,
481
+ headers,
482
+ body: fetchBody
483
+ });
484
+ } catch (err) {
485
+ throw new AlvaError(
486
+ "NETWORK_ERROR",
487
+ err instanceof Error ? err.message : "Network request failed",
488
+ 0
489
+ );
490
+ }
491
+ if (!response.ok) {
492
+ const bodyText = await response.text().catch(() => "");
493
+ const contentType2 = response.headers.get("content-type") ?? "";
494
+ if (contentType2.includes("application/json") && bodyText) {
495
+ try {
496
+ const data = JSON.parse(bodyText);
497
+ if (data.error) {
498
+ throw new AlvaError(
499
+ data.error.code ?? "UNKNOWN",
500
+ data.error.message ?? `HTTP ${response.status}`,
501
+ response.status
502
+ );
503
+ }
504
+ } catch (e) {
505
+ if (e instanceof AlvaError) throw e;
506
+ }
507
+ }
508
+ throw new AlvaError(
509
+ "UNKNOWN",
510
+ `HTTP ${response.status}: ${bodyText.slice(0, 200)}`,
511
+ response.status
512
+ );
513
+ }
514
+ if (response.status === 204) {
515
+ return void 0;
516
+ }
517
+ const contentType = response.headers.get("content-type") ?? "";
518
+ if (contentType.includes("application/octet-stream") || contentType.includes("image/")) {
519
+ return response.arrayBuffer();
520
+ }
521
+ return response.json();
522
+ }
523
+ };
524
+
525
+ // src/cli/config.ts
526
+ function configPath(deps) {
527
+ const configDir2 = deps.env.XDG_CONFIG_HOME || `${deps.homedir()}/.config`;
528
+ return `${configDir2}/alva/config.json`;
529
+ }
530
+ function configDir(deps) {
531
+ const configRoot = deps.env.XDG_CONFIG_HOME || `${deps.homedir()}/.config`;
532
+ return `${configRoot}/alva`;
533
+ }
534
+ function readConfigFile(raw) {
535
+ const parsed = JSON.parse(raw);
536
+ return parsed;
537
+ }
538
+ function getProfile(config, profileName) {
539
+ if (config.profiles && config.profiles[profileName]) {
540
+ return config.profiles[profileName];
541
+ }
542
+ if (profileName === "default" && config.apiKey) {
543
+ return { apiKey: config.apiKey, baseUrl: config.baseUrl };
544
+ }
545
+ return {};
546
+ }
547
+ async function writeConfig(config, deps, profileName = "default") {
548
+ const path = configPath(deps);
549
+ const dir = configDir(deps);
550
+ let existing = {};
551
+ try {
552
+ const raw = await deps.readFile(path);
553
+ existing = readConfigFile(raw);
554
+ } catch {
555
+ }
556
+ if (!existing.profiles) {
557
+ existing.profiles = {};
558
+ if (existing.apiKey) {
559
+ existing.profiles["default"] = {
560
+ apiKey: existing.apiKey,
561
+ baseUrl: existing.baseUrl
562
+ };
563
+ }
564
+ }
565
+ const profileData = {
566
+ ...existing.profiles[profileName] || {},
567
+ apiKey: config.apiKey
568
+ };
569
+ if (config.baseUrl) {
570
+ profileData.baseUrl = config.baseUrl;
571
+ } else if (!existing.profiles[profileName]?.baseUrl) {
572
+ delete profileData.baseUrl;
573
+ }
574
+ existing.profiles[profileName] = profileData;
575
+ const output = { profiles: existing.profiles };
576
+ await deps.mkdir(dir, { recursive: true });
577
+ await deps.writeFile(path, JSON.stringify(output, null, 2) + "\n", {
578
+ mode: 384
579
+ });
580
+ return {
581
+ apiKey: profileData.apiKey,
582
+ baseUrl: profileData.baseUrl,
583
+ profile: profileName
584
+ };
585
+ }
586
+ function parseFlag(argv, flag) {
587
+ for (let i = 0; i < argv.length; i++) {
588
+ if (argv[i] === flag && i + 1 < argv.length) {
589
+ return argv[i + 1];
590
+ }
591
+ if (argv[i].startsWith(`${flag}=`)) {
592
+ return argv[i].slice(flag.length + 1);
593
+ }
594
+ }
595
+ return void 0;
596
+ }
597
+ function loadConfig(deps) {
598
+ const { argv, env, readFile: readFile2, homedir: homedir2 } = deps;
599
+ const profileName = parseFlag(argv, "--profile") || env.ALVA_PROFILE || "default";
600
+ const baseUrlFlag = parseFlag(argv, "--base-url");
601
+ const baseUrlEnv = env.ALVA_ENDPOINT;
602
+ const apiKeyFlag = parseFlag(argv, "--api-key");
603
+ const apiKeyEnv = env.ALVA_API_KEY;
604
+ let fileProfile = {};
605
+ const path = configPath({ env, homedir: homedir2 });
606
+ try {
607
+ const raw = readFile2(path);
608
+ let config;
609
+ try {
610
+ config = readConfigFile(raw);
611
+ } catch {
612
+ throw new Error(`Failed to parse ${path}: invalid JSON`);
613
+ }
614
+ fileProfile = getProfile(config, profileName);
615
+ } catch (e) {
616
+ if (e instanceof Error && e.message.startsWith("Failed to parse")) {
617
+ throw e;
618
+ }
619
+ }
620
+ return {
621
+ apiKey: apiKeyFlag ?? apiKeyEnv ?? fileProfile.apiKey,
622
+ baseUrl: baseUrlFlag ?? baseUrlEnv ?? fileProfile.baseUrl,
623
+ profile: profileName
624
+ };
625
+ }
626
+
627
+ // src/cli/index.ts
628
+ import * as fs from "fs";
629
+ import * as os from "os";
630
+ import * as fsPromises from "fs/promises";
631
+ var HELP_TEXT = `Usage: alva <command> [options]
632
+
633
+ Commands:
634
+ configure Save API key and endpoint to a named profile
635
+ whoami Verify credentials and show current user info
636
+ user User profile operations
637
+ fs Filesystem operations (read, write, stat, readdir, mkdir, remove, rename, copy, symlink, readlink, chmod, grant, revoke)
638
+ run Execute code in the Alva runtime
639
+ deploy Cronjob management (create, list, get, update, delete, pause, resume)
640
+ release Feed and playbook releases (feed, playbook-draft, playbook)
641
+ secrets Secret management (create, list, get, update, delete)
642
+ sdk SDK documentation (doc, partitions, partition-summary)
643
+ comments Playbook comments (create, pin, unpin)
644
+ remix Save playbook remix lineage
645
+ screenshot Capture a web screenshot as PNG
646
+
647
+ Global options:
648
+ --api-key <key> API key (overrides env and config file)
649
+ --base-url <url> API base URL (overrides env and config file)
650
+ --profile <name> Named profile to use (default: "default")
651
+ --help Show help (use 'alva <command> --help' for command details)
652
+
653
+ Config resolution: --api-key flag > ALVA_API_KEY env > profile in ~/.config/alva/config.json
654
+ Profile resolution: --profile flag > ALVA_PROFILE env > "default"
655
+
656
+ Quick start:
657
+ npm install -g @alva-ai/toolkit
658
+ alva configure --api-key alva_your_key_here
659
+ alva whoami`;
660
+ var COMMAND_HELP = {
661
+ configure: `Usage: alva configure --api-key <key> [--base-url <url>] [--profile <name>]
662
+
663
+ Save API credentials to ~/.config/alva/config.json (mode 0600).
664
+ After configuring, subsequent commands use the saved key automatically.
665
+ Multiple profiles allow switching between environments (production, staging, etc.).
666
+
667
+ Required:
668
+ --api-key <key> Your Alva API key (starts with "alva_")
669
+
670
+ Optional:
671
+ --base-url <url> API base URL (default: https://api-llm.prd.alva.ai)
672
+ --profile <name> Profile name to save under (default: "default")
673
+
674
+ Config file format:
675
+ {
676
+ "profiles": {
677
+ "default": { "apiKey": "alva_...", "baseUrl": "https://api-llm.prd.alva.ai" },
678
+ "staging": { "apiKey": "alva_...", "baseUrl": "https://api-llm.stg.alva.ai" }
679
+ }
680
+ }
681
+
682
+ Examples:
683
+ alva configure --api-key alva_abc123
684
+ alva configure --api-key alva_abc123 --base-url http://localhost:8080
685
+ alva configure --profile staging --api-key alva_stg_key --base-url https://api-llm.stg.alva.ai
686
+ alva --profile staging whoami`,
687
+ whoami: `Usage: alva whoami [--profile <name>]
688
+
689
+ Verify that your credentials are valid by calling the Alva API. Shows your
690
+ username, subscription tier, and which profile/endpoint is being used.
691
+ Use this after 'alva configure' to confirm everything works.
692
+
693
+ Examples:
694
+ alva whoami
695
+ alva --profile staging whoami`,
696
+ user: `Usage: alva user <subcommand>
697
+
698
+ Subcommands:
699
+ me Get the authenticated user's profile
700
+
701
+ Response fields:
702
+ id User ID
703
+ username Username (used in ALFS paths and playbook URLs)
704
+ subscription_tier "free" or "pro" \u2014 determines release flow and feature gates
705
+ telegram_username Telegram username if connected, null otherwise
706
+
707
+ Examples:
708
+ alva user me`,
709
+ fs: `Usage: alva fs <subcommand> [options]
710
+
711
+ Subcommands:
712
+ read Read a file or time series data
713
+ write Write content to a file (use --data for inline, --file for upload)
714
+ stat Get file metadata (name, size, mode, mod_time, is_dir)
715
+ readdir List directory contents
716
+ mkdir Create a directory (recursive by default)
717
+ remove Delete a file or directory
718
+ rename Move/rename a file
719
+ copy Copy a file
720
+ symlink Create a symbolic link
721
+ readlink Read a symlink target
722
+ chmod Change file permissions (mode is octal, e.g. 755)
723
+ grant Grant access permission to a user or group
724
+ revoke Revoke access permission
725
+
726
+ Common flags:
727
+ --path <path> File or directory path (required for most subcommands)
728
+ --recursive Enable recursive operation (readdir, remove)
729
+ --no-recursive Disable recursive operation
730
+ --mkdir-parents Create parent directories on write
731
+
732
+ Path conventions:
733
+ ~/... Home-relative path (expands to /alva/home/<username>/...)
734
+ /alva/home/alice/... Absolute path (required for public/unauthenticated reads)
735
+
736
+ Time series reads:
737
+ Paths under feed data directories support virtual suffixes:
738
+ @last/{n} Last N data points (chronological order)
739
+ @range/{start}..{end} Between timestamps (RFC 3339 or Unix ms)
740
+ @range/{duration} Recent data within duration (e.g. 7d, 1h)
741
+ @count Data point count
742
+ @now Latest single data point
743
+
744
+ Grant/revoke subjects:
745
+ special:user:* Public (anyone, including unauthenticated)
746
+ special:user:+ Any authenticated user
747
+ user:<id> Specific user by ID
748
+
749
+ Examples:
750
+ alva fs readdir --path ~/
751
+ alva fs readdir --path ~/data --recursive
752
+ alva fs read --path ~/data/prices.json
753
+ alva fs read --path ~/feeds/btc-ema/v1/data/metrics/prices/@last/100
754
+ alva fs read --path /alva/home/alice/feeds/btc-ema/v1/data/metrics/prices/@last/10
755
+ alva fs write --path ~/hello.txt --data "Hello, world!"
756
+ alva fs write --path ~/feeds/my-feed/v1/src/index.js --file ./local-script.js --mkdir-parents
757
+ alva fs stat --path ~/hello.txt
758
+ alva fs mkdir --path ~/feeds/my-feed/v1/src
759
+ alva fs remove --path ~/old-folder --recursive
760
+ alva fs rename --old-path ~/a.txt --new-path ~/b.txt
761
+ alva fs copy --src-path ~/a.txt --dst-path ~/b.txt
762
+ alva fs chmod --path ~/script.js --mode 755
763
+ alva fs grant --path ~/feeds/btc-ema --subject "special:user:*" --permission read
764
+ alva fs revoke --path ~/feeds/btc-ema --subject "special:user:*" --permission read`,
765
+ run: `Usage: alva run [options]
766
+
767
+ Execute JavaScript code in the Alva V8 runtime. Provide either inline code
768
+ or a path to a script file on ALFS. Scripts have access to 250+ financial
769
+ data SDKs, ALFS, HTTP networking, and the Feed SDK.
770
+
771
+ Options:
772
+ --code <code> Inline JavaScript code to execute
773
+ --entry-path <path> Path to a script file on ALFS (home-relative)
774
+ --working-dir <dir> Working directory for require() (inline code only)
775
+ --args <json> JSON object passed to require("env").args
776
+
777
+ At least one of --code or --entry-path is required.
778
+
779
+ Response fields:
780
+ result JSON-encoded return value of the script
781
+ logs Captured stderr output
782
+ status "completed" or "failed"
783
+ error Error message (when status is "failed")
784
+
785
+ Available runtime modules:
786
+ require("alfs") Cloud filesystem (absolute paths only)
787
+ require("env") userId, username, args from request
788
+ require("net/http") fetch(url, init) for HTTP requests
789
+ require("secret-manager") Read user-scoped third-party secrets
790
+ require("@alva/feed") Feed SDK for data pipelines
791
+ require("@alva/algorithm") 50+ technical indicators
792
+ require("@alva/adk") Agent SDK for LLM tool calling
793
+ require("@arrays/...") 250+ financial data SDKs
794
+
795
+ Constraints:
796
+ No top-level await \u2014 wrap in (async () => { ... })();
797
+ No Node.js builtins (fs, path, http) \u2014 use alfs, net/http instead
798
+ 2 GB heap limit per execution
799
+
800
+ Examples:
801
+ alva run --code "1 + 2 + 3;"
802
+ alva run --code "JSON.stringify(require('env').args);" --args '{"symbol":"BTC"}'
803
+ alva run --entry-path ~/feeds/my-feed/v1/src/index.js
804
+ alva run --entry-path ~/tasks/analyze/src/index.js --args '{"symbol":"NVDA","limit":50}'`,
805
+ deploy: `Usage: alva deploy <subcommand> [options]
806
+
807
+ Manage scheduled cronjobs that run your scripts on a cron schedule.
808
+ Max 20 cronjobs per user. Min interval: 1 minute.
809
+
810
+ Subcommands:
811
+ create Create a new cronjob
812
+ list List all cronjobs (supports cursor-based pagination)
813
+ get Get a single cronjob by ID
814
+ update Update a cronjob (partial update \u2014 only include changed fields)
815
+ delete Delete a cronjob
816
+ pause Pause a running cronjob
817
+ resume Resume a paused cronjob
818
+
819
+ Create flags:
820
+ --name <name> Cronjob name (required, 1-63 lowercase alphanumeric/hyphens)
821
+ --path <path> Path to script on ALFS (required, must exist)
822
+ --cron <expression> Cron expression (required, e.g. "0 */4 * * *")
823
+ --args <json> JSON object passed to require("env").args
824
+ --push-notify Enable Telegram push notifications on completion
825
+ --no-push-notify Disable push notifications
826
+
827
+ List flags:
828
+ --limit <n> Max results per page (default: 20)
829
+ --cursor <cursor> Pagination cursor from previous response
830
+
831
+ Get/Update/Delete/Pause/Resume flags:
832
+ --id <id> Cronjob ID (required)
833
+
834
+ Name format: 1-63 lowercase alphanumeric or hyphens, no leading/trailing hyphens.
835
+ Valid: btc-ema-update, my-strategy-1
836
+ Invalid: BTC EMA, -my-job-, my_job
837
+
838
+ Recommended cron schedules:
839
+ "0 */4 * * *" Every 4 hours (stock OHLCV, crypto technicals)
840
+ "0 8 * * *" Daily at 8am (fundamentals, insider trades, earnings)
841
+ "*/5 * * * *" Every 5 minutes (high-frequency alerts)
842
+ "0 0 * * *" Daily at midnight (end-of-day summaries)
843
+
844
+ Examples:
845
+ alva deploy create --name btc-ema --path ~/feeds/btc-ema/v1/src/index.js --cron "0 */4 * * *"
846
+ alva deploy create --name alert --path ~/feeds/alert/v1/src/index.js --cron "*/5 * * * *" --push-notify --args '{"threshold":100}'
847
+ alva deploy list
848
+ alva deploy list --limit 10
849
+ alva deploy get --id 42
850
+ alva deploy update --id 42 --cron "0 */2 * * *" --no-push-notify
851
+ alva deploy pause --id 42
852
+ alva deploy resume --id 42
853
+ alva deploy delete --id 42`,
854
+ release: `Usage: alva release <subcommand> [options]
855
+
856
+ Publish feeds and playbooks to the Alva platform. The typical workflow:
857
+ 1. Deploy cronjob (alva deploy create)
858
+ 2. Register feed (alva release feed)
859
+ 3. Create playbook draft (alva release playbook-draft)
860
+ 4. Write HTML to ALFS (alva fs write --path ~/playbooks/{name}/index.html)
861
+ 5. Release playbook (alva release playbook)
862
+
863
+ Subcommands:
864
+ feed Register a feed after deploying its cronjob
865
+ playbook-draft Create a playbook draft (preview before publishing)
866
+ playbook Publish a playbook (public for free users, choice for pro)
867
+
868
+ Feed flags:
869
+ --name <name> Feed name, unique per user (required)
870
+ --version <version> Semantic version, e.g. "1.0.0" (required)
871
+ --cronjob-id <id> ID of the backing cronjob (required)
872
+ --view-json <json> View configuration JSON
873
+ --description <text> Feed description
874
+
875
+ Playbook-draft flags:
876
+ --name <name> URL-safe playbook name, unique per user (required)
877
+ --display-name <name> Human-readable title, max 40 chars (required)
878
+ --feeds <json> JSON array of {feed_id, feed_major?} (required)
879
+ --description <text> Playbook description
880
+ --trading-symbols <json> JSON array of tickers, e.g. '["BTC","ETH"]' (max 50)
881
+
882
+ Playbook flags:
883
+ --name <name> Playbook name, must already exist as draft (required)
884
+ --version <version> Semantic version, e.g. "v1.0.0" (required)
885
+ --feeds <json> JSON array of {feed_id, feed_major?} (required)
886
+ --changelog <text> Release changelog (required)
887
+
888
+ Display name conventions:
889
+ Format: [subject/theme] [analysis angle/strategy logic]
890
+ Max 40 characters. Avoid "My", "Test", or generic-only titles.
891
+ Good: "BTC Trend Dashboard", "NVDA Insider Activity Tracker"
892
+ Bad: "My Dashboard", "Test V2", "Stock Dashboard"
893
+
894
+ Examples:
895
+ alva release feed --name btc-ema --version 1.0.0 --cronjob-id 42
896
+ alva release feed --name nvda-insiders --version 1.0.0 --cronjob-id 43 --description "NVDA insider trading activity"
897
+ alva release playbook-draft --name btc-dashboard --display-name "BTC Trend Dashboard" --feeds '[{"feed_id":100}]' --trading-symbols '["BTC"]'
898
+ alva release playbook --name btc-dashboard --version v1.0.0 --feeds '[{"feed_id":100}]' --changelog "Initial release"`,
899
+ secrets: `Usage: alva secrets <subcommand> [options]
900
+
901
+ Manage encrypted secrets for use in Alva scripts. Secrets are stored
902
+ encrypted at rest and accessible via require("secret-manager") in the runtime.
903
+
904
+ For sensitive secrets (API keys, tokens), prefer the web UI at https://alva.ai/apikey.
905
+ Use the CLI for agent-managed CRUD operations.
906
+
907
+ Subcommands:
908
+ create Create a new secret (fails if name already exists)
909
+ list List all secrets (metadata only: name, keyPrefix, timestamps)
910
+ get Get a secret's plaintext value
911
+ update Update a secret's value (fails if name doesn't exist)
912
+ delete Delete a secret (fails if name doesn't exist)
913
+
914
+ Flags:
915
+ --name <name> Secret name (required for create, get, update, delete)
916
+ --value <value> Secret value (required for create, update)
917
+
918
+ Runtime usage in scripts:
919
+ const secret = require("secret-manager");
920
+ const key = secret.loadPlaintext("OPENAI_API_KEY");
921
+ // Returns string if found, null if missing
922
+
923
+ Examples:
924
+ alva secrets create --name OPENAI_KEY --value sk-abc123
925
+ alva secrets list
926
+ alva secrets get --name OPENAI_KEY
927
+ alva secrets update --name OPENAI_KEY --value sk-new456
928
+ alva secrets delete --name OPENAI_KEY`,
929
+ sdk: `Usage: alva sdk <subcommand> [options]
930
+
931
+ Browse Alva's 250+ financial data SDKs. Use the two-step discovery flow:
932
+ 1. List partitions to find the right category
933
+ 2. Get partition summary to see available modules
934
+ 3. Get full documentation for a specific module
935
+
936
+ Subcommands:
937
+ doc Get documentation for a specific SDK module
938
+ partitions List all available data partitions
939
+ partition-summary Get a summary of modules in a partition
940
+
941
+ Flags:
942
+ --name <module> Module name for 'doc' (required)
943
+ --partition <name> Partition name for 'partition-summary' (required)
944
+
945
+ Key partitions:
946
+ spot_market_price_and_volume Spot OHLCV for crypto and equities
947
+ crypto_futures_data Perpetual futures, funding rates, OI
948
+ crypto_technical_metrics MA, RSI, MACD, MVRV, SOPR, NUPL (20 modules)
949
+ equity_fundamentals Income, balance sheet, PE, ROE (31 modules)
950
+ equity_estimates_and_targets Analyst targets, consensus estimates
951
+ equity_ownership_and_flow Insider trades, senator trading, institutions
952
+ macro_and_economics_data CPI, GDP, Treasury rates, VIX (20 modules)
953
+ technical_indicator_calculation_helpers 50+ pure calculators (RSI, MACD, Bollinger)
954
+
955
+ Examples:
956
+ alva sdk partitions
957
+ alva sdk partition-summary --partition spot_market_price_and_volume
958
+ alva sdk doc --name "@arrays/crypto/ohlcv:v1.0.0"
959
+ alva sdk doc --name "@arrays/data/stock/ohlcv:v1.0.0"`,
960
+ comments: `Usage: alva comments <subcommand> [options]
961
+
962
+ Manage comments on Alva playbooks. Supports top-level comments and threaded
963
+ replies. One comment per playbook can be pinned (pinning a new one unpins
964
+ the previous).
965
+
966
+ Subcommands:
967
+ create Post a comment on a playbook (or reply to an existing comment)
968
+ pin Pin a top-level comment (owner/admin only)
969
+ unpin Unpin a comment (owner/admin only)
970
+
971
+ Create flags:
972
+ --username <user> Playbook owner's username (required)
973
+ --name <name> Playbook name (required)
974
+ --content <text> Comment content (required)
975
+ --parent-id <id> Parent comment ID (for threaded replies, omit for top-level)
976
+
977
+ Pin/Unpin flags:
978
+ --comment-id <id> Comment ID (required)
979
+
980
+ Examples:
981
+ alva comments create --username alice --name btc-dashboard --content "Great analysis!"
982
+ alva comments create --username alice --name btc-dashboard --content "Thanks!" --parent-id 5
983
+ alva comments pin --comment-id 12
984
+ alva comments unpin --comment-id 12`,
985
+ remix: `Usage: alva remix --child-username <u> --child-name <n> --parents <json>
986
+
987
+ Record remix lineage when creating a playbook based on existing playbooks.
988
+ Call this after releasing a remixed playbook to establish the parent-child
989
+ relationship in the database.
990
+
991
+ Required:
992
+ --child-username <username> Your username (the remixer)
993
+ --child-name <name> Your new playbook name
994
+ --parents <json> JSON array of source playbooks: [{"username":"...", "name":"..."}]
995
+
996
+ Examples:
997
+ alva remix --child-username bob --child-name my-btc --parents '[{"username":"alice","name":"btc-signals"}]'`,
998
+ screenshot: `Usage: alva screenshot --url <url> --out <file> [--selector <css>] [--xpath <xpath>]
999
+
1000
+ Capture a screenshot of an Alva page and save it as PNG. Useful for verifying
1001
+ playbook rendering before release.
1002
+
1003
+ Required:
1004
+ --url <url> URL or path to capture (e.g. /playbook/alice/dashboard)
1005
+ --out <file> Local file path to write the PNG output
1006
+
1007
+ Optional:
1008
+ --selector <css> CSS selector to capture a specific element
1009
+ --xpath <xpath> XPath selector to capture a specific element
1010
+
1011
+ Examples:
1012
+ alva screenshot --url /playbook/alice/btc-dashboard --out dashboard.png
1013
+ alva screenshot --url /playbook/alice/btc-dashboard --out chart.png --selector ".chart-container"`
1014
+ };
1015
+ async function handleConfigure(args, deps) {
1016
+ const flags = parseFlags(args.slice(1));
1017
+ const apiKey = flags["api-key"];
1018
+ if (!apiKey) {
1019
+ throw new Error(
1020
+ "--api-key is required. Usage: alva configure --api-key <key> [--base-url <url>] [--profile <name>]"
1021
+ );
1022
+ }
1023
+ if (!apiKey.startsWith("alva_")) {
1024
+ process.stderr?.write?.(
1025
+ 'Warning: API key does not start with "alva_". This may not be a valid Alva API key.\n'
1026
+ );
1027
+ }
1028
+ const baseUrl = flags["base-url"];
1029
+ const profileName = flags["profile"] || "default";
1030
+ const configInput = { apiKey };
1031
+ if (baseUrl) configInput.baseUrl = baseUrl;
1032
+ const writeDeps = deps ?? {
1033
+ env: process.env,
1034
+ homedir: () => os.homedir(),
1035
+ mkdir: (path, options) => fsPromises.mkdir(path, options).then(() => void 0),
1036
+ writeFile: (path, data, options) => fsPromises.writeFile(path, data, options).then(() => void 0),
1037
+ readFile: (path) => fsPromises.readFile(path, "utf-8")
1038
+ };
1039
+ const result = await writeConfig(configInput, writeDeps, profileName);
1040
+ return {
1041
+ status: "configured",
1042
+ apiKey: result.apiKey,
1043
+ baseUrl: result.baseUrl,
1044
+ profile: profileName
1045
+ };
1046
+ }
1047
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1048
+ "recursive",
1049
+ "mkdir-parents",
1050
+ "push-notify",
1051
+ "help"
1052
+ ]);
1053
+ function parseFlags(argv) {
1054
+ const flags = {};
1055
+ for (let i = 0; i < argv.length; i++) {
1056
+ const arg = argv[i];
1057
+ if (arg.startsWith("--no-") && BOOLEAN_FLAGS.has(arg.slice(5))) {
1058
+ flags[arg.slice(5)] = "false";
1059
+ } else if (arg.startsWith("--")) {
1060
+ const eqIdx = arg.indexOf("=");
1061
+ if (eqIdx !== -1) {
1062
+ flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
1063
+ } else if (BOOLEAN_FLAGS.has(arg.slice(2))) {
1064
+ flags[arg.slice(2)] = "true";
1065
+ } else if (i + 1 < argv.length) {
1066
+ flags[arg.slice(2)] = argv[i + 1];
1067
+ i++;
1068
+ }
1069
+ }
1070
+ }
1071
+ return flags;
1072
+ }
1073
+ function boolFlag(val) {
1074
+ if (val === "true") return true;
1075
+ if (val === "false") return false;
1076
+ return void 0;
1077
+ }
1078
+ function requireFlag(flags, name, command) {
1079
+ const val = flags[name];
1080
+ if (val === void 0) {
1081
+ throw new Error(`--${name} is required for '${command}'`);
1082
+ }
1083
+ return val;
1084
+ }
1085
+ function requireNumericFlag(flags, name, command) {
1086
+ const val = requireFlag(flags, name, command);
1087
+ const n = Number(val);
1088
+ if (Number.isNaN(n)) {
1089
+ throw new Error(
1090
+ `--${name} must be a number for '${command}', got '${val}'`
1091
+ );
1092
+ }
1093
+ return n;
1094
+ }
1095
+ function num(val) {
1096
+ if (val === void 0) return void 0;
1097
+ const n = Number(val);
1098
+ return Number.isNaN(n) ? void 0 : n;
1099
+ }
1100
+ function jsonParse(val) {
1101
+ if (val === void 0) return void 0;
1102
+ try {
1103
+ return JSON.parse(val);
1104
+ } catch {
1105
+ return val;
1106
+ }
1107
+ }
1108
+ async function dispatch(client, args, meta) {
1109
+ const group = args[0];
1110
+ if (!group || group === "--help" || group === "-h") {
1111
+ return { _help: true, text: HELP_TEXT };
1112
+ }
1113
+ if (COMMAND_HELP[group] && (args[1] === "--help" || args[1] === "-h")) {
1114
+ return { _help: true, text: COMMAND_HELP[group] };
1115
+ }
1116
+ if (group === "whoami") {
1117
+ const user = await client.user.me();
1118
+ return {
1119
+ ...user,
1120
+ _meta: {
1121
+ profile: meta?.profile ?? "default",
1122
+ endpoint: meta?.baseUrl ?? client.baseUrl
1123
+ }
1124
+ };
1125
+ }
1126
+ const subcommand = args[1];
1127
+ const flags = parseFlags(
1128
+ args.slice(
1129
+ group === "run" || group === "remix" || group === "screenshot" ? 1 : 2
1130
+ )
1131
+ );
1132
+ if (flags["help"] !== void 0) {
1133
+ const helpText = COMMAND_HELP[group];
1134
+ if (helpText) return { _help: true, text: helpText };
1135
+ }
1136
+ switch (group) {
1137
+ case "user":
1138
+ if (!subcommand) throw new Error("Missing subcommand for user");
1139
+ if (subcommand === "me") return client.user.me();
1140
+ throw new Error(`Unknown subcommand: user ${subcommand}`);
1141
+ case "fs": {
1142
+ if (!subcommand) throw new Error("Missing subcommand for fs");
1143
+ switch (subcommand) {
1144
+ case "read":
1145
+ return client.fs.read({
1146
+ path: requireFlag(flags, "path", "fs read"),
1147
+ offset: num(flags["offset"]),
1148
+ size: num(flags["size"])
1149
+ });
1150
+ case "write":
1151
+ if (flags["file"]) {
1152
+ const fileData = fs.readFileSync(flags["file"]);
1153
+ return client.fs.rawWrite({
1154
+ path: requireFlag(flags, "path", "fs write"),
1155
+ body: fileData,
1156
+ mkdir_parents: boolFlag(flags["mkdir-parents"])
1157
+ });
1158
+ }
1159
+ return client.fs.write({
1160
+ path: requireFlag(flags, "path", "fs write"),
1161
+ data: requireFlag(flags, "data", "fs write"),
1162
+ mkdir_parents: boolFlag(flags["mkdir-parents"])
1163
+ });
1164
+ case "stat":
1165
+ return client.fs.stat({
1166
+ path: requireFlag(flags, "path", "fs stat")
1167
+ });
1168
+ case "readdir":
1169
+ return client.fs.readdir({
1170
+ path: requireFlag(flags, "path", "fs readdir"),
1171
+ recursive: boolFlag(flags["recursive"])
1172
+ });
1173
+ case "mkdir":
1174
+ return client.fs.mkdir({
1175
+ path: requireFlag(flags, "path", "fs mkdir")
1176
+ });
1177
+ case "remove":
1178
+ return client.fs.remove({
1179
+ path: requireFlag(flags, "path", "fs remove"),
1180
+ recursive: boolFlag(flags["recursive"])
1181
+ });
1182
+ case "rename":
1183
+ return client.fs.rename({
1184
+ old_path: requireFlag(flags, "old-path", "fs rename"),
1185
+ new_path: requireFlag(flags, "new-path", "fs rename")
1186
+ });
1187
+ case "copy":
1188
+ return client.fs.copy({
1189
+ src_path: requireFlag(flags, "src-path", "fs copy"),
1190
+ dst_path: requireFlag(flags, "dst-path", "fs copy")
1191
+ });
1192
+ case "symlink":
1193
+ return client.fs.symlink({
1194
+ target_path: requireFlag(flags, "target-path", "fs symlink"),
1195
+ link_path: requireFlag(flags, "link-path", "fs symlink")
1196
+ });
1197
+ case "readlink":
1198
+ return client.fs.readlink({
1199
+ path: requireFlag(flags, "path", "fs readlink")
1200
+ });
1201
+ case "chmod":
1202
+ return client.fs.chmod({
1203
+ path: requireFlag(flags, "path", "fs chmod"),
1204
+ mode: parseInt(requireFlag(flags, "mode", "fs chmod"), 8)
1205
+ });
1206
+ case "grant":
1207
+ return client.fs.grant({
1208
+ path: requireFlag(flags, "path", "fs grant"),
1209
+ subject: requireFlag(flags, "subject", "fs grant"),
1210
+ permission: requireFlag(flags, "permission", "fs grant")
1211
+ });
1212
+ case "revoke":
1213
+ return client.fs.revoke({
1214
+ path: requireFlag(flags, "path", "fs revoke"),
1215
+ subject: requireFlag(flags, "subject", "fs revoke"),
1216
+ permission: requireFlag(flags, "permission", "fs revoke")
1217
+ });
1218
+ default:
1219
+ throw new Error(`Unknown subcommand: fs ${subcommand}`);
1220
+ }
1221
+ }
1222
+ case "run":
1223
+ return client.run.execute({
1224
+ code: flags["code"],
1225
+ entry_path: flags["entry-path"],
1226
+ working_dir: flags["working-dir"],
1227
+ args: jsonParse(flags["args"])
1228
+ });
1229
+ case "deploy": {
1230
+ if (!subcommand) throw new Error("Missing subcommand for deploy");
1231
+ switch (subcommand) {
1232
+ case "create":
1233
+ return client.deploy.create({
1234
+ name: requireFlag(flags, "name", "deploy create"),
1235
+ path: requireFlag(flags, "path", "deploy create"),
1236
+ cron_expression: requireFlag(flags, "cron", "deploy create"),
1237
+ args: jsonParse(flags["args"]),
1238
+ push_notify: boolFlag(flags["push-notify"])
1239
+ });
1240
+ case "list":
1241
+ return client.deploy.list({
1242
+ limit: num(flags["limit"]),
1243
+ cursor: flags["cursor"]
1244
+ });
1245
+ case "get":
1246
+ return client.deploy.get({
1247
+ id: requireNumericFlag(flags, "id", "deploy get")
1248
+ });
1249
+ case "update":
1250
+ return client.deploy.update({
1251
+ id: requireNumericFlag(flags, "id", "deploy update"),
1252
+ name: flags["name"],
1253
+ cron_expression: flags["cron"],
1254
+ args: jsonParse(flags["args"]),
1255
+ push_notify: boolFlag(flags["push-notify"])
1256
+ });
1257
+ case "delete":
1258
+ return client.deploy.delete({
1259
+ id: requireNumericFlag(flags, "id", "deploy delete")
1260
+ });
1261
+ case "pause":
1262
+ return client.deploy.pause({
1263
+ id: requireNumericFlag(flags, "id", "deploy pause")
1264
+ });
1265
+ case "resume":
1266
+ return client.deploy.resume({
1267
+ id: requireNumericFlag(flags, "id", "deploy resume")
1268
+ });
1269
+ default:
1270
+ throw new Error(`Unknown subcommand: deploy ${subcommand}`);
1271
+ }
1272
+ }
1273
+ case "release": {
1274
+ if (!subcommand) throw new Error("Missing subcommand for release");
1275
+ switch (subcommand) {
1276
+ case "feed":
1277
+ return client.release.feed({
1278
+ name: requireFlag(flags, "name", "release feed"),
1279
+ version: requireFlag(flags, "version", "release feed"),
1280
+ cronjob_id: requireNumericFlag(flags, "cronjob-id", "release feed"),
1281
+ view_json: jsonParse(flags["view-json"]),
1282
+ description: flags["description"]
1283
+ });
1284
+ case "playbook-draft":
1285
+ return client.release.playbookDraft({
1286
+ name: requireFlag(flags, "name", "release playbook-draft"),
1287
+ display_name: requireFlag(
1288
+ flags,
1289
+ "display-name",
1290
+ "release playbook-draft"
1291
+ ),
1292
+ description: flags["description"],
1293
+ feeds: jsonParse(
1294
+ requireFlag(flags, "feeds", "release playbook-draft")
1295
+ ),
1296
+ trading_symbols: flags["trading-symbols"] ? jsonParse(flags["trading-symbols"]) : void 0
1297
+ });
1298
+ case "playbook":
1299
+ return client.release.playbook({
1300
+ name: requireFlag(flags, "name", "release playbook"),
1301
+ version: requireFlag(flags, "version", "release playbook"),
1302
+ feeds: jsonParse(
1303
+ requireFlag(flags, "feeds", "release playbook")
1304
+ ),
1305
+ changelog: requireFlag(flags, "changelog", "release playbook")
1306
+ });
1307
+ default:
1308
+ throw new Error(`Unknown subcommand: release ${subcommand}`);
1309
+ }
1310
+ }
1311
+ case "secrets": {
1312
+ if (!subcommand) throw new Error("Missing subcommand for secrets");
1313
+ switch (subcommand) {
1314
+ case "create":
1315
+ return client.secrets.create({
1316
+ name: requireFlag(flags, "name", "secrets create"),
1317
+ value: requireFlag(flags, "value", "secrets create")
1318
+ });
1319
+ case "list":
1320
+ return client.secrets.list();
1321
+ case "get":
1322
+ return client.secrets.get({
1323
+ name: requireFlag(flags, "name", "secrets get")
1324
+ });
1325
+ case "update":
1326
+ return client.secrets.update({
1327
+ name: requireFlag(flags, "name", "secrets update"),
1328
+ value: requireFlag(flags, "value", "secrets update")
1329
+ });
1330
+ case "delete":
1331
+ return client.secrets.delete({
1332
+ name: requireFlag(flags, "name", "secrets delete")
1333
+ });
1334
+ default:
1335
+ throw new Error(`Unknown subcommand: secrets ${subcommand}`);
1336
+ }
1337
+ }
1338
+ case "sdk": {
1339
+ if (!subcommand) throw new Error("Missing subcommand for sdk");
1340
+ switch (subcommand) {
1341
+ case "doc":
1342
+ return client.sdk.doc({
1343
+ name: requireFlag(flags, "name", "sdk doc")
1344
+ });
1345
+ case "partitions":
1346
+ return client.sdk.partitions();
1347
+ case "partition-summary":
1348
+ return client.sdk.partitionSummary({
1349
+ partition: requireFlag(flags, "partition", "sdk partition-summary")
1350
+ });
1351
+ default:
1352
+ throw new Error(`Unknown subcommand: sdk ${subcommand}`);
1353
+ }
1354
+ }
1355
+ case "comments": {
1356
+ if (!subcommand) throw new Error("Missing subcommand for comments");
1357
+ switch (subcommand) {
1358
+ case "create":
1359
+ return client.comments.create({
1360
+ username: requireFlag(flags, "username", "comments create"),
1361
+ name: requireFlag(flags, "name", "comments create"),
1362
+ content: requireFlag(flags, "content", "comments create"),
1363
+ parent_id: num(flags["parent-id"])
1364
+ });
1365
+ case "pin":
1366
+ return client.comments.pin({
1367
+ comment_id: requireNumericFlag(flags, "comment-id", "comments pin")
1368
+ });
1369
+ case "unpin":
1370
+ return client.comments.unpin({
1371
+ comment_id: requireNumericFlag(
1372
+ flags,
1373
+ "comment-id",
1374
+ "comments unpin"
1375
+ )
1376
+ });
1377
+ default:
1378
+ throw new Error(`Unknown subcommand: comments ${subcommand}`);
1379
+ }
1380
+ }
1381
+ case "remix":
1382
+ return client.remix.save({
1383
+ child: {
1384
+ username: requireFlag(flags, "child-username", "remix"),
1385
+ name: requireFlag(flags, "child-name", "remix")
1386
+ },
1387
+ parents: jsonParse(requireFlag(flags, "parents", "remix"))
1388
+ });
1389
+ case "screenshot": {
1390
+ const outFile = requireFlag(flags, "out", "screenshot");
1391
+ const result = await client.screenshot.capture({
1392
+ url: requireFlag(flags, "url", "screenshot"),
1393
+ selector: flags["selector"],
1394
+ xpath: flags["xpath"]
1395
+ });
1396
+ const buf = Buffer.from(result);
1397
+ fs.writeFileSync(outFile, buf);
1398
+ return { written: outFile, bytes: buf.length };
1399
+ }
1400
+ default:
1401
+ throw new Error(
1402
+ `Unknown command: '${group}'. Run 'alva --help' to see available commands.`
1403
+ );
1404
+ }
1405
+ }
1406
+ async function main() {
1407
+ try {
1408
+ const rawArgs = process.argv.slice(2);
1409
+ if (rawArgs[0] === "configure") {
1410
+ if (rawArgs[1] === "--help" || rawArgs[1] === "-h") {
1411
+ process.stdout.write(COMMAND_HELP["configure"] + "\n");
1412
+ return;
1413
+ }
1414
+ const result2 = await handleConfigure(rawArgs);
1415
+ process.stdout.write(JSON.stringify(result2, null, 2) + "\n");
1416
+ return;
1417
+ }
1418
+ const config = loadConfig({
1419
+ argv: rawArgs,
1420
+ env: process.env,
1421
+ readFile: (path) => fs.readFileSync(path, "utf-8"),
1422
+ homedir: () => os.homedir()
1423
+ });
1424
+ const client = new AlvaClient({
1425
+ apiKey: config.apiKey,
1426
+ baseUrl: config.baseUrl
1427
+ });
1428
+ const cleanArgs = [];
1429
+ for (let i = 0; i < rawArgs.length; i++) {
1430
+ const a = rawArgs[i];
1431
+ if (a === "--api-key" || a === "--base-url" || a === "--profile") {
1432
+ i++;
1433
+ continue;
1434
+ }
1435
+ if (a.startsWith("--api-key=") || a.startsWith("--base-url=") || a.startsWith("--profile=")) {
1436
+ continue;
1437
+ }
1438
+ cleanArgs.push(a);
1439
+ }
1440
+ const result = await dispatch(client, cleanArgs, {
1441
+ profile: config.profile,
1442
+ baseUrl: config.baseUrl
1443
+ });
1444
+ if (result && typeof result === "object" && "_help" in result) {
1445
+ const helpResult = result;
1446
+ process.stdout.write(helpResult.text + "\n");
1447
+ return;
1448
+ }
1449
+ if (result !== void 0) {
1450
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1451
+ }
1452
+ } catch (err) {
1453
+ const error = err instanceof AlvaError ? { code: err.code, message: err.message, status: err.status } : {
1454
+ code: "CLI_ERROR",
1455
+ message: err instanceof Error ? err.message : String(err)
1456
+ };
1457
+ process.stderr.write(JSON.stringify({ error }, null, 2) + "\n");
1458
+ process.exit(1);
1459
+ }
1460
+ }
1461
+ var isDirectRun = typeof process !== "undefined" && process.argv[1] && (process.argv[1].endsWith("cli.mjs") || process.argv[1].endsWith("cli.js") || process.argv[1].endsWith("/alva") || process.argv[1].endsWith("\\alva"));
1462
+ if (isDirectRun) {
1463
+ main();
1464
+ }
1465
+ export {
1466
+ dispatch,
1467
+ handleConfigure
1468
+ };
1469
+ //# sourceMappingURL=cli.js.map