@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/README.md +7 -4
- package/dist/browser.global.js +1 -1
- package/dist/browser.global.js.map +1 -1
- package/dist/cli.js +360 -82
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +24 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -3
- package/dist/index.d.ts +37 -3
- package/dist/index.js +24 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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.
|
|
552
|
+
if (!this.viewer_token && !this.apiKey) {
|
|
528
553
|
throw new AlvaError(
|
|
529
554
|
"UNAUTHENTICATED",
|
|
530
|
-
"Authentication is required. Pass
|
|
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.
|
|
551
|
-
headers
|
|
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:
|
|
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:
|
|
716
|
+
const path = configPath({ env, homedir: homedir3 });
|
|
692
717
|
try {
|
|
693
|
-
const raw =
|
|
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/
|
|
714
|
-
import * as
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
833
|
-
--path
|
|
834
|
-
--
|
|
835
|
-
|
|
836
|
-
--
|
|
837
|
-
--
|
|
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 =
|
|
1377
|
+
const flags = parseFlags2(args.slice(1));
|
|
1191
1378
|
const apiKey = flags["api-key"];
|
|
1192
1379
|
if (!apiKey) {
|
|
1193
|
-
throw new
|
|
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: () =>
|
|
1209
|
-
mkdir: (path, options) =>
|
|
1210
|
-
writeFile: (path, data, options) =>
|
|
1211
|
-
readFile: (path) =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1266
|
-
|
|
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 =
|
|
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)
|
|
1509
|
+
if (!subcommand)
|
|
1510
|
+
throw new CliUsageError("Missing subcommand for user", "user");
|
|
1322
1511
|
if (subcommand === "me") return client.user.me();
|
|
1323
|
-
throw new
|
|
1512
|
+
throw new CliUsageError(`Unknown subcommand: user ${subcommand}`, "user");
|
|
1324
1513
|
case "fs": {
|
|
1325
|
-
if (!subcommand)
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
1669
|
+
throw new CliUsageError(
|
|
1670
|
+
`Unknown subcommand: deploy ${subcommand}`,
|
|
1671
|
+
"deploy"
|
|
1672
|
+
);
|
|
1454
1673
|
}
|
|
1455
1674
|
}
|
|
1456
1675
|
case "release": {
|
|
1457
|
-
if (!subcommand)
|
|
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
|
|
1716
|
+
throw new CliUsageError(
|
|
1717
|
+
`Unknown subcommand: release ${subcommand}`,
|
|
1718
|
+
"release"
|
|
1719
|
+
);
|
|
1497
1720
|
}
|
|
1498
1721
|
}
|
|
1499
1722
|
case "secrets": {
|
|
1500
|
-
if (!subcommand)
|
|
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
|
|
1747
|
+
throw new CliUsageError(
|
|
1748
|
+
`Unknown subcommand: secrets ${subcommand}`,
|
|
1749
|
+
"secrets"
|
|
1750
|
+
);
|
|
1524
1751
|
}
|
|
1525
1752
|
}
|
|
1526
1753
|
case "sdk": {
|
|
1527
|
-
if (!subcommand)
|
|
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
|
|
1768
|
+
throw new CliUsageError(
|
|
1769
|
+
`Unknown subcommand: sdk ${subcommand}`,
|
|
1770
|
+
"sdk"
|
|
1771
|
+
);
|
|
1541
1772
|
}
|
|
1542
1773
|
}
|
|
1543
1774
|
case "comments": {
|
|
1544
|
-
if (!subcommand)
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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: () =>
|
|
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
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
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"));
|