@zhive/cli 0.6.7 → 0.6.9

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 (70) hide show
  1. package/dist/CLAUDE.md +7 -0
  2. package/dist/backtest/CLAUDE.md +7 -0
  3. package/dist/cli.js +20 -0
  4. package/dist/commands/agent/commands/profile.js +3 -9
  5. package/dist/commands/create/commands/index.js +2 -5
  6. package/dist/commands/create/generate.js +18 -22
  7. package/dist/commands/create/presets.js +613 -0
  8. package/dist/commands/create/ui/CreateApp.js +2 -2
  9. package/dist/commands/create/ui/steps/ScaffoldStep.js +17 -2
  10. package/dist/commands/indicator/commands/bollinger.js +37 -0
  11. package/dist/commands/indicator/commands/ema.js +37 -0
  12. package/dist/commands/indicator/commands/index.js +14 -0
  13. package/dist/commands/indicator/commands/macd.js +51 -0
  14. package/dist/commands/indicator/commands/rsi.js +37 -0
  15. package/dist/commands/indicator/commands/sma.js +37 -0
  16. package/dist/commands/market/commands/index.js +5 -0
  17. package/dist/commands/market/commands/price.js +25 -0
  18. package/dist/commands/shared/utils.js +12 -0
  19. package/dist/commands/start/ui/AsciiTicker.js +81 -0
  20. package/dist/index.js +4 -0
  21. package/dist/services/agent/analysis.js +160 -0
  22. package/dist/services/agent/config.js +75 -0
  23. package/dist/services/agent/env.js +30 -0
  24. package/dist/services/agent/helpers/model.js +92 -0
  25. package/dist/services/agent/helpers.js +22 -0
  26. package/dist/services/agent/prompts/chat-prompt.js +65 -0
  27. package/dist/services/agent/prompts/memory-prompt.js +45 -0
  28. package/dist/services/agent/prompts/prompt.js +379 -0
  29. package/dist/services/agent/skills/index.js +2 -0
  30. package/dist/services/agent/skills/skill-parser.js +149 -0
  31. package/dist/services/agent/skills/types.js +1 -0
  32. package/dist/services/agent/tools/edit-section.js +59 -0
  33. package/dist/services/agent/tools/fetch-rules.js +21 -0
  34. package/dist/services/agent/tools/index.js +76 -0
  35. package/dist/services/agent/tools/market/client.js +41 -0
  36. package/dist/services/agent/tools/market/index.js +3 -0
  37. package/dist/services/agent/tools/market/tools.js +518 -0
  38. package/dist/services/agent/tools/mindshare/client.js +124 -0
  39. package/dist/services/agent/tools/mindshare/index.js +3 -0
  40. package/dist/services/agent/tools/mindshare/tools.js +563 -0
  41. package/dist/services/agent/tools/read-skill-tool.js +30 -0
  42. package/dist/services/agent/tools/ta/index.js +1 -0
  43. package/dist/services/agent/tools/ta/indicators.js +201 -0
  44. package/dist/services/agent/types.js +1 -0
  45. package/dist/services/ai-providers.js +66 -0
  46. package/dist/services/config/agent.js +110 -0
  47. package/dist/services/config/config.js +22 -0
  48. package/dist/services/config/constant.js +8 -0
  49. package/dist/shared/agent/agent-runtime.js +144 -0
  50. package/dist/shared/agent/analysis.js +2 -12
  51. package/dist/shared/agent/cache.js +10 -0
  52. package/dist/shared/agent/config.js +75 -0
  53. package/dist/shared/agent/env.js +30 -0
  54. package/dist/shared/agent/handler.js +3 -9
  55. package/dist/shared/agent/helpers/model.js +92 -0
  56. package/dist/shared/agent/prompts/megathread.js +0 -8
  57. package/dist/shared/agent/tools/execute-skill-tool.js +2 -1
  58. package/dist/shared/agent/tools/formatting.js +0 -19
  59. package/dist/shared/agent/tools/market/client.js +3 -3
  60. package/dist/shared/agent/tools/market/tools.js +88 -312
  61. package/dist/shared/agent/tools/market/utils.js +71 -0
  62. package/dist/shared/agent/tools/mindshare/tools.js +1 -1
  63. package/dist/shared/agent/tools/ta/index.js +3 -1
  64. package/dist/shared/agent/types.js +1 -0
  65. package/dist/shared/agent/utils.js +44 -0
  66. package/dist/shared/ai-providers.js +66 -0
  67. package/dist/shared/ta/error.js +12 -0
  68. package/dist/shared/ta/service.js +93 -0
  69. package/dist/shared/ta/utils.js +16 -0
  70. package/package.json +3 -2
@@ -0,0 +1,518 @@
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
+ };
@@ -0,0 +1,124 @@
1
+ import { HIVE_API_URL } from '../../../config/constant.js';
2
+ export class MindshareClient {
3
+ constructor(baseUrl = HIVE_API_URL) {
4
+ this._baseUrl = baseUrl;
5
+ }
6
+ async _fetch(path, params) {
7
+ const url = new URL(path, this._baseUrl);
8
+ if (params) {
9
+ for (const [key, value] of Object.entries(params)) {
10
+ if (value !== undefined) {
11
+ url.searchParams.set(key, String(value));
12
+ }
13
+ }
14
+ }
15
+ const response = await fetch(url.toString());
16
+ if (!response.ok) {
17
+ const text = await response.text();
18
+ throw new Error(`Mindshare API request failed: ${response.status} - ${text}`);
19
+ }
20
+ const data = (await response.json());
21
+ return data;
22
+ }
23
+ async getProjectLeaderboard(timeframe, rankBy, limit) {
24
+ const result = await this._fetch('/mindshare/project/leaderboard', {
25
+ timeframe,
26
+ rankBy,
27
+ limit,
28
+ });
29
+ return result;
30
+ }
31
+ async getProjectMindshare(projectId, timeframe) {
32
+ const result = await this._fetch(`/mindshare/project/${encodeURIComponent(projectId)}`, {
33
+ timeframe,
34
+ });
35
+ return result;
36
+ }
37
+ async getProjectMindshareTimeseries(projectId, timeframe) {
38
+ const result = await this._fetch(`/mindshare/project/${encodeURIComponent(projectId)}/timeseries`, { timeframe });
39
+ return result;
40
+ }
41
+ async getProjectLeaderboardBySector(sectorId, timeframe, rankBy, limit, filterBy) {
42
+ const result = await this._fetch(`/mindshare/project/sector/${encodeURIComponent(sectorId)}/leaderboard`, { timeframe, rankBy, limit, filterBy });
43
+ return result;
44
+ }
45
+ async getSectorLeaderboard(timeframe, rankBy, limit) {
46
+ const result = await this._fetch('/mindshare/sector/leaderboard', {
47
+ timeframe,
48
+ rankBy,
49
+ limit,
50
+ });
51
+ return result;
52
+ }
53
+ async getSectorMindshare(sectorId, timeframe) {
54
+ const result = await this._fetch(`/mindshare/sector/${encodeURIComponent(sectorId)}`, {
55
+ timeframe,
56
+ });
57
+ return result;
58
+ }
59
+ async getSectorMindshareTimeseries(sectorId, timeframe) {
60
+ const result = await this._fetch(`/mindshare/sector/${encodeURIComponent(sectorId)}/timeseries`, { timeframe });
61
+ return result;
62
+ }
63
+ async getUserLeaderboard(timeframe, rankBy, limit, page) {
64
+ const result = await this._fetch('/mindshare/user/leaderboard', {
65
+ timeframe,
66
+ rankBy,
67
+ limit,
68
+ page,
69
+ });
70
+ return result;
71
+ }
72
+ async getUserLeaderboardByProject(projectId, timeframe, rankBy, limit, page) {
73
+ const result = await this._fetch(`/mindshare/user/project/${encodeURIComponent(projectId)}/leaderboard`, { timeframe, rankBy, limit, page });
74
+ return result;
75
+ }
76
+ async getUserMindshare(userId, timeframe, withTimeseries) {
77
+ const result = await this._fetch(`/mindshare/user/${encodeURIComponent(userId)}`, {
78
+ timeframe,
79
+ withTimeseries,
80
+ });
81
+ return result;
82
+ }
83
+ async getUserMindshareTimeseries(userId, timeframe) {
84
+ const result = await this._fetch(`/mindshare/user/${encodeURIComponent(userId)}/timeseries`, { timeframe });
85
+ return result;
86
+ }
87
+ async getMindshareDeltaSignals(projectId, minThreshold, limit, page, includeTrendingTopics) {
88
+ const result = await this._fetch('/signal/mindshare/delta', {
89
+ projectId,
90
+ minThreshold,
91
+ limit,
92
+ page,
93
+ includeTrendingTopics,
94
+ });
95
+ return result;
96
+ }
97
+ async getMindshareMASignals(projectId, minThreshold, limit, page, includeTrendingTopics) {
98
+ const result = await this._fetch('/signal/mindshare/ma', {
99
+ projectId,
100
+ minThreshold,
101
+ limit,
102
+ page,
103
+ includeTrendingTopics,
104
+ });
105
+ return result;
106
+ }
107
+ async getMindshareSMAZScoreSignals(projectId, limit, cursor, mode, includeSignalSummary) {
108
+ const result = await this._fetch('/signal/mindshare/sma-zscore', {
109
+ projectId,
110
+ limit,
111
+ cursor,
112
+ mode,
113
+ includeSignalSummary,
114
+ });
115
+ return result;
116
+ }
117
+ }
118
+ let clientInstance = null;
119
+ export function getMindshareClient() {
120
+ if (clientInstance === null) {
121
+ clientInstance = new MindshareClient();
122
+ }
123
+ return clientInstance;
124
+ }
@@ -0,0 +1,3 @@
1
+ export { mindshareTools } from './tools.js';
2
+ export { getProjectLeaderboardTool, getProjectMindshareTool, getProjectMindshareTimeseriesTool, getProjectLeaderboardBySectorTool, getSectorLeaderboardTool, getSectorMindshareTool, getSectorMindshareTimeseriesTool, getUserLeaderboardTool, getUserLeaderboardByProjectTool, getUserMindshareTool, getUserMindshareTimeseriesTool, getMindshareDeltaSignalsTool, getMindshareMASignalsTool, getMindshareSMAZScoreSignalsTool, } from './tools.js';
3
+ export { MindshareClient, getMindshareClient } from './client.js';