@liquiditytech/rapidx-cli 1.0.26
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 +81 -0
- package/dist/cli/audit.js +10 -0
- package/dist/cli/bin.js +41 -0
- package/dist/cli/commands/account.js +34 -0
- package/dist/cli/commands/algo.js +35 -0
- package/dist/cli/commands/auth.js +22 -0
- package/dist/cli/commands/config.js +46 -0
- package/dist/cli/commands/doctor.js +42 -0
- package/dist/cli/commands/index.js +73 -0
- package/dist/cli/commands/market.js +26 -0
- package/dist/cli/commands/order.js +81 -0
- package/dist/cli/commands/position.js +35 -0
- package/dist/cli/commands/schema.js +5 -0
- package/dist/cli/commands/self-check.js +24 -0
- package/dist/cli/commands/trade-gate.js +26 -0
- package/dist/cli/commands/trade.js +30 -0
- package/dist/cli/commands/update.js +27 -0
- package/dist/cli/envelope.js +29 -0
- package/dist/cli/help.js +34 -0
- package/dist/cli/invocation-checker.js +39 -0
- package/dist/cli/mcp-entry.js +4 -0
- package/dist/cli/parser.js +87 -0
- package/dist/core/audit/redaction.js +56 -0
- package/dist/core/audit/writer.js +27 -0
- package/dist/core/client/capability-executor.js +400 -0
- package/dist/core/client/rapid-x-client.js +156 -0
- package/dist/core/client/signing.js +24 -0
- package/dist/core/client/symbol.js +36 -0
- package/dist/core/config/credential.js +42 -0
- package/dist/core/config/resolve.js +24 -0
- package/dist/core/contracts/capabilities.js +77 -0
- package/dist/core/contracts/compatibility.js +65 -0
- package/dist/core/contracts/events.js +29 -0
- package/dist/core/contracts/evidence.js +7 -0
- package/dist/core/contracts/input-schema.js +370 -0
- package/dist/core/contracts/types.js +1 -0
- package/dist/core/errors/product-error.js +74 -0
- package/dist/core/index.js +24 -0
- package/dist/core/safety/policy.js +215 -0
- package/dist/core/safety/raw-api.js +19 -0
- package/dist/core/self-check/live-read-only-probes.js +25 -0
- package/dist/core/self-check/live-trading-verification-probes.js +252 -0
- package/dist/core/self-check/run-self-check.js +91 -0
- package/dist/core/trading/preview.js +330 -0
- package/dist/core/trading/trading-verification.js +137 -0
- package/dist/core/update/check-update.js +295 -0
- package/dist/core/version.js +1 -0
- package/dist/mcp/audit.js +10 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/tool-registry.js +31 -0
- package/dist/mcp/tool-runner.js +144 -0
- package/package.json +48 -0
- package/packages/distribution/docs/cli-only-agent.md +12 -0
- package/packages/distribution/docs/cli.md +49 -0
- package/packages/distribution/docs/index.md +58 -0
- package/packages/distribution/docs/mcp.md +36 -0
- package/packages/distribution/docs/quickstart.md +129 -0
- package/packages/distribution/docs/self-check.md +7 -0
- package/packages/distribution/docs/skills.md +140 -0
- package/packages/distribution/docs/tools.md +35 -0
- package/packages/distribution/docs/trading-verification.md +17 -0
- package/packages/distribution/docs/troubleshooting/index.md +27 -0
- package/packages/distribution/manifests/offline-manifest.json +26 -0
- package/packages/distribution/registry/rapidx.mcp.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# RapidX CLI
|
|
2
|
+
|
|
3
|
+
RapidX CLI provides a local command-line entrypoint and MCP server mode for RapidX account, market, order, position, and portfolio workflows.
|
|
4
|
+
|
|
5
|
+
The package is distributed as `@liquiditytech/rapidx-cli`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @liquiditytech/rapidx-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configure
|
|
14
|
+
|
|
15
|
+
Provide credentials through the agent host secret mechanism or environment variables:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
LTP_ACCESS_KEY=<your-access-key>
|
|
19
|
+
LTP_SECRET_KEY=<your-secret-key>
|
|
20
|
+
LTP_API_HOST=<provided-api-host>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Do not commit credentials into project files.
|
|
24
|
+
|
|
25
|
+
## Use As CLI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
rapidx schema --json
|
|
29
|
+
rapidx self-check --read-only --json
|
|
30
|
+
rapidx market get-ticker --input '{"symbol":"BINANCE_PERP_BTC_USDT"}' --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Trade writes use a preview-first flow:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
rapidx order place-preview --input '{"symbol":"BINANCE_PERP_BTC_USDT","side":"BUY","orderType":"LIMIT","price":"65000","quantity":"0.001","maxNotional":"10","clientOrderId":"example-001"}' --json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Submit write operations only after reviewing the preview result and providing the returned `previewId` plus `confirmation.submitToken` as `continueConsentId`.
|
|
40
|
+
|
|
41
|
+
## Use As MCP
|
|
42
|
+
|
|
43
|
+
Configure the agent host to start the MCP server with:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"command": "rapidx",
|
|
48
|
+
"args": ["mcp", "serve"],
|
|
49
|
+
"env": {
|
|
50
|
+
"LTP_ACCESS_KEY": "<your-access-key>",
|
|
51
|
+
"LTP_SECRET_KEY": "<your-secret-key>",
|
|
52
|
+
"LTP_API_HOST": "<provided-api-host>"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
MCP tools expose the same capabilities as the CLI through structured tool schemas. Trade-write tools require preview and consent fields.
|
|
58
|
+
|
|
59
|
+
## Skills
|
|
60
|
+
|
|
61
|
+
RapidX skills are distributed separately through GitHub. Use the config skill to guide installation, credential configuration, MCP registration, access review, and read-only self-check. Use the trading skill for normal query and preview-first trading workflows.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx skills add git@github.com:LiquidityTech/ltp-rapidx-skill.git \
|
|
65
|
+
--skill ltp-rapidx-config \
|
|
66
|
+
--skill ltp-rapidx-trading \
|
|
67
|
+
-a codex -y
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If the skills repository is public and the agent host has HTTPS GitHub access, `LiquidityTech/ltp-rapidx-skill` may be used instead of the SSH URL. Private repositories should use the SSH URL so Git can use the configured deploy key or user key.
|
|
71
|
+
|
|
72
|
+
## Verification
|
|
73
|
+
|
|
74
|
+
After installation:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
rapidx schema --json
|
|
78
|
+
rapidx self-check --read-only --json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Do not treat missing real API evidence as success. If RapidX cannot be called, the expected status is `NOT_VERIFIED`.
|
package/dist/cli/bin.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { stdin as input, stdout, stderr } from "node:process";
|
|
3
|
+
import { dispatchCli } from "./commands/index.js";
|
|
4
|
+
import { parseArgv } from "./parser.js";
|
|
5
|
+
import { startMcpServer } from "./mcp-entry.js";
|
|
6
|
+
import { formatCliHelp, formatCliVersion, isHelpArg, isVersionArg } from "./help.js";
|
|
7
|
+
async function readStdinIfPiped() {
|
|
8
|
+
if (input.isTTY) {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
const chunks = [];
|
|
12
|
+
for await (const chunk of input) {
|
|
13
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
14
|
+
}
|
|
15
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
16
|
+
}
|
|
17
|
+
async function main() {
|
|
18
|
+
const argv = process.argv.slice(2);
|
|
19
|
+
if (argv.some(isHelpArg)) {
|
|
20
|
+
stdout.write(`${formatCliHelp()}\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (argv.some(isVersionArg)) {
|
|
24
|
+
stdout.write(`${formatCliVersion()}\n`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (argv[0] === "mcp" && argv[1] === "serve") {
|
|
28
|
+
await startMcpServer();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const parsed = parseArgv(argv, { stdin: await readStdinIfPiped() });
|
|
32
|
+
const result = await dispatchCli(parsed);
|
|
33
|
+
stdout.write(`${JSON.stringify(result)}\n`);
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
main().catch((error) => {
|
|
39
|
+
stderr.write(`${JSON.stringify({ ok: false, status: "FAIL", code: "RCLI00001", message: error instanceof Error ? error.message : String(error) })}\n`);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError } from "../../core/index.js";
|
|
2
|
+
import { writeCliAudit } from "../audit.js";
|
|
3
|
+
import { fail, ok } from "../envelope.js";
|
|
4
|
+
import { enforceCliPreviewGate } from "./trade-gate.js";
|
|
5
|
+
const ACCOUNT_CAPABILITY_BY_ACTION = {
|
|
6
|
+
overview: "account.overview",
|
|
7
|
+
balance: "account.balance",
|
|
8
|
+
"set-position-mode": "account.set-position-mode"
|
|
9
|
+
};
|
|
10
|
+
export async function runAccountCommand(action, input) {
|
|
11
|
+
const capabilityId = ACCOUNT_CAPABILITY_BY_ACTION[action];
|
|
12
|
+
if (!capabilityId) {
|
|
13
|
+
return fail("RCLI12001", `Unknown account command: ${action}`, "FAIL", `rapidx account ${action}`);
|
|
14
|
+
}
|
|
15
|
+
const capability = findCapabilityById(capabilityId);
|
|
16
|
+
if (!capability) {
|
|
17
|
+
return fail("RCLI30002", `Missing capability: ${capabilityId}`, "FAIL", `rapidx account ${action}`);
|
|
18
|
+
}
|
|
19
|
+
const blocked = enforceCliPreviewGate(capability, input, `rapidx account ${action}`);
|
|
20
|
+
if (blocked) {
|
|
21
|
+
return blocked;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const data = await executeRapidXCapability(capabilityId, input);
|
|
25
|
+
const auditId = capability.operationType === "TRADE_WRITE"
|
|
26
|
+
? writeCliAudit("trade-write", "PASS", { command: `rapidx account ${action}`, capabilityId })
|
|
27
|
+
: undefined;
|
|
28
|
+
return ok(data, `rapidx account ${action}`, "PASS", "real_tool_call", auditId);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const productError = normalizeUnknownError(error, "RCLI01001");
|
|
32
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx account ${action}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError } from "../../core/index.js";
|
|
2
|
+
import { writeCliAudit } from "../audit.js";
|
|
3
|
+
import { fail, ok } from "../envelope.js";
|
|
4
|
+
import { enforceCliPreviewGate } from "./trade-gate.js";
|
|
5
|
+
const ALGO_CAPABILITY_BY_ACTION = {
|
|
6
|
+
place: "algo.place",
|
|
7
|
+
amend: "algo.amend",
|
|
8
|
+
cancel: "algo.cancel",
|
|
9
|
+
list: "algo.list"
|
|
10
|
+
};
|
|
11
|
+
export async function runAlgoCommand(action, input) {
|
|
12
|
+
const capabilityId = ALGO_CAPABILITY_BY_ACTION[action];
|
|
13
|
+
if (!capabilityId) {
|
|
14
|
+
return fail("RCLI12001", `Unknown algo command: ${action}`, "FAIL", `rapidx algo ${action}`);
|
|
15
|
+
}
|
|
16
|
+
const capability = findCapabilityById(capabilityId);
|
|
17
|
+
if (!capability) {
|
|
18
|
+
return fail("RCLI30002", `Missing capability: ${capabilityId}`, "FAIL", `rapidx algo ${action}`);
|
|
19
|
+
}
|
|
20
|
+
const blocked = enforceCliPreviewGate(capability, input, `rapidx algo ${action}`);
|
|
21
|
+
if (blocked) {
|
|
22
|
+
return blocked;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const data = await executeRapidXCapability(capabilityId, input);
|
|
26
|
+
const auditId = capability.operationType === "TRADE_WRITE"
|
|
27
|
+
? writeCliAudit("trade-write", "PASS", { command: `rapidx algo ${action}`, capabilityId })
|
|
28
|
+
: undefined;
|
|
29
|
+
return ok(data, `rapidx algo ${action}`, "PASS", "real_tool_call", auditId);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
33
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx algo ${action}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { makeCredentialRef, resolveCredential } from "../../core/index.js";
|
|
2
|
+
import { fail, ok } from "../envelope.js";
|
|
3
|
+
export function runAuthCheck(input) {
|
|
4
|
+
try {
|
|
5
|
+
const source = typeof input.source === "string" ? input.source : "env";
|
|
6
|
+
const material = {};
|
|
7
|
+
if (typeof input.accessKey === "string") {
|
|
8
|
+
material.accessKey = input.accessKey;
|
|
9
|
+
}
|
|
10
|
+
if (typeof input.secretKey === "string") {
|
|
11
|
+
material.secretKey = input.secretKey;
|
|
12
|
+
}
|
|
13
|
+
const credential = resolveCredential({
|
|
14
|
+
ref: makeCredentialRef(source),
|
|
15
|
+
material
|
|
16
|
+
});
|
|
17
|
+
return ok({ status: "PASS", maskedAccessKey: credential.maskedAccessKey }, "rapidx auth check");
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return fail("RCLI01001", error instanceof Error ? error.message : String(error), "NOT_VERIFIED", "rapidx auth check");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { makeCredentialRef } from "../../core/index.js";
|
|
4
|
+
import { ok } from "../envelope.js";
|
|
5
|
+
export function runConfigCommand(action, input) {
|
|
6
|
+
const filePath = resolveConfigFile();
|
|
7
|
+
const config = loadConfig(filePath);
|
|
8
|
+
if (action === "set") {
|
|
9
|
+
const key = String(input.key ?? "");
|
|
10
|
+
config[key] = shouldRedactConfigKey(key) ? "[REDACTED]" : input.value;
|
|
11
|
+
saveConfig(filePath, config);
|
|
12
|
+
return ok({ updated: [key], credentialRef: makeCredentialRef("chat-secret") }, "rapidx config set");
|
|
13
|
+
}
|
|
14
|
+
if (action === "unset") {
|
|
15
|
+
const key = String(input.key ?? "");
|
|
16
|
+
delete config[key];
|
|
17
|
+
saveConfig(filePath, config);
|
|
18
|
+
return ok({ unset: key }, "rapidx config unset");
|
|
19
|
+
}
|
|
20
|
+
if (action === "get") {
|
|
21
|
+
const key = String(input.key ?? "");
|
|
22
|
+
return ok({ key, value: config[key] ?? null }, "rapidx config get");
|
|
23
|
+
}
|
|
24
|
+
return ok({ entries: config }, "rapidx config list");
|
|
25
|
+
}
|
|
26
|
+
function resolveConfigFile(env = process.env, cwd = process.cwd()) {
|
|
27
|
+
const stateDir = env.RAPIDX_STATE_DIR ? resolve(env.RAPIDX_STATE_DIR) : resolve(cwd, ".rapidx");
|
|
28
|
+
return join(stateDir, "config.json");
|
|
29
|
+
}
|
|
30
|
+
function loadConfig(filePath) {
|
|
31
|
+
if (!existsSync(filePath)) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
35
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
function saveConfig(filePath, config) {
|
|
41
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
42
|
+
writeFileSync(filePath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
43
|
+
}
|
|
44
|
+
function shouldRedactConfigKey(key) {
|
|
45
|
+
return /secret|token|password|credential|access[_-]?key|api[_-]?key/i.test(key);
|
|
46
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { buildCompatibilityReport, getSchemaResult } from "../../core/index.js";
|
|
2
|
+
import { checkInvocationMode } from "../invocation-checker.js";
|
|
3
|
+
import { ok } from "../envelope.js";
|
|
4
|
+
export function runDoctorCommand(input) {
|
|
5
|
+
const invocationInput = {
|
|
6
|
+
commandLine: typeof input.commandLine === "string" ? input.commandLine : "rapidx doctor --json"
|
|
7
|
+
};
|
|
8
|
+
if (typeof input.preflightError === "string") {
|
|
9
|
+
invocationInput.preflightError = input.preflightError;
|
|
10
|
+
}
|
|
11
|
+
const invocation = checkInvocationMode(invocationInput);
|
|
12
|
+
const compatibilityInput = {};
|
|
13
|
+
if (typeof input.mcpSchemaVersion === "string") {
|
|
14
|
+
compatibilityInput.mcpSchemaVersion = input.mcpSchemaVersion;
|
|
15
|
+
}
|
|
16
|
+
if (typeof input.skillsVersion === "string") {
|
|
17
|
+
compatibilityInput.skillsVersion = input.skillsVersion;
|
|
18
|
+
}
|
|
19
|
+
if (typeof input.skillsSchemaVersion === "string") {
|
|
20
|
+
compatibilityInput.skillsSchemaVersion = input.skillsSchemaVersion;
|
|
21
|
+
}
|
|
22
|
+
const compatibility = buildCompatibilityReport(compatibilityInput);
|
|
23
|
+
const credentialSource = {
|
|
24
|
+
LTP_ACCESS_KEY: Boolean(process.env.LTP_ACCESS_KEY),
|
|
25
|
+
LTP_SECRET_KEY: Boolean(process.env.LTP_SECRET_KEY),
|
|
26
|
+
LTP_API_HOST: Boolean(process.env.LTP_API_HOST)
|
|
27
|
+
};
|
|
28
|
+
const doctorStatus = !invocation.compliant || compatibility.status === "BLOCKED" ? "FAIL" : "PASS";
|
|
29
|
+
return ok({
|
|
30
|
+
schemaVersion: getSchemaResult().schemaVersion,
|
|
31
|
+
invocation,
|
|
32
|
+
compatibility,
|
|
33
|
+
credentialSource,
|
|
34
|
+
checks: [
|
|
35
|
+
{ name: "node", status: "PASS" },
|
|
36
|
+
{ name: "canonical-schema", status: "PASS" },
|
|
37
|
+
{ name: "version-compatibility", status: compatibility.status },
|
|
38
|
+
{ name: "credential-source", status: credentialSource.LTP_ACCESS_KEY && credentialSource.LTP_SECRET_KEY && credentialSource.LTP_API_HOST ? "PASS" : "NOT_VERIFIED" },
|
|
39
|
+
{ name: "invocation", status: invocation.compliant ? "PASS" : "FAIL" }
|
|
40
|
+
]
|
|
41
|
+
}, "rapidx doctor --json", doctorStatus);
|
|
42
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { checkInvocationMode } from "../invocation-checker.js";
|
|
2
|
+
import { fail, ok } from "../envelope.js";
|
|
3
|
+
import { runAccountCommand } from "./account.js";
|
|
4
|
+
import { runAlgoCommand } from "./algo.js";
|
|
5
|
+
import { runAuthCheck } from "./auth.js";
|
|
6
|
+
import { runConfigCommand } from "./config.js";
|
|
7
|
+
import { runDoctorCommand } from "./doctor.js";
|
|
8
|
+
import { runMarketCommand } from "./market.js";
|
|
9
|
+
import { runOrderCommand } from "./order.js";
|
|
10
|
+
import { runPositionCommand } from "./position.js";
|
|
11
|
+
import { runSchemaCommand } from "./schema.js";
|
|
12
|
+
import { runSelfCheckCommand, runTradingVerificationCommand } from "./self-check.js";
|
|
13
|
+
import { runTradeCommand } from "./trade.js";
|
|
14
|
+
import { runUpdateCommand } from "./update.js";
|
|
15
|
+
export async function dispatchCli(parsed) {
|
|
16
|
+
const [domain, action, subAction] = parsed.commandParts;
|
|
17
|
+
if (!domain) {
|
|
18
|
+
return fail("RCLI00001", "Command is required.");
|
|
19
|
+
}
|
|
20
|
+
if (domain === "schema") {
|
|
21
|
+
return runSchemaCommand();
|
|
22
|
+
}
|
|
23
|
+
if (domain === "doctor") {
|
|
24
|
+
return runDoctorCommand(parsed.input);
|
|
25
|
+
}
|
|
26
|
+
if (domain === "auth" && action === "check") {
|
|
27
|
+
return runAuthCheck(parsed.input);
|
|
28
|
+
}
|
|
29
|
+
if (domain === "config") {
|
|
30
|
+
return runConfigCommand(action ?? "list", parsed.input);
|
|
31
|
+
}
|
|
32
|
+
if (domain === "self-check" && action === "trade-verify") {
|
|
33
|
+
return runTradingVerificationCommand(parsed.input);
|
|
34
|
+
}
|
|
35
|
+
if (domain === "trade" && action === "verify-live") {
|
|
36
|
+
return runTradingVerificationCommand(parsed.input, "rapidx trade verify-live --json");
|
|
37
|
+
}
|
|
38
|
+
if (domain === "self-check") {
|
|
39
|
+
return runSelfCheckCommand(parsed.input);
|
|
40
|
+
}
|
|
41
|
+
if (domain === "update") {
|
|
42
|
+
return runUpdateCommand(action ?? "check", parsed.input);
|
|
43
|
+
}
|
|
44
|
+
if (domain === "invocation" && action === "check") {
|
|
45
|
+
const invocationInput = {};
|
|
46
|
+
if (typeof parsed.input.commandLine === "string") {
|
|
47
|
+
invocationInput.commandLine = parsed.input.commandLine;
|
|
48
|
+
}
|
|
49
|
+
if (typeof parsed.input.preflightError === "string") {
|
|
50
|
+
invocationInput.preflightError = parsed.input.preflightError;
|
|
51
|
+
}
|
|
52
|
+
return ok(checkInvocationMode(invocationInput), "rapidx invocation check");
|
|
53
|
+
}
|
|
54
|
+
if (domain === "market") {
|
|
55
|
+
return runMarketCommand(action ?? "get-ticker", parsed.input);
|
|
56
|
+
}
|
|
57
|
+
if (domain === "account") {
|
|
58
|
+
return runAccountCommand(action ?? "overview", parsed.input);
|
|
59
|
+
}
|
|
60
|
+
if (domain === "order") {
|
|
61
|
+
return runOrderCommand(action ?? "list", parsed.input);
|
|
62
|
+
}
|
|
63
|
+
if (domain === "trade") {
|
|
64
|
+
return runTradeCommand(action ?? "preview", parsed.input);
|
|
65
|
+
}
|
|
66
|
+
if (domain === "position") {
|
|
67
|
+
return runPositionCommand(action ?? "list", parsed.input);
|
|
68
|
+
}
|
|
69
|
+
if (domain === "algo") {
|
|
70
|
+
return runAlgoCommand(subAction ?? action ?? "list", parsed.input);
|
|
71
|
+
}
|
|
72
|
+
return fail("RCLI12001", `Unknown command: ${parsed.command}`);
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { executeRapidXCapability, normalizeUnknownError } from "../../core/index.js";
|
|
2
|
+
import { ok } from "../envelope.js";
|
|
3
|
+
import { fail } from "../envelope.js";
|
|
4
|
+
const MARKET_CAPABILITY_BY_ACTION = {
|
|
5
|
+
"get-ticker": "market.ticker",
|
|
6
|
+
"get-orderbook": "market.orderbook",
|
|
7
|
+
"get-klines": "market.klines",
|
|
8
|
+
"get-funding-rate": "market.funding-rate",
|
|
9
|
+
"get-mark-price": "market.mark-price",
|
|
10
|
+
"get-symbol-info": "market.symbol-info",
|
|
11
|
+
"get-open-interest": "market.open-interest"
|
|
12
|
+
};
|
|
13
|
+
export async function runMarketCommand(action, input) {
|
|
14
|
+
const capabilityId = MARKET_CAPABILITY_BY_ACTION[action];
|
|
15
|
+
if (!capabilityId) {
|
|
16
|
+
return fail("RCLI12001", `Unknown market command: ${action}`, "FAIL", `rapidx market ${action}`);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const data = await executeRapidXCapability(capabilityId, input);
|
|
20
|
+
return ok(data, `rapidx market ${action}`, "PASS", "real_tool_call");
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
24
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx market ${action}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, loadPreviewStoreFromFile, makeSafetyState, normalizeUnknownError, resolvePreviewStoreFile, savePreviewStoreToFile, verifyPreview } from "../../core/index.js";
|
|
2
|
+
import { fail, ok } from "../envelope.js";
|
|
3
|
+
import { writeCliAudit } from "../audit.js";
|
|
4
|
+
const safetyState = makeSafetyState();
|
|
5
|
+
export async function runOrderCommand(action, input) {
|
|
6
|
+
const previewStoreFile = resolvePreviewStoreFile();
|
|
7
|
+
const previewStore = loadPreviewStoreFromFile(previewStoreFile);
|
|
8
|
+
if (action === "preview" || action === "place-preview") {
|
|
9
|
+
const capability = findCapabilityById("order.preview");
|
|
10
|
+
if (!capability) {
|
|
11
|
+
return fail("RCLI30001", "order.preview capability missing.");
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const preview = createTradePreview(capability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
15
|
+
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
16
|
+
return ok(preview, `rapidx order ${action}`);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const productError = normalizeUnknownError(error, "RCLI20002");
|
|
20
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const previewTarget = previewTargetForAction(action);
|
|
24
|
+
if (previewTarget) {
|
|
25
|
+
const targetCapability = findCapabilityById(previewTarget);
|
|
26
|
+
if (!targetCapability) {
|
|
27
|
+
return fail("RCLI30001", `${previewTarget} capability missing.`);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
31
|
+
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
32
|
+
return ok(preview, `rapidx order ${action}`);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const productError = normalizeUnknownError(error, "RCLI20002");
|
|
36
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const capability = findCapabilityById(`order.${action}`);
|
|
40
|
+
if (!capability) {
|
|
41
|
+
return fail("RCLI12001", `Unknown order command: ${action}`);
|
|
42
|
+
}
|
|
43
|
+
if (capability.previewRequired) {
|
|
44
|
+
try {
|
|
45
|
+
verifyPreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return fail("RCLI20002", error instanceof Error ? error.message : String(error), "BLOCKED", `rapidx order ${action}`);
|
|
49
|
+
}
|
|
50
|
+
if (typeof input.continueConsentId !== "string" || input.continueConsentId.length === 0) {
|
|
51
|
+
return fail("RCLI20001", "continueConsentId is required for trade writes.", "BLOCKED", `rapidx order ${action}`);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
consumePreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
55
|
+
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return fail("RCLI20002", error instanceof Error ? error.message : String(error), "BLOCKED", `rapidx order ${action}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const data = await executeRapidXCapability(capability.capabilityId, input);
|
|
63
|
+
const auditId = capability.operationType === "TRADE_WRITE"
|
|
64
|
+
? writeCliAudit("trade-write", "PASS", { command: `rapidx order ${action}`, capabilityId: capability.capabilityId, clientOrderId: input.clientOrderId })
|
|
65
|
+
: undefined;
|
|
66
|
+
return ok(data, `rapidx order ${action}`, "PASS", "real_tool_call", auditId);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
70
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function previewTargetForAction(action) {
|
|
74
|
+
if (action === "amend-preview") {
|
|
75
|
+
return "order.amend";
|
|
76
|
+
}
|
|
77
|
+
if (action === "cancel-preview") {
|
|
78
|
+
return "order.cancel";
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError } from "../../core/index.js";
|
|
2
|
+
import { writeCliAudit } from "../audit.js";
|
|
3
|
+
import { fail, ok } from "../envelope.js";
|
|
4
|
+
import { enforceCliPreviewGate } from "./trade-gate.js";
|
|
5
|
+
const POSITION_CAPABILITY_BY_ACTION = {
|
|
6
|
+
list: "position.list",
|
|
7
|
+
history: "position.history",
|
|
8
|
+
close: "position.close",
|
|
9
|
+
"set-leverage": "position.set-leverage"
|
|
10
|
+
};
|
|
11
|
+
export async function runPositionCommand(action, input) {
|
|
12
|
+
const capabilityId = POSITION_CAPABILITY_BY_ACTION[action];
|
|
13
|
+
if (!capabilityId) {
|
|
14
|
+
return fail("RCLI12001", `Unknown position command: ${action}`, "FAIL", `rapidx position ${action}`);
|
|
15
|
+
}
|
|
16
|
+
const capability = findCapabilityById(capabilityId);
|
|
17
|
+
if (!capability) {
|
|
18
|
+
return fail("RCLI30002", `Missing capability: ${capabilityId}`, "FAIL", `rapidx position ${action}`);
|
|
19
|
+
}
|
|
20
|
+
const blocked = enforceCliPreviewGate(capability, input, `rapidx position ${action}`);
|
|
21
|
+
if (blocked) {
|
|
22
|
+
return blocked;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const data = await executeRapidXCapability(capabilityId, input);
|
|
26
|
+
const auditId = capability.operationType === "TRADE_WRITE"
|
|
27
|
+
? writeCliAudit("trade-write", "PASS", { command: `rapidx position ${action}`, capabilityId })
|
|
28
|
+
: undefined;
|
|
29
|
+
return ok(data, `rapidx position ${action}`, "PASS", "real_tool_call", auditId);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
33
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx position ${action}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { buildLiveTradingVerificationProbes, buildLiveReadOnlySelfCheck, runSelfCheck, runTradingVerification } from "../../core/index.js";
|
|
2
|
+
import { failWithData, ok } from "../envelope.js";
|
|
3
|
+
export async function runSelfCheckCommand(input) {
|
|
4
|
+
const options = {
|
|
5
|
+
input: {
|
|
6
|
+
scope: input.scope === "deep" ? "deep" : "quick",
|
|
7
|
+
readOnly: input.readOnly !== false,
|
|
8
|
+
checkUpdates: input.checkUpdates === true
|
|
9
|
+
},
|
|
10
|
+
...buildLiveReadOnlySelfCheck(input)
|
|
11
|
+
};
|
|
12
|
+
const report = await runSelfCheck(options);
|
|
13
|
+
return ok(report, "rapidx self-check --read-only --json", report.status);
|
|
14
|
+
}
|
|
15
|
+
export async function runTradingVerificationCommand(input, evidenceText = "rapidx self-check trade-verify --json") {
|
|
16
|
+
const report = await runTradingVerification(input, buildLiveTradingVerificationProbes(input));
|
|
17
|
+
if (report.status === "PASS") {
|
|
18
|
+
return ok(report, evidenceText, report.status);
|
|
19
|
+
}
|
|
20
|
+
return failWithData(tradingVerificationCliCode(report), report.errorMessage ?? `Trading verification failed: ${report.errorCode ?? report.status}.`, report, report.status, evidenceText);
|
|
21
|
+
}
|
|
22
|
+
function tradingVerificationCliCode(report) {
|
|
23
|
+
return (report.errorCode ?? "RCORE10002").replace(/^RCORE/, "RCLI");
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { consumePreview, loadPreviewStoreFromFile, resolvePreviewStoreFile, savePreviewStoreToFile, verifyPreview } from "../../core/index.js";
|
|
2
|
+
import { fail } from "../envelope.js";
|
|
3
|
+
export function enforceCliPreviewGate(capability, input, evidenceText) {
|
|
4
|
+
if (!capability.previewRequired) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const previewStoreFile = resolvePreviewStoreFile();
|
|
8
|
+
const previewStore = loadPreviewStoreFromFile(previewStoreFile);
|
|
9
|
+
try {
|
|
10
|
+
verifyPreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return fail("RCLI20002", error instanceof Error ? error.message : String(error), "BLOCKED", evidenceText);
|
|
14
|
+
}
|
|
15
|
+
if (typeof input.continueConsentId !== "string" || input.continueConsentId.length === 0) {
|
|
16
|
+
return fail("RCLI20001", "continueConsentId is required for trade writes.", "BLOCKED", evidenceText);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
consumePreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
20
|
+
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return fail("RCLI20002", error instanceof Error ? error.message : String(error), "BLOCKED", evidenceText);
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createTargetedTradePreview, defaultSafetyPolicy, findCapabilityById, loadPreviewStoreFromFile, makeSafetyState, normalizeUnknownError, resolvePreviewStoreFile, savePreviewStoreToFile } from "../../core/index.js";
|
|
2
|
+
import { fail, ok } from "../envelope.js";
|
|
3
|
+
const safetyState = makeSafetyState();
|
|
4
|
+
export async function runTradeCommand(action, input) {
|
|
5
|
+
if (action !== "preview") {
|
|
6
|
+
return fail("RCLI12001", `Unknown trade command: ${action}`, "FAIL", `rapidx trade ${action}`);
|
|
7
|
+
}
|
|
8
|
+
const targetCapabilityId = typeof input.targetCapabilityId === "string" ? input.targetCapabilityId : "";
|
|
9
|
+
const targetCapability = targetCapabilityId ? findCapabilityById(targetCapabilityId) : undefined;
|
|
10
|
+
if (!targetCapability) {
|
|
11
|
+
return fail("RCLI12001", `Unknown trade preview target: ${targetCapabilityId || "<missing>"}`, "FAIL", "rapidx trade preview");
|
|
12
|
+
}
|
|
13
|
+
if (targetCapability.operationType !== "TRADE_WRITE" || isPreviewCapability(targetCapability.capabilityId)) {
|
|
14
|
+
return fail("RCLI20002", "trade preview target must be a non-preview trade write capability.", "BLOCKED", "rapidx trade preview");
|
|
15
|
+
}
|
|
16
|
+
const previewStoreFile = resolvePreviewStoreFile();
|
|
17
|
+
const previewStore = loadPreviewStoreFromFile(previewStoreFile);
|
|
18
|
+
try {
|
|
19
|
+
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
20
|
+
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
21
|
+
return ok(preview, "rapidx trade preview");
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const productError = normalizeUnknownError(error, "RCLI20002");
|
|
25
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, "rapidx trade preview");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isPreviewCapability(capabilityId) {
|
|
29
|
+
return capabilityId.endsWith(".preview") || capabilityId.endsWith("-preview");
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { checkForUpdate } from "../../core/index.js";
|
|
2
|
+
import { fail, ok } from "../envelope.js";
|
|
3
|
+
export async function runUpdateCommand(action, input) {
|
|
4
|
+
if (action !== "check") {
|
|
5
|
+
return fail("RCLI12001", `Unknown update command: ${action}`, "FAIL", "rapidx update");
|
|
6
|
+
}
|
|
7
|
+
const updateInput = {
|
|
8
|
+
force: input.force === true
|
|
9
|
+
};
|
|
10
|
+
if (typeof input.manifestUrl === "string") {
|
|
11
|
+
updateInput.manifestUrl = input.manifestUrl;
|
|
12
|
+
}
|
|
13
|
+
if (typeof input.maxCacheAgeSeconds === "number") {
|
|
14
|
+
updateInput.maxCacheAgeSeconds = input.maxCacheAgeSeconds;
|
|
15
|
+
}
|
|
16
|
+
const result = await checkForUpdate(updateInput);
|
|
17
|
+
return ok(result, "rapidx update check --json", envelopeStatusForUpdate(result.status));
|
|
18
|
+
}
|
|
19
|
+
function envelopeStatusForUpdate(status) {
|
|
20
|
+
if (status === "WRITE_BLOCKED" || status === "UPGRADE_REQUIRED") {
|
|
21
|
+
return "BLOCKED";
|
|
22
|
+
}
|
|
23
|
+
if (status === "UNKNOWN") {
|
|
24
|
+
return "NOT_VERIFIED";
|
|
25
|
+
}
|
|
26
|
+
return "PASS";
|
|
27
|
+
}
|