@alva-ai/toolkit 0.1.4 → 0.2.1

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
@@ -472,7 +497,7 @@ var TradingResource = class {
472
497
  var DEFAULT_BASE_URL = "https://api-llm.prd.alva.ai";
473
498
  var AlvaClient = class {
474
499
  baseUrl;
475
- token;
500
+ viewer_token;
476
501
  apiKey;
477
502
  _fs;
478
503
  _run;
@@ -487,7 +512,7 @@ var AlvaClient = class {
487
512
  _trading;
488
513
  constructor(config) {
489
514
  this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
490
- this.token = config.token;
515
+ this.viewer_token = config.viewer_token;
491
516
  this.apiKey = config.apiKey;
492
517
  }
493
518
  get fs() {
@@ -524,10 +549,10 @@ var AlvaClient = class {
524
549
  return this._trading ??= new TradingResource(this);
525
550
  }
526
551
  _requireAuth() {
527
- if (!this.token && !this.apiKey) {
552
+ if (!this.viewer_token && !this.apiKey) {
528
553
  throw new AlvaError(
529
554
  "UNAUTHENTICATED",
530
- "Authentication is required. Pass token or apiKey in the constructor.",
555
+ "Authentication is required. Pass viewer_token or apiKey in the constructor.",
531
556
  401
532
557
  );
533
558
  }
@@ -547,8 +572,8 @@ var AlvaClient = class {
547
572
  }
548
573
  }
549
574
  const headers = {};
550
- if (this.token) {
551
- headers.Authorization = `${this.token}`;
575
+ if (this.viewer_token) {
576
+ headers["x-Playbook-Viewer"] = this.viewer_token;
552
577
  } else if (this.apiKey) {
553
578
  headers["X-Alva-Api-Key"] = this.apiKey;
554
579
  }
@@ -681,16 +706,16 @@ function parseFlag(argv, flag) {
681
706
  return void 0;
682
707
  }
683
708
  function loadConfig(deps) {
684
- const { argv, env, readFile: readFile2, homedir: homedir2 } = deps;
709
+ const { argv, env, readFile: readFile3, homedir: homedir3 } = deps;
685
710
  const profileName = parseFlag(argv, "--profile") || env.ALVA_PROFILE || "default";
686
711
  const baseUrlFlag = parseFlag(argv, "--base-url");
687
712
  const baseUrlEnv = env.ALVA_ENDPOINT;
688
713
  const apiKeyFlag = parseFlag(argv, "--api-key");
689
714
  const apiKeyEnv = env.ALVA_API_KEY;
690
715
  let fileProfile = {};
691
- const path = configPath({ env, homedir: homedir2 });
716
+ const path = configPath({ env, homedir: homedir3 });
692
717
  try {
693
- const raw = readFile2(path);
718
+ const raw = readFile3(path);
694
719
  let config;
695
720
  try {
696
721
  config = readConfigFile(raw);
@@ -710,11 +735,135 @@ function loadConfig(deps) {
710
735
  };
711
736
  }
712
737
 
713
- // src/cli/index.ts
714
- 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";
715
742
  import * as os from "os";
716
743
  import * as fsPromises from "fs/promises";
717
- var CLI_VERSION = true ? "0.1.4" : "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.1" : "dev";
718
867
  function isVersionOlderThan(a, b) {
719
868
  const parse = (v) => {
720
869
  if (!v) return null;
@@ -736,17 +885,18 @@ var HELP_TEXT = `Usage: alva <command> [options]
736
885
 
737
886
  Commands:
738
887
  configure Save API key and endpoint to a named profile
739
- whoami Verify credentials and show current user info
740
- user User profile operations
888
+ whoami Verify credentials and show current identity
889
+ user User profile operations (me)
741
890
  fs Filesystem operations (read, write, stat, readdir, mkdir, remove, rename, copy, symlink, readlink, chmod, grant, revoke)
742
891
  run Execute code in the Alva runtime
743
- deploy Cronjob management (create, list, get, update, delete, pause, resume)
892
+ deploy Cronjob management (create, list, get, update, delete, pause, resume, runs, run-logs)
744
893
  release Feed and playbook releases (feed, playbook-draft, playbook)
745
894
  secrets Secret management (create, list, get, update, delete)
746
895
  sdk SDK documentation (doc, partitions, partition-summary)
747
896
  comments Playbook comments (create, pin, unpin)
748
897
  remix Save playbook remix lineage
749
898
  trading Trading operations (accounts, portfolio, orders, subscriptions, equity-history, risk-rules, subscribe, unsubscribe, execute, update-risk-rules)
899
+ auth Authentication (login)
750
900
  screenshot Capture a web screenshot as PNG
751
901
 
752
902
  Global options:
@@ -790,6 +940,14 @@ Examples:
790
940
  alva configure --api-key alva_abc123 --base-url http://localhost:8080
791
941
  alva configure --profile staging --api-key alva_stg_key --base-url https://api-llm.stg.alva.ai
792
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`,
793
951
  whoami: `Usage: alva whoami [--profile <name>]
794
952
 
795
953
  Verify that your credentials are valid by calling the Alva API. Shows your
@@ -829,16 +987,26 @@ Subcommands:
829
987
  grant Grant access permission to a user or group
830
988
  revoke Revoke access permission
831
989
 
832
- Common flags:
833
- --path <path> File or directory path (required for most subcommands)
834
- --recursive Enable recursive operation (readdir, remove)
835
- --no-recursive Disable recursive operation
836
- --mkdir-parents Create parent directories on write (default for write)
837
- --no-mkdir-parents Disable automatic parent directory creation on write
990
+ Subcommand flags:
991
+ read --path (required), [--offset <n>], [--size <n>]
992
+ write --path (required), --data <text> OR --file <local-path> (one required),
993
+ [--mkdir-parents | --no-mkdir-parents]
994
+ stat --path (required)
995
+ readdir --path (required), [--recursive | --no-recursive]
996
+ mkdir --path (required)
997
+ remove --path (required), [--recursive | --no-recursive]
998
+ rename --old-path (required), --new-path (required)
999
+ copy --src-path (required), --dst-path (required)
1000
+ symlink --target-path (required), --link-path (required)
1001
+ readlink --path (required)
1002
+ chmod --path (required), --mode <octal> (required)
1003
+ grant --path (required), --subject <s> (required), --permission <p> (required)
1004
+ revoke --path (required), --subject <s> (required), --permission <p> (required)
838
1005
 
839
1006
  Path conventions:
840
1007
  ~/... Home-relative path (expands to /alva/home/<username>/...)
841
1008
  /alva/home/alice/... Absolute path (required for public/unauthenticated reads)
1009
+ Quote tilde paths to prevent shell expansion: --path "~/data" (not --path ~/data).
842
1010
 
843
1011
  Time series reads:
844
1012
  Paths under feed data directories support virtual suffixes:
@@ -854,21 +1022,23 @@ Grant/revoke subjects:
854
1022
  user:<id> Specific user by ID
855
1023
 
856
1024
  Examples:
857
- alva fs readdir --path ~/
858
- alva fs readdir --path ~/data --recursive
859
- alva fs read --path ~/data/prices.json
860
- alva fs read --path ~/feeds/btc-ema/v1/data/metrics/prices/@last/100
1025
+ alva fs readdir --path "~/"
1026
+ alva fs readdir --path "~/data" --recursive
1027
+ alva fs read --path "~/data/prices.json"
1028
+ alva fs read --path "~/feeds/btc-ema/v1/data/metrics/prices/@last/100"
861
1029
  alva fs read --path /alva/home/alice/feeds/btc-ema/v1/data/metrics/prices/@last/10
862
- alva fs write --path ~/hello.txt --data "Hello, world!"
863
- alva fs write --path ~/feeds/my-feed/v1/src/index.js --file ./local-script.js --mkdir-parents
864
- alva fs stat --path ~/hello.txt
865
- alva fs mkdir --path ~/feeds/my-feed/v1/src
866
- alva fs remove --path ~/old-folder --recursive
867
- alva fs rename --old-path ~/a.txt --new-path ~/b.txt
868
- alva fs copy --src-path ~/a.txt --dst-path ~/b.txt
869
- alva fs chmod --path ~/script.js --mode 755
870
- alva fs grant --path ~/feeds/btc-ema --subject "special:user:*" --permission read
871
- alva fs revoke --path ~/feeds/btc-ema --subject "special:user:*" --permission read`,
1030
+ alva fs write --path "~/hello.txt" --data "Hello, world!"
1031
+ alva fs write --path "~/feeds/my-feed/v1/src/index.js" --file ./local-script.js --mkdir-parents
1032
+ alva fs stat --path "~/hello.txt"
1033
+ alva fs mkdir --path "~/feeds/my-feed/v1/src"
1034
+ alva fs remove --path "~/old-folder" --recursive
1035
+ alva fs rename --old-path "~/a.txt" --new-path "~/b.txt"
1036
+ alva fs copy --src-path "~/a.txt" --dst-path "~/b.txt"
1037
+ alva fs chmod --path "~/script.js" --mode 755
1038
+ alva fs grant --path "~/feeds/btc-ema" --subject "special:user:*" --permission read
1039
+ alva fs revoke --path "~/feeds/btc-ema" --subject "special:user:*" --permission read
1040
+ alva fs symlink --target-path "~/real-file.txt" --link-path "~/my-link.txt"
1041
+ alva fs readlink --path "~/my-link.txt"`,
872
1042
  run: `Usage: alva run [options]
873
1043
 
874
1044
  Execute JavaScript code in the Alva V8 runtime. Provide either inline code
@@ -877,11 +1047,13 @@ data SDKs, ALFS, HTTP networking, and the Feed SDK.
877
1047
 
878
1048
  Options:
879
1049
  --code <code> Inline JavaScript code to execute
1050
+ --local-file <path> Path to a local file whose contents are sent as code
880
1051
  --entry-path <path> Path to a script file on ALFS (home-relative)
881
1052
  --working-dir <dir> Working directory for require() (inline code only)
882
1053
  --args <json> JSON object passed to require("env").args
883
1054
 
884
- At least one of --code or --entry-path is required.
1055
+ At least one of --code, --local-file, or --entry-path is required.
1056
+ These three options are mutually exclusive.
885
1057
 
886
1058
  Response fields:
887
1059
  result JSON-encoded return value of the script
@@ -907,8 +1079,9 @@ Constraints:
907
1079
  Examples:
908
1080
  alva run --code "1 + 2 + 3;"
909
1081
  alva run --code "JSON.stringify(require('env').args);" --args '{"symbol":"BTC"}'
910
- alva run --entry-path ~/feeds/my-feed/v1/src/index.js
911
- alva run --entry-path ~/tasks/analyze/src/index.js --args '{"symbol":"NVDA","limit":50}'`,
1082
+ alva run --entry-path "~/feeds/my-feed/v1/src/index.js"
1083
+ alva run --entry-path "~/tasks/analyze/src/index.js" --args '{"symbol":"NVDA","limit":50}'
1084
+ alva run --local-file ./my-script.js --args '{"symbol":"BTC"}'`,
912
1085
  deploy: `Usage: alva deploy <subcommand> [options]
913
1086
 
914
1087
  Manage scheduled cronjobs that run your scripts on a cron schedule.
@@ -922,6 +1095,8 @@ Subcommands:
922
1095
  delete Delete a cronjob
923
1096
  pause Pause a running cronjob
924
1097
  resume Resume a paused cronjob
1098
+ runs List runs for a cronjob (cursor-paginated)
1099
+ run-logs Get stdout/stderr logs for a single cronjob run
925
1100
 
926
1101
  Create flags:
927
1102
  --name <name> Cronjob name (required, 1-63 lowercase alphanumeric/hyphens)
@@ -938,6 +1113,15 @@ List flags:
938
1113
  Get/Update/Delete/Pause/Resume flags:
939
1114
  --id <id> Cronjob ID (required)
940
1115
 
1116
+ Runs flags:
1117
+ --id <id> Cronjob ID (required)
1118
+ --first <n> Max results per page
1119
+ --cursor <cursor> Pagination cursor from previous response
1120
+
1121
+ Run-logs flags:
1122
+ --id <id> Cronjob ID (required)
1123
+ --run-id <id> Run ID (required)
1124
+
941
1125
  Name format: 1-63 lowercase alphanumeric or hyphens, no leading/trailing hyphens.
942
1126
  Valid: btc-ema-update, my-strategy-1
943
1127
  Invalid: BTC EMA, -my-job-, my_job
@@ -949,15 +1133,18 @@ Recommended cron schedules:
949
1133
  "0 0 * * *" Daily at midnight (end-of-day summaries)
950
1134
 
951
1135
  Examples:
952
- alva deploy create --name btc-ema --path ~/feeds/btc-ema/v1/src/index.js --cron "0 */4 * * *"
953
- alva deploy create --name alert --path ~/feeds/alert/v1/src/index.js --cron "*/5 * * * *" --push-notify --args '{"threshold":100}'
1136
+ alva deploy create --name btc-ema --path "~/feeds/btc-ema/v1/src/index.js" --cron "0 */4 * * *"
1137
+ alva deploy create --name alert --path "~/feeds/alert/v1/src/index.js" --cron "*/5 * * * *" --push-notify --args '{"threshold":100}'
954
1138
  alva deploy list
955
1139
  alva deploy list --limit 10
956
1140
  alva deploy get --id 42
957
1141
  alva deploy update --id 42 --cron "0 */2 * * *" --no-push-notify
958
1142
  alva deploy pause --id 42
959
1143
  alva deploy resume --id 42
960
- alva deploy delete --id 42`,
1144
+ alva deploy delete --id 42
1145
+ alva deploy runs --id 42
1146
+ alva deploy runs --id 42 --first 10
1147
+ alva deploy run-logs --id 42 --run-id 123`,
961
1148
  release: `Usage: alva release <subcommand> [options]
962
1149
 
963
1150
  Publish feeds and playbooks to the Alva platform. The typical workflow:
@@ -1187,12 +1374,10 @@ Examples:
1187
1374
  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`
1188
1375
  };
1189
1376
  async function handleConfigure(args, deps) {
1190
- const flags = parseFlags(args.slice(1));
1377
+ const flags = parseFlags2(args.slice(1));
1191
1378
  const apiKey = flags["api-key"];
1192
1379
  if (!apiKey) {
1193
- throw new Error(
1194
- "--api-key is required. Usage: alva configure --api-key <key> [--base-url <url>] [--profile <name>]"
1195
- );
1380
+ throw new CliUsageError("--api-key is required", "configure");
1196
1381
  }
1197
1382
  if (!apiKey.startsWith("alva_")) {
1198
1383
  process.stderr?.write?.(
@@ -1205,10 +1390,10 @@ async function handleConfigure(args, deps) {
1205
1390
  if (baseUrl) configInput.baseUrl = baseUrl;
1206
1391
  const writeDeps = deps ?? {
1207
1392
  env: process.env,
1208
- homedir: () => os.homedir(),
1209
- mkdir: (path, options) => fsPromises.mkdir(path, options).then(() => void 0),
1210
- writeFile: (path, data, options) => fsPromises.writeFile(path, data, options).then(() => void 0),
1211
- readFile: (path) => fsPromises.readFile(path, "utf-8")
1393
+ homedir: () => os2.homedir(),
1394
+ mkdir: (path, options) => fsPromises2.mkdir(path, options).then(() => void 0),
1395
+ writeFile: (path, data, options) => fsPromises2.writeFile(path, data, options).then(() => void 0),
1396
+ readFile: (path) => fsPromises2.readFile(path, "utf-8")
1212
1397
  };
1213
1398
  const result = await writeConfig(configInput, writeDeps, profileName);
1214
1399
  return {
@@ -1226,7 +1411,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1226
1411
  "execute-latest",
1227
1412
  "dry-run"
1228
1413
  ]);
1229
- function parseFlags(argv) {
1414
+ function parseFlags2(argv) {
1230
1415
  const flags = {};
1231
1416
  for (let i = 0; i < argv.length; i++) {
1232
1417
  const arg = argv[i];
@@ -1254,7 +1439,8 @@ function boolFlag(val) {
1254
1439
  function requireFlag(flags, name, command) {
1255
1440
  const val = flags[name];
1256
1441
  if (val === void 0) {
1257
- throw new Error(`--${name} is required for '${command}'`);
1442
+ const group = command.split(" ")[0];
1443
+ throw new CliUsageError(`--${name} is required for '${command}'`, group);
1258
1444
  }
1259
1445
  return val;
1260
1446
  }
@@ -1262,8 +1448,10 @@ function requireNumericFlag(flags, name, command) {
1262
1448
  const val = requireFlag(flags, name, command);
1263
1449
  const n = Number(val);
1264
1450
  if (Number.isNaN(n)) {
1265
- throw new Error(
1266
- `--${name} must be a number for '${command}', got '${val}'`
1451
+ const group = command.split(" ")[0];
1452
+ throw new CliUsageError(
1453
+ `--${name} must be a number for '${command}', got '${val}'`,
1454
+ group
1267
1455
  );
1268
1456
  }
1269
1457
  return n;
@@ -1307,7 +1495,7 @@ async function dispatch(client, args, meta) {
1307
1495
  return result;
1308
1496
  }
1309
1497
  const subcommand = args[1];
1310
- const flags = parseFlags(
1498
+ const flags = parseFlags2(
1311
1499
  args.slice(
1312
1500
  group === "run" || group === "remix" || group === "screenshot" ? 1 : 2
1313
1501
  )
@@ -1318,11 +1506,13 @@ async function dispatch(client, args, meta) {
1318
1506
  }
1319
1507
  switch (group) {
1320
1508
  case "user":
1321
- if (!subcommand) throw new Error("Missing subcommand for user");
1509
+ if (!subcommand)
1510
+ throw new CliUsageError("Missing subcommand for user", "user");
1322
1511
  if (subcommand === "me") return client.user.me();
1323
- throw new Error(`Unknown subcommand: user ${subcommand}`);
1512
+ throw new CliUsageError(`Unknown subcommand: user ${subcommand}`, "user");
1324
1513
  case "fs": {
1325
- if (!subcommand) throw new Error("Missing subcommand for fs");
1514
+ if (!subcommand)
1515
+ throw new CliUsageError("Missing subcommand for fs", "fs");
1326
1516
  switch (subcommand) {
1327
1517
  case "read":
1328
1518
  return client.fs.read({
@@ -1399,18 +1589,33 @@ async function dispatch(client, args, meta) {
1399
1589
  permission: requireFlag(flags, "permission", "fs revoke")
1400
1590
  });
1401
1591
  default:
1402
- throw new Error(`Unknown subcommand: fs ${subcommand}`);
1592
+ throw new CliUsageError(`Unknown subcommand: fs ${subcommand}`, "fs");
1403
1593
  }
1404
1594
  }
1405
- case "run":
1595
+ case "run": {
1596
+ const sourceFlags = ["code", "local-file", "entry-path"].filter(
1597
+ (f) => flags[f] !== void 0
1598
+ );
1599
+ if (sourceFlags.length > 1) {
1600
+ throw new CliUsageError(
1601
+ `--${sourceFlags.join(" and --")} are mutually exclusive`,
1602
+ "run"
1603
+ );
1604
+ }
1605
+ let code = flags["code"];
1606
+ if (flags["local-file"]) {
1607
+ code = fs.readFileSync(flags["local-file"], "utf-8");
1608
+ }
1406
1609
  return client.run.execute({
1407
- code: flags["code"],
1610
+ code,
1408
1611
  entry_path: flags["entry-path"],
1409
1612
  working_dir: flags["working-dir"],
1410
1613
  args: jsonParse(flags["args"])
1411
1614
  });
1615
+ }
1412
1616
  case "deploy": {
1413
- if (!subcommand) throw new Error("Missing subcommand for deploy");
1617
+ if (!subcommand)
1618
+ throw new CliUsageError("Missing subcommand for deploy", "deploy");
1414
1619
  switch (subcommand) {
1415
1620
  case "create":
1416
1621
  return client.deploy.create({
@@ -1449,12 +1654,27 @@ async function dispatch(client, args, meta) {
1449
1654
  return client.deploy.resume({
1450
1655
  id: requireNumericFlag(flags, "id", "deploy resume")
1451
1656
  });
1657
+ case "runs":
1658
+ return client.deploy.listRuns({
1659
+ cronjob_id: requireNumericFlag(flags, "id", "deploy runs"),
1660
+ first: num(flags["first"]),
1661
+ cursor: num(flags["cursor"])
1662
+ });
1663
+ case "run-logs":
1664
+ return client.deploy.getRunLogs({
1665
+ cronjob_id: requireNumericFlag(flags, "id", "deploy run-logs"),
1666
+ run_id: requireNumericFlag(flags, "run-id", "deploy run-logs")
1667
+ });
1452
1668
  default:
1453
- throw new Error(`Unknown subcommand: deploy ${subcommand}`);
1669
+ throw new CliUsageError(
1670
+ `Unknown subcommand: deploy ${subcommand}`,
1671
+ "deploy"
1672
+ );
1454
1673
  }
1455
1674
  }
1456
1675
  case "release": {
1457
- if (!subcommand) throw new Error("Missing subcommand for release");
1676
+ if (!subcommand)
1677
+ throw new CliUsageError("Missing subcommand for release", "release");
1458
1678
  switch (subcommand) {
1459
1679
  case "feed":
1460
1680
  return client.release.feed({
@@ -1493,11 +1713,15 @@ async function dispatch(client, args, meta) {
1493
1713
  changelog: requireFlag(flags, "changelog", "release playbook")
1494
1714
  });
1495
1715
  default:
1496
- throw new Error(`Unknown subcommand: release ${subcommand}`);
1716
+ throw new CliUsageError(
1717
+ `Unknown subcommand: release ${subcommand}`,
1718
+ "release"
1719
+ );
1497
1720
  }
1498
1721
  }
1499
1722
  case "secrets": {
1500
- if (!subcommand) throw new Error("Missing subcommand for secrets");
1723
+ if (!subcommand)
1724
+ throw new CliUsageError("Missing subcommand for secrets", "secrets");
1501
1725
  switch (subcommand) {
1502
1726
  case "create":
1503
1727
  return client.secrets.create({
@@ -1520,11 +1744,15 @@ async function dispatch(client, args, meta) {
1520
1744
  name: requireFlag(flags, "name", "secrets delete")
1521
1745
  });
1522
1746
  default:
1523
- throw new Error(`Unknown subcommand: secrets ${subcommand}`);
1747
+ throw new CliUsageError(
1748
+ `Unknown subcommand: secrets ${subcommand}`,
1749
+ "secrets"
1750
+ );
1524
1751
  }
1525
1752
  }
1526
1753
  case "sdk": {
1527
- if (!subcommand) throw new Error("Missing subcommand for sdk");
1754
+ if (!subcommand)
1755
+ throw new CliUsageError("Missing subcommand for sdk", "sdk");
1528
1756
  switch (subcommand) {
1529
1757
  case "doc":
1530
1758
  return client.sdk.doc({
@@ -1537,11 +1765,15 @@ async function dispatch(client, args, meta) {
1537
1765
  partition: requireFlag(flags, "partition", "sdk partition-summary")
1538
1766
  });
1539
1767
  default:
1540
- throw new Error(`Unknown subcommand: sdk ${subcommand}`);
1768
+ throw new CliUsageError(
1769
+ `Unknown subcommand: sdk ${subcommand}`,
1770
+ "sdk"
1771
+ );
1541
1772
  }
1542
1773
  }
1543
1774
  case "comments": {
1544
- if (!subcommand) throw new Error("Missing subcommand for comments");
1775
+ if (!subcommand)
1776
+ throw new CliUsageError("Missing subcommand for comments", "comments");
1545
1777
  switch (subcommand) {
1546
1778
  case "create":
1547
1779
  return client.comments.create({
@@ -1563,7 +1795,10 @@ async function dispatch(client, args, meta) {
1563
1795
  )
1564
1796
  });
1565
1797
  default:
1566
- throw new Error(`Unknown subcommand: comments ${subcommand}`);
1798
+ throw new CliUsageError(
1799
+ `Unknown subcommand: comments ${subcommand}`,
1800
+ "comments"
1801
+ );
1567
1802
  }
1568
1803
  }
1569
1804
  case "remix":
@@ -1586,7 +1821,8 @@ async function dispatch(client, args, meta) {
1586
1821
  return { written: outFile, bytes: buf.length };
1587
1822
  }
1588
1823
  case "trading": {
1589
- if (!subcommand) throw new Error("Missing subcommand for trading");
1824
+ if (!subcommand)
1825
+ throw new CliUsageError("Missing subcommand for trading", "trading");
1590
1826
  switch (subcommand) {
1591
1827
  case "accounts":
1592
1828
  return client.trading.accounts();
@@ -1687,13 +1923,21 @@ async function dispatch(client, args, meta) {
1687
1923
  }
1688
1924
  });
1689
1925
  default:
1690
- throw new Error(`Unknown subcommand: trading ${subcommand}`);
1926
+ throw new CliUsageError(
1927
+ `Unknown subcommand: trading ${subcommand}`,
1928
+ "trading"
1929
+ );
1691
1930
  }
1692
1931
  }
1932
+ case "auth": {
1933
+ const authSub = args[1];
1934
+ if (!authSub || authSub === "--help" || authSub === "-h" || args[2] === "--help" || args[2] === "-h") {
1935
+ return { _help: true, text: COMMAND_HELP.auth };
1936
+ }
1937
+ throw new CliUsageError(`Unknown auth subcommand: '${authSub}'`, "auth");
1938
+ }
1693
1939
  default:
1694
- throw new Error(
1695
- `Unknown command: '${group}'. Run 'alva --help' to see available commands.`
1696
- );
1940
+ throw new CliUsageError(`Unknown command: '${group}'`);
1697
1941
  }
1698
1942
  }
1699
1943
  async function main() {
@@ -1713,11 +1957,28 @@ async function main() {
1713
1957
  process.stdout.write(JSON.stringify(result2, null, 2) + "\n");
1714
1958
  return;
1715
1959
  }
1960
+ if (rawArgs[0] === "auth") {
1961
+ const authSub = rawArgs[1];
1962
+ if (!authSub || authSub === "--help" || authSub === "-h" || rawArgs[2] === "--help" || rawArgs[2] === "-h") {
1963
+ process.stdout.write(`${COMMAND_HELP.auth}
1964
+ `);
1965
+ return;
1966
+ }
1967
+ if (authSub === "login") {
1968
+ const result2 = await handleAuthLogin(rawArgs);
1969
+ process.stdout.write(`${JSON.stringify(result2, null, 2)}
1970
+ `);
1971
+ return;
1972
+ }
1973
+ process.stdout.write(`${COMMAND_HELP.auth}
1974
+ `);
1975
+ return;
1976
+ }
1716
1977
  const config = loadConfig({
1717
1978
  argv: rawArgs,
1718
1979
  env: process.env,
1719
1980
  readFile: (path) => fs.readFileSync(path, "utf-8"),
1720
- homedir: () => os.homedir()
1981
+ homedir: () => os2.homedir()
1721
1982
  });
1722
1983
  const client = new AlvaClient({
1723
1984
  apiKey: config.apiKey,
@@ -1759,12 +2020,29 @@ async function main() {
1759
2020
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1760
2021
  }
1761
2022
  } catch (err) {
1762
- const error = err instanceof AlvaError ? { code: err.code, message: err.message, status: err.status } : {
1763
- code: "CLI_ERROR",
1764
- message: err instanceof Error ? err.message : String(err)
1765
- };
1766
- process.stderr.write(JSON.stringify({ error }, null, 2) + "\n");
1767
- process.exit(1);
2023
+ if (err instanceof CliUsageError) {
2024
+ const help = err.command ? COMMAND_HELP[err.command] : HELP_TEXT;
2025
+ process.stderr.write(`Error: ${err.message}
2026
+ `);
2027
+ if (help) process.stderr.write(`
2028
+ ${help}
2029
+ `);
2030
+ process.exit(1);
2031
+ } else if (err instanceof AlvaError) {
2032
+ const error = {
2033
+ code: err.code,
2034
+ message: err.message,
2035
+ status: err.status
2036
+ };
2037
+ process.stderr.write(`${JSON.stringify({ error }, null, 2)}
2038
+ `);
2039
+ process.exit(1);
2040
+ } else {
2041
+ const message = err instanceof Error ? err.message : String(err);
2042
+ process.stderr.write(`Error: ${message}
2043
+ `);
2044
+ process.exit(1);
2045
+ }
1768
2046
  }
1769
2047
  }
1770
2048
  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"));