@aicoin/aicoin-mcp 1.0.3 → 1.0.5
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 +8 -7
- package/build/index.js +142 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ AiCoin MCP Server — real-time crypto market data & exchange trading for any AI
|
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
|
-
Works out of the box — a free API key is built in (
|
|
9
|
+
Works out of the box — a free API key is built in (15 req/min, IP rate-limited).
|
|
10
10
|
|
|
11
11
|
### Claude Code
|
|
12
12
|
|
|
@@ -137,12 +137,13 @@ Exchange API keys are stored locally only and never sent to AiCoin servers.
|
|
|
137
137
|
|
|
138
138
|
## API Tiers
|
|
139
139
|
|
|
140
|
-
| Tier | Price | Rate Limit | Features |
|
|
141
|
-
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
140
|
+
| Tier | Price | Rate Limit | Monthly Quota | Features |
|
|
141
|
+
|------|-------|------------|---------------|----------|
|
|
142
|
+
| Free | $0 | 15 req/min | 20K | Market, coin & special data |
|
|
143
|
+
| Basic | $29/mo | 30 req/min | 20K | + Content data |
|
|
144
|
+
| Standard | $79/mo | 80 req/min | 500K | + Content data |
|
|
145
|
+
| Advanced | $299/mo | 300 req/min | 1.5M | + Content data, commercial use |
|
|
146
|
+
| Professional | $699/mo | 1200 req/min | 3.5M | + Content data, commercial use |
|
|
146
147
|
|
|
147
148
|
Upgrade at [aicoin.com/opendata](https://www.aicoin.com/opendata). Your existing key is automatically upgraded.
|
|
148
149
|
|
package/build/index.js
CHANGED
|
@@ -741,7 +741,7 @@ function generateSignature(accessKeyId, accessSecret) {
|
|
|
741
741
|
// src/client/api.ts
|
|
742
742
|
var DOMAIN = process.env.AICOIN_BASE_URL || "https://open.aicoin.com";
|
|
743
743
|
var TIMEOUT_MS = 3e4;
|
|
744
|
-
var UPGRADE_GUIDE = "\n\n--- How to fix ---\nYour current API tier does not have access to this endpoint. Please upgrade your plan.\n1. Visit https://www.aicoin.com/opendata to purchase or upgrade\n2. Tiers: Basic(
|
|
744
|
+
var UPGRADE_GUIDE = "\n\n--- How to fix ---\nYour current API tier does not have access to this endpoint. Please upgrade your plan.\n1. Visit https://www.aicoin.com/opendata to purchase or upgrade\n2. Tiers: Free($0) | Basic($29/mo) | Standard($79/mo) | Advanced($299/mo) | Professional($699/mo)\n3. Set AICOIN_ACCESS_KEY_ID and AICOIN_ACCESS_SECRET in your MCP config\n4. Restart your MCP client";
|
|
745
745
|
var AUTH_GUIDE = "\n\n--- How to fix ---\nAn API key is required to access this endpoint.\n1. Visit https://www.aicoin.com/opendata to register and create an API key\n2. Set AICOIN_ACCESS_KEY_ID and AICOIN_ACCESS_SECRET in your MCP config\n3. Restart your MCP client";
|
|
746
746
|
function throwApiError(status, body, path) {
|
|
747
747
|
let msg = `API ${status}: ${body}`;
|
|
@@ -1109,6 +1109,72 @@ function registerContentTools(server2) {
|
|
|
1109
1109
|
}
|
|
1110
1110
|
}
|
|
1111
1111
|
);
|
|
1112
|
+
server2.tool(
|
|
1113
|
+
"twitter",
|
|
1114
|
+
"Twitter/X crypto tweets.\n\u2022 latest \u2014 latest crypto tweets, cursor-paginated\n\u2022 search \u2014 search tweets by keyword. Requires: keyword\n\u2022 members \u2014 search Twitter KOL/users. Requires: word\n\u2022 interaction_stats \u2014 tweet engagement stats. Requires: flash_ids (POST)",
|
|
1115
|
+
{
|
|
1116
|
+
action: z4.enum(["latest", "search", "members", "interaction_stats"]).describe(
|
|
1117
|
+
"latest: latest crypto tweets; search: search by keyword; members: search KOL/users; interaction_stats: tweet engagement"
|
|
1118
|
+
),
|
|
1119
|
+
keyword: z4.string().optional().describe("REQUIRED for search. Search keyword"),
|
|
1120
|
+
word: z4.string().optional().describe("REQUIRED for members. User search keyword"),
|
|
1121
|
+
flash_ids: z4.string().optional().describe(
|
|
1122
|
+
'REQUIRED for interaction_stats. Comma-separated tweet flash IDs, e.g. "123,456,789" (max 50)'
|
|
1123
|
+
),
|
|
1124
|
+
language: z4.string().optional().describe("For latest/search: language filter (cn, en)"),
|
|
1125
|
+
last_time: z4.string().optional().describe("For latest/search: cursor for pagination (last item timestamp)"),
|
|
1126
|
+
page_size: z4.string().optional().describe("For latest/search: page size, default 20"),
|
|
1127
|
+
page: z4.string().optional().describe("For members: page number, default 1"),
|
|
1128
|
+
size: z4.string().optional().describe("For members: page size, default 20"),
|
|
1129
|
+
...maxItemsParam
|
|
1130
|
+
},
|
|
1131
|
+
async ({ action, keyword, word, flash_ids, language, last_time, page_size, page, size, _max_items }) => {
|
|
1132
|
+
try {
|
|
1133
|
+
switch (action) {
|
|
1134
|
+
case "latest": {
|
|
1135
|
+
const params = {};
|
|
1136
|
+
if (language) params.language = language;
|
|
1137
|
+
if (last_time) params.last_time = last_time;
|
|
1138
|
+
if (page_size) params.page_size = page_size;
|
|
1139
|
+
return okList(
|
|
1140
|
+
await apiGet("/api/upgrade/v2/content/twitter/latest", params),
|
|
1141
|
+
parseMax(_max_items, 20)
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
case "search": {
|
|
1145
|
+
if (!keyword) return err("keyword is required for search action");
|
|
1146
|
+
const params = { keyword };
|
|
1147
|
+
if (language) params.language = language;
|
|
1148
|
+
if (last_time) params.last_time = last_time;
|
|
1149
|
+
if (page_size) params.page_size = page_size;
|
|
1150
|
+
return okList(
|
|
1151
|
+
await apiGet("/api/upgrade/v2/content/twitter/search", params),
|
|
1152
|
+
parseMax(_max_items, 20)
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
case "members": {
|
|
1156
|
+
if (!word) return err("word is required for members action");
|
|
1157
|
+
const params = { word };
|
|
1158
|
+
if (page) params.page = page;
|
|
1159
|
+
if (size) params.size = size;
|
|
1160
|
+
return okList(
|
|
1161
|
+
await apiGet("/api/upgrade/v2/content/twitter/members", params),
|
|
1162
|
+
parseMax(_max_items, 20)
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
case "interaction_stats": {
|
|
1166
|
+
if (!flash_ids) return err("flash_ids is required for interaction_stats action");
|
|
1167
|
+
const ids = flash_ids.split(",").map((s) => Number(s.trim())).filter((n) => !isNaN(n));
|
|
1168
|
+
if (ids.length === 0) return err("flash_ids must contain valid numeric IDs");
|
|
1169
|
+
if (ids.length > 50) return err("flash_ids max 50 items");
|
|
1170
|
+
return ok(await apiPost("/api/upgrade/v2/content/twitter/interaction-stats", { flash_ids: ids }));
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
} catch (e) {
|
|
1174
|
+
return err(e);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1112
1178
|
}
|
|
1113
1179
|
|
|
1114
1180
|
// src/tools/markets.ts
|
|
@@ -1632,15 +1698,17 @@ function registerHyperliquidTools(server2) {
|
|
|
1632
1698
|
);
|
|
1633
1699
|
server2.tool(
|
|
1634
1700
|
"hl_liquidation",
|
|
1635
|
-
"Hyperliquid liquidation data. All actions support optional coin filter.\n\u2022 history \u2014 liquidation history\n\u2022 stats \u2014 aggregate stats\n\u2022 stats_by_coin \u2014 per-coin stats\n\u2022 top_positions \u2014 top liquidated positions",
|
|
1701
|
+
"Hyperliquid liquidation data. All actions support optional coin filter.\n\u2022 history \u2014 liquidation history\n\u2022 stats \u2014 aggregate stats\n\u2022 stats_by_coin \u2014 per-coin stats\n\u2022 top_positions \u2014 top liquidated positions. Requires: coin + interval",
|
|
1636
1702
|
{
|
|
1637
1703
|
action: z7.enum(["history", "stats", "stats_by_coin", "top_positions"]).describe(
|
|
1638
1704
|
"history: liquidation history; stats: aggregate stats; stats_by_coin: per-coin stats; top_positions: top liquidated"
|
|
1639
1705
|
),
|
|
1640
|
-
coin: z7.string().optional().describe("Optional. Coin filter in uppercase, e.g. BTC"),
|
|
1706
|
+
coin: z7.string().optional().describe("Optional. Coin filter in uppercase, e.g. BTC. REQUIRED for top_positions"),
|
|
1707
|
+
interval: z7.string().optional().describe("REQUIRED for top_positions. Interval: 4h, 1d"),
|
|
1708
|
+
limit: z7.string().optional().describe("Optional for top_positions. Max results"),
|
|
1641
1709
|
...maxItemsParam
|
|
1642
1710
|
},
|
|
1643
|
-
async ({ action, coin, _max_items }) => {
|
|
1711
|
+
async ({ action, coin, interval, limit, _max_items }) => {
|
|
1644
1712
|
try {
|
|
1645
1713
|
const params = {};
|
|
1646
1714
|
if (coin) params.coin = coin;
|
|
@@ -1654,11 +1722,16 @@ function registerHyperliquidTools(server2) {
|
|
|
1654
1722
|
return ok(await apiGet("/api/upgrade/v2/hl/liquidations/stat"));
|
|
1655
1723
|
case "stats_by_coin":
|
|
1656
1724
|
return ok(await apiGet("/api/upgrade/v2/hl/liquidations/stat-by-coin", params));
|
|
1657
|
-
case "top_positions":
|
|
1725
|
+
case "top_positions": {
|
|
1726
|
+
if (!coin) return err("coin is required for top_positions action");
|
|
1727
|
+
if (!interval) return err('interval is required for top_positions action (e.g. "4h", "1d")');
|
|
1728
|
+
const tpParams = { coin, interval };
|
|
1729
|
+
if (limit) tpParams.limit = limit;
|
|
1658
1730
|
return okList(
|
|
1659
|
-
await apiGet("/api/upgrade/v2/hl/liquidations/top-positions",
|
|
1731
|
+
await apiGet("/api/upgrade/v2/hl/liquidations/top-positions", tpParams),
|
|
1660
1732
|
parseMax(_max_items, 50)
|
|
1661
1733
|
);
|
|
1734
|
+
}
|
|
1662
1735
|
}
|
|
1663
1736
|
} catch (e) {
|
|
1664
1737
|
return err(e);
|
|
@@ -1799,7 +1872,7 @@ function registerHyperliquidTools(server2) {
|
|
|
1799
1872
|
);
|
|
1800
1873
|
server2.tool(
|
|
1801
1874
|
"hl_fills",
|
|
1802
|
-
"Hyperliquid trade fills.\n\u2022 by_address \u2014 fills by wallet. Requires: address\n\u2022 by_oid \u2014 fills by order ID. Requires: oid\n\u2022 by_twapid \u2014 fills by TWAP ID. Requires: twapid\n\u2022 top_trades \u2014 top trades
|
|
1875
|
+
"Hyperliquid trade fills.\n\u2022 by_address \u2014 fills by wallet. Requires: address\n\u2022 by_oid \u2014 fills by order ID. Requires: oid\n\u2022 by_twapid \u2014 fills by TWAP ID. Requires: twapid\n\u2022 top_trades \u2014 top trades. Requires: interval + coin",
|
|
1803
1876
|
{
|
|
1804
1877
|
action: z7.enum(["by_address", "by_oid", "by_twapid", "top_trades"]).describe(
|
|
1805
1878
|
"by_address: fills by wallet; by_oid: fills by order ID; by_twapid: fills by TWAP ID; top_trades: top trades"
|
|
@@ -1807,8 +1880,8 @@ function registerHyperliquidTools(server2) {
|
|
|
1807
1880
|
address: z7.string().optional().describe("REQUIRED for by_address. Wallet address (0x...)"),
|
|
1808
1881
|
oid: z7.string().optional().describe("REQUIRED for by_oid. Order ID"),
|
|
1809
1882
|
twapid: z7.string().optional().describe("REQUIRED for by_twapid. TWAP ID"),
|
|
1810
|
-
coin: z7.string().optional().describe("Optional. Coin filter in uppercase, e.g. BTC"),
|
|
1811
|
-
interval: z7.string().optional().describe("
|
|
1883
|
+
coin: z7.string().optional().describe("Optional. Coin filter in uppercase, e.g. BTC. REQUIRED for top_trades"),
|
|
1884
|
+
interval: z7.string().optional().describe("REQUIRED for top_trades. Interval: 4h, 1d"),
|
|
1812
1885
|
limit: z7.string().optional().describe("Max results"),
|
|
1813
1886
|
...maxItemsParam
|
|
1814
1887
|
},
|
|
@@ -1834,9 +1907,9 @@ function registerHyperliquidTools(server2) {
|
|
|
1834
1907
|
return ok(await apiGet(`/api/upgrade/v2/hl/fills/twapid/${twapid}`));
|
|
1835
1908
|
}
|
|
1836
1909
|
case "top_trades": {
|
|
1837
|
-
|
|
1838
|
-
if (
|
|
1839
|
-
|
|
1910
|
+
if (!coin) return err("coin is required for top_trades action");
|
|
1911
|
+
if (!interval) return err('interval is required for top_trades action (e.g. "4h", "1d")');
|
|
1912
|
+
const params = { coin, interval };
|
|
1840
1913
|
if (limit) params.limit = limit;
|
|
1841
1914
|
return okList(
|
|
1842
1915
|
await apiGet("/api/upgrade/v2/hl/fills/top-trades", params),
|
|
@@ -1984,9 +2057,10 @@ function registerHyperliquidTools(server2) {
|
|
|
1984
2057
|
window: z7.string().optional().describe('REQUIRED for portfolio ONLY. Time window: "day", "week", "month", "allTime". NOT used by max_drawdown/net_flow'),
|
|
1985
2058
|
period: z7.string().optional().describe("Optional for pnls. Period in days: 0=allTime, 1, 7, 30"),
|
|
1986
2059
|
days: z7.string().optional().describe("REQUIRED for max_drawdown, net_flow. Number of days: 7, 30, 90. NOT used by portfolio"),
|
|
2060
|
+
scope: z7.string().optional().describe('Optional for max_drawdown. Scope: "perp" (default), "all"'),
|
|
1987
2061
|
...maxItemsParam
|
|
1988
2062
|
},
|
|
1989
|
-
async ({ action, address, window: win, period, days, _max_items }) => {
|
|
2063
|
+
async ({ action, address, window: win, period, days, scope, _max_items }) => {
|
|
1990
2064
|
try {
|
|
1991
2065
|
switch (action) {
|
|
1992
2066
|
case "portfolio": {
|
|
@@ -2006,7 +2080,8 @@ function registerHyperliquidTools(server2) {
|
|
|
2006
2080
|
}
|
|
2007
2081
|
case "max_drawdown": {
|
|
2008
2082
|
if (!days) return err("days is required for max_drawdown action");
|
|
2009
|
-
|
|
2083
|
+
const params = { days, scope: scope || "perp" };
|
|
2084
|
+
return ok(await apiGet(`/api/upgrade/v2/hl/max-drawdown/${address}`, params));
|
|
2010
2085
|
}
|
|
2011
2086
|
case "net_flow": {
|
|
2012
2087
|
if (!days) return err("days is required for net_flow action");
|
|
@@ -2099,12 +2174,13 @@ Visit https://www.aicoin.com/opendata to manage your API keys.
|
|
|
2099
2174
|
## Step 3: Create API Key
|
|
2100
2175
|
Click "Create Key" and select your desired tier:
|
|
2101
2176
|
|
|
2102
|
-
| Tier | Price | Rate Limit | Features |
|
|
2103
|
-
|
|
2104
|
-
|
|
|
2105
|
-
|
|
|
2106
|
-
|
|
|
2107
|
-
|
|
|
2177
|
+
| Tier | Price | Rate Limit | Monthly Quota | Features |
|
|
2178
|
+
|------|-------|------------|---------------|----------|
|
|
2179
|
+
| Free | $0 | 15 req/min | 20K | Market, coin & special data |
|
|
2180
|
+
| Basic | $29/mo | 30 req/min | 20K | + Content data |
|
|
2181
|
+
| Standard | $79/mo | 80 req/min | 500K | + Content data |
|
|
2182
|
+
| Advanced | $299/mo | 300 req/min | 1.5M | + Content data, commercial use |
|
|
2183
|
+
| Professional | $699/mo | 1200 req/min | 3.5M | + Content data, commercial use |
|
|
2108
2184
|
|
|
2109
2185
|
## Step 4: Configure MCP Server
|
|
2110
2186
|
After creating the key, you will get two values:
|
|
@@ -2139,18 +2215,15 @@ ${endpoint ? `Endpoint: ${endpoint}` : ""}
|
|
|
2139
2215
|
|
|
2140
2216
|
## API Tier Comparison
|
|
2141
2217
|
|
|
2142
|
-
| Feature | Basic (
|
|
2143
|
-
|
|
2144
|
-
| Market
|
|
2145
|
-
|
|
|
2146
|
-
|
|
|
2147
|
-
|
|
|
2148
|
-
|
|
|
2149
|
-
|
|
|
2150
|
-
|
|
|
2151
|
-
| Depth data | \u274C | \u274C | \u2705 | \u2705 |
|
|
2152
|
-
| Strategy signals | \u274C | \u274C | \u274C | \u2705 |
|
|
2153
|
-
| Full depth & grouped | \u274C | \u274C | \u274C | \u2705 |
|
|
2218
|
+
| Feature | Free ($0) | Basic ($29) | Standard ($79) | Advanced ($299) | Professional ($699) |
|
|
2219
|
+
|---------|:---:|:---:|:---:|:---:|:---:|
|
|
2220
|
+
| Market data | \u2705 | \u2705 | \u2705 | \u2705 | \u2705 |
|
|
2221
|
+
| Coin data | \u2705 | \u2705 | \u2705 | \u2705 | \u2705 |
|
|
2222
|
+
| Special data | \u2705 | \u2705 | \u2705 | \u2705 | \u2705 |
|
|
2223
|
+
| Content data | \u274C | \u2705 | \u2705 | \u2705 | \u2705 |
|
|
2224
|
+
| Rate limit | 15/min | 30/min | 80/min | 300/min | 1200/min |
|
|
2225
|
+
| Monthly quota | 20K | 20K | 500K | 1.5M | 3.5M |
|
|
2226
|
+
| Commercial use | \u274C | \u274C | \u274C | \u2705 | \u2705 |
|
|
2154
2227
|
|
|
2155
2228
|
## How to Upgrade
|
|
2156
2229
|
1. Visit https://www.aicoin.com/opendata
|
|
@@ -2289,6 +2362,21 @@ var FreqtradeClient = class {
|
|
|
2289
2362
|
);
|
|
2290
2363
|
}
|
|
2291
2364
|
if (res.status === 503) {
|
|
2365
|
+
const endpoint = url.pathname.replace("/api/v1/", "");
|
|
2366
|
+
const devEndpoints = [
|
|
2367
|
+
"strategies",
|
|
2368
|
+
"strategy/",
|
|
2369
|
+
"available_pairs",
|
|
2370
|
+
"backtest",
|
|
2371
|
+
"pair_history",
|
|
2372
|
+
"freqaimodels"
|
|
2373
|
+
];
|
|
2374
|
+
const isDev = devEndpoints.some((e) => endpoint.startsWith(e));
|
|
2375
|
+
if (isDev) {
|
|
2376
|
+
throw new Error(
|
|
2377
|
+
`This endpoint requires webserver mode (503). Freqtrade separates "trade" and "webserver" modes: strategy/backtest/candle-analysis endpoints are only available in webserver mode. To use this feature, restart Freqtrade with: freqtrade webserver --config user_data/config.json. Note: webserver mode and trade mode cannot run simultaneously.`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2292
2380
|
throw new Error(
|
|
2293
2381
|
`Bot is not in the correct state (503). This usually means the bot was started in webserver-only mode without a strategy. Use ft_info action=config to check the current state. If "state" is empty and "strategy" is null, restart Freqtrade with a strategy: freqtrade trade --strategy <name> --config user_data/config.json`
|
|
2294
2382
|
);
|
|
@@ -3009,7 +3097,7 @@ ${roiStr}
|
|
|
3009
3097
|
}
|
|
3010
3098
|
|
|
3011
3099
|
// src/tools/freqtrade-dev.ts
|
|
3012
|
-
import { readFile, writeFile, access, mkdir } from "fs/promises";
|
|
3100
|
+
import { readFile, writeFile, readdir, access, mkdir } from "fs/promises";
|
|
3013
3101
|
import { join, dirname } from "path";
|
|
3014
3102
|
import { fileURLToPath } from "url";
|
|
3015
3103
|
var __pkgroot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -3033,7 +3121,7 @@ async function fileExists(path) {
|
|
|
3033
3121
|
function registerFreqtradeDevTools(server2) {
|
|
3034
3122
|
server2.tool(
|
|
3035
3123
|
"ft_backtest",
|
|
3036
|
-
"Freqtrade backtesting.\n- start: start a backtest (requires strategy, optional params)\n- status: get current backtest progress/results\n- abort: abort running backtest\n- delete: clear backtest state\n- history: list all backtest history entries\n- history_result: get specific result (requires filename & strategy)",
|
|
3124
|
+
"Freqtrade backtesting.\n- start: start a backtest (requires strategy, optional params)\n- status: get current backtest progress/results\n- abort: abort running backtest\n- delete: clear backtest state\n- history: list all backtest history entries\n- history_result: get specific result (requires filename & strategy)\nNOTE: Requires Freqtrade in webserver mode (freqtrade webserver). Not available in trade mode.",
|
|
3037
3125
|
{
|
|
3038
3126
|
action: z10.enum(["start", "status", "abort", "delete", "history", "history_result"]).describe("Backtest action"),
|
|
3039
3127
|
strategy: z10.string().optional().describe("Strategy class name (required for start & history_result)"),
|
|
@@ -3093,7 +3181,7 @@ function registerFreqtradeDevTools(server2) {
|
|
|
3093
3181
|
);
|
|
3094
3182
|
server2.tool(
|
|
3095
3183
|
"ft_candles",
|
|
3096
|
-
|
|
3184
|
+
'Freqtrade K-line / candle data.\n- live: live candlestick data (pair + timeframe required)\n- analyzed: historical data with strategy indicators (pair + timeframe + strategy required)\n- available: list available pairs with downloaded data\nNOTE: "analyzed" and "available" require Freqtrade in webserver mode. "live" works in trade mode.',
|
|
3097
3185
|
{
|
|
3098
3186
|
action: z10.enum(["live", "analyzed", "available"]).describe("Candle data query type"),
|
|
3099
3187
|
pair: z10.string().optional().describe("Trading pair, e.g. BTC/USDT (for live & analyzed)"),
|
|
@@ -3164,13 +3252,28 @@ function registerFreqtradeDevTools(server2) {
|
|
|
3164
3252
|
},
|
|
3165
3253
|
async ({ action, strategy, name, template, timeframe, stoploss, roi, overwrite }) => {
|
|
3166
3254
|
try {
|
|
3167
|
-
const client = getFreqtradeClient();
|
|
3168
3255
|
switch (action) {
|
|
3169
|
-
case "list":
|
|
3170
|
-
|
|
3256
|
+
case "list": {
|
|
3257
|
+
const userDataPath = getUserDataPath();
|
|
3258
|
+
const strategiesDir = join(userDataPath, "strategies");
|
|
3259
|
+
try {
|
|
3260
|
+
const files = await readdir(strategiesDir);
|
|
3261
|
+
const strategies = files.filter((f) => f.endsWith(".py") && !f.startsWith("__") && f !== "aicoin_provider.py").map((f) => f.replace(/\.py$/, ""));
|
|
3262
|
+
return ok({ strategies });
|
|
3263
|
+
} catch {
|
|
3264
|
+
return ok({ strategies: [], note: `No strategies directory found at ${strategiesDir}` });
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3171
3267
|
case "get": {
|
|
3172
3268
|
if (!strategy) return err("strategy name is required for get action");
|
|
3173
|
-
|
|
3269
|
+
const userDataPath = getUserDataPath();
|
|
3270
|
+
const filePath = join(userDataPath, "strategies", `${strategy}.py`);
|
|
3271
|
+
try {
|
|
3272
|
+
const code = await readFile(filePath, "utf-8");
|
|
3273
|
+
return ok({ strategy, code });
|
|
3274
|
+
} catch {
|
|
3275
|
+
return err(`Strategy file not found: ${filePath}`);
|
|
3276
|
+
}
|
|
3174
3277
|
}
|
|
3175
3278
|
case "create": {
|
|
3176
3279
|
if (!name) return err("name is required for create action");
|