@jellyos/agent 0.1.4 → 0.1.6
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.npm.md +212 -0
- package/bin/jellyos-mcp +26 -0
- package/dist/api/ExtensionAPI.d.ts +11 -0
- package/dist/cli.js +127 -49
- package/dist/index.d.ts +15 -2
- package/dist/index.js +13 -3
- package/dist/loader.d.ts +2 -9
- package/dist/loader.js +2 -1
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +71 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.js +128 -0
- package/dist/models/ModelRegistry.d.ts +12 -1
- package/dist/models/ModelRegistry.js +105 -9
- package/dist/runner/AgentRunner.d.ts +19 -2
- package/dist/runner/AgentRunner.js +247 -17
- package/dist/runner/ModelClient.d.ts +10 -1
- package/dist/runner/ModelClient.js +79 -6
- package/dist/runner/SwarmRouter.d.ts +6 -6
- package/dist/runner/SwarmRouter.js +73 -24
- package/dist/runner/ToolDispatcher.d.ts +10 -0
- package/dist/runner/ToolDispatcher.js +106 -2
- package/dist/scheduler/AgentScheduler.d.ts +118 -0
- package/dist/scheduler/AgentScheduler.js +253 -0
- package/dist/session/ContextStore.d.ts +96 -0
- package/dist/session/ContextStore.js +207 -0
- package/dist/session/GoalManager.d.ts +101 -0
- package/dist/session/GoalManager.js +167 -0
- package/dist/session/MemoryStore.d.ts +48 -0
- package/dist/session/MemoryStore.js +166 -0
- package/dist/session/SessionManager.d.ts +45 -4
- package/dist/session/SessionManager.js +151 -8
- package/dist/telemetry/Tracer.d.ts +48 -0
- package/dist/telemetry/Tracer.js +102 -0
- package/dist/tools/MarketSentiment.d.ts +166 -0
- package/dist/tools/MarketSentiment.js +209 -0
- package/dist/tools/NewsSentiment.js +40 -13
- package/dist/tools/PriceFeed.d.ts +2 -0
- package/dist/tools/PriceFeed.js +79 -27
- package/dist/tools/TechnicalAnalysis.d.ts +37 -0
- package/dist/tools/TechnicalAnalysis.js +85 -0
- package/dist/tui/App.d.ts +4 -3
- package/dist/tui/App.js +346 -119
- package/dist/tui/ModelSelector.d.ts +22 -0
- package/dist/tui/ModelSelector.js +86 -0
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +11 -6
- package/package.json +10 -6
- package/dist/api/ExtensionAPI.d.ts.map +0 -1
- package/dist/api/ExtensionAPI.js.map +0 -1
- package/dist/api/Registry.d.ts.map +0 -1
- package/dist/api/Registry.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js.map +0 -1
- package/dist/models/CostTracker.d.ts.map +0 -1
- package/dist/models/CostTracker.js.map +0 -1
- package/dist/models/ModelRegistry.d.ts.map +0 -1
- package/dist/models/ModelRegistry.js.map +0 -1
- package/dist/models/index.d.ts.map +0 -1
- package/dist/models/index.js.map +0 -1
- package/dist/runner/AgentRunner.d.ts.map +0 -1
- package/dist/runner/AgentRunner.js.map +0 -1
- package/dist/runner/ModelClient.d.ts.map +0 -1
- package/dist/runner/ModelClient.js.map +0 -1
- package/dist/runner/SwarmRouter.d.ts.map +0 -1
- package/dist/runner/SwarmRouter.js.map +0 -1
- package/dist/runner/ToolDispatcher.d.ts.map +0 -1
- package/dist/runner/ToolDispatcher.js.map +0 -1
- package/dist/session/SessionManager.d.ts.map +0 -1
- package/dist/session/SessionManager.js.map +0 -1
- package/dist/tools/NewsSentiment.d.ts.map +0 -1
- package/dist/tools/NewsSentiment.js.map +0 -1
- package/dist/tools/PriceFeed.d.ts.map +0 -1
- package/dist/tools/PriceFeed.js.map +0 -1
- package/dist/tools/TechnicalAnalysis.d.ts.map +0 -1
- package/dist/tools/TechnicalAnalysis.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tui/App.d.ts.map +0 -1
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/REPL.d.ts.map +0 -1
- package/dist/tui/REPL.js.map +0 -1
- package/dist/tui/StatusBar.d.ts.map +0 -1
- package/dist/tui/StatusBar.js.map +0 -1
- package/dist/tui/theme.d.ts.map +0 -1
- package/dist/tui/theme.js.map +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MarketSentiment — free, no-key market data tools.
|
|
3
|
+
*
|
|
4
|
+
* #20: Fear & Greed Index (alternative.me)
|
|
5
|
+
* #19: BTC Mempool stats (mempool.space — free, no key)
|
|
6
|
+
* #19: ETH / EVM Gas prices (blocknative public — free)
|
|
7
|
+
* #19: Solana TPS (public RPC — no key)
|
|
8
|
+
* #19: DeFi TVL by chain (DeFiLlama — free, no key)
|
|
9
|
+
* #20: Binance perp funding rates (public endpoint — no key)
|
|
10
|
+
*/
|
|
11
|
+
import { type Static } from "@sinclair/typebox";
|
|
12
|
+
export declare const fearGreedParams: import("@sinclair/typebox").TObject<{
|
|
13
|
+
days: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function getFearGreedTool(_id: string, params: Static<typeof fearGreedParams>): Promise<{
|
|
16
|
+
content: {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
details: {
|
|
21
|
+
current: number;
|
|
22
|
+
classification: string;
|
|
23
|
+
avg7: number;
|
|
24
|
+
trend: number[];
|
|
25
|
+
direction: string;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
content: {
|
|
29
|
+
type: "text";
|
|
30
|
+
text: string;
|
|
31
|
+
}[];
|
|
32
|
+
details: {
|
|
33
|
+
current?: undefined;
|
|
34
|
+
classification?: undefined;
|
|
35
|
+
avg7?: undefined;
|
|
36
|
+
trend?: undefined;
|
|
37
|
+
direction?: undefined;
|
|
38
|
+
};
|
|
39
|
+
}>;
|
|
40
|
+
export declare const fundingRatesParams: import("@sinclair/typebox").TObject<{
|
|
41
|
+
symbol: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
42
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function getFundingRatesTool(_id: string, params: Static<typeof fundingRatesParams>): Promise<{
|
|
45
|
+
content: {
|
|
46
|
+
type: "text";
|
|
47
|
+
text: string;
|
|
48
|
+
}[];
|
|
49
|
+
details: {
|
|
50
|
+
symbol: string;
|
|
51
|
+
currentRate: number;
|
|
52
|
+
annualizedPct: number;
|
|
53
|
+
sentiment: string;
|
|
54
|
+
history: {
|
|
55
|
+
fundingRate: string;
|
|
56
|
+
fundingTime: number;
|
|
57
|
+
}[];
|
|
58
|
+
};
|
|
59
|
+
} | {
|
|
60
|
+
content: {
|
|
61
|
+
type: "text";
|
|
62
|
+
text: string;
|
|
63
|
+
}[];
|
|
64
|
+
details: {
|
|
65
|
+
symbol?: undefined;
|
|
66
|
+
currentRate?: undefined;
|
|
67
|
+
annualizedPct?: undefined;
|
|
68
|
+
sentiment?: undefined;
|
|
69
|
+
history?: undefined;
|
|
70
|
+
};
|
|
71
|
+
}>;
|
|
72
|
+
export declare const btcMempoolParams: import("@sinclair/typebox").TObject<{}>;
|
|
73
|
+
export declare function getBtcMempoolTool(): Promise<{
|
|
74
|
+
content: {
|
|
75
|
+
type: "text";
|
|
76
|
+
text: string;
|
|
77
|
+
}[];
|
|
78
|
+
details: {
|
|
79
|
+
pendingTxs: number;
|
|
80
|
+
vsizeMB: number;
|
|
81
|
+
fees: {
|
|
82
|
+
fastestFee: number;
|
|
83
|
+
halfHourFee: number;
|
|
84
|
+
hourFee: number;
|
|
85
|
+
economyFee: number;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
} | {
|
|
89
|
+
content: {
|
|
90
|
+
type: "text";
|
|
91
|
+
text: string;
|
|
92
|
+
}[];
|
|
93
|
+
details: {
|
|
94
|
+
pendingTxs?: undefined;
|
|
95
|
+
vsizeMB?: undefined;
|
|
96
|
+
fees?: undefined;
|
|
97
|
+
};
|
|
98
|
+
}>;
|
|
99
|
+
export declare const defiTvlParams: import("@sinclair/typebox").TObject<{
|
|
100
|
+
chain: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
101
|
+
}>;
|
|
102
|
+
export declare function getDefiTvlTool(_id: string, params: Static<typeof defiTvlParams>): Promise<{
|
|
103
|
+
content: {
|
|
104
|
+
type: "text";
|
|
105
|
+
text: string;
|
|
106
|
+
}[];
|
|
107
|
+
details: {
|
|
108
|
+
chain: string;
|
|
109
|
+
tvl: number;
|
|
110
|
+
change7d: string | null;
|
|
111
|
+
chains?: undefined;
|
|
112
|
+
totalTvl?: undefined;
|
|
113
|
+
};
|
|
114
|
+
} | {
|
|
115
|
+
content: {
|
|
116
|
+
type: "text";
|
|
117
|
+
text: string;
|
|
118
|
+
}[];
|
|
119
|
+
details: {
|
|
120
|
+
chains: {
|
|
121
|
+
name: string;
|
|
122
|
+
tvl: number;
|
|
123
|
+
}[];
|
|
124
|
+
totalTvl: number;
|
|
125
|
+
chain?: undefined;
|
|
126
|
+
tvl?: undefined;
|
|
127
|
+
change7d?: undefined;
|
|
128
|
+
};
|
|
129
|
+
} | {
|
|
130
|
+
content: {
|
|
131
|
+
type: "text";
|
|
132
|
+
text: string;
|
|
133
|
+
}[];
|
|
134
|
+
details: {
|
|
135
|
+
chain?: undefined;
|
|
136
|
+
tvl?: undefined;
|
|
137
|
+
change7d?: undefined;
|
|
138
|
+
chains?: undefined;
|
|
139
|
+
totalTvl?: undefined;
|
|
140
|
+
};
|
|
141
|
+
}>;
|
|
142
|
+
export declare const solanaStatsParams: import("@sinclair/typebox").TObject<{}>;
|
|
143
|
+
export declare function getSolanaStatsTool(): Promise<{
|
|
144
|
+
content: {
|
|
145
|
+
type: "text";
|
|
146
|
+
text: string;
|
|
147
|
+
}[];
|
|
148
|
+
details: {
|
|
149
|
+
avgTps: number;
|
|
150
|
+
maxTps: number;
|
|
151
|
+
latestSlot: number;
|
|
152
|
+
samples: number[];
|
|
153
|
+
};
|
|
154
|
+
} | {
|
|
155
|
+
content: {
|
|
156
|
+
type: "text";
|
|
157
|
+
text: string;
|
|
158
|
+
}[];
|
|
159
|
+
details: {
|
|
160
|
+
avgTps?: undefined;
|
|
161
|
+
maxTps?: undefined;
|
|
162
|
+
latestSlot?: undefined;
|
|
163
|
+
samples?: undefined;
|
|
164
|
+
};
|
|
165
|
+
}>;
|
|
166
|
+
//# sourceMappingURL=MarketSentiment.d.ts.map
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MarketSentiment — free, no-key market data tools.
|
|
3
|
+
*
|
|
4
|
+
* #20: Fear & Greed Index (alternative.me)
|
|
5
|
+
* #19: BTC Mempool stats (mempool.space — free, no key)
|
|
6
|
+
* #19: ETH / EVM Gas prices (blocknative public — free)
|
|
7
|
+
* #19: Solana TPS (public RPC — no key)
|
|
8
|
+
* #19: DeFi TVL by chain (DeFiLlama — free, no key)
|
|
9
|
+
* #20: Binance perp funding rates (public endpoint — no key)
|
|
10
|
+
*/
|
|
11
|
+
import { Type } from "@sinclair/typebox";
|
|
12
|
+
// ── Fear & Greed Index ────────────────────────────────────────────────────────
|
|
13
|
+
export const fearGreedParams = Type.Object({
|
|
14
|
+
days: Type.Optional(Type.Number({ description: "Number of historical days to return (default: 7, max: 30)" })),
|
|
15
|
+
});
|
|
16
|
+
export async function getFearGreedTool(_id, params) {
|
|
17
|
+
const days = Math.min(params.days ?? 7, 30);
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(`https://api.alternative.me/fng/?limit=${days}`, { signal: AbortSignal.timeout(8_000), headers: { "User-Agent": "JellyOS/1.0" } });
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
throw new Error(`HTTP ${res.status}`);
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
const items = data.data ?? [];
|
|
24
|
+
const current = items[0];
|
|
25
|
+
if (!current)
|
|
26
|
+
throw new Error("No data returned");
|
|
27
|
+
const val = parseInt(current.value);
|
|
28
|
+
const emoji = val >= 75 ? "😱 Extreme Greed" : val >= 55 ? "😀 Greed"
|
|
29
|
+
: val >= 45 ? "😐 Neutral" : val >= 25 ? "😨 Fear"
|
|
30
|
+
: "😱 Extreme Fear";
|
|
31
|
+
const trend = items.slice(0, 7).map(d => parseInt(d.value));
|
|
32
|
+
const avg7 = Math.round(trend.reduce((a, b) => a + b, 0) / trend.length);
|
|
33
|
+
const direction = trend.length > 1
|
|
34
|
+
? (trend[0] > trend[trend.length - 1] ? "↑ rising" : trend[0] < trend[trend.length - 1] ? "↓ falling" : "→ flat")
|
|
35
|
+
: "";
|
|
36
|
+
const historyLine = items.slice(0, Math.min(days, 7))
|
|
37
|
+
.map(d => `${d.value}(${d.value_classification.slice(0, 4)})`)
|
|
38
|
+
.join(" → ");
|
|
39
|
+
const text = [
|
|
40
|
+
`Fear & Greed Index: ${val} — ${emoji}`,
|
|
41
|
+
`7-day avg: ${avg7} Trend: ${direction}`,
|
|
42
|
+
`History: ${historyLine}`,
|
|
43
|
+
].join("\n");
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text }],
|
|
46
|
+
details: { current: val, classification: current.value_classification, avg7, trend, direction },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
51
|
+
return { content: [{ type: "text", text: `Fear & Greed fetch failed: ${msg}` }], details: {} };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ── Binance Perp Funding Rates ────────────────────────────────────────────────
|
|
55
|
+
export const fundingRatesParams = Type.Object({
|
|
56
|
+
symbol: Type.Optional(Type.String({ description: "Symbol e.g. BTC, ETH (default: BTC)" })),
|
|
57
|
+
limit: Type.Optional(Type.Number({ description: "Number of historical funding periods (default: 8)" })),
|
|
58
|
+
});
|
|
59
|
+
export async function getFundingRatesTool(_id, params) {
|
|
60
|
+
const sym = (params.symbol?.toUpperCase() ?? "BTC").replace(/USDT$/, "") + "USDT";
|
|
61
|
+
const limit = Math.min(params.limit ?? 8, 100);
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(`https://fapi.binance.com/fapi/v1/fundingRate?symbol=${sym}&limit=${limit}`, { signal: AbortSignal.timeout(8_000), headers: { "User-Agent": "JellyOS/1.0" } });
|
|
64
|
+
if (!res.ok)
|
|
65
|
+
throw new Error(`HTTP ${res.status}`);
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
if (!data.length)
|
|
68
|
+
throw new Error("No data");
|
|
69
|
+
const current = data[data.length - 1];
|
|
70
|
+
const rate = parseFloat(current.fundingRate);
|
|
71
|
+
const annRate = rate * 3 * 365 * 100; // 3x daily → annual %
|
|
72
|
+
const sentiment = rate > 0.001 ? "Longs paying shorts (bullish bias)" :
|
|
73
|
+
rate < -0.001 ? "Shorts paying longs (bearish bias)" : "Neutral";
|
|
74
|
+
const history = data.map(d => (parseFloat(d.fundingRate) * 100).toFixed(4) + "%").join(", ");
|
|
75
|
+
const text = [
|
|
76
|
+
`${sym} Funding Rate: ${(rate * 100).toFixed(4)}% per 8h`,
|
|
77
|
+
`Annualized: ${annRate.toFixed(1)}%`,
|
|
78
|
+
`Sentiment: ${sentiment}`,
|
|
79
|
+
`History (${data.length} periods): ${history}`,
|
|
80
|
+
].join("\n");
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text }],
|
|
83
|
+
details: { symbol: sym, currentRate: rate, annualizedPct: annRate, sentiment, history: data },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
88
|
+
return { content: [{ type: "text", text: `Funding rate fetch failed for ${sym}: ${msg}` }], details: {} };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ── BTC Mempool Stats ─────────────────────────────────────────────────────────
|
|
92
|
+
export const btcMempoolParams = Type.Object({});
|
|
93
|
+
export async function getBtcMempoolTool() {
|
|
94
|
+
try {
|
|
95
|
+
const [statsRes, feesRes] = await Promise.all([
|
|
96
|
+
fetch("https://mempool.space/api/mempool", { signal: AbortSignal.timeout(8_000), headers: { "User-Agent": "JellyOS/1.0" } }),
|
|
97
|
+
fetch("https://mempool.space/api/v1/fees/recommended", { signal: AbortSignal.timeout(8_000), headers: { "User-Agent": "JellyOS/1.0" } }),
|
|
98
|
+
]);
|
|
99
|
+
if (!statsRes.ok || !feesRes.ok)
|
|
100
|
+
throw new Error("mempool.space API error");
|
|
101
|
+
const stats = await statsRes.json();
|
|
102
|
+
const fees = await feesRes.json();
|
|
103
|
+
const congestion = stats.count > 100_000 ? "🔴 HIGH" : stats.count > 50_000 ? "🟡 MEDIUM" : "🟢 LOW";
|
|
104
|
+
const text = [
|
|
105
|
+
`BTC Mempool`,
|
|
106
|
+
`Pending txs: ${stats.count.toLocaleString()} Size: ${(stats.vsize / 1_000_000).toFixed(1)} MB`,
|
|
107
|
+
`Congestion: ${congestion}`,
|
|
108
|
+
`Fee rates (sat/vB):`,
|
|
109
|
+
` Next block: ${fees.fastestFee}`,
|
|
110
|
+
` ~30 min: ${fees.halfHourFee}`,
|
|
111
|
+
` ~1 hour: ${fees.hourFee}`,
|
|
112
|
+
` Economy: ${fees.economyFee}`,
|
|
113
|
+
].join("\n");
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text }],
|
|
116
|
+
details: { pendingTxs: stats.count, vsizeMB: stats.vsize / 1_000_000, fees },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
121
|
+
return { content: [{ type: "text", text: `BTC mempool fetch failed: ${msg}` }], details: {} };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ── DeFi TVL ─────────────────────────────────────────────────────────────────
|
|
125
|
+
export const defiTvlParams = Type.Object({
|
|
126
|
+
chain: Type.Optional(Type.String({ description: "Chain name e.g. ethereum, bsc, solana, arbitrum (omit for all chains)" })),
|
|
127
|
+
});
|
|
128
|
+
export async function getDefiTvlTool(_id, params) {
|
|
129
|
+
try {
|
|
130
|
+
const url = params.chain
|
|
131
|
+
? `https://api.llama.fi/v2/historicalChainTvl/${encodeURIComponent(params.chain)}`
|
|
132
|
+
: "https://api.llama.fi/v2/chains";
|
|
133
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(10_000), headers: { "User-Agent": "JellyOS/1.0" } });
|
|
134
|
+
if (!res.ok)
|
|
135
|
+
throw new Error(`HTTP ${res.status}`);
|
|
136
|
+
const data = await res.json();
|
|
137
|
+
if (params.chain) {
|
|
138
|
+
// Historical TVL for one chain
|
|
139
|
+
const arr = data;
|
|
140
|
+
if (!arr.length)
|
|
141
|
+
throw new Error("No data for chain");
|
|
142
|
+
const latest = arr[arr.length - 1];
|
|
143
|
+
const prev7 = arr[Math.max(0, arr.length - 8)];
|
|
144
|
+
const chg7 = prev7 ? ((latest.tvl - prev7.tvl) / prev7.tvl * 100).toFixed(1) : null;
|
|
145
|
+
const text = [
|
|
146
|
+
`DeFi TVL — ${params.chain}`,
|
|
147
|
+
`Current TVL: $${(latest.tvl / 1e9).toFixed(2)}B`,
|
|
148
|
+
chg7 ? `7-day change: ${parseFloat(chg7) >= 0 ? "+" : ""}${chg7}%` : "",
|
|
149
|
+
`As of: ${new Date(latest.date * 1000).toLocaleDateString()}`,
|
|
150
|
+
].filter(Boolean).join("\n");
|
|
151
|
+
return { content: [{ type: "text", text }], details: { chain: params.chain, tvl: latest.tvl, change7d: chg7 } };
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// All chains ranked by TVL
|
|
155
|
+
const chains = data
|
|
156
|
+
.filter(c => c.tvl > 100_000_000)
|
|
157
|
+
.sort((a, b) => b.tvl - a.tvl)
|
|
158
|
+
.slice(0, 15);
|
|
159
|
+
const totalTvl = chains.reduce((s, c) => s + c.tvl, 0);
|
|
160
|
+
const rows = chains.map((c, i) => ` ${String(i + 1).padStart(2)}. ${c.name.padEnd(16)} $${(c.tvl / 1e9).toFixed(2)}B`);
|
|
161
|
+
const text = [
|
|
162
|
+
`DeFi TVL by Chain (top 15)`,
|
|
163
|
+
`Total tracked: $${(totalTvl / 1e9).toFixed(1)}B`,
|
|
164
|
+
...rows,
|
|
165
|
+
].join("\n");
|
|
166
|
+
return { content: [{ type: "text", text }], details: { chains, totalTvl } };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
171
|
+
return { content: [{ type: "text", text: `DeFi TVL fetch failed: ${msg}` }], details: {} };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ── Solana Network Stats ──────────────────────────────────────────────────────
|
|
175
|
+
export const solanaStatsParams = Type.Object({});
|
|
176
|
+
export async function getSolanaStatsTool() {
|
|
177
|
+
try {
|
|
178
|
+
const res = await fetch("https://api.mainnet-beta.solana.com", {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json", "User-Agent": "JellyOS/1.0" },
|
|
181
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getRecentPerformanceSamples", params: [5] }),
|
|
182
|
+
signal: AbortSignal.timeout(10_000),
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok)
|
|
185
|
+
throw new Error(`HTTP ${res.status}`);
|
|
186
|
+
const body = await res.json();
|
|
187
|
+
const samples = body.result ?? [];
|
|
188
|
+
if (!samples.length)
|
|
189
|
+
throw new Error("No samples");
|
|
190
|
+
const tpsArr = samples.map(s => Math.round(s.numTransactions / s.samplePeriodSecs));
|
|
191
|
+
const avgTps = Math.round(tpsArr.reduce((a, b) => a + b, 0) / tpsArr.length);
|
|
192
|
+
const maxTps = Math.max(...tpsArr);
|
|
193
|
+
const latestSlot = samples[0]?.slot ?? 0;
|
|
194
|
+
const health = avgTps > 3000 ? "🟢 Healthy" : avgTps > 1500 ? "🟡 Moderate" : "🔴 Degraded";
|
|
195
|
+
const text = [
|
|
196
|
+
`Solana Network Stats`,
|
|
197
|
+
`TPS (avg 5 samples): ${avgTps.toLocaleString()}`,
|
|
198
|
+
`TPS (peak sample): ${maxTps.toLocaleString()}`,
|
|
199
|
+
`Latest slot: ${latestSlot.toLocaleString()}`,
|
|
200
|
+
`Network health: ${health}`,
|
|
201
|
+
].join("\n");
|
|
202
|
+
return { content: [{ type: "text", text }], details: { avgTps, maxTps, latestSlot, samples: tpsArr } };
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
206
|
+
return { content: [{ type: "text", text: `Solana stats fetch failed: ${msg}` }], details: {} };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=MarketSentiment.js.map
|
|
@@ -41,34 +41,61 @@ export function scoreSentiment(text) {
|
|
|
41
41
|
return hits === 0 ? 0 : Math.max(-1, Math.min(1, score / hits));
|
|
42
42
|
}
|
|
43
43
|
// ── Fetch & parse ────────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Proper RSS item extractor — handles CDATA, encoded entities, attributes.
|
|
46
|
+
* No regex on the whole document; scans block by block.
|
|
47
|
+
*/
|
|
48
|
+
function extractTag(block, tag) {
|
|
49
|
+
// Try CDATA form first: <tag><![CDATA[...]]></tag>
|
|
50
|
+
const cdataRe = new RegExp(`<${tag}[^>]*><!\\[CDATA\\[([\\s\\S]*?)\\]\\]>`, "i");
|
|
51
|
+
const cdataM = block.match(cdataRe);
|
|
52
|
+
if (cdataM?.[1])
|
|
53
|
+
return cdataM[1].trim();
|
|
54
|
+
// Plain text form: <tag ...>content</tag>
|
|
55
|
+
const plainRe = new RegExp(`<${tag}[^>]*>([^<]*)`, "i");
|
|
56
|
+
const plainM = block.match(plainRe);
|
|
57
|
+
if (plainM?.[1]) {
|
|
58
|
+
return plainM[1]
|
|
59
|
+
.replace(/&/g, "&")
|
|
60
|
+
.replace(/</g, "<")
|
|
61
|
+
.replace(/>/g, ">")
|
|
62
|
+
.replace(/"/g, '"')
|
|
63
|
+
.replace(/'/g, "'")
|
|
64
|
+
.trim();
|
|
65
|
+
}
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
44
68
|
async function fetchRSS(url, source) {
|
|
45
69
|
try {
|
|
46
70
|
const res = await fetch(url, {
|
|
47
71
|
signal: AbortSignal.timeout(10_000),
|
|
48
|
-
headers: { "User-Agent": "JellyOS/1.0", "Accept": "application/rss+xml, application/xml, text/xml" },
|
|
72
|
+
headers: { "User-Agent": "JellyOS/1.0", "Accept": "application/rss+xml, application/xml, text/xml, */*" },
|
|
49
73
|
});
|
|
50
74
|
if (!res.ok)
|
|
51
75
|
return [];
|
|
52
|
-
const
|
|
53
|
-
//
|
|
76
|
+
const xml = await res.text();
|
|
77
|
+
// Find all <item>...</item> blocks (also handles <entry> for Atom feeds)
|
|
78
|
+
const TAG_RE = /<(?:item|entry)[^>]*>([\s\S]*?)<\/(?:item|entry)>/gi;
|
|
54
79
|
const items = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
if (!title
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = TAG_RE.exec(xml)) !== null) {
|
|
82
|
+
const block = match[1] ?? "";
|
|
83
|
+
const title = extractTag(block, "title");
|
|
84
|
+
const link = extractTag(block, "link") || extractTag(block, "guid");
|
|
85
|
+
const pubDate = extractTag(block, "pubDate") || extractTag(block, "updated") || extractTag(block, "published");
|
|
86
|
+
if (!title)
|
|
62
87
|
continue;
|
|
63
88
|
items.push({
|
|
64
89
|
title,
|
|
65
90
|
source,
|
|
66
|
-
url:
|
|
67
|
-
published:
|
|
91
|
+
url: link,
|
|
92
|
+
published: pubDate ? (new Date(pubDate).getTime() || Date.now()) : Date.now(),
|
|
68
93
|
sentiment: scoreSentiment(title),
|
|
69
94
|
});
|
|
95
|
+
if (items.length >= 25)
|
|
96
|
+
break; // cap per feed
|
|
70
97
|
}
|
|
71
|
-
return items
|
|
98
|
+
return items;
|
|
72
99
|
}
|
|
73
100
|
catch {
|
|
74
101
|
return [];
|
|
@@ -43,6 +43,8 @@ export declare class PriceFeed extends EventEmitter {
|
|
|
43
43
|
/** Add symbols to track. IDs are auto-resolved from the symbol map. */
|
|
44
44
|
track(...symbols: string[]): void;
|
|
45
45
|
fetchAll(): Promise<void>;
|
|
46
|
+
private fetchBinance;
|
|
47
|
+
private fetchCoinGecko;
|
|
46
48
|
get(symbol: string): PriceTick | undefined;
|
|
47
49
|
getMultiple(symbols: string[]): PriceTick[];
|
|
48
50
|
getAll(): PriceTick[];
|
package/dist/tools/PriceFeed.js
CHANGED
|
@@ -81,37 +81,89 @@ export class PriceFeed extends EventEmitter {
|
|
|
81
81
|
}
|
|
82
82
|
// ── Fetch ────────────────────────────────────────────────────────────────
|
|
83
83
|
async fetchAll() {
|
|
84
|
+
// #17: Fan-out to Binance (primary) + CoinGecko (secondary)
|
|
85
|
+
// Binance: no rate limits on public endpoints, real-time order book prices
|
|
86
|
+
// CoinGecko: covers long-tail tokens Binance doesn't list, includes market cap
|
|
87
|
+
const [binanceResult, cgResult] = await Promise.allSettled([
|
|
88
|
+
this.fetchBinance(),
|
|
89
|
+
this.fetchCoinGecko(),
|
|
90
|
+
]);
|
|
91
|
+
let updated = false;
|
|
92
|
+
// Binance wins for price precision on listed pairs
|
|
93
|
+
if (binanceResult.status === "fulfilled") {
|
|
94
|
+
for (const tick of binanceResult.value) {
|
|
95
|
+
this.prices.set(tick.symbol.toLowerCase(), tick);
|
|
96
|
+
updated = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// CoinGecko fills gaps (market cap data, unlisted tokens)
|
|
100
|
+
if (cgResult.status === "fulfilled") {
|
|
101
|
+
for (const tick of cgResult.value) {
|
|
102
|
+
const key = tick.symbol.toLowerCase();
|
|
103
|
+
const existing = this.prices.get(key);
|
|
104
|
+
if (!existing) {
|
|
105
|
+
// Token not on Binance — add from CoinGecko
|
|
106
|
+
this.prices.set(key, tick);
|
|
107
|
+
updated = true;
|
|
108
|
+
}
|
|
109
|
+
else if (tick.marketCap > 0) {
|
|
110
|
+
// Enrich Binance tick with market cap from CoinGecko
|
|
111
|
+
existing.marketCap = tick.marketCap;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (updated)
|
|
116
|
+
this.emit("prices", [...this.prices.values()]);
|
|
117
|
+
}
|
|
118
|
+
async fetchBinance() {
|
|
119
|
+
// Build list of USDT pairs for tracked symbols
|
|
120
|
+
const pairs = [...this.tracking]
|
|
121
|
+
.map(s => s.toUpperCase() + "USDT")
|
|
122
|
+
.filter(s => !s.startsWith("USDT") && !s.startsWith("DAI")); // skip stablecoins
|
|
123
|
+
if (!pairs.length)
|
|
124
|
+
return [];
|
|
125
|
+
const url = `https://api.binance.com/api/v3/ticker/24hr?symbols=${encodeURIComponent(JSON.stringify(pairs))}`;
|
|
126
|
+
const res = await fetch(url, {
|
|
127
|
+
signal: AbortSignal.timeout(8_000),
|
|
128
|
+
headers: { "User-Agent": "JellyOS/1.0" },
|
|
129
|
+
});
|
|
130
|
+
if (!res.ok)
|
|
131
|
+
throw new Error(`Binance ticker HTTP ${res.status}`);
|
|
132
|
+
const data = await res.json();
|
|
133
|
+
return data.map(d => ({
|
|
134
|
+
symbol: d.symbol.replace(/USDT$/, ""),
|
|
135
|
+
price: parseFloat(d.lastPrice),
|
|
136
|
+
change24h: parseFloat(d.priceChangePercent),
|
|
137
|
+
volume24h: parseFloat(d.quoteVolume),
|
|
138
|
+
marketCap: 0, // Binance doesn't provide market cap — CoinGecko fills this
|
|
139
|
+
high24h: parseFloat(d.highPrice),
|
|
140
|
+
low24h: parseFloat(d.lowPrice),
|
|
141
|
+
source: "binance",
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
async fetchCoinGecko() {
|
|
84
146
|
const ids = [...this.tracking]
|
|
85
147
|
.map(s => SYMBOL_TO_ID[s])
|
|
86
|
-
.filter(Boolean)
|
|
148
|
+
.filter((id) => Boolean(id))
|
|
87
149
|
.join(",");
|
|
88
150
|
if (!ids)
|
|
89
|
-
return;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
source: "coingecko",
|
|
106
|
-
timestamp: Date.now(),
|
|
107
|
-
};
|
|
108
|
-
this.prices.set(symbol.toLowerCase(), tick);
|
|
109
|
-
}
|
|
110
|
-
this.emit("prices", [...this.prices.values()]);
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// Silent — will retry next interval
|
|
114
|
-
}
|
|
151
|
+
return [];
|
|
152
|
+
const res = await fetch(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${ids}&order=market_cap_desc&sparkline=false`, { signal: AbortSignal.timeout(10_000), headers: { "User-Agent": "JellyOS/1.0" } });
|
|
153
|
+
if (!res.ok)
|
|
154
|
+
throw new Error(`CoinGecko HTTP ${res.status}`);
|
|
155
|
+
const data = await res.json();
|
|
156
|
+
return data.map(coin => ({
|
|
157
|
+
symbol: coin.symbol.toUpperCase(),
|
|
158
|
+
price: coin.current_price,
|
|
159
|
+
change24h: coin.price_change_percentage_24h ?? 0,
|
|
160
|
+
volume24h: coin.total_volume ?? 0,
|
|
161
|
+
marketCap: coin.market_cap ?? 0,
|
|
162
|
+
high24h: coin.high_24h ?? 0,
|
|
163
|
+
low24h: coin.low_24h ?? 0,
|
|
164
|
+
source: "coingecko",
|
|
165
|
+
timestamp: Date.now(),
|
|
166
|
+
}));
|
|
115
167
|
}
|
|
116
168
|
// ── Query ─────────────────────────────────────────────────────────────────
|
|
117
169
|
get(symbol) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* All calculations are pure math on price arrays — no external API needed.
|
|
6
6
|
* Designed to be exposed as tools the AI can call autonomously.
|
|
7
7
|
*/
|
|
8
|
+
import { type Static } from "@sinclair/typebox";
|
|
8
9
|
export interface OHLCV {
|
|
9
10
|
timestamp: number;
|
|
10
11
|
open: number;
|
|
@@ -70,4 +71,40 @@ export declare const macdParams: import("@sinclair/typebox").TObject<{
|
|
|
70
71
|
slow: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
71
72
|
signal: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
72
73
|
}>;
|
|
74
|
+
export declare const getCandlesParams: import("@sinclair/typebox").TObject<{
|
|
75
|
+
symbol: import("@sinclair/typebox").TString;
|
|
76
|
+
interval: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
77
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
78
|
+
analyze: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
79
|
+
}>;
|
|
80
|
+
export declare function getCandlesTool(_id: string, params: Static<typeof getCandlesParams>): Promise<{
|
|
81
|
+
content: {
|
|
82
|
+
type: "text";
|
|
83
|
+
text: string;
|
|
84
|
+
}[];
|
|
85
|
+
details: {
|
|
86
|
+
symbol?: undefined;
|
|
87
|
+
interval?: undefined;
|
|
88
|
+
candleCount?: undefined;
|
|
89
|
+
latest?: undefined;
|
|
90
|
+
priceChangePercent?: undefined;
|
|
91
|
+
};
|
|
92
|
+
} | {
|
|
93
|
+
content: {
|
|
94
|
+
type: "text";
|
|
95
|
+
text: string;
|
|
96
|
+
}[];
|
|
97
|
+
details: {
|
|
98
|
+
symbol: string;
|
|
99
|
+
interval: string;
|
|
100
|
+
candleCount: number;
|
|
101
|
+
latest: {
|
|
102
|
+
close: number;
|
|
103
|
+
high: number;
|
|
104
|
+
low: number;
|
|
105
|
+
volume: number;
|
|
106
|
+
};
|
|
107
|
+
priceChangePercent: number;
|
|
108
|
+
};
|
|
109
|
+
}>;
|
|
73
110
|
//# sourceMappingURL=TechnicalAnalysis.d.ts.map
|