@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.
Files changed (64) hide show
  1. package/README.md +81 -0
  2. package/dist/cli/audit.js +10 -0
  3. package/dist/cli/bin.js +41 -0
  4. package/dist/cli/commands/account.js +34 -0
  5. package/dist/cli/commands/algo.js +35 -0
  6. package/dist/cli/commands/auth.js +22 -0
  7. package/dist/cli/commands/config.js +46 -0
  8. package/dist/cli/commands/doctor.js +42 -0
  9. package/dist/cli/commands/index.js +73 -0
  10. package/dist/cli/commands/market.js +26 -0
  11. package/dist/cli/commands/order.js +81 -0
  12. package/dist/cli/commands/position.js +35 -0
  13. package/dist/cli/commands/schema.js +5 -0
  14. package/dist/cli/commands/self-check.js +24 -0
  15. package/dist/cli/commands/trade-gate.js +26 -0
  16. package/dist/cli/commands/trade.js +30 -0
  17. package/dist/cli/commands/update.js +27 -0
  18. package/dist/cli/envelope.js +29 -0
  19. package/dist/cli/help.js +34 -0
  20. package/dist/cli/invocation-checker.js +39 -0
  21. package/dist/cli/mcp-entry.js +4 -0
  22. package/dist/cli/parser.js +87 -0
  23. package/dist/core/audit/redaction.js +56 -0
  24. package/dist/core/audit/writer.js +27 -0
  25. package/dist/core/client/capability-executor.js +400 -0
  26. package/dist/core/client/rapid-x-client.js +156 -0
  27. package/dist/core/client/signing.js +24 -0
  28. package/dist/core/client/symbol.js +36 -0
  29. package/dist/core/config/credential.js +42 -0
  30. package/dist/core/config/resolve.js +24 -0
  31. package/dist/core/contracts/capabilities.js +77 -0
  32. package/dist/core/contracts/compatibility.js +65 -0
  33. package/dist/core/contracts/events.js +29 -0
  34. package/dist/core/contracts/evidence.js +7 -0
  35. package/dist/core/contracts/input-schema.js +370 -0
  36. package/dist/core/contracts/types.js +1 -0
  37. package/dist/core/errors/product-error.js +74 -0
  38. package/dist/core/index.js +24 -0
  39. package/dist/core/safety/policy.js +215 -0
  40. package/dist/core/safety/raw-api.js +19 -0
  41. package/dist/core/self-check/live-read-only-probes.js +25 -0
  42. package/dist/core/self-check/live-trading-verification-probes.js +252 -0
  43. package/dist/core/self-check/run-self-check.js +91 -0
  44. package/dist/core/trading/preview.js +330 -0
  45. package/dist/core/trading/trading-verification.js +137 -0
  46. package/dist/core/update/check-update.js +295 -0
  47. package/dist/core/version.js +1 -0
  48. package/dist/mcp/audit.js +10 -0
  49. package/dist/mcp/server.js +73 -0
  50. package/dist/mcp/tool-registry.js +31 -0
  51. package/dist/mcp/tool-runner.js +144 -0
  52. package/package.json +48 -0
  53. package/packages/distribution/docs/cli-only-agent.md +12 -0
  54. package/packages/distribution/docs/cli.md +49 -0
  55. package/packages/distribution/docs/index.md +58 -0
  56. package/packages/distribution/docs/mcp.md +36 -0
  57. package/packages/distribution/docs/quickstart.md +129 -0
  58. package/packages/distribution/docs/self-check.md +7 -0
  59. package/packages/distribution/docs/skills.md +140 -0
  60. package/packages/distribution/docs/tools.md +35 -0
  61. package/packages/distribution/docs/trading-verification.md +17 -0
  62. package/packages/distribution/docs/troubleshooting/index.md +27 -0
  63. package/packages/distribution/manifests/offline-manifest.json +26 -0
  64. package/packages/distribution/registry/rapidx.mcp.json +26 -0
@@ -0,0 +1,215 @@
1
+ import { createHash } from "node:crypto";
2
+ import { buildCompatibilityReport } from "../contracts/compatibility.js";
3
+ import { ProductError } from "../errors/product-error.js";
4
+ const TRADING_DOMAINS = new Set(["account", "order", "position", "algo"]);
5
+ export function defaultSafetyPolicy() {
6
+ const policy = {
7
+ readOnly: process.env.RAPIDX_READ_ONLY === "true",
8
+ tradingEnabled: process.env.RAPIDX_TRADING_ENABLED === "true",
9
+ duplicateWindowMs: 30_000,
10
+ requirePostOnlyForVerification: true
11
+ };
12
+ if (process.env.RAPIDX_MAX_NOTIONAL) {
13
+ policy.maxNotional = process.env.RAPIDX_MAX_NOTIONAL;
14
+ }
15
+ return policy;
16
+ }
17
+ export function makeSafetyState(nowMs = () => Date.now()) {
18
+ return {
19
+ recentClientOrderIds: new Map(),
20
+ nowMs
21
+ };
22
+ }
23
+ export function stableParamsHash(input) {
24
+ return createHash("sha256").update(stableStringify(input)).digest("hex");
25
+ }
26
+ export function stableStringify(input) {
27
+ if (Array.isArray(input)) {
28
+ return `[${input.map((item) => stableStringify(item)).join(",")}]`;
29
+ }
30
+ if (input && typeof input === "object") {
31
+ return `{${Object.entries(input)
32
+ .sort(([a], [b]) => a.localeCompare(b))
33
+ .map(([key, value]) => `${JSON.stringify(key)}:${stableStringify(value)}`)
34
+ .join(",")}}`;
35
+ }
36
+ return JSON.stringify(input);
37
+ }
38
+ export function evaluateSafety(capability, input, policy, state = makeSafetyState()) {
39
+ const paramsHash = stableParamsHash(input);
40
+ if (capability.operationType !== "TRADE_WRITE") {
41
+ return { allowed: true, paramsHash };
42
+ }
43
+ if (policy.readOnly) {
44
+ return { allowed: false, code: "RCORE21001", reason: "Read-only mode blocks trade write operations.", paramsHash };
45
+ }
46
+ if (!policy.tradingEnabled) {
47
+ return { allowed: false, code: "RCORE21001", reason: "Trading is not enabled.", paramsHash };
48
+ }
49
+ const compatibility = buildCompatibilityReport({
50
+ mcpSchemaVersion: stringValue(input.mcpSchemaVersion),
51
+ skillsVersion: stringValue(input.skillsVersion),
52
+ skillsSchemaVersion: stringValue(input.skillsSchemaVersion)
53
+ });
54
+ if (compatibility.status === "BLOCKED") {
55
+ return { allowed: false, code: "RCORE30001", reason: "CLI/MCP/Skills version compatibility check failed.", paramsHash };
56
+ }
57
+ const domain = capability.capabilityId.split(".")[0] ?? "";
58
+ if (TRADING_DOMAINS.has(domain)) {
59
+ const missing = requiredTradeFields(capability, input);
60
+ if (missing.length > 0) {
61
+ return {
62
+ allowed: false,
63
+ code: "RCORE00001",
64
+ reason: `Missing required trade fields: ${missing.join(", ")}.`,
65
+ paramsHash
66
+ };
67
+ }
68
+ }
69
+ if (capability.capabilityId === "position.set-leverage") {
70
+ const leverage = Number(input.leverage);
71
+ if (!Number.isInteger(leverage) || leverage < 1 || leverage > 125) {
72
+ return { allowed: false, code: "RCORE00001", reason: "leverage must be an integer from 1 to 125.", paramsHash };
73
+ }
74
+ }
75
+ if (capability.capabilityId === "account.set-position-mode") {
76
+ const mode = String(input.mode ?? "");
77
+ if (mode !== "BOTH" && mode !== "NET") {
78
+ return { allowed: false, code: "RCORE00001", reason: "mode must be BOTH or NET.", paramsHash };
79
+ }
80
+ }
81
+ if (String(input.orderType ?? "").toUpperCase() === "MARKET" && (policy.marketOrderPolicy ?? "BLOCK_BY_DEFAULT") === "BLOCK_BY_DEFAULT") {
82
+ return { allowed: false, code: "RCORE20002", reason: "Market orders are blocked by default.", paramsHash };
83
+ }
84
+ if (requiresNotionalLimit(capability)) {
85
+ const maxNotional = input.maxNotional ?? policy.maxNotional;
86
+ if (!maxNotional) {
87
+ return { allowed: false, code: "RCORE00001", reason: "maxNotional is required for exposure-changing trade writes.", paramsHash };
88
+ }
89
+ const notional = input.notional ?? estimateDirectNotional(input);
90
+ if (notional && Number(notional) > Number(maxNotional)) {
91
+ return { allowed: false, code: "RCORE24001", reason: "Requested notional exceeds maxNotional.", paramsHash };
92
+ }
93
+ }
94
+ const clientOrderId = typeof input.clientOrderId === "string" ? input.clientOrderId : undefined;
95
+ if (clientOrderId) {
96
+ const now = state.nowMs();
97
+ const seenAt = state.recentClientOrderIds.get(clientOrderId);
98
+ if (seenAt && now - seenAt < policy.duplicateWindowMs) {
99
+ return { allowed: false, code: "RCORE24004", reason: "Duplicate clientOrderId detected.", paramsHash };
100
+ }
101
+ state.recentClientOrderIds.set(clientOrderId, now);
102
+ }
103
+ return { allowed: true, paramsHash };
104
+ }
105
+ function requiredTradeFields(capability, input) {
106
+ const missing = [];
107
+ switch (capability.capabilityId) {
108
+ case "order.preview":
109
+ case "order.place-preview":
110
+ case "order.place":
111
+ case "algo.place":
112
+ requireString(input, "symbol", missing);
113
+ requireString(input, "side", missing);
114
+ requireString(input, "orderType", missing);
115
+ requireString(input, "clientOrderId", missing);
116
+ requireString(input, "maxNotional", missing);
117
+ if (!hasString(input, "quantity") && !hasString(input, "amount")) {
118
+ missing.push("quantity or amount");
119
+ }
120
+ if (String(input.orderType ?? "").toUpperCase() === "LIMIT") {
121
+ requireString(input, "price", missing);
122
+ }
123
+ if (capability.capabilityId === "algo.place") {
124
+ requireString(input, "algoType", missing);
125
+ }
126
+ break;
127
+ case "order.amend":
128
+ requireOneOf(input, ["orderId", "clientOrderId"], "orderId or clientOrderId", missing);
129
+ if (!hasString(input, "price") && !hasString(input, "quantity")) {
130
+ missing.push("price or quantity");
131
+ }
132
+ break;
133
+ case "order.cancel":
134
+ requireOneOf(input, ["orderId", "clientOrderId"], "orderId or clientOrderId", missing);
135
+ break;
136
+ case "position.close":
137
+ requireString(input, "symbol", missing);
138
+ if (input.reduceOnly !== true) {
139
+ missing.push("reduceOnly=true");
140
+ }
141
+ requireString(input, "maxNotional", missing);
142
+ break;
143
+ case "position.set-leverage":
144
+ requireString(input, "symbol", missing);
145
+ if (input.leverage === undefined) {
146
+ missing.push("leverage");
147
+ }
148
+ break;
149
+ case "account.set-position-mode":
150
+ requireString(input, "mode", missing);
151
+ requireString(input, "exchange", missing);
152
+ break;
153
+ case "algo.amend":
154
+ requireOneOf(input, ["algoOrderId", "clientOrderId"], "algoOrderId or clientOrderId", missing);
155
+ break;
156
+ case "algo.cancel":
157
+ requireOneOf(input, ["algoOrderId", "clientOrderId"], "algoOrderId or clientOrderId", missing);
158
+ break;
159
+ }
160
+ return missing;
161
+ }
162
+ function requiresNotionalLimit(capability) {
163
+ return capability.capabilityId === "order.preview"
164
+ || capability.capabilityId === "order.place-preview"
165
+ || capability.capabilityId === "order.place"
166
+ || capability.capabilityId === "algo.place"
167
+ || capability.capabilityId === "position.close";
168
+ }
169
+ function estimateDirectNotional(input) {
170
+ const symbol = typeof input.symbol === "string" ? input.symbol : "";
171
+ if (!symbol.startsWith("BINANCE_")) {
172
+ return undefined;
173
+ }
174
+ const price = parsePositiveNumber(input.price);
175
+ const quantity = parsePositiveNumber(input.quantity);
176
+ if (price === undefined || quantity === undefined) {
177
+ return undefined;
178
+ }
179
+ return String(price * quantity);
180
+ }
181
+ function parsePositiveNumber(value) {
182
+ if (typeof value !== "string" && typeof value !== "number") {
183
+ return undefined;
184
+ }
185
+ const parsed = Number(value);
186
+ if (!Number.isFinite(parsed) || parsed <= 0) {
187
+ return undefined;
188
+ }
189
+ return parsed;
190
+ }
191
+ function stringValue(value) {
192
+ return typeof value === "string" && value.length > 0 ? value : undefined;
193
+ }
194
+ function hasString(input, field) {
195
+ return typeof input[field] === "string" && input[field].length > 0;
196
+ }
197
+ function requireString(input, field, missing) {
198
+ if (!hasString(input, field)) {
199
+ missing.push(field);
200
+ }
201
+ }
202
+ function requireOneOf(input, fields, label, missing) {
203
+ if (!fields.some((field) => hasString(input, field))) {
204
+ missing.push(label);
205
+ }
206
+ }
207
+ export function assertSafety(decision) {
208
+ if (!decision.allowed) {
209
+ throw new ProductError({
210
+ code: decision.code ?? "RCORE20001",
211
+ status: "BLOCKED",
212
+ message: decision.reason ?? "Safety policy blocked the operation."
213
+ });
214
+ }
215
+ }
@@ -0,0 +1,19 @@
1
+ import { ProductError } from "../errors/product-error.js";
2
+ const RAW_TRADING_WRITE_DOMAINS = new Set(["order", "position", "algo", "trading"]);
3
+ export function assertRawApiAllowed(input) {
4
+ if (input.bypassesAuth || input.bypassesPreview || input.bypassesSafety || input.bypassesAudit) {
5
+ return new ProductError({
6
+ code: "RCORE25002",
7
+ status: "BLOCKED",
8
+ message: "Raw API cannot bypass authentication, preview, safety policy, or audit."
9
+ });
10
+ }
11
+ if (input.operationType === "WRITE" && RAW_TRADING_WRITE_DOMAINS.has(input.domain)) {
12
+ return new ProductError({
13
+ code: "RCORE25001",
14
+ status: "BLOCKED",
15
+ message: "Raw trading write operations are not allowed."
16
+ });
17
+ }
18
+ return null;
19
+ }
@@ -0,0 +1,25 @@
1
+ import { executeRapidXCapability } from "../client/capability-executor.js";
2
+ import { resolveCredential } from "../config/resolve.js";
3
+ export function buildLiveReadOnlySelfCheck(input = {}) {
4
+ const credential = tryResolveEnvCredential();
5
+ if (!credential) {
6
+ return {};
7
+ }
8
+ const symbol = typeof input.symbol === "string" ? input.symbol : "BINANCE_PERP_BTC_USDT";
9
+ const probes = {
10
+ publicMarket: async () => executeRapidXCapability("market.ticker", { symbol }),
11
+ credentialAuth: async () => executeRapidXCapability("account.overview", {}),
12
+ accountRead: async () => executeRapidXCapability("account.balance", {}),
13
+ orderRead: async () => executeRapidXCapability("order.list", {}),
14
+ positionRead: async () => executeRapidXCapability("position.list", {})
15
+ };
16
+ return { credential, probes };
17
+ }
18
+ function tryResolveEnvCredential() {
19
+ try {
20
+ return resolveCredential({ ref: { source: "env" } });
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
@@ -0,0 +1,252 @@
1
+ import { executeRapidXCapability } from "../client/capability-executor.js";
2
+ import { CORE_ERRORS, ProductError } from "../errors/product-error.js";
3
+ import { buildLiveReadOnlySelfCheck } from "./live-read-only-probes.js";
4
+ export function buildLiveTradingVerificationProbes(input = {}) {
5
+ const readOnly = buildLiveReadOnlySelfCheck(input).probes ?? {};
6
+ return {
7
+ ...readOnly,
8
+ marketRules: async (verificationInput) => buildMarketRules(verificationInput),
9
+ placeOrder: async (orderInput) => {
10
+ const result = await executeRapidXCapability("order.place", {
11
+ symbol: orderInput.symbol,
12
+ side: orderInput.side,
13
+ orderType: "LIMIT",
14
+ price: orderInput.price,
15
+ quantity: orderInput.quantity,
16
+ maxNotional: orderInput.maxNotional,
17
+ clientOrderId: orderInput.clientOrderId,
18
+ postOnly: true,
19
+ previewId: orderInput.previewId,
20
+ continueConsentId: continueConsentId(orderInput.clientOrderId, "place")
21
+ });
22
+ const data = extractDataObject(result);
23
+ const orderId = stringField(data, "orderId") ?? orderInput.clientOrderId;
24
+ return {
25
+ orderId,
26
+ requestId: stringField(data, "clientOrderId") ?? orderInput.clientOrderId,
27
+ status: normalizeOrderStatus(stringField(data, "orderState") ?? "OPEN")
28
+ };
29
+ },
30
+ queryOrder: async (order) => queryOrder(order),
31
+ cancelOrder: async (order) => {
32
+ const result = await executeRapidXCapability("order.cancel", {
33
+ orderId: order.orderId,
34
+ previewId: `internal-${order.orderId}`,
35
+ continueConsentId: continueConsentId(order.orderId, "cancel")
36
+ });
37
+ const data = extractDataObject(result);
38
+ const status = normalizeOrderStatus(stringField(data, "orderState") ?? stringField(data, "action") ?? "");
39
+ if (status === "CANCELED") {
40
+ return { orderId: order.orderId, status };
41
+ }
42
+ return queryOrder(order);
43
+ },
44
+ cleanupCheck: async (_order) => cleanupCheck(input)
45
+ };
46
+ }
47
+ async function buildMarketRules(input) {
48
+ const [symbolInfoResult, orderbookResult] = await Promise.all([
49
+ executeRapidXCapability("market.symbol-info", { symbol: input.symbol }),
50
+ executeRapidXCapability("market.orderbook", { symbol: input.symbol, depth: 5 })
51
+ ]);
52
+ const symbolInfo = extractSymbolInfo(symbolInfoResult, input.symbol);
53
+ const orderbook = extractDataObject(orderbookResult);
54
+ const bestBid = nestedStringField(orderbook, "bestBid", "price");
55
+ const bestAsk = nestedStringField(orderbook, "bestAsk", "price");
56
+ const minNotional = stringField(symbolInfo, "minNotional") ?? "";
57
+ const tickSize = stringField(symbolInfo, "tickSize") ?? "0.000001";
58
+ const lotSize = stringField(symbolInfo, "lotSize") ?? "0.000001";
59
+ const order = choosePostOnlyVerificationOrder({
60
+ side: input.side,
61
+ maxNotional: input.maxNotional,
62
+ minNotional,
63
+ tickSize,
64
+ lotSize,
65
+ bestBid,
66
+ bestAsk
67
+ });
68
+ return {
69
+ minNotional,
70
+ tickSize,
71
+ lotSize,
72
+ bestBid,
73
+ bestAsk,
74
+ orderPrice: order.price,
75
+ orderQuantity: order.quantity
76
+ };
77
+ }
78
+ async function queryOrder(order) {
79
+ const result = await executeRapidXCapability("order.get", { orderId: order.orderId });
80
+ const data = extractDataObject(result);
81
+ const requestId = stringField(data, "clientOrderId") ?? order.requestId;
82
+ return {
83
+ orderId: stringField(data, "orderId") ?? order.orderId,
84
+ status: normalizeOrderStatus(stringField(data, "orderState") ?? ""),
85
+ ...(requestId ? { requestId } : {})
86
+ };
87
+ }
88
+ async function cleanupCheck(input) {
89
+ const symbol = typeof input.symbol === "string" ? input.symbol : "BINANCE_PERP_BTC_USDT";
90
+ const [ordersResult, positionsResult] = await Promise.all([
91
+ executeRapidXCapability("order.list", { symbol, pageSize: 20 }),
92
+ executeRapidXCapability("position.list", { symbol })
93
+ ]);
94
+ const orders = extractList(extractDataObject(ordersResult));
95
+ const positions = extractArrayData(positionsResult);
96
+ return {
97
+ openOrders: orders.filter((order) => {
98
+ const state = normalizeOrderStatus(stringField(order, "orderState") ?? "OPEN");
99
+ const orderSymbol = stringField(order, "sym") ?? stringField(order, "symbol") ?? symbol;
100
+ return orderSymbol === symbol && state !== "CANCELED" && state !== "REJECTED" && state !== "FILLED";
101
+ }).length,
102
+ unexpectedPositions: positions.filter((position) => {
103
+ const positionSymbol = stringField(position, "sym") ?? stringField(position, "symbol");
104
+ return positionSymbol === symbol && Number(stringField(position, "positionQty") ?? "0") !== 0;
105
+ }).length
106
+ };
107
+ }
108
+ function choosePostOnlyVerificationOrder(input) {
109
+ const maxNotional = finitePositiveNumber(input.maxNotional, "maxNotional");
110
+ const minNotional = finitePositiveNumber(input.minNotional, "minNotional");
111
+ const tickSize = finitePositiveNumber(input.tickSize, "tickSize");
112
+ const lotSize = finitePositiveNumber(input.lotSize, "lotSize");
113
+ const referencePrice = finitePositiveNumber(input.side === "BUY" ? input.bestBid : input.bestAsk, "reference price");
114
+ let quantity = ceilToStep(minNotional / referencePrice, lotSize);
115
+ if (quantity <= 0) {
116
+ quantity = lotSize;
117
+ }
118
+ let price;
119
+ if (input.side === "BUY") {
120
+ const maxPriceByNotional = floorToStep(maxNotional / quantity, tickSize);
121
+ price = floorToStep(Math.min(referencePrice, maxPriceByNotional), tickSize);
122
+ }
123
+ else {
124
+ price = ceilToStep(referencePrice + tickSize, tickSize);
125
+ }
126
+ if (price <= 0 || price * quantity > maxNotional + 1e-9 || price * quantity < minNotional - 1e-9) {
127
+ throw new ProductError({
128
+ code: "RCORE24001",
129
+ status: "BLOCKED",
130
+ message: "No post-only verification order can satisfy minNotional and maxNotional."
131
+ });
132
+ }
133
+ return {
134
+ price: formatStepValue(price, input.tickSize),
135
+ quantity: formatStepValue(quantity, input.lotSize)
136
+ };
137
+ }
138
+ function finitePositiveNumber(value, label) {
139
+ const number = Number(value);
140
+ if (!Number.isFinite(number) || number <= 0) {
141
+ throw new ProductError({
142
+ code: CORE_ERRORS.SCHEMA_INVALID,
143
+ status: "FAIL",
144
+ message: `Invalid ${label}.`
145
+ });
146
+ }
147
+ return number;
148
+ }
149
+ function ceilToStep(value, step) {
150
+ return Math.ceil((value / step) - 1e-12) * step;
151
+ }
152
+ function floorToStep(value, step) {
153
+ return Math.floor((value / step) + 1e-12) * step;
154
+ }
155
+ function formatStepValue(value, step) {
156
+ const decimals = decimalPlaces(step);
157
+ return decimals === 0 ? String(Math.round(value)) : value.toFixed(decimals);
158
+ }
159
+ function decimalPlaces(step) {
160
+ const fraction = step.split(".")[1];
161
+ return fraction ? fraction.length : 0;
162
+ }
163
+ function continueConsentId(id, action) {
164
+ return `trade-verify-${action}-${id}`;
165
+ }
166
+ function extractSymbolInfo(result, symbol) {
167
+ const data = extractData(result);
168
+ if (data && typeof data === "object") {
169
+ const record = data;
170
+ const keyed = record[symbol];
171
+ if (keyed && typeof keyed === "object") {
172
+ return keyed;
173
+ }
174
+ return record;
175
+ }
176
+ throw new ProductError({
177
+ code: CORE_ERRORS.UPSTREAM_REJECTED,
178
+ status: "FAIL",
179
+ message: "RapidX symbol info response is missing data."
180
+ });
181
+ }
182
+ function extractDataObject(result) {
183
+ const data = extractData(result);
184
+ if (data && typeof data === "object" && !Array.isArray(data)) {
185
+ return data;
186
+ }
187
+ return {};
188
+ }
189
+ function extractArrayData(result) {
190
+ const data = extractData(result);
191
+ if (Array.isArray(data)) {
192
+ return data.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item));
193
+ }
194
+ return [];
195
+ }
196
+ function extractList(data) {
197
+ const list = data.list;
198
+ if (!Array.isArray(list)) {
199
+ return [];
200
+ }
201
+ return list.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item));
202
+ }
203
+ function extractData(result) {
204
+ if (result && typeof result === "object" && Object.prototype.hasOwnProperty.call(result, "data")) {
205
+ return result.data;
206
+ }
207
+ return result;
208
+ }
209
+ function stringField(record, field) {
210
+ const value = record[field];
211
+ return value === undefined || value === null || value === "" ? undefined : String(value);
212
+ }
213
+ function nestedStringField(record, parent, field) {
214
+ const value = record[parent];
215
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
216
+ throw new ProductError({
217
+ code: CORE_ERRORS.UPSTREAM_REJECTED,
218
+ status: "FAIL",
219
+ message: `RapidX orderbook response is missing ${parent}.${field}.`
220
+ });
221
+ }
222
+ const result = stringField(value, field);
223
+ if (!result) {
224
+ throw new ProductError({
225
+ code: CORE_ERRORS.UPSTREAM_REJECTED,
226
+ status: "FAIL",
227
+ message: `RapidX orderbook response is missing ${parent}.${field}.`
228
+ });
229
+ }
230
+ return result;
231
+ }
232
+ function normalizeOrderStatus(state) {
233
+ switch (state.toUpperCase()) {
234
+ case "NEW":
235
+ case "OPEN":
236
+ case "ACCEPTED":
237
+ case "CANCEL_PENDING":
238
+ return "ACCEPTED";
239
+ case "PARTIALLY_FILLED":
240
+ return "PARTIALLY_FILLED";
241
+ case "FILLED":
242
+ return "FILLED";
243
+ case "CANCELLED":
244
+ case "CANCELED":
245
+ case "CANCEL_COMPLETE":
246
+ return "CANCELED";
247
+ case "REJECTED":
248
+ return "REJECTED";
249
+ default:
250
+ return "UNKNOWN";
251
+ }
252
+ }
@@ -0,0 +1,91 @@
1
+ import { getSchemaResult } from "../contracts/capabilities.js";
2
+ import { ProductError } from "../errors/product-error.js";
3
+ import { checkForUpdate } from "../update/check-update.js";
4
+ export async function runSelfCheck(options = {}) {
5
+ const scope = options.input?.scope ?? "quick";
6
+ const probes = options.probes ?? {};
7
+ const checks = [];
8
+ const evidence = [];
9
+ function add(item) {
10
+ checks.push(item);
11
+ evidence.push({
12
+ source: item.source,
13
+ toolOrCommandEvidence: item.name,
14
+ timestamp: new Date().toISOString()
15
+ });
16
+ }
17
+ add({ name: "discovery", status: "PASS", source: "local_check", message: `${getSchemaResult().capabilities.length} capabilities loaded.` });
18
+ add({ name: "schema-compatibility", status: "PASS", source: "local_check", message: "Canonical schema is available." });
19
+ let update;
20
+ if (options.input?.checkUpdates) {
21
+ update = await checkForUpdate();
22
+ add({
23
+ name: "update-status",
24
+ status: statusForUpdate(update),
25
+ source: "local_check",
26
+ message: `Release manifest status is ${update.status}.`
27
+ });
28
+ }
29
+ await runProbe("public-market", probes.publicMarket, add);
30
+ if (options.credential) {
31
+ if (probes.credentialAuth) {
32
+ await runProbe("credential-auth", probes.credentialAuth, add);
33
+ }
34
+ else {
35
+ add({ name: "credential-auth", status: "NOT_VERIFIED", source: "real_tool_call", message: "Credential exists but no real auth probe was provided." });
36
+ }
37
+ }
38
+ else {
39
+ add({ name: "credential-auth", status: "NOT_VERIFIED", source: "real_tool_call", message: "Portfolio credential was not provided." });
40
+ }
41
+ if (scope === "deep") {
42
+ await runProbe("account-balance", probes.accountRead, add);
43
+ await runProbe("order-list", probes.orderRead, add);
44
+ await runProbe("position-list", probes.positionRead, add);
45
+ }
46
+ const status = checks.some((check) => check.status === "FAIL")
47
+ ? "FAIL"
48
+ : checks.some((check) => check.status === "BLOCKED")
49
+ ? "BLOCKED"
50
+ : checks.some((check) => check.status === "NOT_VERIFIED")
51
+ ? "NOT_VERIFIED"
52
+ : checks.some((check) => check.status === "EXPECTED_ERROR")
53
+ ? "EXPECTED_ERROR"
54
+ : checks.every((check) => check.status === "PASS")
55
+ ? "PASS"
56
+ : "NOT_VERIFIED";
57
+ return {
58
+ scope,
59
+ status,
60
+ checks,
61
+ notVerifiedItems: checks.filter((check) => check.status === "NOT_VERIFIED").map((check) => check.name),
62
+ evidence,
63
+ ...(update ? { update } : {})
64
+ };
65
+ }
66
+ function statusForUpdate(update) {
67
+ if (update.status === "WRITE_BLOCKED" || update.status === "UPGRADE_REQUIRED") {
68
+ return "BLOCKED";
69
+ }
70
+ if (update.status === "UNKNOWN") {
71
+ return "NOT_VERIFIED";
72
+ }
73
+ return "PASS";
74
+ }
75
+ async function runProbe(name, probe, add) {
76
+ if (!probe) {
77
+ add({ name, status: "NOT_VERIFIED", source: "real_tool_call", message: "No real probe was provided; PASS cannot be asserted." });
78
+ return;
79
+ }
80
+ try {
81
+ await probe();
82
+ add({ name, status: "PASS", source: "real_tool_call", message: "Real probe completed." });
83
+ }
84
+ catch (error) {
85
+ if (error instanceof ProductError && error.status === "EXPECTED_ERROR") {
86
+ add({ name, status: "EXPECTED_ERROR", source: "real_tool_call", message: error.message });
87
+ return;
88
+ }
89
+ add({ name, status: "FAIL", source: "real_tool_call", message: error instanceof Error ? error.message : String(error) });
90
+ }
91
+ }