@alva-ai/toolkit 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -11,6 +11,14 @@ var AlvaError = class extends Error {
11
11
  this.status = status;
12
12
  }
13
13
  };
14
+ var CliUsageError = class extends Error {
15
+ command;
16
+ constructor(message, command) {
17
+ super(message);
18
+ this.name = "CliUsageError";
19
+ this.command = command;
20
+ }
21
+ };
14
22
 
15
23
  // src/resources/fs.ts
16
24
  var FsResource = class {
@@ -200,6 +208,23 @@ var DeployResource = class {
200
208
  `/api/v1/deploy/cronjob/${params.id}/resume`
201
209
  );
202
210
  }
211
+ async listRuns(params) {
212
+ this.client._requireAuth();
213
+ return this.client._request(
214
+ "GET",
215
+ `/api/v1/deploy/cronjob/${params.cronjob_id}/runs`,
216
+ {
217
+ query: { first: params.first, cursor: params.cursor }
218
+ }
219
+ );
220
+ }
221
+ async getRunLogs(params) {
222
+ this.client._requireAuth();
223
+ return this.client._request(
224
+ "GET",
225
+ `/api/v1/deploy/cronjob/${params.cronjob_id}/runs/${params.run_id}/logs`
226
+ );
227
+ }
203
228
  };
204
229
 
205
230
  // src/resources/release.ts
@@ -391,10 +416,88 @@ var UserResource = class {
391
416
  }
392
417
  };
393
418
 
419
+ // src/resources/trading.ts
420
+ var TradingResource = class {
421
+ constructor(client) {
422
+ this.client = client;
423
+ }
424
+ client;
425
+ async accounts() {
426
+ this.client._requireAuth();
427
+ return this.client._request("GET", "/api/v1/trading/accounts");
428
+ }
429
+ async portfolio(accountId) {
430
+ this.client._requireAuth();
431
+ return this.client._request("GET", "/api/v1/trading/portfolio", {
432
+ query: { accountId }
433
+ });
434
+ }
435
+ async orders(params) {
436
+ this.client._requireAuth();
437
+ return this.client._request("GET", "/api/v1/trading/orders", {
438
+ query: {
439
+ accountId: params.accountId,
440
+ source: params.source,
441
+ since: params.since,
442
+ limit: params.limit
443
+ }
444
+ });
445
+ }
446
+ async subscriptions(accountId) {
447
+ this.client._requireAuth();
448
+ return this.client._request("GET", "/api/v1/trading/subscriptions", {
449
+ query: { accountId }
450
+ });
451
+ }
452
+ async equityHistory(params) {
453
+ this.client._requireAuth();
454
+ return this.client._request("GET", "/api/v1/trading/equity-history", {
455
+ query: {
456
+ accountId: params.accountId,
457
+ timeframe: params.timeframe,
458
+ sinceMs: params.sinceMs,
459
+ untilMs: params.untilMs
460
+ }
461
+ });
462
+ }
463
+ async riskRules() {
464
+ this.client._requireAuth();
465
+ return this.client._request(
466
+ "GET",
467
+ "/api/v1/trading/risk-rules"
468
+ );
469
+ }
470
+ async subscribe(params) {
471
+ this.client._requireAuth();
472
+ return this.client._request("POST", "/api/v1/trading/subscribe", {
473
+ body: params
474
+ });
475
+ }
476
+ async unsubscribe(subscriptionId) {
477
+ this.client._requireAuth();
478
+ return this.client._request("POST", "/api/v1/trading/unsubscribe", {
479
+ body: { subscriptionId }
480
+ });
481
+ }
482
+ async execute(params) {
483
+ this.client._requireAuth();
484
+ return this.client._request("POST", "/api/v1/trading/execute", {
485
+ body: params
486
+ });
487
+ }
488
+ async updateRiskRules(rules) {
489
+ this.client._requireAuth();
490
+ return this.client._request("PUT", "/api/v1/trading/risk-rules", {
491
+ body: rules
492
+ });
493
+ }
494
+ };
495
+
394
496
  // src/client.ts
395
497
  var DEFAULT_BASE_URL = "https://api-llm.prd.alva.ai";
396
498
  var AlvaClient = class {
397
499
  baseUrl;
500
+ token;
398
501
  apiKey;
399
502
  _fs;
400
503
  _run;
@@ -406,8 +509,10 @@ var AlvaClient = class {
406
509
  _remix;
407
510
  _screenshot;
408
511
  _user;
512
+ _trading;
409
513
  constructor(config) {
410
514
  this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
515
+ this.token = config.token;
411
516
  this.apiKey = config.apiKey;
412
517
  }
413
518
  get fs() {
@@ -440,11 +545,14 @@ var AlvaClient = class {
440
545
  get user() {
441
546
  return this._user ??= new UserResource(this);
442
547
  }
548
+ get trading() {
549
+ return this._trading ??= new TradingResource(this);
550
+ }
443
551
  _requireAuth() {
444
- if (!this.apiKey) {
552
+ if (!this.token && !this.apiKey) {
445
553
  throw new AlvaError(
446
554
  "UNAUTHENTICATED",
447
- "API key is required for this operation. Pass apiKey in the constructor.",
555
+ "Authentication is required. Pass token or apiKey in the constructor.",
448
556
  401
449
557
  );
450
558
  }
@@ -464,7 +572,9 @@ var AlvaClient = class {
464
572
  }
465
573
  }
466
574
  const headers = {};
467
- if (this.apiKey) {
575
+ if (this.token) {
576
+ headers.Authorization = `${this.token}`;
577
+ } else if (this.apiKey) {
468
578
  headers["X-Alva-Api-Key"] = this.apiKey;
469
579
  }
470
580
  let fetchBody;
@@ -596,16 +706,16 @@ function parseFlag(argv, flag) {
596
706
  return void 0;
597
707
  }
598
708
  function loadConfig(deps) {
599
- const { argv, env, readFile: readFile2, homedir: homedir2 } = deps;
709
+ const { argv, env, readFile: readFile3, homedir: homedir3 } = deps;
600
710
  const profileName = parseFlag(argv, "--profile") || env.ALVA_PROFILE || "default";
601
711
  const baseUrlFlag = parseFlag(argv, "--base-url");
602
712
  const baseUrlEnv = env.ALVA_ENDPOINT;
603
713
  const apiKeyFlag = parseFlag(argv, "--api-key");
604
714
  const apiKeyEnv = env.ALVA_API_KEY;
605
715
  let fileProfile = {};
606
- const path = configPath({ env, homedir: homedir2 });
716
+ const path = configPath({ env, homedir: homedir3 });
607
717
  try {
608
- const raw = readFile2(path);
718
+ const raw = readFile3(path);
609
719
  let config;
610
720
  try {
611
721
  config = readConfigFile(raw);
@@ -625,11 +735,135 @@ function loadConfig(deps) {
625
735
  };
626
736
  }
627
737
 
628
- // src/cli/index.ts
629
- import * as fs from "fs";
738
+ // src/cli/auth.ts
739
+ import * as crypto from "crypto";
740
+ import * as http from "http";
741
+ import { exec } from "child_process";
630
742
  import * as os from "os";
631
743
  import * as fsPromises from "fs/promises";
632
- var CLI_VERSION = true ? "0.1.3" : "dev";
744
+ function generateState() {
745
+ return crypto.randomBytes(32).toString("hex");
746
+ }
747
+ function parseFlags(argv) {
748
+ const flags = {};
749
+ for (let i = 0; i < argv.length; i++) {
750
+ const arg = argv[i];
751
+ if (arg.startsWith("--")) {
752
+ const eqIdx = arg.indexOf("=");
753
+ if (eqIdx !== -1) {
754
+ flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
755
+ } else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
756
+ flags[arg.slice(2)] = argv[i + 1];
757
+ i++;
758
+ }
759
+ }
760
+ }
761
+ return flags;
762
+ }
763
+ function defaultOpenBrowser(url) {
764
+ return new Promise((resolve) => {
765
+ const platform = process.platform;
766
+ let cmd;
767
+ if (platform === "darwin") {
768
+ cmd = `open "${url}"`;
769
+ } else if (platform === "win32") {
770
+ cmd = `start "${url}"`;
771
+ } else {
772
+ cmd = `xdg-open "${url}"`;
773
+ }
774
+ exec(cmd, () => {
775
+ resolve();
776
+ });
777
+ });
778
+ }
779
+ function defaultDeps() {
780
+ return {
781
+ generateState,
782
+ openBrowser: defaultOpenBrowser,
783
+ writeConfigDeps: {
784
+ env: process.env,
785
+ homedir: () => os.homedir(),
786
+ mkdir: (path, options) => fsPromises.mkdir(path, options).then(() => void 0),
787
+ writeFile: (path, data, options) => fsPromises.writeFile(path, data, options).then(() => void 0),
788
+ readFile: (path) => fsPromises.readFile(path, "utf-8")
789
+ },
790
+ createServer: (handler) => http.createServer(handler),
791
+ timeout: 12e4,
792
+ log: (msg) => process.stderr.write(msg)
793
+ };
794
+ }
795
+ async function handleAuthLogin(args, deps) {
796
+ const d = { ...defaultDeps(), ...deps };
797
+ const flags = parseFlags(args.slice(1));
798
+ const profileName = flags.profile || "default";
799
+ const authUrl = flags["auth-url"] || "https://alva.ai";
800
+ const timeout = d.timeout ?? 12e4;
801
+ const state = d.generateState();
802
+ return new Promise((resolve, reject) => {
803
+ const server = d.createServer((req, res) => {
804
+ const reqUrl = new URL(req.url ?? "/", "http://127.0.0.1");
805
+ if (reqUrl.pathname !== "/callback") {
806
+ res.writeHead(404);
807
+ res.end("Not found");
808
+ return;
809
+ }
810
+ const callbackState = reqUrl.searchParams.get("state");
811
+ const apiKey = reqUrl.searchParams.get("api_key");
812
+ if (callbackState !== state) {
813
+ res.writeHead(400);
814
+ res.end(
815
+ "<html><body><h1>Error</h1><p>State mismatch. Please try again.</p></body></html>"
816
+ );
817
+ return;
818
+ }
819
+ if (!apiKey) {
820
+ res.writeHead(400);
821
+ res.end(
822
+ "<html><body><h1>Error</h1><p>Missing API key. Please try again.</p></body></html>"
823
+ );
824
+ return;
825
+ }
826
+ res.writeHead(200, { "Content-Type": "text/html" });
827
+ res.end(
828
+ `<html><body style="display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:rgba(246,246,246,1);font-family:system-ui,sans-serif"><div style="text-align:center;display:flex;flex-direction:column;align-items:center;gap:40px"><h1 style="font-size:45px;font-weight:400;line-height:120%;margin:0">Turn Ideas into Live<br>Investing Playbooks in Minutes</h1><p style="font-size:24px;font-weight:400;margin:0">You're all set for Alva.</p></div></body></html>`
829
+ );
830
+ server.close();
831
+ clearTimeout(timer);
832
+ writeConfig({ apiKey }, d.writeConfigDeps, profileName).then(() => {
833
+ resolve({ status: "logged_in", apiKey, profile: profileName });
834
+ }, reject);
835
+ });
836
+ server.on("error", (err) => {
837
+ clearTimeout(timer);
838
+ reject(err);
839
+ });
840
+ server.listen(0, "127.0.0.1", () => {
841
+ const addr = server.address();
842
+ const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
843
+ const loginUrl = `${authUrl}/authorize?callback_url=${encodeURIComponent(callbackUrl)}&state=${state}`;
844
+ d.log(
845
+ `Opening browser...
846
+ If it doesn't open, visit:
847
+ ${loginUrl}
848
+
849
+ Waiting for login callback...
850
+ `
851
+ );
852
+ d.openBrowser(loginUrl).catch(() => {
853
+ });
854
+ });
855
+ const timer = setTimeout(() => {
856
+ server.close();
857
+ reject(new Error("Login timed out waiting for callback"));
858
+ }, timeout);
859
+ });
860
+ }
861
+
862
+ // src/cli/index.ts
863
+ import * as fs from "fs";
864
+ import * as os2 from "os";
865
+ import * as fsPromises2 from "fs/promises";
866
+ var CLI_VERSION = true ? "0.2.0" : "dev";
633
867
  function isVersionOlderThan(a, b) {
634
868
  const parse = (v) => {
635
869
  if (!v) return null;
@@ -661,6 +895,8 @@ Commands:
661
895
  sdk SDK documentation (doc, partitions, partition-summary)
662
896
  comments Playbook comments (create, pin, unpin)
663
897
  remix Save playbook remix lineage
898
+ trading Trading operations (accounts, portfolio, orders, subscriptions, equity-history, risk-rules, subscribe, unsubscribe, execute, update-risk-rules)
899
+ auth Authentication (login via browser)
664
900
  screenshot Capture a web screenshot as PNG
665
901
 
666
902
  Global options:
@@ -704,6 +940,14 @@ Examples:
704
940
  alva configure --api-key alva_abc123 --base-url http://localhost:8080
705
941
  alva configure --profile staging --api-key alva_stg_key --base-url https://api-llm.stg.alva.ai
706
942
  alva --profile staging whoami`,
943
+ auth: `Usage: alva auth <subcommand>
944
+
945
+ Subcommands:
946
+ login Open browser to authenticate and save credentials
947
+
948
+ Examples:
949
+ alva auth login
950
+ alva auth login --profile staging`,
707
951
  whoami: `Usage: alva whoami [--profile <name>]
708
952
 
709
953
  Verify that your credentials are valid by calling the Alva API. Shows your
@@ -791,11 +1035,13 @@ data SDKs, ALFS, HTTP networking, and the Feed SDK.
791
1035
 
792
1036
  Options:
793
1037
  --code <code> Inline JavaScript code to execute
1038
+ --local-file <path> Path to a local file whose contents are sent as code
794
1039
  --entry-path <path> Path to a script file on ALFS (home-relative)
795
1040
  --working-dir <dir> Working directory for require() (inline code only)
796
1041
  --args <json> JSON object passed to require("env").args
797
1042
 
798
- At least one of --code or --entry-path is required.
1043
+ At least one of --code, --local-file, or --entry-path is required.
1044
+ These three options are mutually exclusive.
799
1045
 
800
1046
  Response fields:
801
1047
  result JSON-encoded return value of the script
@@ -822,7 +1068,8 @@ Examples:
822
1068
  alva run --code "1 + 2 + 3;"
823
1069
  alva run --code "JSON.stringify(require('env').args);" --args '{"symbol":"BTC"}'
824
1070
  alva run --entry-path ~/feeds/my-feed/v1/src/index.js
825
- alva run --entry-path ~/tasks/analyze/src/index.js --args '{"symbol":"NVDA","limit":50}'`,
1071
+ alva run --entry-path ~/tasks/analyze/src/index.js --args '{"symbol":"NVDA","limit":50}'
1072
+ alva run --local-file ./my-script.js --args '{"symbol":"BTC"}'`,
826
1073
  deploy: `Usage: alva deploy <subcommand> [options]
827
1074
 
828
1075
  Manage scheduled cronjobs that run your scripts on a cron schedule.
@@ -871,7 +1118,10 @@ Examples:
871
1118
  alva deploy update --id 42 --cron "0 */2 * * *" --no-push-notify
872
1119
  alva deploy pause --id 42
873
1120
  alva deploy resume --id 42
874
- alva deploy delete --id 42`,
1121
+ alva deploy delete --id 42
1122
+ alva deploy runs --id 42
1123
+ alva deploy runs --id 42 --first 10
1124
+ alva deploy run-logs --id 42 --run-id 123`,
875
1125
  release: `Usage: alva release <subcommand> [options]
876
1126
 
877
1127
  Publish feeds and playbooks to the Alva platform. The typical workflow:
@@ -1032,15 +1282,79 @@ Optional:
1032
1282
 
1033
1283
  Examples:
1034
1284
  alva screenshot --url /playbook/alice/btc-dashboard --out dashboard.png
1035
- alva screenshot --url /playbook/alice/btc-dashboard --out chart.png --selector ".chart-container"`
1285
+ alva screenshot --url /playbook/alice/btc-dashboard --out chart.png --selector ".chart-container"`,
1286
+ trading: `Usage: alva trading <subcommand> [options]
1287
+
1288
+ Manage trading accounts, portfolios, orders, subscriptions, and risk rules.
1289
+
1290
+ Subcommands:
1291
+ accounts List all trading accounts
1292
+ portfolio Get portfolio for an account
1293
+ orders List orders for an account
1294
+ subscriptions List subscriptions for an account
1295
+ equity-history Get equity history for an account
1296
+ risk-rules Show risk rules
1297
+ subscribe Subscribe an account to a source feed
1298
+ unsubscribe Unsubscribe by subscription ID
1299
+ execute Execute a signal on an account
1300
+ update-risk-rules Update risk rules
1301
+
1302
+ Portfolio/Orders/Subscriptions/Equity-history flags:
1303
+ --account-id <id> Trading account ID (required)
1304
+
1305
+ Orders optional flags:
1306
+ --limit <n> Max results
1307
+ --source <source> Filter by source
1308
+ --since <timestamp> Filter orders since timestamp
1309
+
1310
+ Equity-history optional flags:
1311
+ --timeframe <tf> Timeframe (e.g. "1d", "1h")
1312
+ --since-ms <ms> Start timestamp in ms
1313
+ --until-ms <ms> End timestamp in ms
1314
+
1315
+ Subscribe flags:
1316
+ --account-id <id> Account ID (required)
1317
+ --source-username <user> Source username (required)
1318
+ --source-feed <feed> Source feed (required)
1319
+ --playbook-id <id> Playbook ID (required)
1320
+ --playbook-version <ver> Playbook version (required)
1321
+ --execute-latest Execute latest signal on subscribe
1322
+
1323
+ Unsubscribe flags:
1324
+ --subscription-id <id> Subscription ID (required)
1325
+
1326
+ Execute flags:
1327
+ --account-id <id> Account ID (required)
1328
+ --signal <json> Signal JSON (required)
1329
+ --dry-run Dry run mode
1330
+ --source-username <user> Source username (optional)
1331
+ --source-feed <feed> Source feed (optional)
1332
+
1333
+ Update-risk-rules flags:
1334
+ --max-single-order-value <n> Max single order value (required)
1335
+ --max-single-order-enabled <bool> Max single order enabled (required)
1336
+ --max-daily-turnover-value <n> Max daily turnover value (required)
1337
+ --max-daily-turnover-enabled <bool> Max daily turnover enabled (required)
1338
+ --max-daily-orders-value <n> Max daily orders value (required)
1339
+ --max-daily-orders-enabled <bool> Max daily orders enabled (required)
1340
+
1341
+ Examples:
1342
+ alva trading accounts
1343
+ alva trading portfolio --account-id acc_123
1344
+ alva trading orders --account-id acc_123 --limit 10
1345
+ alva trading subscriptions --account-id acc_123
1346
+ alva trading equity-history --account-id acc_123 --timeframe 1d
1347
+ alva trading risk-rules
1348
+ alva trading subscribe --account-id acc_123 --source-username alice --source-feed btc-signals --playbook-id pb_1 --playbook-version v1.0.0
1349
+ alva trading unsubscribe --subscription-id sub_456
1350
+ alva trading execute --account-id acc_123 --signal '{"symbol":"BTC","side":"buy","qty":0.1}' --dry-run
1351
+ alva trading update-risk-rules --max-single-order-value 10000 --max-single-order-enabled true --max-daily-turnover-value 50000 --max-daily-turnover-enabled true --max-daily-orders-value 100 --max-daily-orders-enabled true`
1036
1352
  };
1037
1353
  async function handleConfigure(args, deps) {
1038
- const flags = parseFlags(args.slice(1));
1354
+ const flags = parseFlags2(args.slice(1));
1039
1355
  const apiKey = flags["api-key"];
1040
1356
  if (!apiKey) {
1041
- throw new Error(
1042
- "--api-key is required. Usage: alva configure --api-key <key> [--base-url <url>] [--profile <name>]"
1043
- );
1357
+ throw new CliUsageError("--api-key is required", "configure");
1044
1358
  }
1045
1359
  if (!apiKey.startsWith("alva_")) {
1046
1360
  process.stderr?.write?.(
@@ -1053,10 +1367,10 @@ async function handleConfigure(args, deps) {
1053
1367
  if (baseUrl) configInput.baseUrl = baseUrl;
1054
1368
  const writeDeps = deps ?? {
1055
1369
  env: process.env,
1056
- homedir: () => os.homedir(),
1057
- mkdir: (path, options) => fsPromises.mkdir(path, options).then(() => void 0),
1058
- writeFile: (path, data, options) => fsPromises.writeFile(path, data, options).then(() => void 0),
1059
- readFile: (path) => fsPromises.readFile(path, "utf-8")
1370
+ homedir: () => os2.homedir(),
1371
+ mkdir: (path, options) => fsPromises2.mkdir(path, options).then(() => void 0),
1372
+ writeFile: (path, data, options) => fsPromises2.writeFile(path, data, options).then(() => void 0),
1373
+ readFile: (path) => fsPromises2.readFile(path, "utf-8")
1060
1374
  };
1061
1375
  const result = await writeConfig(configInput, writeDeps, profileName);
1062
1376
  return {
@@ -1070,9 +1384,11 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1070
1384
  "recursive",
1071
1385
  "mkdir-parents",
1072
1386
  "push-notify",
1073
- "help"
1387
+ "help",
1388
+ "execute-latest",
1389
+ "dry-run"
1074
1390
  ]);
1075
- function parseFlags(argv) {
1391
+ function parseFlags2(argv) {
1076
1392
  const flags = {};
1077
1393
  for (let i = 0; i < argv.length; i++) {
1078
1394
  const arg = argv[i];
@@ -1100,7 +1416,8 @@ function boolFlag(val) {
1100
1416
  function requireFlag(flags, name, command) {
1101
1417
  const val = flags[name];
1102
1418
  if (val === void 0) {
1103
- throw new Error(`--${name} is required for '${command}'`);
1419
+ const group = command.split(" ")[0];
1420
+ throw new CliUsageError(`--${name} is required for '${command}'`, group);
1104
1421
  }
1105
1422
  return val;
1106
1423
  }
@@ -1108,8 +1425,10 @@ function requireNumericFlag(flags, name, command) {
1108
1425
  const val = requireFlag(flags, name, command);
1109
1426
  const n = Number(val);
1110
1427
  if (Number.isNaN(n)) {
1111
- throw new Error(
1112
- `--${name} must be a number for '${command}', got '${val}'`
1428
+ const group = command.split(" ")[0];
1429
+ throw new CliUsageError(
1430
+ `--${name} must be a number for '${command}', got '${val}'`,
1431
+ group
1113
1432
  );
1114
1433
  }
1115
1434
  return n;
@@ -1153,7 +1472,7 @@ async function dispatch(client, args, meta) {
1153
1472
  return result;
1154
1473
  }
1155
1474
  const subcommand = args[1];
1156
- const flags = parseFlags(
1475
+ const flags = parseFlags2(
1157
1476
  args.slice(
1158
1477
  group === "run" || group === "remix" || group === "screenshot" ? 1 : 2
1159
1478
  )
@@ -1164,11 +1483,13 @@ async function dispatch(client, args, meta) {
1164
1483
  }
1165
1484
  switch (group) {
1166
1485
  case "user":
1167
- if (!subcommand) throw new Error("Missing subcommand for user");
1486
+ if (!subcommand)
1487
+ throw new CliUsageError("Missing subcommand for user", "user");
1168
1488
  if (subcommand === "me") return client.user.me();
1169
- throw new Error(`Unknown subcommand: user ${subcommand}`);
1489
+ throw new CliUsageError(`Unknown subcommand: user ${subcommand}`, "user");
1170
1490
  case "fs": {
1171
- if (!subcommand) throw new Error("Missing subcommand for fs");
1491
+ if (!subcommand)
1492
+ throw new CliUsageError("Missing subcommand for fs", "fs");
1172
1493
  switch (subcommand) {
1173
1494
  case "read":
1174
1495
  return client.fs.read({
@@ -1245,18 +1566,33 @@ async function dispatch(client, args, meta) {
1245
1566
  permission: requireFlag(flags, "permission", "fs revoke")
1246
1567
  });
1247
1568
  default:
1248
- throw new Error(`Unknown subcommand: fs ${subcommand}`);
1569
+ throw new CliUsageError(`Unknown subcommand: fs ${subcommand}`, "fs");
1249
1570
  }
1250
1571
  }
1251
- case "run":
1572
+ case "run": {
1573
+ const sourceFlags = ["code", "local-file", "entry-path"].filter(
1574
+ (f) => flags[f] !== void 0
1575
+ );
1576
+ if (sourceFlags.length > 1) {
1577
+ throw new CliUsageError(
1578
+ `--${sourceFlags.join(" and --")} are mutually exclusive`,
1579
+ "run"
1580
+ );
1581
+ }
1582
+ let code = flags["code"];
1583
+ if (flags["local-file"]) {
1584
+ code = fs.readFileSync(flags["local-file"], "utf-8");
1585
+ }
1252
1586
  return client.run.execute({
1253
- code: flags["code"],
1587
+ code,
1254
1588
  entry_path: flags["entry-path"],
1255
1589
  working_dir: flags["working-dir"],
1256
1590
  args: jsonParse(flags["args"])
1257
1591
  });
1592
+ }
1258
1593
  case "deploy": {
1259
- if (!subcommand) throw new Error("Missing subcommand for deploy");
1594
+ if (!subcommand)
1595
+ throw new CliUsageError("Missing subcommand for deploy", "deploy");
1260
1596
  switch (subcommand) {
1261
1597
  case "create":
1262
1598
  return client.deploy.create({
@@ -1295,12 +1631,27 @@ async function dispatch(client, args, meta) {
1295
1631
  return client.deploy.resume({
1296
1632
  id: requireNumericFlag(flags, "id", "deploy resume")
1297
1633
  });
1634
+ case "runs":
1635
+ return client.deploy.listRuns({
1636
+ cronjob_id: requireNumericFlag(flags, "id", "deploy runs"),
1637
+ first: num(flags["first"]),
1638
+ cursor: num(flags["cursor"])
1639
+ });
1640
+ case "run-logs":
1641
+ return client.deploy.getRunLogs({
1642
+ cronjob_id: requireNumericFlag(flags, "id", "deploy run-logs"),
1643
+ run_id: requireNumericFlag(flags, "run-id", "deploy run-logs")
1644
+ });
1298
1645
  default:
1299
- throw new Error(`Unknown subcommand: deploy ${subcommand}`);
1646
+ throw new CliUsageError(
1647
+ `Unknown subcommand: deploy ${subcommand}`,
1648
+ "deploy"
1649
+ );
1300
1650
  }
1301
1651
  }
1302
1652
  case "release": {
1303
- if (!subcommand) throw new Error("Missing subcommand for release");
1653
+ if (!subcommand)
1654
+ throw new CliUsageError("Missing subcommand for release", "release");
1304
1655
  switch (subcommand) {
1305
1656
  case "feed":
1306
1657
  return client.release.feed({
@@ -1339,11 +1690,15 @@ async function dispatch(client, args, meta) {
1339
1690
  changelog: requireFlag(flags, "changelog", "release playbook")
1340
1691
  });
1341
1692
  default:
1342
- throw new Error(`Unknown subcommand: release ${subcommand}`);
1693
+ throw new CliUsageError(
1694
+ `Unknown subcommand: release ${subcommand}`,
1695
+ "release"
1696
+ );
1343
1697
  }
1344
1698
  }
1345
1699
  case "secrets": {
1346
- if (!subcommand) throw new Error("Missing subcommand for secrets");
1700
+ if (!subcommand)
1701
+ throw new CliUsageError("Missing subcommand for secrets", "secrets");
1347
1702
  switch (subcommand) {
1348
1703
  case "create":
1349
1704
  return client.secrets.create({
@@ -1366,11 +1721,15 @@ async function dispatch(client, args, meta) {
1366
1721
  name: requireFlag(flags, "name", "secrets delete")
1367
1722
  });
1368
1723
  default:
1369
- throw new Error(`Unknown subcommand: secrets ${subcommand}`);
1724
+ throw new CliUsageError(
1725
+ `Unknown subcommand: secrets ${subcommand}`,
1726
+ "secrets"
1727
+ );
1370
1728
  }
1371
1729
  }
1372
1730
  case "sdk": {
1373
- if (!subcommand) throw new Error("Missing subcommand for sdk");
1731
+ if (!subcommand)
1732
+ throw new CliUsageError("Missing subcommand for sdk", "sdk");
1374
1733
  switch (subcommand) {
1375
1734
  case "doc":
1376
1735
  return client.sdk.doc({
@@ -1383,11 +1742,15 @@ async function dispatch(client, args, meta) {
1383
1742
  partition: requireFlag(flags, "partition", "sdk partition-summary")
1384
1743
  });
1385
1744
  default:
1386
- throw new Error(`Unknown subcommand: sdk ${subcommand}`);
1745
+ throw new CliUsageError(
1746
+ `Unknown subcommand: sdk ${subcommand}`,
1747
+ "sdk"
1748
+ );
1387
1749
  }
1388
1750
  }
1389
1751
  case "comments": {
1390
- if (!subcommand) throw new Error("Missing subcommand for comments");
1752
+ if (!subcommand)
1753
+ throw new CliUsageError("Missing subcommand for comments", "comments");
1391
1754
  switch (subcommand) {
1392
1755
  case "create":
1393
1756
  return client.comments.create({
@@ -1409,7 +1772,10 @@ async function dispatch(client, args, meta) {
1409
1772
  )
1410
1773
  });
1411
1774
  default:
1412
- throw new Error(`Unknown subcommand: comments ${subcommand}`);
1775
+ throw new CliUsageError(
1776
+ `Unknown subcommand: comments ${subcommand}`,
1777
+ "comments"
1778
+ );
1413
1779
  }
1414
1780
  }
1415
1781
  case "remix":
@@ -1431,10 +1797,124 @@ async function dispatch(client, args, meta) {
1431
1797
  fs.writeFileSync(outFile, buf);
1432
1798
  return { written: outFile, bytes: buf.length };
1433
1799
  }
1800
+ case "trading": {
1801
+ if (!subcommand)
1802
+ throw new CliUsageError("Missing subcommand for trading", "trading");
1803
+ switch (subcommand) {
1804
+ case "accounts":
1805
+ return client.trading.accounts();
1806
+ case "portfolio":
1807
+ return client.trading.portfolio(
1808
+ requireFlag(flags, "account-id", "trading portfolio")
1809
+ );
1810
+ case "orders":
1811
+ return client.trading.orders({
1812
+ accountId: requireFlag(flags, "account-id", "trading orders"),
1813
+ source: flags["source"],
1814
+ since: num(flags["since"]),
1815
+ limit: num(flags["limit"])
1816
+ });
1817
+ case "subscriptions":
1818
+ return client.trading.subscriptions(
1819
+ requireFlag(flags, "account-id", "trading subscriptions")
1820
+ );
1821
+ case "equity-history":
1822
+ return client.trading.equityHistory({
1823
+ accountId: requireFlag(
1824
+ flags,
1825
+ "account-id",
1826
+ "trading equity-history"
1827
+ ),
1828
+ timeframe: flags["timeframe"],
1829
+ sinceMs: num(flags["since-ms"]),
1830
+ untilMs: num(flags["until-ms"])
1831
+ });
1832
+ case "risk-rules":
1833
+ return client.trading.riskRules();
1834
+ case "subscribe":
1835
+ return client.trading.subscribe({
1836
+ accountId: requireFlag(flags, "account-id", "trading subscribe"),
1837
+ sourceUsername: requireFlag(
1838
+ flags,
1839
+ "source-username",
1840
+ "trading subscribe"
1841
+ ),
1842
+ sourceFeed: requireFlag(flags, "source-feed", "trading subscribe"),
1843
+ playbookId: requireFlag(flags, "playbook-id", "trading subscribe"),
1844
+ playbookVersion: requireFlag(
1845
+ flags,
1846
+ "playbook-version",
1847
+ "trading subscribe"
1848
+ ),
1849
+ executeLatest: boolFlag(flags["execute-latest"])
1850
+ });
1851
+ case "unsubscribe":
1852
+ return client.trading.unsubscribe(
1853
+ requireFlag(flags, "subscription-id", "trading unsubscribe")
1854
+ );
1855
+ case "execute":
1856
+ return client.trading.execute({
1857
+ accountId: requireFlag(flags, "account-id", "trading execute"),
1858
+ signalJson: requireFlag(flags, "signal", "trading execute"),
1859
+ dryRun: boolFlag(flags["dry-run"]) ?? false,
1860
+ sourceUsername: flags["source-username"],
1861
+ sourceFeed: flags["source-feed"]
1862
+ });
1863
+ case "update-risk-rules":
1864
+ return client.trading.updateRiskRules({
1865
+ maxSingleOrder: {
1866
+ value: requireNumericFlag(
1867
+ flags,
1868
+ "max-single-order-value",
1869
+ "trading update-risk-rules"
1870
+ ),
1871
+ enabled: requireFlag(
1872
+ flags,
1873
+ "max-single-order-enabled",
1874
+ "trading update-risk-rules"
1875
+ ) === "true"
1876
+ },
1877
+ maxDailyTurnover: {
1878
+ value: requireNumericFlag(
1879
+ flags,
1880
+ "max-daily-turnover-value",
1881
+ "trading update-risk-rules"
1882
+ ),
1883
+ enabled: requireFlag(
1884
+ flags,
1885
+ "max-daily-turnover-enabled",
1886
+ "trading update-risk-rules"
1887
+ ) === "true"
1888
+ },
1889
+ maxDailyOrders: {
1890
+ value: requireNumericFlag(
1891
+ flags,
1892
+ "max-daily-orders-value",
1893
+ "trading update-risk-rules"
1894
+ ),
1895
+ enabled: requireFlag(
1896
+ flags,
1897
+ "max-daily-orders-enabled",
1898
+ "trading update-risk-rules"
1899
+ ) === "true"
1900
+ }
1901
+ });
1902
+ default:
1903
+ throw new CliUsageError(
1904
+ `Unknown subcommand: trading ${subcommand}`,
1905
+ "trading"
1906
+ );
1907
+ }
1908
+ }
1909
+ case "auth": {
1910
+ const authSub = args[1];
1911
+ if (!authSub || authSub === "--help" || authSub === "-h" || args[2] === "--help" || args[2] === "-h") {
1912
+ return { _help: true, text: COMMAND_HELP.auth };
1913
+ }
1914
+ throw new CliUsageError(`Unknown auth subcommand: '${authSub}'`, "auth");
1915
+ }
1434
1916
  default:
1435
- throw new Error(
1436
- `Unknown command: '${group}'. Run 'alva --help' to see available commands.`
1437
- );
1917
+ throw new CliUsageError(`Unknown command: '${group}'`);
1438
1918
  }
1439
1919
  }
1440
1920
  async function main() {
@@ -1454,11 +1934,28 @@ async function main() {
1454
1934
  process.stdout.write(JSON.stringify(result2, null, 2) + "\n");
1455
1935
  return;
1456
1936
  }
1937
+ if (rawArgs[0] === "auth") {
1938
+ const authSub = rawArgs[1];
1939
+ if (!authSub || authSub === "--help" || authSub === "-h" || rawArgs[2] === "--help" || rawArgs[2] === "-h") {
1940
+ process.stdout.write(`${COMMAND_HELP.auth}
1941
+ `);
1942
+ return;
1943
+ }
1944
+ if (authSub === "login") {
1945
+ const result2 = await handleAuthLogin(rawArgs);
1946
+ process.stdout.write(`${JSON.stringify(result2, null, 2)}
1947
+ `);
1948
+ return;
1949
+ }
1950
+ process.stdout.write(`${COMMAND_HELP.auth}
1951
+ `);
1952
+ return;
1953
+ }
1457
1954
  const config = loadConfig({
1458
1955
  argv: rawArgs,
1459
1956
  env: process.env,
1460
1957
  readFile: (path) => fs.readFileSync(path, "utf-8"),
1461
- homedir: () => os.homedir()
1958
+ homedir: () => os2.homedir()
1462
1959
  });
1463
1960
  const client = new AlvaClient({
1464
1961
  apiKey: config.apiKey,
@@ -1500,12 +1997,29 @@ async function main() {
1500
1997
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1501
1998
  }
1502
1999
  } catch (err) {
1503
- const error = err instanceof AlvaError ? { code: err.code, message: err.message, status: err.status } : {
1504
- code: "CLI_ERROR",
1505
- message: err instanceof Error ? err.message : String(err)
1506
- };
1507
- process.stderr.write(JSON.stringify({ error }, null, 2) + "\n");
1508
- process.exit(1);
2000
+ if (err instanceof CliUsageError) {
2001
+ const help = err.command ? COMMAND_HELP[err.command] : HELP_TEXT;
2002
+ process.stderr.write(`Error: ${err.message}
2003
+ `);
2004
+ if (help) process.stderr.write(`
2005
+ ${help}
2006
+ `);
2007
+ process.exit(1);
2008
+ } else if (err instanceof AlvaError) {
2009
+ const error = {
2010
+ code: err.code,
2011
+ message: err.message,
2012
+ status: err.status
2013
+ };
2014
+ process.stderr.write(`${JSON.stringify({ error }, null, 2)}
2015
+ `);
2016
+ process.exit(1);
2017
+ } else {
2018
+ const message = err instanceof Error ? err.message : String(err);
2019
+ process.stderr.write(`Error: ${message}
2020
+ `);
2021
+ process.exit(1);
2022
+ }
1509
2023
  }
1510
2024
  }
1511
2025
  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"));