@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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/browser.global.js +2 -0
- package/dist/browser.global.js.map +1 -0
- package/dist/cli.js +1469 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +554 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +402 -0
- package/dist/index.d.ts +402 -0
- package/dist/index.js +526 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
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
|