@jellyos/agent 0.1.4 → 0.1.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.npm.md +212 -0
- package/bin/jellyos-mcp +26 -0
- package/dist/api/ExtensionAPI.d.ts +6 -0
- package/dist/cli.js +114 -48
- package/dist/index.d.ts +15 -2
- package/dist/index.js +13 -3
- 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/tests/ContextStore.test.d.ts +2 -0
- package/dist/tests/ContextStore.test.js +74 -0
- package/dist/tests/ModelRegistry.test.d.ts +2 -0
- package/dist/tests/ModelRegistry.test.js +69 -0
- package/dist/tests/SessionManager.test.d.ts +2 -0
- package/dist/tests/SessionManager.test.js +108 -0
- package/dist/tests/TechnicalAnalysis.test.d.ts +2 -0
- package/dist/tests/TechnicalAnalysis.test.js +109 -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 +2 -2
- package/dist/tui/App.js +280 -117
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +11 -6
- package/package.json +9 -4
- 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
|
@@ -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
|
|
@@ -269,4 +269,89 @@ export const macdParams = Type.Object({
|
|
|
269
269
|
slow: Type.Optional(Type.Number({ default: 26 })),
|
|
270
270
|
signal: Type.Optional(Type.Number({ default: 9 })),
|
|
271
271
|
});
|
|
272
|
+
// ── Tool: get_candles (#18 — fetch real OHLCV data from Binance) ─────────────
|
|
273
|
+
export const getCandlesParams = Type.Object({
|
|
274
|
+
symbol: Type.String({ description: "Crypto symbol e.g. BTC, ETH, SOL" }),
|
|
275
|
+
interval: Type.Optional(Type.String({ description: "Candle interval: 1m 5m 15m 1h 4h 1d (default: 1h)" })),
|
|
276
|
+
limit: Type.Optional(Type.Number({ description: "Number of candles 1-500 (default: 100)" })),
|
|
277
|
+
analyze: Type.Optional(Type.Boolean({ description: "Run full TA analysis on fetched candles (default: true)" })),
|
|
278
|
+
});
|
|
279
|
+
export async function getCandlesTool(_id, params) {
|
|
280
|
+
const symbol = params.symbol.toUpperCase().replace(/USDT$/, "") + "USDT";
|
|
281
|
+
const interval = params.interval ?? "1h";
|
|
282
|
+
const limit = Math.min(Math.max(params.limit ?? 100, 10), 500);
|
|
283
|
+
const analyze = params.analyze !== false;
|
|
284
|
+
const VALID_INTERVALS = new Set(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"]);
|
|
285
|
+
if (!VALID_INTERVALS.has(interval)) {
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: "text", text: `Invalid interval "${interval}". Valid: ${[...VALID_INTERVALS].join(", ")}` }],
|
|
288
|
+
details: {},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const res = await fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}&limit=${limit}`, { signal: AbortSignal.timeout(12_000), headers: { "User-Agent": "JellyOS/1.0" } });
|
|
293
|
+
if (!res.ok) {
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: "text", text: `Binance API error ${res.status} for ${symbol}. Check symbol.` }],
|
|
296
|
+
details: {},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const raw = await res.json();
|
|
300
|
+
const candles = raw.map(c => ({
|
|
301
|
+
timestamp: c[0],
|
|
302
|
+
open: parseFloat(c[1]),
|
|
303
|
+
high: parseFloat(c[2]),
|
|
304
|
+
low: parseFloat(c[3]),
|
|
305
|
+
close: parseFloat(c[4]),
|
|
306
|
+
volume: parseFloat(c[5]),
|
|
307
|
+
}));
|
|
308
|
+
const latest = candles[candles.length - 1];
|
|
309
|
+
const oldest = candles[0];
|
|
310
|
+
const chgPct = ((latest.close - oldest.close) / oldest.close * 100).toFixed(2);
|
|
311
|
+
const priceStr = latest.close < 1
|
|
312
|
+
? `$${latest.close.toFixed(6)}`
|
|
313
|
+
: `$${latest.close.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
|
|
314
|
+
const lines = [
|
|
315
|
+
`${symbol} | ${interval} | ${candles.length} candles`,
|
|
316
|
+
`Close: ${priceStr} Period change: ${parseFloat(chgPct) >= 0 ? "+" : ""}${chgPct}%`,
|
|
317
|
+
`High: $${latest.high.toLocaleString()} Low: $${latest.low.toLocaleString()}`,
|
|
318
|
+
`Volume (last bar): ${latest.volume.toLocaleString()}`,
|
|
319
|
+
];
|
|
320
|
+
if (analyze && candles.length >= 30) {
|
|
321
|
+
const results = fullAnalysis(candles);
|
|
322
|
+
const summary = results.find(r => r.indicator === "SUMMARY");
|
|
323
|
+
lines.push("", "── Technical Analysis ──");
|
|
324
|
+
for (const r of results) {
|
|
325
|
+
if (r.indicator === "SUMMARY")
|
|
326
|
+
continue;
|
|
327
|
+
const icon = r.signal === "bullish" ? "🟢" : r.signal === "bearish" ? "🔴" : "⚪";
|
|
328
|
+
const val = typeof r.value === "number" ? r.value.toFixed(2) : String(r.value);
|
|
329
|
+
const meta = r.metadata?.condition ? ` (${String(r.metadata.condition)})` : "";
|
|
330
|
+
lines.push(` ${icon} ${r.indicator.padEnd(16)} ${val}${meta}`);
|
|
331
|
+
}
|
|
332
|
+
if (summary) {
|
|
333
|
+
const icon = summary.signal === "bullish" ? "🟢" : summary.signal === "bearish" ? "🔴" : "⚪";
|
|
334
|
+
lines.push("", `${icon} OVERALL: ${String(summary.signal).toUpperCase()} — ${String(summary.metadata?.bullish_indicators ?? 0)} bull / ${String(summary.metadata?.bearish_indicators ?? 0)} bear signals`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (analyze && candles.length < 30) {
|
|
338
|
+
lines.push(`(need 30+ candles for TA, got ${candles.length} — increase limit)`);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
342
|
+
details: {
|
|
343
|
+
symbol, interval, candleCount: candles.length,
|
|
344
|
+
latest: { close: latest.close, high: latest.high, low: latest.low, volume: latest.volume },
|
|
345
|
+
priceChangePercent: parseFloat(chgPct),
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
351
|
+
return {
|
|
352
|
+
content: [{ type: "text", text: `Failed to fetch candles for ${symbol}: ${msg}` }],
|
|
353
|
+
details: {},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
272
357
|
//# sourceMappingURL=TechnicalAnalysis.js.map
|
package/dist/tui/App.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* App — root Ink component.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Multi-pane TUI with context bar, syntax-highlighted tool output,
|
|
4
|
+
* live side panel with ticker/prices, and command palette triggered via /palette.
|
|
5
5
|
*/
|
|
6
6
|
import { Registry } from "../api/Registry.js";
|
|
7
7
|
import type { ModelRegistry } from "../models/ModelRegistry.js";
|