@zhive/cli 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/agent/analysis.js +160 -0
- package/dist/agent/app.js +122 -0
- package/dist/agent/chat-prompt.js +65 -0
- package/dist/agent/commands/registry.js +12 -0
- package/dist/agent/components/AsciiTicker.js +81 -0
- package/dist/agent/components/CommandInput.js +65 -0
- package/dist/agent/components/HoneycombBoot.js +291 -0
- package/dist/agent/components/Spinner.js +37 -0
- package/dist/agent/config.js +75 -0
- package/dist/agent/edit-section.js +59 -0
- package/dist/agent/fetch-rules.js +21 -0
- package/dist/agent/helpers.js +22 -0
- package/dist/agent/hooks/useAgent.js +480 -0
- package/dist/agent/memory-prompt.js +47 -0
- package/dist/agent/model.js +92 -0
- package/dist/agent/objects.js +1 -0
- package/dist/agent/process-lifecycle.js +18 -0
- package/dist/agent/prompt.js +353 -0
- package/dist/agent/run-headless.js +189 -0
- package/dist/agent/skills/index.js +2 -0
- package/dist/agent/skills/skill-parser.js +149 -0
- package/dist/agent/skills/types.js +1 -0
- package/dist/agent/theme.js +41 -0
- package/dist/agent/tools/index.js +76 -0
- package/dist/agent/tools/market/client.js +41 -0
- package/dist/agent/tools/market/index.js +3 -0
- package/dist/agent/tools/market/tools.js +518 -0
- package/dist/agent/tools/mindshare/client.js +124 -0
- package/dist/agent/tools/mindshare/index.js +3 -0
- package/dist/agent/tools/mindshare/tools.js +563 -0
- package/dist/agent/tools/read-skill-tool.js +30 -0
- package/dist/agent/tools/ta/index.js +1 -0
- package/dist/agent/tools/ta/indicators.js +201 -0
- package/dist/agent/types.js +1 -0
- package/dist/agents.js +110 -0
- package/dist/ai-providers.js +66 -0
- package/dist/avatar.js +34 -0
- package/dist/backtest/default-backtest-data.js +200 -0
- package/dist/backtest/fetch.js +41 -0
- package/dist/backtest/import.js +106 -0
- package/dist/backtest/index.js +10 -0
- package/dist/backtest/results.js +113 -0
- package/dist/backtest/runner.js +134 -0
- package/dist/backtest/storage.js +11 -0
- package/dist/backtest/types.js +1 -0
- package/dist/commands/create/ai-generate.js +126 -0
- package/dist/commands/create/commands/index.js +10 -0
- package/dist/commands/create/generate.js +73 -0
- package/dist/commands/create/presets/data.js +225 -0
- package/dist/commands/create/presets/formatting.js +81 -0
- package/dist/commands/create/presets/index.js +3 -0
- package/dist/commands/create/presets/options.js +307 -0
- package/dist/commands/create/presets/types.js +1 -0
- package/dist/commands/create/presets.js +613 -0
- package/dist/commands/create/ui/CreateApp.js +172 -0
- package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
- package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
- package/dist/commands/create/ui/steps/DoneStep.js +14 -0
- package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
- package/dist/commands/create/ui/steps/NameStep.js +148 -0
- package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
- package/dist/commands/create/ui/steps/SoulStep.js +21 -0
- package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
- package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
- package/dist/commands/create/ui/validation.js +34 -0
- package/dist/commands/create/validate-api-key.js +27 -0
- package/dist/commands/install.js +50 -0
- package/dist/commands/list/commands/index.js +7 -0
- package/dist/commands/list/ui/ListApp.js +79 -0
- package/dist/commands/migrate-templates/commands/index.js +9 -0
- package/dist/commands/migrate-templates/migrate.js +87 -0
- package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
- package/dist/commands/run/commands/index.js +17 -0
- package/dist/commands/run/run-headless.js +111 -0
- package/dist/commands/shared/theme.js +57 -0
- package/dist/commands/shared/welcome.js +304 -0
- package/dist/commands/start/commands/backtest.js +35 -0
- package/dist/commands/start/commands/index.js +62 -0
- package/dist/commands/start/commands/prediction.js +73 -0
- package/dist/commands/start/commands/skills.js +44 -0
- package/dist/commands/start/commands/skills.test.js +140 -0
- package/dist/commands/start/hooks/types.js +1 -0
- package/dist/commands/start/hooks/useAgent.js +177 -0
- package/dist/commands/start/hooks/useChat.js +266 -0
- package/dist/commands/start/hooks/usePollActivity.js +45 -0
- package/dist/commands/start/hooks/utils.js +152 -0
- package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
- package/dist/commands/start/services/backtest/fetch.js +42 -0
- package/dist/commands/start/services/backtest/import.js +109 -0
- package/dist/commands/start/services/backtest/index.js +10 -0
- package/dist/commands/start/services/backtest/results.js +113 -0
- package/dist/commands/start/services/backtest/runner.js +103 -0
- package/dist/commands/start/services/backtest/storage.js +11 -0
- package/dist/commands/start/services/backtest/types.js +1 -0
- package/dist/commands/start/services/command-registry.js +13 -0
- package/dist/commands/start/ui/AsciiTicker.js +81 -0
- package/dist/commands/start/ui/CommandInput.js +65 -0
- package/dist/commands/start/ui/HoneycombBoot.js +291 -0
- package/dist/commands/start/ui/PollText.js +23 -0
- package/dist/commands/start/ui/PredictionsPanel.js +88 -0
- package/dist/commands/start/ui/SelectAgentApp.js +93 -0
- package/dist/commands/start/ui/Spinner.js +29 -0
- package/dist/commands/start/ui/SpinnerContext.js +20 -0
- package/dist/commands/start/ui/app.js +36 -0
- package/dist/commands/start-all/AgentProcessManager.js +98 -0
- package/dist/commands/start-all/commands/index.js +24 -0
- package/dist/commands/start-all/ui/Dashboard.js +91 -0
- package/dist/components/AsciiTicker.js +81 -0
- package/dist/components/CharacterSummaryCard.js +33 -0
- package/dist/components/CodeBlock.js +11 -0
- package/dist/components/ColoredStats.js +18 -0
- package/dist/components/Header.js +10 -0
- package/dist/components/HoneycombLoader.js +190 -0
- package/dist/components/InputGuard.js +6 -0
- package/dist/components/MultiSelectPrompt.js +45 -0
- package/dist/components/SelectPrompt.js +20 -0
- package/dist/components/Spinner.js +16 -0
- package/dist/components/StepIndicator.js +31 -0
- package/dist/components/StreamingText.js +50 -0
- package/dist/components/TextPrompt.js +28 -0
- package/dist/components/stdout-spinner.js +48 -0
- package/dist/config.js +28 -0
- package/dist/create/CreateApp.js +153 -0
- package/dist/create/ai-generate.js +147 -0
- package/dist/create/generate.js +73 -0
- package/dist/create/steps/ApiKeyStep.js +97 -0
- package/dist/create/steps/AvatarStep.js +16 -0
- package/dist/create/steps/BioStep.js +14 -0
- package/dist/create/steps/DoneStep.js +14 -0
- package/dist/create/steps/IdentityStep.js +163 -0
- package/dist/create/steps/NameStep.js +71 -0
- package/dist/create/steps/ScaffoldStep.js +58 -0
- package/dist/create/steps/SoulStep.js +58 -0
- package/dist/create/steps/StrategyStep.js +58 -0
- package/dist/create/validate-api-key.js +47 -0
- package/dist/create/welcome.js +304 -0
- package/dist/index.js +60 -0
- package/dist/list/ListApp.js +79 -0
- package/dist/load-agent-env.js +30 -0
- package/dist/migrate-templates/MigrateApp.js +131 -0
- package/dist/migrate-templates/migrate.js +86 -0
- package/dist/presets.js +613 -0
- package/dist/shared/agent/agent-runtime.js +144 -0
- package/dist/shared/agent/analysis.js +171 -0
- package/dist/shared/agent/helpers.js +1 -0
- package/dist/shared/agent/prompts/chat-prompt.js +60 -0
- package/dist/shared/agent/prompts/megathread.js +202 -0
- package/dist/shared/agent/prompts/memory-prompt.js +47 -0
- package/dist/shared/agent/prompts/prompt.js +18 -0
- package/dist/shared/agent/skills/index.js +2 -0
- package/dist/shared/agent/skills/skill-parser.js +167 -0
- package/dist/shared/agent/skills/skill-parser.test.js +190 -0
- package/dist/shared/agent/skills/types.js +1 -0
- package/dist/shared/agent/tools/edit-section.js +60 -0
- package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
- package/dist/shared/agent/tools/fetch-rules.js +22 -0
- package/dist/shared/agent/tools/formatting.js +48 -0
- package/dist/shared/agent/tools/index.js +87 -0
- package/dist/shared/agent/tools/market/client.js +41 -0
- package/dist/shared/agent/tools/market/index.js +3 -0
- package/dist/shared/agent/tools/market/tools.js +497 -0
- package/dist/shared/agent/tools/mindshare/client.js +124 -0
- package/dist/shared/agent/tools/mindshare/index.js +3 -0
- package/dist/shared/agent/tools/mindshare/tools.js +167 -0
- package/dist/shared/agent/tools/read-skill-tool.js +30 -0
- package/dist/shared/agent/tools/ta/index.js +1 -0
- package/dist/shared/agent/tools/ta/indicators.js +201 -0
- package/dist/shared/agent/types.js +1 -0
- package/dist/shared/agent/utils.js +43 -0
- package/dist/shared/config/agent.js +177 -0
- package/dist/shared/config/ai-providers.js +156 -0
- package/dist/shared/config/config.js +22 -0
- package/dist/shared/config/constant.js +8 -0
- package/dist/shared/config/env-loader.js +30 -0
- package/dist/shared/types.js +1 -0
- package/dist/start/AgentProcessManager.js +98 -0
- package/dist/start/Dashboard.js +92 -0
- package/dist/start/SelectAgentApp.js +81 -0
- package/dist/start/StartApp.js +189 -0
- package/dist/start/patch-headless.js +101 -0
- package/dist/start/patch-managed-mode.js +142 -0
- package/dist/start/start-command.js +24 -0
- package/dist/theme.js +54 -0
- package/package.json +68 -0
- package/templates/components/HoneycombBoot.tsx +343 -0
- package/templates/fetch-rules.ts +23 -0
- package/templates/skills/mindshare/SKILL.md +197 -0
- package/templates/skills/ta/SKILL.md +179 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract close prices from OHLC data.
|
|
3
|
+
*/
|
|
4
|
+
function extractClosePrices(data) {
|
|
5
|
+
const result = [];
|
|
6
|
+
for (const point of data) {
|
|
7
|
+
const timestamp = point[0];
|
|
8
|
+
const close = point[4];
|
|
9
|
+
result.push({ timestamp, close });
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Compute Simple Moving Average (SMA).
|
|
15
|
+
* SMA = sum of close prices over period / period
|
|
16
|
+
*/
|
|
17
|
+
export function computeSMA(data, period) {
|
|
18
|
+
const prices = extractClosePrices(data);
|
|
19
|
+
const result = [];
|
|
20
|
+
if (prices.length < period) {
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
for (let i = period - 1; i < prices.length; i++) {
|
|
24
|
+
let sum = 0;
|
|
25
|
+
for (let j = i - period + 1; j <= i; j++) {
|
|
26
|
+
sum += prices[j].close;
|
|
27
|
+
}
|
|
28
|
+
const sma = sum / period;
|
|
29
|
+
result.push({
|
|
30
|
+
timestamp: prices[i].timestamp,
|
|
31
|
+
value: sma,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compute Exponential Moving Average (EMA).
|
|
38
|
+
* EMA = (Close - Previous EMA) * multiplier + Previous EMA
|
|
39
|
+
* multiplier = 2 / (period + 1)
|
|
40
|
+
*/
|
|
41
|
+
export function computeEMA(data, period) {
|
|
42
|
+
const prices = extractClosePrices(data);
|
|
43
|
+
const result = [];
|
|
44
|
+
if (prices.length < period) {
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
const multiplier = 2 / (period + 1);
|
|
48
|
+
let initialSum = 0;
|
|
49
|
+
for (let i = 0; i < period; i++) {
|
|
50
|
+
initialSum += prices[i].close;
|
|
51
|
+
}
|
|
52
|
+
let ema = initialSum / period;
|
|
53
|
+
result.push({
|
|
54
|
+
timestamp: prices[period - 1].timestamp,
|
|
55
|
+
value: ema,
|
|
56
|
+
});
|
|
57
|
+
for (let i = period; i < prices.length; i++) {
|
|
58
|
+
ema = (prices[i].close - ema) * multiplier + ema;
|
|
59
|
+
result.push({
|
|
60
|
+
timestamp: prices[i].timestamp,
|
|
61
|
+
value: ema,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute Relative Strength Index (RSI).
|
|
68
|
+
* RSI = 100 - (100 / (1 + RS))
|
|
69
|
+
* RS = Average Gain / Average Loss over period
|
|
70
|
+
*/
|
|
71
|
+
export function computeRSI(data, period) {
|
|
72
|
+
const prices = extractClosePrices(data);
|
|
73
|
+
const result = [];
|
|
74
|
+
if (prices.length < period + 1) {
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
const gains = [];
|
|
78
|
+
const losses = [];
|
|
79
|
+
for (let i = 1; i < prices.length; i++) {
|
|
80
|
+
const change = prices[i].close - prices[i - 1].close;
|
|
81
|
+
if (change > 0) {
|
|
82
|
+
gains.push(change);
|
|
83
|
+
losses.push(0);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
gains.push(0);
|
|
87
|
+
losses.push(Math.abs(change));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let avgGain = 0;
|
|
91
|
+
let avgLoss = 0;
|
|
92
|
+
for (let i = 0; i < period; i++) {
|
|
93
|
+
avgGain += gains[i];
|
|
94
|
+
avgLoss += losses[i];
|
|
95
|
+
}
|
|
96
|
+
avgGain /= period;
|
|
97
|
+
avgLoss /= period;
|
|
98
|
+
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
99
|
+
const rsi = 100 - 100 / (1 + rs);
|
|
100
|
+
result.push({
|
|
101
|
+
timestamp: prices[period].timestamp,
|
|
102
|
+
value: rsi,
|
|
103
|
+
});
|
|
104
|
+
for (let i = period; i < gains.length; i++) {
|
|
105
|
+
avgGain = (avgGain * (period - 1) + gains[i]) / period;
|
|
106
|
+
avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
|
|
107
|
+
const currentRs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
108
|
+
const currentRsi = 100 - 100 / (1 + currentRs);
|
|
109
|
+
result.push({
|
|
110
|
+
timestamp: prices[i + 1].timestamp,
|
|
111
|
+
value: currentRsi,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Compute MACD (Moving Average Convergence Divergence).
|
|
118
|
+
* MACD Line = Fast EMA - Slow EMA
|
|
119
|
+
* Signal Line = EMA of MACD Line
|
|
120
|
+
* Histogram = MACD Line - Signal Line
|
|
121
|
+
*/
|
|
122
|
+
export function computeMACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
|
|
123
|
+
const fastEma = computeEMA(data, fastPeriod);
|
|
124
|
+
const slowEma = computeEMA(data, slowPeriod);
|
|
125
|
+
const result = [];
|
|
126
|
+
if (fastEma.length === 0 || slowEma.length === 0) {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
const fastMap = new Map();
|
|
130
|
+
for (const point of fastEma) {
|
|
131
|
+
fastMap.set(point.timestamp, point.value);
|
|
132
|
+
}
|
|
133
|
+
const macdLine = [];
|
|
134
|
+
for (const slow of slowEma) {
|
|
135
|
+
const fast = fastMap.get(slow.timestamp);
|
|
136
|
+
if (fast !== undefined) {
|
|
137
|
+
macdLine.push({
|
|
138
|
+
timestamp: slow.timestamp,
|
|
139
|
+
value: fast - slow.value,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (macdLine.length < signalPeriod) {
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
const signalMultiplier = 2 / (signalPeriod + 1);
|
|
147
|
+
let signalSum = 0;
|
|
148
|
+
for (let i = 0; i < signalPeriod; i++) {
|
|
149
|
+
signalSum += macdLine[i].value;
|
|
150
|
+
}
|
|
151
|
+
let signal = signalSum / signalPeriod;
|
|
152
|
+
result.push({
|
|
153
|
+
timestamp: macdLine[signalPeriod - 1].timestamp,
|
|
154
|
+
macd: macdLine[signalPeriod - 1].value,
|
|
155
|
+
signal,
|
|
156
|
+
histogram: macdLine[signalPeriod - 1].value - signal,
|
|
157
|
+
});
|
|
158
|
+
for (let i = signalPeriod; i < macdLine.length; i++) {
|
|
159
|
+
signal = (macdLine[i].value - signal) * signalMultiplier + signal;
|
|
160
|
+
result.push({
|
|
161
|
+
timestamp: macdLine[i].timestamp,
|
|
162
|
+
macd: macdLine[i].value,
|
|
163
|
+
signal,
|
|
164
|
+
histogram: macdLine[i].value - signal,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Compute Bollinger Bands.
|
|
171
|
+
* Middle Band = SMA
|
|
172
|
+
* Upper Band = SMA + (stdDev * standard deviation)
|
|
173
|
+
* Lower Band = SMA - (stdDev * standard deviation)
|
|
174
|
+
*/
|
|
175
|
+
export function computeBollingerBands(data, period = 20, stdDevMultiplier = 2) {
|
|
176
|
+
const prices = extractClosePrices(data);
|
|
177
|
+
const result = [];
|
|
178
|
+
if (prices.length < period) {
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
for (let i = period - 1; i < prices.length; i++) {
|
|
182
|
+
let sum = 0;
|
|
183
|
+
for (let j = i - period + 1; j <= i; j++) {
|
|
184
|
+
sum += prices[j].close;
|
|
185
|
+
}
|
|
186
|
+
const sma = sum / period;
|
|
187
|
+
let squaredDiffSum = 0;
|
|
188
|
+
for (let j = i - period + 1; j <= i; j++) {
|
|
189
|
+
const diff = prices[j].close - sma;
|
|
190
|
+
squaredDiffSum += diff * diff;
|
|
191
|
+
}
|
|
192
|
+
const stdDev = Math.sqrt(squaredDiffSum / period);
|
|
193
|
+
result.push({
|
|
194
|
+
timestamp: prices[i].timestamp,
|
|
195
|
+
upper: sma + stdDevMultiplier * stdDev,
|
|
196
|
+
middle: sma,
|
|
197
|
+
lower: sma - stdDevMultiplier * stdDev,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { AI_PROVIDERS } from './ai-providers.js';
|
|
6
|
+
import { HIVE_API_URL } from './config.js';
|
|
7
|
+
function extractField(content, pattern) {
|
|
8
|
+
const match = content.match(pattern);
|
|
9
|
+
if (match === null) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const value = match[1].trim();
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export async function scanAgents() {
|
|
16
|
+
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
17
|
+
const exists = await fs.pathExists(agentsDir);
|
|
18
|
+
if (!exists) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
22
|
+
const agents = [];
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.isDirectory()) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const soulPath = path.join(agentsDir, entry.name, 'SOUL.md');
|
|
28
|
+
const soulExists = await fs.pathExists(soulPath);
|
|
29
|
+
if (!soulExists) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const agentDir = path.join(agentsDir, entry.name);
|
|
33
|
+
const provider = await detectProvider(agentDir);
|
|
34
|
+
const stat = await fs.stat(soulPath);
|
|
35
|
+
const soulContent = await fs.readFile(soulPath, 'utf-8');
|
|
36
|
+
const parsedName = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
|
|
37
|
+
const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
|
|
38
|
+
const bio = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
|
|
39
|
+
agents.push({
|
|
40
|
+
name: parsedName ?? entry.name,
|
|
41
|
+
dir: agentDir,
|
|
42
|
+
provider,
|
|
43
|
+
created: stat.birthtime,
|
|
44
|
+
avatar_url: avatarUrl ?? undefined,
|
|
45
|
+
bio: bio ?? undefined,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return agents;
|
|
49
|
+
}
|
|
50
|
+
export function sortByHoney(rows) {
|
|
51
|
+
const sorted = [...rows].sort((a, b) => (b.stats?.honey ?? 0) - (a.stats?.honey ?? 0));
|
|
52
|
+
return sorted;
|
|
53
|
+
}
|
|
54
|
+
export function sortAgentsByHoney(agents, statsMap) {
|
|
55
|
+
const sorted = [...agents].sort((a, b) => {
|
|
56
|
+
const honeyA = statsMap.get(a.name)?.honey ?? 0;
|
|
57
|
+
const honeyB = statsMap.get(b.name)?.honey ?? 0;
|
|
58
|
+
return honeyB - honeyA;
|
|
59
|
+
});
|
|
60
|
+
return sorted;
|
|
61
|
+
}
|
|
62
|
+
export async function fetchBulkStats(names) {
|
|
63
|
+
const statsMap = new Map();
|
|
64
|
+
if (names.length === 0) {
|
|
65
|
+
return statsMap;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const response = await axios.post(`${HIVE_API_URL}/agent/by-names`, { names });
|
|
69
|
+
for (const agent of response.data) {
|
|
70
|
+
statsMap.set(agent.name, {
|
|
71
|
+
honey: agent.honey ?? 0,
|
|
72
|
+
wax: agent.wax ?? 0,
|
|
73
|
+
win_rate: agent.win_rate ?? 0,
|
|
74
|
+
confidence: agent.confidence ?? 0,
|
|
75
|
+
total_comments: agent.total_comments ?? 0,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// API unreachable — return empty map, CLI will show dashes
|
|
81
|
+
}
|
|
82
|
+
return statsMap;
|
|
83
|
+
}
|
|
84
|
+
async function detectProvider(agentDir) {
|
|
85
|
+
// Try old-style detection: check package.json dependencies
|
|
86
|
+
const pkgPath = path.join(agentDir, 'package.json');
|
|
87
|
+
const pkgExists = await fs.pathExists(pkgPath);
|
|
88
|
+
if (pkgExists) {
|
|
89
|
+
const pkg = await fs.readJson(pkgPath);
|
|
90
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
91
|
+
for (const provider of AI_PROVIDERS) {
|
|
92
|
+
if (deps[provider.package]) {
|
|
93
|
+
return provider.label;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// New-style detection: check .env for provider API keys
|
|
98
|
+
const envPath = path.join(agentDir, '.env');
|
|
99
|
+
const envExists = await fs.pathExists(envPath);
|
|
100
|
+
if (envExists) {
|
|
101
|
+
const envContent = await fs.readFile(envPath, 'utf-8');
|
|
102
|
+
for (const provider of AI_PROVIDERS) {
|
|
103
|
+
const pattern = new RegExp(`^${provider.envVar}=.+`, 'm');
|
|
104
|
+
if (pattern.test(envContent)) {
|
|
105
|
+
return provider.label;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return 'unknown';
|
|
110
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const AI_PROVIDERS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'openai',
|
|
4
|
+
label: 'OpenAI',
|
|
5
|
+
package: '@ai-sdk/openai',
|
|
6
|
+
envVar: 'OPENAI_API_KEY',
|
|
7
|
+
models: { validation: 'gpt-4o-mini', generation: 'gpt-5-mini', runtime: 'gpt-5-mini' },
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: 'anthropic',
|
|
11
|
+
label: 'Anthropic',
|
|
12
|
+
package: '@ai-sdk/anthropic',
|
|
13
|
+
envVar: 'ANTHROPIC_API_KEY',
|
|
14
|
+
models: {
|
|
15
|
+
validation: 'claude-haiku-4-5-20251001',
|
|
16
|
+
generation: 'claude-haiku-4-5',
|
|
17
|
+
runtime: 'claude-haiku-4-5',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'google',
|
|
22
|
+
label: 'Google',
|
|
23
|
+
package: '@ai-sdk/google',
|
|
24
|
+
envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
25
|
+
models: {
|
|
26
|
+
validation: 'gemini-2.0-flash',
|
|
27
|
+
generation: 'gemini-3-flash-preview',
|
|
28
|
+
runtime: 'gemini-3-flash-preview',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'xai',
|
|
33
|
+
label: 'xAI',
|
|
34
|
+
package: '@ai-sdk/xai',
|
|
35
|
+
envVar: 'XAI_API_KEY',
|
|
36
|
+
models: {
|
|
37
|
+
validation: 'grok-2',
|
|
38
|
+
generation: 'grok-4-1-fast-reasoning',
|
|
39
|
+
runtime: 'grok-4-1-fast-reasoning',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'openrouter',
|
|
44
|
+
label: 'OpenRouter',
|
|
45
|
+
package: '@openrouter/ai-sdk-provider',
|
|
46
|
+
envVar: 'OPENROUTER_API_KEY',
|
|
47
|
+
models: {
|
|
48
|
+
validation: 'openai/gpt-4o-mini',
|
|
49
|
+
generation: 'openai/gpt-5.1-mini',
|
|
50
|
+
runtime: 'openai/gpt-5.1-mini',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* All env-var names used by AI providers.
|
|
56
|
+
* Used to clear shell-inherited keys before loading an agent's .env,
|
|
57
|
+
* so only the agent's chosen provider is active.
|
|
58
|
+
*/
|
|
59
|
+
export const AI_PROVIDER_ENV_VARS = AI_PROVIDERS.map((p) => p.envVar);
|
|
60
|
+
export function getProvider(id) {
|
|
61
|
+
const provider = AI_PROVIDERS.find((p) => p.id === id);
|
|
62
|
+
if (!provider) {
|
|
63
|
+
throw new Error(`Unknown AI provider: ${id}`);
|
|
64
|
+
}
|
|
65
|
+
return provider;
|
|
66
|
+
}
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import terminalImage from 'terminal-image';
|
|
3
|
+
const AVATAR_WIDTH = 6;
|
|
4
|
+
const AVATAR_HEIGHT = 3;
|
|
5
|
+
export async function renderAvatar(url) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await axios.get(url, {
|
|
8
|
+
responseType: 'arraybuffer',
|
|
9
|
+
timeout: 5000,
|
|
10
|
+
});
|
|
11
|
+
const buffer = Buffer.from(response.data);
|
|
12
|
+
const rendered = await terminalImage.buffer(buffer, {
|
|
13
|
+
width: AVATAR_WIDTH,
|
|
14
|
+
height: AVATAR_HEIGHT,
|
|
15
|
+
preserveAspectRatio: true,
|
|
16
|
+
});
|
|
17
|
+
return rendered.trimEnd();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function renderAvatars(agents) {
|
|
24
|
+
const avatarMap = new Map();
|
|
25
|
+
const entries = agents.filter((a) => a.avatar_url);
|
|
26
|
+
const results = await Promise.all(entries.map((a) => renderAvatar(a.avatar_url)));
|
|
27
|
+
for (let i = 0; i < entries.length; i++) {
|
|
28
|
+
const rendered = results[i];
|
|
29
|
+
if (rendered !== null) {
|
|
30
|
+
avatarMap.set(entries[i].name, rendered);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return avatarMap;
|
|
34
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default backtest dataset bundled with the CLI.
|
|
3
|
+
* Contains 10 threads from Bitcoin, Ethereum, Solana, and Dogecoin.
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_BACKTEST_DATA = {
|
|
6
|
+
metadata: {
|
|
7
|
+
id: 'default',
|
|
8
|
+
name: 'Default Backtest Dataset',
|
|
9
|
+
created_at: '2026-02-26T00:00:00Z',
|
|
10
|
+
},
|
|
11
|
+
threads: [
|
|
12
|
+
{
|
|
13
|
+
project_id: 'ethereum',
|
|
14
|
+
project_name: 'Ethereum',
|
|
15
|
+
project_symbol: '$ETH',
|
|
16
|
+
project_categories: [
|
|
17
|
+
'smart-contract-platform',
|
|
18
|
+
'layer-1',
|
|
19
|
+
'ethereum-ecosystem',
|
|
20
|
+
'proof-of-stake-pos',
|
|
21
|
+
'world-liberty-financial-portfolio',
|
|
22
|
+
],
|
|
23
|
+
project_description: 'Ethereum is a global, open-source platform for decentralized applications.',
|
|
24
|
+
text: 'Balaji Srinivasan describes a "Global Privacy Era" as crypto\'s third phase, where the next decade of Web3 focuses on protecting user data and financial sovereignty through strong encryption rather than scalability or speed. Ethereum is highlighted as part of this shift from fully transparent ledgers to more confidential, programmable computation.',
|
|
25
|
+
timestamp: '2025-12-31T20:00:00Z',
|
|
26
|
+
price_on_fetch: 2969.01,
|
|
27
|
+
price_on_eval: 2971.55,
|
|
28
|
+
citations: [{ title: '2006719380713869499' }, { title: '2006683920881037680' }],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
project_id: 'solana',
|
|
32
|
+
project_name: 'Solana',
|
|
33
|
+
project_symbol: '$SOL',
|
|
34
|
+
project_categories: [
|
|
35
|
+
'smart-contract-platform',
|
|
36
|
+
'solana-ecosystem',
|
|
37
|
+
'layer-1',
|
|
38
|
+
'alleged-sec-securities',
|
|
39
|
+
'proof-of-stake-pos',
|
|
40
|
+
'made-in-usa',
|
|
41
|
+
'coinlist-launchpad',
|
|
42
|
+
],
|
|
43
|
+
project_description: 'Solana is a high-performance Layer 1 blockchain designed for mass adoption by providing a fast, secure, and low-cost environment for decentralized applications.',
|
|
44
|
+
text: 'ORE partners with PrivacyCash to launch an official shielded pool on Solana, enabling live private transfers of ORE as a native store-of-value token. Builders also announce Pandora OS v2.0 with stealth transfers and one-time unlinkable addresses on Solana mainnet, signaling major new privacy initiatives on the network.',
|
|
45
|
+
timestamp: '2026-01-01T01:00:00Z',
|
|
46
|
+
price_on_fetch: 124.98,
|
|
47
|
+
price_on_eval: 124.94,
|
|
48
|
+
citations: [{ title: '2006787547519918102' }, { title: '2006787600028410119' }],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
project_id: 'solana',
|
|
52
|
+
project_name: 'Solana',
|
|
53
|
+
project_symbol: '$SOL',
|
|
54
|
+
project_categories: [
|
|
55
|
+
'smart-contract-platform',
|
|
56
|
+
'solana-ecosystem',
|
|
57
|
+
'layer-1',
|
|
58
|
+
'alleged-sec-securities',
|
|
59
|
+
'proof-of-stake-pos',
|
|
60
|
+
'made-in-usa',
|
|
61
|
+
'coinlist-launchpad',
|
|
62
|
+
],
|
|
63
|
+
project_description: 'Solana is a high-performance Layer 1 blockchain designed for mass adoption by providing a fast, secure, and low-cost environment for decentralized applications.',
|
|
64
|
+
text: 'Solana is introducing the c-spl confidential token standard in Q1 2026, enabling encrypted balances and native private transfers. This privacy infrastructure targets institutional adoption by allowing banks and enterprises to conduct confidential onchain transactions, potentially unlocking complex financial instruments for the $16B in Solana stablecoins.',
|
|
65
|
+
timestamp: '2026-01-01T06:00:00Z',
|
|
66
|
+
price_on_fetch: 125.06,
|
|
67
|
+
price_on_eval: 124.43,
|
|
68
|
+
citations: [{ title: '2006857900543848485' }, { title: '2006862849327435958' }],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
project_id: 'bitcoin',
|
|
72
|
+
project_name: 'Bitcoin',
|
|
73
|
+
project_symbol: '$BTC',
|
|
74
|
+
project_categories: [
|
|
75
|
+
'smart-contract-platform',
|
|
76
|
+
'layer-1',
|
|
77
|
+
'proof-of-work-pow',
|
|
78
|
+
'bitcoin-ecosystem',
|
|
79
|
+
],
|
|
80
|
+
project_description: "Bitcoin is the world's first decentralized cryptocurrency, created in 2009 by the pseudonymous Satoshi Nakamoto.",
|
|
81
|
+
text: 'BlackRock transferred 1,134 BTC (~$101.4M) and 7,255 ETH (~$22.1M) to Coinbase/ Coinbase Prime and is described as selling millions in crypto ahead of a key PMI data release. The move coincides with $2.2B in BTC and ETH options expiry, drawing market attention to potential selling pressure on Bitcoin and Ethereum.',
|
|
82
|
+
timestamp: '2026-01-01T19:00:00Z',
|
|
83
|
+
price_on_fetch: 88053,
|
|
84
|
+
price_on_eval: 88266,
|
|
85
|
+
citations: [{ title: '2007064336708354389' }, { title: '2007066963671924967' }],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
project_id: 'solana',
|
|
89
|
+
project_name: 'Solana',
|
|
90
|
+
project_symbol: '$SOL',
|
|
91
|
+
project_categories: [
|
|
92
|
+
'smart-contract-platform',
|
|
93
|
+
'solana-ecosystem',
|
|
94
|
+
'layer-1',
|
|
95
|
+
'alleged-sec-securities',
|
|
96
|
+
'proof-of-stake-pos',
|
|
97
|
+
'made-in-usa',
|
|
98
|
+
'coinlist-launchpad',
|
|
99
|
+
],
|
|
100
|
+
project_description: 'Solana is a high-performance Layer 1 blockchain designed for mass adoption by providing a fast, secure, and low-cost environment for decentralized applications.',
|
|
101
|
+
text: 'Tweets highlight Solana\'s active memecoin culture, with users trading and promoting tokens like $KITKAT, $wif, SolanaWhale, and Epileptic, and tools such as MemeMax and 24/7 "Solana Memecoin Hunting" communities emerging. References to Trump\'s 2025 Solana memecoin and expectations for 2026 ("Make Solana Memecoins great again") show sustained speculative interest and growing infrastructure around Solana memecoin trading.',
|
|
102
|
+
timestamp: '2026-01-02T01:00:00Z',
|
|
103
|
+
price_on_fetch: 126.59,
|
|
104
|
+
price_on_eval: 127.47,
|
|
105
|
+
citations: [{ title: '2007142205451055298' }, { title: '2007150298876522740' }],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
project_id: 'dogecoin',
|
|
109
|
+
project_name: 'Dogecoin',
|
|
110
|
+
project_symbol: '$DOGE',
|
|
111
|
+
project_categories: [
|
|
112
|
+
'smart-contract-platform',
|
|
113
|
+
'meme-token',
|
|
114
|
+
'dog-themed-coins',
|
|
115
|
+
'elon-musk-inspired-coins',
|
|
116
|
+
'proof-of-work-pow',
|
|
117
|
+
'4chan-themed',
|
|
118
|
+
],
|
|
119
|
+
project_description: 'Dogecoin is a cryptocurrency based on the popular "Doge" Internet meme and features a Shiba Inu on its logo.',
|
|
120
|
+
text: 'Tweets highlight growing focus on a proposed Bitwise Dogecoin ETF (ticker "BWOW"), emphasizing that it carries high risk, significant volatility, and potential for complete loss of investment, and is not registered under the 1940 Act. Market chatter also notes a January 9 final decision deadline for a Dogecoin ETF and rising Dogecoin price and open interest into 2026.',
|
|
121
|
+
timestamp: '2026-01-02T07:00:00Z',
|
|
122
|
+
price_on_fetch: 0.128493,
|
|
123
|
+
price_on_eval: 0.132792,
|
|
124
|
+
citations: [{ title: '2007168051129840065' }, { title: '2007118652018196634' }],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
project_id: 'bitcoin',
|
|
128
|
+
project_name: 'Bitcoin',
|
|
129
|
+
project_symbol: '$BTC',
|
|
130
|
+
project_categories: [
|
|
131
|
+
'smart-contract-platform',
|
|
132
|
+
'layer-1',
|
|
133
|
+
'proof-of-work-pow',
|
|
134
|
+
'bitcoin-ecosystem',
|
|
135
|
+
],
|
|
136
|
+
project_description: "Bitcoin is the world's first decentralized cryptocurrency, created in 2009 by the pseudonymous Satoshi Nakamoto.",
|
|
137
|
+
text: 'Bitcoin users mark Proof of Keys Day, coinciding with the 17th anniversary of the Genesis Block, by urging holders to withdraw BTC from exchanges into cold storage and verify self-custody. Tweets emphasize the slogan "Not your keys, not your coins" and frame the day as a security checkup for Bitcoin holdings.',
|
|
138
|
+
timestamp: '2026-01-02T15:00:00Z',
|
|
139
|
+
price_on_fetch: 89551,
|
|
140
|
+
price_on_eval: 90310,
|
|
141
|
+
citations: [{ title: '2007362498282901908' }, { title: '2007373924133417089' }],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
project_id: 'solana',
|
|
145
|
+
project_name: 'Solana',
|
|
146
|
+
project_symbol: '$SOL',
|
|
147
|
+
project_categories: [
|
|
148
|
+
'smart-contract-platform',
|
|
149
|
+
'solana-ecosystem',
|
|
150
|
+
'layer-1',
|
|
151
|
+
'alleged-sec-securities',
|
|
152
|
+
'proof-of-stake-pos',
|
|
153
|
+
'made-in-usa',
|
|
154
|
+
'coinlist-launchpad',
|
|
155
|
+
],
|
|
156
|
+
project_description: 'Solana is a high-performance Layer 1 blockchain designed for mass adoption by providing a fast, secure, and low-cost environment for decentralized applications.',
|
|
157
|
+
text: "Circle mints $750M USDC on Solana, marking the first injection of fresh USDC stablecoin liquidity on the network in 2026. This follows $56.25B USDC minted on Solana in 2025, reinforcing Solana's growing role as a major stablecoin and DeFi liquidity hub.",
|
|
158
|
+
timestamp: '2026-01-02T17:00:00Z',
|
|
159
|
+
price_on_fetch: 130.55,
|
|
160
|
+
price_on_eval: 131.54,
|
|
161
|
+
citations: [{ title: '2007386708032270424' }, { title: '2007383683783963093' }],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
project_id: 'ethereum',
|
|
165
|
+
project_name: 'Ethereum',
|
|
166
|
+
project_symbol: '$ETH',
|
|
167
|
+
project_categories: [
|
|
168
|
+
'smart-contract-platform',
|
|
169
|
+
'layer-1',
|
|
170
|
+
'ethereum-ecosystem',
|
|
171
|
+
'proof-of-stake-pos',
|
|
172
|
+
'world-liberty-financial-portfolio',
|
|
173
|
+
],
|
|
174
|
+
project_description: 'Ethereum is a global, open-source platform for decentralized applications.',
|
|
175
|
+
text: 'Ethereum spot ETFs record $174M in net inflows on Jan. 2, their strongest day since early December, after about $2B in outflows from November through December. Products from Grayscale and BlackRock lead the renewed institutional demand, signaling a shift back to risk-on sentiment for ETH in early 2026.',
|
|
176
|
+
timestamp: '2026-01-03T02:00:00Z',
|
|
177
|
+
price_on_fetch: 3129.52,
|
|
178
|
+
price_on_eval: 3114.78,
|
|
179
|
+
citations: [{ title: '2007536719055167799' }, { title: '2007492243502858607' }],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
project_id: 'ethereum',
|
|
183
|
+
project_name: 'Ethereum',
|
|
184
|
+
project_symbol: '$ETH',
|
|
185
|
+
project_categories: [
|
|
186
|
+
'smart-contract-platform',
|
|
187
|
+
'layer-1',
|
|
188
|
+
'ethereum-ecosystem',
|
|
189
|
+
'proof-of-stake-pos',
|
|
190
|
+
'world-liberty-financial-portfolio',
|
|
191
|
+
],
|
|
192
|
+
project_description: 'Ethereum is a global, open-source platform for decentralized applications.',
|
|
193
|
+
text: 'Ethereum set a new all-time high with about 2.2-2.23 million transactions processed in a single day around Dec 29, 2025, with several subsequent days near that level. This record onchain activity indicates strong and growing network demand and utility across DeFi, NFTs, and emerging L2 ecosystems amid ongoing upgrades.',
|
|
194
|
+
timestamp: '2026-01-03T03:00:00Z',
|
|
195
|
+
price_on_fetch: 3125.17,
|
|
196
|
+
price_on_eval: 3110.91,
|
|
197
|
+
citations: [{ title: '2007529962350207036' }, { title: '2007525811075665958' }],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HiveClient } from '@hive-org/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches locked threads from the API and converts them to BacktestData format.
|
|
4
|
+
* Only threads with price_on_eval are included (guaranteed by the API).
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchBacktestThreads(limit, baseUrl) {
|
|
7
|
+
try {
|
|
8
|
+
const client = new HiveClient(baseUrl);
|
|
9
|
+
const threads = await client.getLockedThreads(limit);
|
|
10
|
+
const backtestThreads = threads
|
|
11
|
+
.filter((t) => t.price_on_eval !== undefined && t.price_on_eval !== null)
|
|
12
|
+
.map((t) => ({
|
|
13
|
+
project_id: t.project_id,
|
|
14
|
+
project_name: t.project_name,
|
|
15
|
+
project_symbol: t.project_symbol,
|
|
16
|
+
project_categories: t.project_categories,
|
|
17
|
+
project_description: t.project_description,
|
|
18
|
+
text: t.text,
|
|
19
|
+
timestamp: t.timestamp,
|
|
20
|
+
price_on_fetch: t.price_on_fetch,
|
|
21
|
+
price_on_eval: t.price_on_eval,
|
|
22
|
+
citations: t.citations,
|
|
23
|
+
}));
|
|
24
|
+
if (backtestThreads.length === 0) {
|
|
25
|
+
return { success: false, error: 'No resolved threads with price data found' };
|
|
26
|
+
}
|
|
27
|
+
const data = {
|
|
28
|
+
metadata: {
|
|
29
|
+
id: `api-${Date.now()}`,
|
|
30
|
+
name: `API fetch (${backtestThreads.length} threads)`,
|
|
31
|
+
created_at: new Date().toISOString(),
|
|
32
|
+
},
|
|
33
|
+
threads: backtestThreads,
|
|
34
|
+
};
|
|
35
|
+
return { success: true, data };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
return { success: false, error: message };
|
|
40
|
+
}
|
|
41
|
+
}
|