@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.
Files changed (94) hide show
  1. package/README.npm.md +212 -0
  2. package/bin/jellyos-mcp +26 -0
  3. package/dist/api/ExtensionAPI.d.ts +6 -0
  4. package/dist/cli.js +114 -48
  5. package/dist/index.d.ts +15 -2
  6. package/dist/index.js +13 -3
  7. package/dist/mcp/entry.d.ts +2 -0
  8. package/dist/mcp/entry.js +71 -0
  9. package/dist/mcp/server.d.ts +31 -0
  10. package/dist/mcp/server.js +128 -0
  11. package/dist/models/ModelRegistry.d.ts +12 -1
  12. package/dist/models/ModelRegistry.js +105 -9
  13. package/dist/runner/AgentRunner.d.ts +19 -2
  14. package/dist/runner/AgentRunner.js +247 -17
  15. package/dist/runner/ModelClient.d.ts +10 -1
  16. package/dist/runner/ModelClient.js +79 -6
  17. package/dist/runner/SwarmRouter.d.ts +6 -6
  18. package/dist/runner/SwarmRouter.js +73 -24
  19. package/dist/runner/ToolDispatcher.d.ts +10 -0
  20. package/dist/runner/ToolDispatcher.js +106 -2
  21. package/dist/scheduler/AgentScheduler.d.ts +118 -0
  22. package/dist/scheduler/AgentScheduler.js +253 -0
  23. package/dist/session/ContextStore.d.ts +96 -0
  24. package/dist/session/ContextStore.js +207 -0
  25. package/dist/session/GoalManager.d.ts +101 -0
  26. package/dist/session/GoalManager.js +167 -0
  27. package/dist/session/MemoryStore.d.ts +48 -0
  28. package/dist/session/MemoryStore.js +166 -0
  29. package/dist/session/SessionManager.d.ts +45 -4
  30. package/dist/session/SessionManager.js +151 -8
  31. package/dist/telemetry/Tracer.d.ts +48 -0
  32. package/dist/telemetry/Tracer.js +102 -0
  33. package/dist/tests/ContextStore.test.d.ts +2 -0
  34. package/dist/tests/ContextStore.test.js +74 -0
  35. package/dist/tests/ModelRegistry.test.d.ts +2 -0
  36. package/dist/tests/ModelRegistry.test.js +69 -0
  37. package/dist/tests/SessionManager.test.d.ts +2 -0
  38. package/dist/tests/SessionManager.test.js +108 -0
  39. package/dist/tests/TechnicalAnalysis.test.d.ts +2 -0
  40. package/dist/tests/TechnicalAnalysis.test.js +109 -0
  41. package/dist/tools/MarketSentiment.d.ts +166 -0
  42. package/dist/tools/MarketSentiment.js +209 -0
  43. package/dist/tools/NewsSentiment.js +40 -13
  44. package/dist/tools/PriceFeed.d.ts +2 -0
  45. package/dist/tools/PriceFeed.js +79 -27
  46. package/dist/tools/TechnicalAnalysis.d.ts +37 -0
  47. package/dist/tools/TechnicalAnalysis.js +85 -0
  48. package/dist/tui/App.d.ts +2 -2
  49. package/dist/tui/App.js +280 -117
  50. package/dist/tui/REPL.d.ts +2 -1
  51. package/dist/tui/REPL.js +11 -6
  52. package/package.json +9 -4
  53. package/dist/api/ExtensionAPI.d.ts.map +0 -1
  54. package/dist/api/ExtensionAPI.js.map +0 -1
  55. package/dist/api/Registry.d.ts.map +0 -1
  56. package/dist/api/Registry.js.map +0 -1
  57. package/dist/cli.d.ts.map +0 -1
  58. package/dist/cli.js.map +0 -1
  59. package/dist/index.d.ts.map +0 -1
  60. package/dist/index.js.map +0 -1
  61. package/dist/loader.d.ts.map +0 -1
  62. package/dist/loader.js.map +0 -1
  63. package/dist/models/CostTracker.d.ts.map +0 -1
  64. package/dist/models/CostTracker.js.map +0 -1
  65. package/dist/models/ModelRegistry.d.ts.map +0 -1
  66. package/dist/models/ModelRegistry.js.map +0 -1
  67. package/dist/models/index.d.ts.map +0 -1
  68. package/dist/models/index.js.map +0 -1
  69. package/dist/runner/AgentRunner.d.ts.map +0 -1
  70. package/dist/runner/AgentRunner.js.map +0 -1
  71. package/dist/runner/ModelClient.d.ts.map +0 -1
  72. package/dist/runner/ModelClient.js.map +0 -1
  73. package/dist/runner/SwarmRouter.d.ts.map +0 -1
  74. package/dist/runner/SwarmRouter.js.map +0 -1
  75. package/dist/runner/ToolDispatcher.d.ts.map +0 -1
  76. package/dist/runner/ToolDispatcher.js.map +0 -1
  77. package/dist/session/SessionManager.d.ts.map +0 -1
  78. package/dist/session/SessionManager.js.map +0 -1
  79. package/dist/tools/NewsSentiment.d.ts.map +0 -1
  80. package/dist/tools/NewsSentiment.js.map +0 -1
  81. package/dist/tools/PriceFeed.d.ts.map +0 -1
  82. package/dist/tools/PriceFeed.js.map +0 -1
  83. package/dist/tools/TechnicalAnalysis.d.ts.map +0 -1
  84. package/dist/tools/TechnicalAnalysis.js.map +0 -1
  85. package/dist/tools/index.d.ts.map +0 -1
  86. package/dist/tools/index.js.map +0 -1
  87. package/dist/tui/App.d.ts.map +0 -1
  88. package/dist/tui/App.js.map +0 -1
  89. package/dist/tui/REPL.d.ts.map +0 -1
  90. package/dist/tui/REPL.js.map +0 -1
  91. package/dist/tui/StatusBar.d.ts.map +0 -1
  92. package/dist/tui/StatusBar.js.map +0 -1
  93. package/dist/tui/theme.d.ts.map +0 -1
  94. package/dist/tui/theme.js.map +0 -1
@@ -0,0 +1,69 @@
1
+ /**
2
+ * ModelRegistry tests — tier classification + temperature profiles.
3
+ */
4
+ import { describe, it, expect } from "vitest";
5
+ import { classifyModel } from "../models/ModelRegistry.js";
6
+ function makeModel(id, promptPrice = "0.000005") {
7
+ return {
8
+ id,
9
+ name: id,
10
+ created: Date.now(),
11
+ description: "",
12
+ context_length: 128_000,
13
+ architecture: { modality: "text", tokenizer: "cl100k", instruct_type: null },
14
+ pricing: { prompt: promptPrice, completion: "0.000015" },
15
+ top_provider: { context_length: 128_000, max_completion_tokens: 4096, is_moderated: false },
16
+ };
17
+ }
18
+ describe("classifyModel() — 2025 models", () => {
19
+ // Orchestrator tier
20
+ it("classifies claude-opus-4.7 as orchestrator", () => {
21
+ expect(classifyModel(makeModel("anthropic/claude-opus-4.7"))).toBe("orchestrator");
22
+ });
23
+ it("classifies claude-opus-4.6 as orchestrator", () => {
24
+ expect(classifyModel(makeModel("anthropic/claude-opus-4.6"))).toBe("orchestrator");
25
+ });
26
+ it("classifies gpt-5.5 as orchestrator", () => {
27
+ expect(classifyModel(makeModel("openai/gpt-5.5"))).toBe("orchestrator");
28
+ });
29
+ it("classifies gpt-5.5-pro as orchestrator", () => {
30
+ expect(classifyModel(makeModel("openai/gpt-5.5-pro"))).toBe("orchestrator");
31
+ });
32
+ it("classifies gemini-3.1-pro as orchestrator", () => {
33
+ expect(classifyModel(makeModel("google/gemini-3.1-pro-preview"))).toBe("orchestrator");
34
+ });
35
+ it("classifies deepseek-v4-pro as orchestrator", () => {
36
+ expect(classifyModel(makeModel("deepseek/deepseek-v4-pro"))).toBe("orchestrator");
37
+ });
38
+ it("classifies grok-4.0 as orchestrator", () => {
39
+ expect(classifyModel(makeModel("x-ai/grok-4.0"))).toBe("orchestrator");
40
+ });
41
+ // Analyst tier
42
+ it("classifies claude-sonnet-4.6 as analyst", () => {
43
+ expect(classifyModel(makeModel("anthropic/claude-sonnet-4.6"))).toBe("analyst");
44
+ });
45
+ it("classifies claude-sonnet-4.5 as analyst", () => {
46
+ expect(classifyModel(makeModel("anthropic/claude-sonnet-4.5"))).toBe("analyst");
47
+ });
48
+ it("classifies gemini-3.5-flash as analyst", () => {
49
+ expect(classifyModel(makeModel("google/gemini-3.5-flash"))).toBe("analyst");
50
+ });
51
+ it("classifies deepseek-v4 (non-pro) as analyst", () => {
52
+ expect(classifyModel(makeModel("deepseek/deepseek-v4-flash"))).toBe("analyst");
53
+ });
54
+ // Free tier
55
+ it("classifies :free models as free", () => {
56
+ expect(classifyModel(makeModel("deepseek/deepseek-v4-flash:free", "0"))).toBe("free");
57
+ });
58
+ it("classifies free-priced models as free", () => {
59
+ expect(classifyModel(makeModel("meta-llama/llama-3.1-8b-instruct:free", "0"))).toBe("free");
60
+ });
61
+ // Worker fallback
62
+ it("classifies unknown model as worker", () => {
63
+ expect(classifyModel(makeModel("some-unknown/model-v1"))).toBe("worker");
64
+ });
65
+ it("classifies small llama as worker", () => {
66
+ expect(classifyModel(makeModel("meta-llama/llama-3.1-8b-instruct", "0.0002"))).toBe("worker");
67
+ });
68
+ });
69
+ //# sourceMappingURL=ModelRegistry.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SessionManager.test.d.ts.map
@@ -0,0 +1,108 @@
1
+ /**
2
+ * SessionManager tests — atomic compaction, context pressure, semantic anchors.
3
+ */
4
+ import { describe, it, expect, beforeEach } from "vitest";
5
+ import { SessionManager } from "../session/SessionManager.js";
6
+ function makeUserTurn(userContent, assistantContent) {
7
+ return [
8
+ { role: "user", content: userContent },
9
+ { role: "assistant", content: assistantContent },
10
+ ];
11
+ }
12
+ function makeToolTurn(userContent, toolContent) {
13
+ const tc_id = `call_${Math.random().toString(36).slice(2)}`;
14
+ return [
15
+ { role: "user", content: userContent },
16
+ { role: "assistant", content: null, tool_calls: [{ id: tc_id, type: "function", function: { name: "get_prices", arguments: "{}" } }] },
17
+ { role: "tool", content: toolContent, name: "get_prices", tool_call_id: tc_id },
18
+ ];
19
+ }
20
+ describe("SessionManager", () => {
21
+ let session;
22
+ beforeEach(() => {
23
+ session = new SessionManager();
24
+ session.setSystemPrompt("You are JellyOS.");
25
+ });
26
+ it("getMessages() prepends system prompt", () => {
27
+ session.addMessage({ role: "user", content: "hello" });
28
+ const msgs = session.getMessages();
29
+ expect(msgs[0]?.role).toBe("system");
30
+ expect(msgs[0]?.content).toContain("JellyOS");
31
+ expect(msgs.length).toBe(2);
32
+ });
33
+ it("charCount() counts history chars", () => {
34
+ session.addMessage({ role: "user", content: "hello" });
35
+ expect(session.charCount()).toBe(5);
36
+ });
37
+ it("getContextPressure() returns green for small history", () => {
38
+ session.addMessage({ role: "user", content: "hi" });
39
+ const p = session.getContextPressure();
40
+ expect(p.level).toBe("green");
41
+ expect(p.turboReady).toBe(true);
42
+ expect(p.pct).toBeLessThan(50);
43
+ });
44
+ it("getContextPressure() reports yellow at 60%", () => {
45
+ // Add ~48KB of content (60% of 80KB)
46
+ const bigMsg = "x".repeat(48_000);
47
+ session.addMessage({ role: "user", content: bigMsg });
48
+ const p = session.getContextPressure();
49
+ expect(p.pct).toBeGreaterThanOrEqual(50);
50
+ });
51
+ it("turboReady is false when context >70%", () => {
52
+ const bigMsg = "x".repeat(60_000);
53
+ session.addMessage({ role: "user", content: bigMsg });
54
+ const p = session.getContextPressure();
55
+ expect(p.turboReady).toBe(false);
56
+ });
57
+ it("compaction never orphans tool_call_id pairs", () => {
58
+ // Add enough turns to trigger compaction
59
+ for (let i = 0; i < 20; i++) {
60
+ for (const msg of makeToolTurn(`question ${i}`, `tool result ${i} — ${"data".repeat(200)}`)) {
61
+ session.addMessage(msg);
62
+ }
63
+ }
64
+ const history = session.getHistory();
65
+ // Find all assistant messages with tool_calls
66
+ const toolCallMsgs = history.filter(m => m.tool_calls && m.tool_calls.length > 0);
67
+ for (const tcMsg of toolCallMsgs) {
68
+ for (const tc of tcMsg.tool_calls) {
69
+ // Every tool_call_id must have a corresponding tool result
70
+ const hasResult = history.some(m => m.role === "tool" && m.tool_call_id === tc.id);
71
+ expect(hasResult).toBe(true);
72
+ }
73
+ }
74
+ });
75
+ it("forceCompact() reduces history", () => {
76
+ for (let i = 0; i < 30; i++) {
77
+ for (const msg of makeUserTurn(`q${i}`, `a${i}`)) {
78
+ session.addMessage(msg);
79
+ }
80
+ }
81
+ const before = session.getHistory().length;
82
+ session.forceCompact();
83
+ const after = session.getHistory().length;
84
+ expect(after).toBeLessThanOrEqual(before);
85
+ });
86
+ it("clear() empties history", () => {
87
+ session.addMessage({ role: "user", content: "hello" });
88
+ session.clear();
89
+ expect(session.getHistory().length).toBe(0);
90
+ });
91
+ });
92
+ describe("SessionManager — SwarmRouter integration", () => {
93
+ it("scoreComplexity returns low score for simple price check", async () => {
94
+ const { scoreComplexity } = await import("../runner/SwarmRouter.js");
95
+ expect(scoreComplexity("what is the price of ETH")).toBeLessThan(40);
96
+ });
97
+ it("scoreComplexity returns high score for complex multi-chain analysis", async () => {
98
+ const { scoreComplexity } = await import("../runner/SwarmRouter.js");
99
+ const score = scoreComplexity("analyze ETH and BTC then compare their RSI and predict which will pump first");
100
+ expect(score).toBeGreaterThanOrEqual(40);
101
+ });
102
+ it("decomposeHeuristic splits on conjunctions", async () => {
103
+ const { decomposeHeuristic } = await import("../runner/SwarmRouter.js");
104
+ const tasks = decomposeHeuristic("analyze ETH and check BTC mempool and get SOL TPS", 5);
105
+ expect(tasks.length).toBeGreaterThanOrEqual(2);
106
+ });
107
+ });
108
+ //# sourceMappingURL=SessionManager.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TechnicalAnalysis.test.d.ts.map
@@ -0,0 +1,109 @@
1
+ /**
2
+ * TechnicalAnalysis tests — pure math functions, no network calls.
3
+ */
4
+ import { describe, it, expect } from "vitest";
5
+ import { rsi, macd, ema, sma, bollingerBands, rsiSignal, macdSignal } from "../tools/TechnicalAnalysis.js";
6
+ // Generate price series for testing
7
+ const risingPrices = Array.from({ length: 60 }, (_, i) => 100 + i * 2); // 100→218
8
+ const fallingPrices = Array.from({ length: 60 }, (_, i) => 220 - i * 2); // 220→102
9
+ const flatPrices = Array.from({ length: 60 }, () => 150);
10
+ describe("sma()", () => {
11
+ it("returns empty array when insufficient data", () => {
12
+ expect(sma([100, 99], 14)).toEqual([]);
13
+ });
14
+ it("calculates correct 3-period SMA", () => {
15
+ const result = sma([10, 20, 30, 40], 3);
16
+ expect(result[0]).toBeCloseTo(20, 2); // (10+20+30)/3
17
+ expect(result[1]).toBeCloseTo(30, 2); // (20+30+40)/3
18
+ });
19
+ it("result length = prices.length - period + 1", () => {
20
+ const result = sma(risingPrices, 20);
21
+ expect(result.length).toBe(risingPrices.length - 20 + 1);
22
+ });
23
+ });
24
+ describe("ema()", () => {
25
+ it("returns first price as first EMA value", () => {
26
+ const result = ema([100, 110, 120], 3);
27
+ expect(result[0]).toBe(100);
28
+ });
29
+ it("EMA tracks price direction", () => {
30
+ const result = ema(risingPrices, 12);
31
+ expect(result[result.length - 1]).toBeGreaterThan(result[0]);
32
+ });
33
+ });
34
+ describe("rsi()", () => {
35
+ it("returns empty array when insufficient data", () => {
36
+ expect(rsi([100, 99], 14)).toEqual([]);
37
+ });
38
+ it("returns oversold (<30) for falling prices", () => {
39
+ const result = rsi(fallingPrices, 14);
40
+ expect(result[result.length - 1]).toBeLessThan(30);
41
+ });
42
+ it("returns overbought (>70) for rising prices", () => {
43
+ const result = rsi(risingPrices, 14);
44
+ expect(result[result.length - 1]).toBeGreaterThan(70);
45
+ });
46
+ it("returns neutral (~50) for flat prices", () => {
47
+ // RSI is undefined for perfectly flat — but should handle gracefully
48
+ const result = rsi(flatPrices, 14);
49
+ // avgLoss = 0, so RSI = 100 (no change)
50
+ expect(result.length).toBeGreaterThan(0);
51
+ });
52
+ it("all values are in [0, 100]", () => {
53
+ const result = rsi(risingPrices, 14);
54
+ for (const v of result) {
55
+ expect(v).toBeGreaterThanOrEqual(0);
56
+ expect(v).toBeLessThanOrEqual(100);
57
+ }
58
+ });
59
+ });
60
+ describe("rsiSignal()", () => {
61
+ it("returns bearish for overbought RSI", () => {
62
+ const sig = rsiSignal([75]);
63
+ expect(sig.signal).toBe("bearish");
64
+ expect(sig.metadata?.condition).toBe("overbought");
65
+ });
66
+ it("returns bullish for oversold RSI", () => {
67
+ const sig = rsiSignal([25]);
68
+ expect(sig.signal).toBe("bullish");
69
+ expect(sig.metadata?.condition).toBe("oversold");
70
+ });
71
+ it("returns neutral for mid-range RSI", () => {
72
+ const sig = rsiSignal([50]);
73
+ expect(sig.signal).toBe("neutral");
74
+ });
75
+ });
76
+ describe("macd()", () => {
77
+ it("returns empty arrays for insufficient data", () => {
78
+ const result = macd([100, 99], 12, 26, 9);
79
+ expect(result.signal).toEqual([]);
80
+ });
81
+ it("produces histogram for sufficient data", () => {
82
+ const result = macd(risingPrices, 12, 26, 9);
83
+ expect(result.macd.length).toBeGreaterThan(0);
84
+ expect(result.histogram.length).toBeGreaterThan(0);
85
+ });
86
+ });
87
+ describe("macdSignal()", () => {
88
+ it("returns neutral for empty histogram", () => {
89
+ expect(macdSignal({ macd: [], signal: [], histogram: [] }).signal).toBe("neutral");
90
+ });
91
+ it("returns bullish when histogram crosses above zero", () => {
92
+ const sig = macdSignal({ macd: [0.1], signal: [0], histogram: [-0.1, 0.1] });
93
+ expect(sig.signal).toBe("bullish");
94
+ });
95
+ });
96
+ describe("bollingerBands()", () => {
97
+ it("upper band > middle > lower band", () => {
98
+ const bands = bollingerBands(risingPrices, 20, 2);
99
+ const last = bands.upper.length - 1;
100
+ expect(bands.upper[last]).toBeGreaterThan(bands.middle[last]);
101
+ expect(bands.middle[last]).toBeGreaterThan(bands.lower[last]);
102
+ });
103
+ it("flat prices produce narrow bands", () => {
104
+ const bands = bollingerBands(flatPrices, 20, 2);
105
+ const last = bands.width.length - 1;
106
+ expect(bands.width[last]).toBeLessThan(1); // near-zero width for flat prices
107
+ });
108
+ });
109
+ //# sourceMappingURL=TechnicalAnalysis.test.js.map
@@ -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