@zhive/cli 0.6.6 → 0.6.7

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 (57) hide show
  1. package/dist/commands/agent/commands/profile.js +0 -6
  2. package/dist/commands/agent/commands/profile.test.js +2 -23
  3. package/dist/commands/create/presets/index.js +1 -1
  4. package/dist/commands/create/presets/options.js +18 -15
  5. package/dist/commands/create/ui/steps/IdentityStep.js +3 -2
  6. package/dist/commands/doctor/commands/index.js +5 -11
  7. package/dist/commands/megathread/commands/create-comment.js +2 -7
  8. package/dist/commands/megathread/commands/create-comment.test.js +3 -30
  9. package/dist/commands/megathread/commands/create-comments.js +2 -7
  10. package/dist/commands/megathread/commands/list.js +5 -10
  11. package/dist/commands/megathread/commands/list.test.js +3 -21
  12. package/dist/commands/migrate-templates/ui/MigrateApp.js +1 -1
  13. package/dist/commands/start/commands/prediction.js +1 -1
  14. package/dist/commands/start/commands/skills.test.js +1 -2
  15. package/dist/components/MultiSelectPrompt.js +3 -3
  16. package/dist/shared/config/agent.js +4 -0
  17. package/dist/shared/config/agent.test.js +0 -5
  18. package/package.json +1 -1
  19. package/dist/CLAUDE.md +0 -7
  20. package/dist/backtest/CLAUDE.md +0 -7
  21. package/dist/cli.js +0 -20
  22. package/dist/commands/create/presets.js +0 -613
  23. package/dist/commands/start/ui/AsciiTicker.js +0 -81
  24. package/dist/services/agent/analysis.js +0 -160
  25. package/dist/services/agent/config.js +0 -75
  26. package/dist/services/agent/env.js +0 -30
  27. package/dist/services/agent/helpers/model.js +0 -92
  28. package/dist/services/agent/helpers.js +0 -22
  29. package/dist/services/agent/prompts/chat-prompt.js +0 -65
  30. package/dist/services/agent/prompts/memory-prompt.js +0 -45
  31. package/dist/services/agent/prompts/prompt.js +0 -379
  32. package/dist/services/agent/skills/index.js +0 -2
  33. package/dist/services/agent/skills/skill-parser.js +0 -149
  34. package/dist/services/agent/skills/types.js +0 -1
  35. package/dist/services/agent/tools/edit-section.js +0 -59
  36. package/dist/services/agent/tools/fetch-rules.js +0 -21
  37. package/dist/services/agent/tools/index.js +0 -76
  38. package/dist/services/agent/tools/market/client.js +0 -41
  39. package/dist/services/agent/tools/market/index.js +0 -3
  40. package/dist/services/agent/tools/market/tools.js +0 -518
  41. package/dist/services/agent/tools/mindshare/client.js +0 -124
  42. package/dist/services/agent/tools/mindshare/index.js +0 -3
  43. package/dist/services/agent/tools/mindshare/tools.js +0 -563
  44. package/dist/services/agent/tools/read-skill-tool.js +0 -30
  45. package/dist/services/agent/tools/ta/index.js +0 -1
  46. package/dist/services/agent/tools/ta/indicators.js +0 -201
  47. package/dist/services/agent/types.js +0 -1
  48. package/dist/services/ai-providers.js +0 -66
  49. package/dist/services/config/agent.js +0 -110
  50. package/dist/services/config/config.js +0 -22
  51. package/dist/services/config/constant.js +0 -8
  52. package/dist/shared/agent/agent-runtime.js +0 -144
  53. package/dist/shared/agent/config.js +0 -75
  54. package/dist/shared/agent/env.js +0 -30
  55. package/dist/shared/agent/helpers/model.js +0 -92
  56. package/dist/shared/agent/types.js +0 -1
  57. package/dist/shared/ai-providers.js +0 -66
@@ -1,76 +0,0 @@
1
- import * as path from 'node:path';
2
- import { discoverSkills } from '../skills/skill-parser.js';
3
- import { createReadSkillTool } from './read-skill-tool.js';
4
- import { marketTools } from './market/index.js';
5
- import { mindshareTools } from './mindshare/index.js';
6
- /**
7
- * Get all tools that are always available to agents.
8
- * Tools are bundled with the CLI and don't require skill installation.
9
- * Skills provide knowledge/guidance on when and how to use these tools.
10
- */
11
- export function getAllTools() {
12
- const tools = {};
13
- for (const [name, tool] of Object.entries(marketTools)) {
14
- const namespacedName = `market_${name}`;
15
- tools[namespacedName] = tool;
16
- }
17
- for (const [name, tool] of Object.entries(mindshareTools)) {
18
- const namespacedName = `mindshare_${name}`;
19
- tools[namespacedName] = tool;
20
- }
21
- return tools;
22
- }
23
- /**
24
- * Initialize skills from agent's skills/ directory.
25
- * Skills are knowledge-only documents that help agents understand
26
- * when and how to use the always-available tools.
27
- * Returns a registry map that should be passed to other skill functions.
28
- */
29
- export async function initializeSkills(agentPath) {
30
- const skillRegistry = new Map();
31
- const skillsPath = path.join(agentPath, 'skills');
32
- const agentSkills = await discoverSkills(skillsPath);
33
- for (const skill of agentSkills) {
34
- skillRegistry.set(skill.id, skill);
35
- }
36
- return skillRegistry;
37
- }
38
- /**
39
- * Get a skill metadata list for prompt injection.
40
- * Shows skill ID and description to help the agent decide what to use.
41
- */
42
- export function getSkillMetadataList(skillRegistry) {
43
- if (skillRegistry.size === 0) {
44
- return '';
45
- }
46
- const lines = Array.from(skillRegistry.values())
47
- .map((s) => `- ${s.id}: ${s.metadata.description}`)
48
- .join('\n');
49
- const output = `Available skills:\n${lines}`;
50
- return output;
51
- }
52
- /**
53
- * Get the readSkill tool for agent to read skill knowledge.
54
- */
55
- export function getReadSkillTool(skillRegistry) {
56
- const readSkillTool = createReadSkillTool(skillRegistry);
57
- return readSkillTool;
58
- }
59
- /**
60
- * Get a skill definition by ID.
61
- */
62
- export function getSkill(skillRegistry, id) {
63
- return skillRegistry.get(id);
64
- }
65
- /**
66
- * Get all registered skills.
67
- */
68
- export function getAllSkills(skillRegistry) {
69
- return Array.from(skillRegistry.values());
70
- }
71
- /**
72
- * Check if any skills are registered.
73
- */
74
- export function hasSkills(skillRegistry) {
75
- return skillRegistry.size > 0;
76
- }
@@ -1,41 +0,0 @@
1
- import { HIVE_API_URL } from '../../../config/constant.js';
2
- /**
3
- * Client for the backend Market API.
4
- */
5
- export class MarketClient {
6
- constructor(baseUrl = HIVE_API_URL) {
7
- this._baseUrl = baseUrl;
8
- }
9
- async getPrice(projectId, timestamp) {
10
- const url = `${this._baseUrl}/market/price/${encodeURIComponent(projectId)}?timestamp=${encodeURIComponent(timestamp)}`;
11
- const response = await fetch(url);
12
- if (!response.ok) {
13
- const text = await response.text();
14
- throw new Error(`Market price request failed: ${response.status} - ${text}`);
15
- }
16
- const data = (await response.json());
17
- return data;
18
- }
19
- async getOHLC(id, from, to, interval = 'daily') {
20
- const params = new URLSearchParams({
21
- from,
22
- to,
23
- interval,
24
- });
25
- const url = `${this._baseUrl}/market/ohlc/${encodeURIComponent(id)}?${params.toString()}`;
26
- const response = await fetch(url);
27
- if (!response.ok) {
28
- const text = await response.text();
29
- throw new Error(`Market OHLC request failed: ${response.status} - ${text}`);
30
- }
31
- const data = (await response.json());
32
- return data;
33
- }
34
- }
35
- let clientInstance = null;
36
- export function getMarketClient() {
37
- if (clientInstance === null) {
38
- clientInstance = new MarketClient();
39
- }
40
- return clientInstance;
41
- }
@@ -1,3 +0,0 @@
1
- export { marketTools } from './tools.js';
2
- export { getPriceTool, getOHLCTool, getSMATool, getEMATool, getRSITool, getMACDTool, getBollingerTool, } from './tools.js';
3
- export { MarketClient, getMarketClient } from './client.js';
@@ -1,518 +0,0 @@
1
- import { tool } from 'ai';
2
- import { z } from 'zod';
3
- import { getMarketClient } from './client.js';
4
- import { computeSMA, computeEMA, computeRSI, computeMACD, computeBollingerBands, } from '../ta/indicators.js';
5
- export const getPriceTool = tool({
6
- description: 'Get cryptocurrency price. Returns the current price by default, or the price at a specific timestamp if provided.',
7
- inputSchema: z.object({
8
- projectId: z
9
- .string()
10
- .describe('Project ID to get price for (e.g., "bitcoin", "ethereum"). Use lowercase.'),
11
- timestamp: z
12
- .string()
13
- .optional()
14
- .describe('Optional timestamp in ISO 8601 format. If omitted, returns the current price.'),
15
- }),
16
- execute: async ({ projectId, timestamp }) => {
17
- try {
18
- const effectiveTimestamp = timestamp ?? new Date().toISOString();
19
- const client = getMarketClient();
20
- const priceData = await client.getPrice(projectId, effectiveTimestamp);
21
- if (priceData.price === null) {
22
- return `No price data available for ${projectId} at ${effectiveTimestamp}.`;
23
- }
24
- const price = priceData.price;
25
- const priceFormatted = price.toLocaleString('en-US', {
26
- style: 'currency',
27
- currency: 'USD',
28
- minimumFractionDigits: 2,
29
- maximumFractionDigits: price < 1 ? 6 : 2,
30
- });
31
- const timeLabel = timestamp ? `at ${timestamp}` : '(current)';
32
- const output = `${projectId}: ${priceFormatted} ${timeLabel}`;
33
- return output;
34
- }
35
- catch (err) {
36
- const message = err instanceof Error ? err.message : String(err);
37
- return `Error fetching price: ${message}`;
38
- }
39
- },
40
- });
41
- export const getOHLCTool = tool({
42
- description: 'Get historical OHLC (Open, High, Low, Close) candlestick data for a cryptocurrency. Use this to analyze price history and patterns.',
43
- inputSchema: z.object({
44
- id: z.string().describe('Coin ID (e.g., "bitcoin", "ethereum"). Use lowercase.'),
45
- from: z
46
- .string()
47
- .describe('Start date in ISO 8601 format. Use recent dates relative to the current date from context (e.g., 30 days ago).'),
48
- to: z
49
- .string()
50
- .describe('End date in ISO 8601 format. Use the current date from context as the end date.'),
51
- interval: z
52
- .enum(['daily', 'hourly'])
53
- .optional()
54
- .describe('Data interval: "daily" or "hourly". Defaults to "daily".'),
55
- }),
56
- execute: async ({ id, from, to, interval }) => {
57
- try {
58
- const client = getMarketClient();
59
- const effectiveInterval = interval ?? 'daily';
60
- const ohlcData = await client.getOHLC(id, from, to, effectiveInterval);
61
- if (ohlcData.length === 0) {
62
- return `No OHLC data available for ${id} from ${from} to ${to}.`;
63
- }
64
- const lines = [
65
- `OHLC data for ${id} (${effectiveInterval}, ${ohlcData.length} data points):`,
66
- '',
67
- 'Date | Open | High | Low | Close',
68
- '--- | --- | --- | --- | ---',
69
- ];
70
- const maxPoints = 30;
71
- const displayData = ohlcData.length > maxPoints
72
- ? [...ohlcData.slice(0, 10), null, ...ohlcData.slice(-10)]
73
- : ohlcData;
74
- for (const point of displayData) {
75
- if (point === null) {
76
- lines.push(`... (${ohlcData.length - 20} more rows) ...`);
77
- continue;
78
- }
79
- const date = new Date(point[0]).toISOString().split('T')[0];
80
- const open = point[1].toFixed(2);
81
- const high = point[2].toFixed(2);
82
- const low = point[3].toFixed(2);
83
- const close = point[4].toFixed(2);
84
- lines.push(`${date} | $${open} | $${high} | $${low} | $${close}`);
85
- }
86
- const firstClose = ohlcData[0][4];
87
- const lastClose = ohlcData[ohlcData.length - 1][4];
88
- const changePercent = ((lastClose - firstClose) / firstClose) * 100;
89
- const sign = changePercent >= 0 ? '+' : '';
90
- lines.push('');
91
- lines.push(`Period change: ${sign}${changePercent.toFixed(2)}% ($${firstClose.toFixed(2)} → $${lastClose.toFixed(2)})`);
92
- const output = lines.join('\n');
93
- return output;
94
- }
95
- catch (err) {
96
- const message = err instanceof Error ? err.message : String(err);
97
- return `Error fetching OHLC data: ${message}`;
98
- }
99
- },
100
- });
101
- export const getSMATool = tool({
102
- description: 'Calculate Simple Moving Average (SMA) for a cryptocurrency. SMA smooths price data by averaging prices over a specified period. Commonly used periods: 20 (short-term), 50 (medium-term), 200 (long-term).',
103
- inputSchema: z.object({
104
- id: z.string().describe('Coin ID (e.g., "bitcoin", "ethereum"). Use lowercase.'),
105
- period: z
106
- .number()
107
- .int()
108
- .min(2)
109
- .max(200)
110
- .describe('Number of periods for the SMA (e.g., 20, 50, 200).'),
111
- from: z
112
- .string()
113
- .describe('Start date in ISO 8601 format. Use recent dates relative to the current date from context (e.g., 30 days ago).'),
114
- to: z
115
- .string()
116
- .describe('End date in ISO 8601 format. Use the current date from context as the end date.'),
117
- interval: z
118
- .enum(['daily', 'hourly'])
119
- .optional()
120
- .describe('Data interval: "daily" or "hourly". Defaults to "daily".'),
121
- }),
122
- execute: async ({ id, period, from, to, interval }) => {
123
- try {
124
- const client = getMarketClient();
125
- const effectiveInterval = interval ?? 'daily';
126
- const ohlcData = await client.getOHLC(id, from, to, effectiveInterval);
127
- if (ohlcData.length < period) {
128
- return `Insufficient data: got ${ohlcData.length} data points but need at least ${period} for SMA-${period}.`;
129
- }
130
- const smaData = computeSMA(ohlcData, period);
131
- if (smaData.length === 0) {
132
- return `Could not compute SMA-${period} for ${id}.`;
133
- }
134
- const lines = [
135
- `SMA-${period} for ${id} (${effectiveInterval}, ${smaData.length} values):`,
136
- '',
137
- ];
138
- const maxPoints = 20;
139
- const displayData = smaData.length > maxPoints
140
- ? [...smaData.slice(0, 5), null, ...smaData.slice(-10)]
141
- : smaData;
142
- for (const point of displayData) {
143
- if (point === null) {
144
- lines.push(`... (${smaData.length - 15} more values) ...`);
145
- continue;
146
- }
147
- const date = new Date(point.timestamp).toISOString().split('T')[0];
148
- lines.push(`${date}: $${point.value.toFixed(2)}`);
149
- }
150
- const latestSMA = smaData[smaData.length - 1].value;
151
- const latestPrice = ohlcData[ohlcData.length - 1][4];
152
- const aboveBelow = latestPrice > latestSMA ? 'above' : 'below';
153
- const diff = ((latestPrice - latestSMA) / latestSMA) * 100;
154
- const sign = diff >= 0 ? '+' : '';
155
- lines.push('');
156
- lines.push(`Current price ($${latestPrice.toFixed(2)}) is ${aboveBelow} SMA-${period} ($${latestSMA.toFixed(2)}) by ${sign}${diff.toFixed(2)}%`);
157
- const output = lines.join('\n');
158
- return output;
159
- }
160
- catch (err) {
161
- const message = err instanceof Error ? err.message : String(err);
162
- return `Error calculating SMA: ${message}`;
163
- }
164
- },
165
- });
166
- export const getEMATool = tool({
167
- description: 'Calculate Exponential Moving Average (EMA) for a cryptocurrency. EMA gives more weight to recent prices, making it more responsive than SMA. Common periods: 12, 26 (for MACD), 9 (signal line), 20, 50, 200.',
168
- inputSchema: z.object({
169
- id: z.string().describe('Coin ID (e.g., "bitcoin", "ethereum"). Use lowercase.'),
170
- period: z
171
- .number()
172
- .int()
173
- .min(2)
174
- .max(200)
175
- .describe('Number of periods for the EMA (e.g., 12, 26, 50, 200).'),
176
- from: z
177
- .string()
178
- .describe('Start date in ISO 8601 format. Use recent dates relative to the current date from context (e.g., 30 days ago).'),
179
- to: z
180
- .string()
181
- .describe('End date in ISO 8601 format. Use the current date from context as the end date.'),
182
- interval: z
183
- .enum(['daily', 'hourly'])
184
- .optional()
185
- .describe('Data interval: "daily" or "hourly". Defaults to "daily".'),
186
- }),
187
- execute: async ({ id, period, from, to, interval }) => {
188
- try {
189
- const client = getMarketClient();
190
- const effectiveInterval = interval ?? 'daily';
191
- const ohlcData = await client.getOHLC(id, from, to, effectiveInterval);
192
- if (ohlcData.length < period) {
193
- return `Insufficient data: got ${ohlcData.length} data points but need at least ${period} for EMA-${period}.`;
194
- }
195
- const emaData = computeEMA(ohlcData, period);
196
- if (emaData.length === 0) {
197
- return `Could not compute EMA-${period} for ${id}.`;
198
- }
199
- const lines = [
200
- `EMA-${period} for ${id} (${effectiveInterval}, ${emaData.length} values):`,
201
- '',
202
- ];
203
- const maxPoints = 20;
204
- const displayData = emaData.length > maxPoints
205
- ? [...emaData.slice(0, 5), null, ...emaData.slice(-10)]
206
- : emaData;
207
- for (const point of displayData) {
208
- if (point === null) {
209
- lines.push(`... (${emaData.length - 15} more values) ...`);
210
- continue;
211
- }
212
- const date = new Date(point.timestamp).toISOString().split('T')[0];
213
- lines.push(`${date}: $${point.value.toFixed(2)}`);
214
- }
215
- const latestEMA = emaData[emaData.length - 1].value;
216
- const latestPrice = ohlcData[ohlcData.length - 1][4];
217
- const aboveBelow = latestPrice > latestEMA ? 'above' : 'below';
218
- const diff = ((latestPrice - latestEMA) / latestEMA) * 100;
219
- const sign = diff >= 0 ? '+' : '';
220
- lines.push('');
221
- lines.push(`Current price ($${latestPrice.toFixed(2)}) is ${aboveBelow} EMA-${period} ($${latestEMA.toFixed(2)}) by ${sign}${diff.toFixed(2)}%`);
222
- const output = lines.join('\n');
223
- return output;
224
- }
225
- catch (err) {
226
- const message = err instanceof Error ? err.message : String(err);
227
- return `Error calculating EMA: ${message}`;
228
- }
229
- },
230
- });
231
- export const getRSITool = tool({
232
- description: 'Calculate Relative Strength Index (RSI) for a cryptocurrency. RSI measures momentum on a 0-100 scale. Readings above 70 suggest overbought conditions; below 30 suggests oversold. Standard period is 14.',
233
- inputSchema: z.object({
234
- id: z.string().describe('Coin ID (e.g., "bitcoin", "ethereum"). Use lowercase.'),
235
- period: z.number().int().min(2).max(50).optional().describe('RSI period. Defaults to 14.'),
236
- from: z
237
- .string()
238
- .describe('Start date in ISO 8601 format. Use recent dates relative to the current date from context (e.g., 30 days ago).'),
239
- to: z
240
- .string()
241
- .describe('End date in ISO 8601 format. Use the current date from context as the end date.'),
242
- interval: z
243
- .enum(['daily', 'hourly'])
244
- .optional()
245
- .describe('Data interval: "daily" or "hourly". Defaults to "daily".'),
246
- }),
247
- execute: async ({ id, period, from, to, interval }) => {
248
- try {
249
- const effectivePeriod = period ?? 14;
250
- const client = getMarketClient();
251
- const effectiveInterval = interval ?? 'daily';
252
- const ohlcData = await client.getOHLC(id, from, to, effectiveInterval);
253
- if (ohlcData.length < effectivePeriod + 1) {
254
- return `Insufficient data: got ${ohlcData.length} data points but need at least ${effectivePeriod + 1} for RSI-${effectivePeriod}.`;
255
- }
256
- const rsiData = computeRSI(ohlcData, effectivePeriod);
257
- if (rsiData.length === 0) {
258
- return `Could not compute RSI-${effectivePeriod} for ${id}.`;
259
- }
260
- const lines = [
261
- `RSI-${effectivePeriod} for ${id} (${effectiveInterval}, ${rsiData.length} values):`,
262
- '',
263
- ];
264
- const maxPoints = 20;
265
- const displayData = rsiData.length > maxPoints
266
- ? [...rsiData.slice(0, 5), null, ...rsiData.slice(-10)]
267
- : rsiData;
268
- for (const point of displayData) {
269
- if (point === null) {
270
- lines.push(`... (${rsiData.length - 15} more values) ...`);
271
- continue;
272
- }
273
- const date = new Date(point.timestamp).toISOString().split('T')[0];
274
- const rsiValue = point.value.toFixed(2);
275
- let status = '';
276
- if (point.value >= 70) {
277
- status = ' [OVERBOUGHT]';
278
- }
279
- else if (point.value <= 30) {
280
- status = ' [OVERSOLD]';
281
- }
282
- lines.push(`${date}: ${rsiValue}${status}`);
283
- }
284
- const latestRSI = rsiData[rsiData.length - 1].value;
285
- let interpretation = '';
286
- if (latestRSI >= 70) {
287
- interpretation =
288
- 'The RSI is in overbought territory (>=70), which may indicate the asset is overvalued and could see a pullback.';
289
- }
290
- else if (latestRSI <= 30) {
291
- interpretation =
292
- 'The RSI is in oversold territory (<=30), which may indicate the asset is undervalued and could see a bounce.';
293
- }
294
- else if (latestRSI >= 50) {
295
- interpretation = 'The RSI shows bullish momentum (above 50).';
296
- }
297
- else {
298
- interpretation = 'The RSI shows bearish momentum (below 50).';
299
- }
300
- lines.push('');
301
- lines.push(`Current RSI: ${latestRSI.toFixed(2)}`);
302
- lines.push(interpretation);
303
- const output = lines.join('\n');
304
- return output;
305
- }
306
- catch (err) {
307
- const message = err instanceof Error ? err.message : String(err);
308
- return `Error calculating RSI: ${message}`;
309
- }
310
- },
311
- });
312
- export const getMACDTool = tool({
313
- description: 'Calculate MACD (Moving Average Convergence Divergence) for a cryptocurrency. MACD is a trend-following momentum indicator. Standard settings: fast=12, slow=26, signal=9. Bullish when MACD crosses above signal line; bearish when below.',
314
- inputSchema: z.object({
315
- id: z.string().describe('Coin ID (e.g., "bitcoin", "ethereum"). Use lowercase.'),
316
- fastPeriod: z
317
- .number()
318
- .int()
319
- .min(2)
320
- .max(50)
321
- .optional()
322
- .describe('Fast EMA period. Defaults to 12.'),
323
- slowPeriod: z
324
- .number()
325
- .int()
326
- .min(2)
327
- .max(100)
328
- .optional()
329
- .describe('Slow EMA period. Defaults to 26.'),
330
- signalPeriod: z
331
- .number()
332
- .int()
333
- .min(2)
334
- .max(50)
335
- .optional()
336
- .describe('Signal line EMA period. Defaults to 9.'),
337
- from: z
338
- .string()
339
- .describe('Start date in ISO 8601 format. Use recent dates relative to the current date from context (e.g., 30 days ago).'),
340
- to: z
341
- .string()
342
- .describe('End date in ISO 8601 format. Use the current date from context as the end date.'),
343
- interval: z
344
- .enum(['daily', 'hourly'])
345
- .optional()
346
- .describe('Data interval: "daily" or "hourly". Defaults to "daily".'),
347
- }),
348
- execute: async ({ id, fastPeriod, slowPeriod, signalPeriod, from, to, interval }) => {
349
- try {
350
- const fast = fastPeriod ?? 12;
351
- const slow = slowPeriod ?? 26;
352
- const signal = signalPeriod ?? 9;
353
- const client = getMarketClient();
354
- const effectiveInterval = interval ?? 'daily';
355
- const ohlcData = await client.getOHLC(id, from, to, effectiveInterval);
356
- const minRequired = slow + signal;
357
- if (ohlcData.length < minRequired) {
358
- return `Insufficient data: got ${ohlcData.length} data points but need at least ${minRequired} for MACD(${fast},${slow},${signal}).`;
359
- }
360
- const macdData = computeMACD(ohlcData, fast, slow, signal);
361
- if (macdData.length === 0) {
362
- return `Could not compute MACD(${fast},${slow},${signal}) for ${id}.`;
363
- }
364
- const lines = [
365
- `MACD(${fast},${slow},${signal}) for ${id} (${effectiveInterval}, ${macdData.length} values):`,
366
- '',
367
- 'Date | MACD | Signal | Histogram',
368
- '--- | --- | --- | ---',
369
- ];
370
- const maxPoints = 20;
371
- const displayData = macdData.length > maxPoints
372
- ? [...macdData.slice(0, 5), null, ...macdData.slice(-10)]
373
- : macdData;
374
- for (const point of displayData) {
375
- if (point === null) {
376
- lines.push(`... (${macdData.length - 15} more values) ...`);
377
- continue;
378
- }
379
- const date = new Date(point.timestamp).toISOString().split('T')[0];
380
- const macdVal = point.macd.toFixed(4);
381
- const signalVal = point.signal.toFixed(4);
382
- const histVal = point.histogram.toFixed(4);
383
- const histSign = point.histogram >= 0 ? '+' : '';
384
- lines.push(`${date} | ${macdVal} | ${signalVal} | ${histSign}${histVal}`);
385
- }
386
- const latest = macdData[macdData.length - 1];
387
- const previous = macdData.length > 1 ? macdData[macdData.length - 2] : null;
388
- let crossover = '';
389
- if (previous !== null) {
390
- if (previous.macd <= previous.signal && latest.macd > latest.signal) {
391
- crossover = 'BULLISH CROSSOVER detected (MACD crossed above Signal line)!';
392
- }
393
- else if (previous.macd >= previous.signal && latest.macd < latest.signal) {
394
- crossover = 'BEARISH CROSSOVER detected (MACD crossed below Signal line)!';
395
- }
396
- }
397
- const trend = latest.macd > latest.signal ? 'bullish (MACD above signal)' : 'bearish (MACD below signal)';
398
- const momentum = latest.histogram > 0 ? 'positive' : 'negative';
399
- lines.push('');
400
- lines.push(`Current trend: ${trend}`);
401
- lines.push(`Histogram momentum: ${momentum}`);
402
- if (crossover) {
403
- lines.push(crossover);
404
- }
405
- const output = lines.join('\n');
406
- return output;
407
- }
408
- catch (err) {
409
- const message = err instanceof Error ? err.message : String(err);
410
- return `Error calculating MACD: ${message}`;
411
- }
412
- },
413
- });
414
- export const getBollingerTool = tool({
415
- description: 'Calculate Bollinger Bands for a cryptocurrency. Bollinger Bands consist of a middle SMA band with upper and lower bands based on standard deviation. Price near upper band may indicate overbought; near lower band may indicate oversold. Standard settings: period=20, stdDev=2.',
416
- inputSchema: z.object({
417
- id: z.string().describe('Coin ID (e.g., "bitcoin", "ethereum"). Use lowercase.'),
418
- period: z
419
- .number()
420
- .int()
421
- .min(2)
422
- .max(100)
423
- .optional()
424
- .describe('SMA period for the middle band. Defaults to 20.'),
425
- stdDev: z
426
- .number()
427
- .min(0.5)
428
- .max(5)
429
- .optional()
430
- .describe('Standard deviation multiplier for bands. Defaults to 2.'),
431
- from: z
432
- .string()
433
- .describe('Start date in ISO 8601 format. Use recent dates relative to the current date from context (e.g., 30 days ago).'),
434
- to: z
435
- .string()
436
- .describe('End date in ISO 8601 format. Use the current date from context as the end date.'),
437
- interval: z
438
- .enum(['daily', 'hourly'])
439
- .optional()
440
- .describe('Data interval: "daily" or "hourly". Defaults to "daily".'),
441
- }),
442
- execute: async ({ id, period, stdDev, from, to, interval }) => {
443
- try {
444
- const effectivePeriod = period ?? 20;
445
- const effectiveStdDev = stdDev ?? 2;
446
- const client = getMarketClient();
447
- const effectiveInterval = interval ?? 'daily';
448
- const ohlcData = await client.getOHLC(id, from, to, effectiveInterval);
449
- if (ohlcData.length < effectivePeriod) {
450
- return `Insufficient data: got ${ohlcData.length} data points but need at least ${effectivePeriod} for Bollinger Bands(${effectivePeriod},${effectiveStdDev}).`;
451
- }
452
- const bbData = computeBollingerBands(ohlcData, effectivePeriod, effectiveStdDev);
453
- if (bbData.length === 0) {
454
- return `Could not compute Bollinger Bands(${effectivePeriod},${effectiveStdDev}) for ${id}.`;
455
- }
456
- const lines = [
457
- `Bollinger Bands(${effectivePeriod},${effectiveStdDev}) for ${id} (${effectiveInterval}, ${bbData.length} values):`,
458
- '',
459
- 'Date | Upper | Middle | Lower',
460
- '--- | --- | --- | ---',
461
- ];
462
- const maxPoints = 20;
463
- const displayData = bbData.length > maxPoints ? [...bbData.slice(0, 5), null, ...bbData.slice(-10)] : bbData;
464
- for (const point of displayData) {
465
- if (point === null) {
466
- lines.push(`... (${bbData.length - 15} more values) ...`);
467
- continue;
468
- }
469
- const date = new Date(point.timestamp).toISOString().split('T')[0];
470
- lines.push(`${date} | $${point.upper.toFixed(2)} | $${point.middle.toFixed(2)} | $${point.lower.toFixed(2)}`);
471
- }
472
- const latest = bbData[bbData.length - 1];
473
- const latestPrice = ohlcData[ohlcData.length - 1][4];
474
- const bandwidth = ((latest.upper - latest.lower) / latest.middle) * 100;
475
- let position = '';
476
- const percentB = (latestPrice - latest.lower) / (latest.upper - latest.lower);
477
- if (latestPrice > latest.upper) {
478
- position = 'ABOVE upper band (potential overbought)';
479
- }
480
- else if (latestPrice < latest.lower) {
481
- position = 'BELOW lower band (potential oversold)';
482
- }
483
- else if (percentB > 0.8) {
484
- position = 'near upper band (approaching overbought)';
485
- }
486
- else if (percentB < 0.2) {
487
- position = 'near lower band (approaching oversold)';
488
- }
489
- else {
490
- position = 'within bands (neutral zone)';
491
- }
492
- lines.push('');
493
- lines.push(`Current price: $${latestPrice.toFixed(2)}`);
494
- lines.push(`Bands: Upper $${latest.upper.toFixed(2)} | Middle $${latest.middle.toFixed(2)} | Lower $${latest.lower.toFixed(2)}`);
495
- lines.push(`Bandwidth: ${bandwidth.toFixed(2)}%`);
496
- lines.push(`%B (position within bands): ${(percentB * 100).toFixed(1)}%`);
497
- lines.push(`Price position: ${position}`);
498
- const output = lines.join('\n');
499
- return output;
500
- }
501
- catch (err) {
502
- const message = err instanceof Error ? err.message : String(err);
503
- return `Error calculating Bollinger Bands: ${message}`;
504
- }
505
- },
506
- });
507
- /**
508
- * All market tools for export.
509
- */
510
- export const marketTools = {
511
- getPrice: getPriceTool,
512
- getOHLC: getOHLCTool,
513
- getSMA: getSMATool,
514
- getEMA: getEMATool,
515
- getRSI: getRSITool,
516
- getMACD: getMACDTool,
517
- getBollinger: getBollingerTool,
518
- };