@zhive/cli 0.5.5 → 0.6.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 +5 -5
- package/dist/CLAUDE.md +7 -0
- package/dist/backtest/CLAUDE.md +7 -0
- package/dist/cli.js +20 -0
- package/dist/commands/agent/commands/index.js +7 -0
- package/dist/commands/agent/commands/profile.js +40 -0
- package/dist/commands/agent/commands/profile.test.js +137 -0
- package/dist/commands/create/commands/index.js +10 -5
- package/dist/commands/list/commands/index.js +8 -3
- package/dist/commands/megathread/commands/create-comment.js +99 -0
- package/dist/commands/megathread/commands/create-comment.test.js +480 -0
- package/dist/commands/megathread/commands/index.js +9 -0
- package/dist/commands/megathread/commands/list.js +102 -0
- package/dist/commands/megathread/commands/list.test.js +206 -0
- package/dist/commands/migrate-templates/commands/index.js +9 -4
- package/dist/commands/run/commands/index.js +17 -12
- package/dist/commands/run/run-headless.js +2 -1
- package/dist/commands/start/commands/index.js +37 -32
- package/dist/commands/start/hooks/useAgent.js +2 -1
- package/dist/commands/start/services/backtest/runner.js +1 -1
- package/dist/commands/start-all/commands/index.js +22 -17
- package/dist/index.js +26 -57
- package/dist/{agent → services/agent}/analysis.js +5 -5
- package/dist/{load-agent-env.js → services/agent/env.js} +1 -1
- package/dist/{agent → services/agent/helpers}/model.js +2 -2
- package/dist/{agent → services/agent/prompts}/memory-prompt.js +20 -22
- package/dist/{agent → services/agent/prompts}/prompt.js +80 -54
- package/dist/{agent → services/agent}/tools/market/client.js +1 -1
- package/dist/{agent → services/agent}/tools/mindshare/client.js +1 -1
- package/dist/{agents.js → services/config/agent.js} +2 -2
- package/dist/{config.js → services/config/config.js} +1 -7
- package/dist/services/config/constant.js +8 -0
- package/dist/shared/agent/config.js +75 -0
- package/dist/shared/agent/env.js +30 -0
- package/dist/shared/agent/handler.js +129 -0
- package/dist/shared/agent/helpers/model.js +92 -0
- package/dist/shared/agent/runtime.js +15 -0
- package/dist/shared/ai-providers.js +66 -0
- package/dist/shared/config/agent.js +19 -0
- package/dist/shared/config/agent.test.js +115 -0
- package/package.json +4 -3
- package/dist/agent/app.js +0 -122
- package/dist/agent/commands/registry.js +0 -12
- package/dist/agent/components/AsciiTicker.js +0 -81
- package/dist/agent/components/CommandInput.js +0 -65
- package/dist/agent/components/HoneycombBoot.js +0 -291
- package/dist/agent/components/Spinner.js +0 -37
- package/dist/agent/hooks/useAgent.js +0 -480
- package/dist/agent/objects.js +0 -1
- package/dist/agent/process-lifecycle.js +0 -18
- package/dist/agent/run-headless.js +0 -189
- package/dist/agent/theme.js +0 -41
- package/dist/avatar.js +0 -34
- package/dist/backtest/default-backtest-data.js +0 -200
- package/dist/backtest/fetch.js +0 -41
- package/dist/backtest/import.js +0 -106
- package/dist/backtest/index.js +0 -10
- package/dist/backtest/results.js +0 -113
- package/dist/backtest/runner.js +0 -134
- package/dist/backtest/storage.js +0 -11
- package/dist/backtest/types.js +0 -1
- package/dist/commands/install.js +0 -50
- package/dist/commands/start/ui/PollText.js +0 -23
- package/dist/commands/start/ui/PredictionsPanel.js +0 -88
- package/dist/commands/start/ui/SpinnerContext.js +0 -20
- package/dist/components/InputGuard.js +0 -6
- package/dist/components/stdout-spinner.js +0 -48
- package/dist/create/CreateApp.js +0 -153
- package/dist/create/ai-generate.js +0 -147
- package/dist/create/generate.js +0 -73
- package/dist/create/steps/ApiKeyStep.js +0 -97
- package/dist/create/steps/AvatarStep.js +0 -16
- package/dist/create/steps/BioStep.js +0 -14
- package/dist/create/steps/DoneStep.js +0 -14
- package/dist/create/steps/IdentityStep.js +0 -163
- package/dist/create/steps/NameStep.js +0 -71
- package/dist/create/steps/ScaffoldStep.js +0 -58
- package/dist/create/steps/SoulStep.js +0 -58
- package/dist/create/steps/StrategyStep.js +0 -58
- package/dist/create/validate-api-key.js +0 -47
- package/dist/create/welcome.js +0 -304
- package/dist/list/ListApp.js +0 -79
- package/dist/migrate-templates/MigrateApp.js +0 -131
- package/dist/migrate-templates/migrate.js +0 -86
- package/dist/presets.js +0 -613
- package/dist/start/AgentProcessManager.js +0 -98
- package/dist/start/Dashboard.js +0 -92
- package/dist/start/SelectAgentApp.js +0 -81
- package/dist/start/StartApp.js +0 -189
- package/dist/start/patch-headless.js +0 -101
- package/dist/start/patch-managed-mode.js +0 -142
- package/dist/start/start-command.js +0 -24
- package/dist/theme.js +0 -54
- /package/dist/{agent → services/agent}/config.js +0 -0
- /package/dist/{agent → services/agent}/helpers.js +0 -0
- /package/dist/{agent → services/agent/prompts}/chat-prompt.js +0 -0
- /package/dist/{agent → services/agent}/skills/index.js +0 -0
- /package/dist/{agent → services/agent}/skills/skill-parser.js +0 -0
- /package/dist/{agent → services/agent}/skills/types.js +0 -0
- /package/dist/{agent → services/agent/tools}/edit-section.js +0 -0
- /package/dist/{agent → services/agent/tools}/fetch-rules.js +0 -0
- /package/dist/{agent → services/agent}/tools/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/market/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/market/tools.js +0 -0
- /package/dist/{agent → services/agent}/tools/mindshare/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/mindshare/tools.js +0 -0
- /package/dist/{agent → services/agent}/tools/read-skill-tool.js +0 -0
- /package/dist/{agent → services/agent}/tools/ta/index.js +0 -0
- /package/dist/{agent → services/agent}/tools/ta/indicators.js +0 -0
- /package/dist/{agent → services/agent}/types.js +0 -0
- /package/dist/{ai-providers.js → services/ai-providers.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// ─── Shared Types ─────────────────────────────────
|
|
1
2
|
function humanDuration(ms) {
|
|
2
3
|
const hours = ms / 3_600_000;
|
|
3
4
|
if (hours >= 24) {
|
|
@@ -79,32 +80,8 @@ Only skip if this project is outside the expertise list defined in your STRATEGY
|
|
|
79
80
|
- Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
|
|
80
81
|
- Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
|
|
81
82
|
|
|
82
|
-
IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across rounds. Each round has different context — your conviction should reflect that
|
|
83
|
-
|
|
84
|
-
## The prediction game
|
|
85
|
-
|
|
86
|
-
This is a price prediction game for megathread rounds. Each round has a project, a duration, and a round-start baseline price. Your conviction = predicted TOTAL % change from the round-start price by end of the round.
|
|
87
|
-
|
|
88
|
-
Key inputs for each round:
|
|
89
|
-
- **Round-start price** — your scoring baseline. You predict % change from this.
|
|
90
|
-
- **Current price** — how much has already moved from baseline. Your prediction should account for movement that already happened.
|
|
91
|
-
- **Time remaining** — less time = less room for further movement. Late in the round, anchor your prediction close to where the price already is relative to baseline.
|
|
92
|
-
- **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.
|
|
93
|
-
|
|
94
|
-
Scoring:
|
|
95
|
-
- Correct direction + close to actual = max honey (up to +100)
|
|
96
|
-
- Correct direction but far off = less honey
|
|
97
|
-
- Wrong direction = -25 honey penalty
|
|
98
|
-
- Skipping = no penalty, no reward
|
|
99
|
-
- Earlier predictions earn dramatically more honey (cubic time decay)
|
|
100
|
-
- Predicting late earns almost nothing even if correct
|
|
101
|
-
|
|
102
|
-
## Using tools
|
|
103
|
-
|
|
104
|
-
If you have tools available, use them to research current price, OHLC data, technical indicators, mindshare data, and social sentiment. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.
|
|
105
|
-
|
|
106
|
-
If your tools return nothing or you have limited data, just run with it. You know crypto. You know this space. Use your general knowledge, recent market conditions, and your trading instincts to form a directional lean. An imperfect prediction beats no prediction. Do NOT mention that you lack data. Never say "no data", "limited data", "couldn't find", "no tools", or anything that reveals you're operating without information. Just post with conviction like you always do. The only exception: if you're deliberately bluffing in character, that's a personality move, not a disclaimer.`;
|
|
107
|
-
// ── Prompt (dynamic per round — changes every call) ──
|
|
83
|
+
IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across rounds. Each round has different context — your conviction should reflect that.`;
|
|
84
|
+
// ── Prompt (dynamic per signal — changes every call) ──
|
|
108
85
|
let recentPostsSection = '';
|
|
109
86
|
if (recentPosts && recentPosts.length > 0) {
|
|
110
87
|
const listed = recentPosts.map((p) => `- "${p}"`).join('\n');
|
|
@@ -130,6 +107,7 @@ ${memory}
|
|
|
130
107
|
const roundStartMs = Math.floor(now / durationMs) * durationMs;
|
|
131
108
|
const timeRemainingMs = Math.max(0, roundStartMs + durationMs - now);
|
|
132
109
|
const timeRemaining = humanDuration(timeRemainingMs);
|
|
110
|
+
const lateInRound = timeRemainingMs < durationMs * 0.25;
|
|
133
111
|
const nowIso = new Date().toISOString();
|
|
134
112
|
// ── Price context lines ──
|
|
135
113
|
const hasBothPrices = priceAtStart !== undefined && currentPrice !== undefined;
|
|
@@ -141,11 +119,33 @@ ${memory}
|
|
|
141
119
|
}
|
|
142
120
|
let priceContextLines = '';
|
|
143
121
|
if (hasBothPrices) {
|
|
144
|
-
priceContextLines = `- Round-start price: $${priceAtStart} (scoring
|
|
122
|
+
priceContextLines = `- Round-start price: $${priceAtStart} (baseline for scoring)
|
|
145
123
|
- Current price: $${currentPrice} (${currentChangeStr} from round start)`;
|
|
146
124
|
}
|
|
147
125
|
else if (priceAtStart !== undefined) {
|
|
148
|
-
priceContextLines = `- Round-start price: $${priceAtStart} (scoring
|
|
126
|
+
priceContextLines = `- Round-start price: $${priceAtStart} (baseline for scoring)`;
|
|
127
|
+
}
|
|
128
|
+
// ── What-matters block ──
|
|
129
|
+
let whatMattersBlock;
|
|
130
|
+
if (hasBothPrices) {
|
|
131
|
+
whatMattersBlock = `What matters:
|
|
132
|
+
- **Round-start price** ($${priceAtStart}) — your baseline. Scoring measures % change from here.
|
|
133
|
+
- **Current price** ($${currentPrice}, ${currentChangeStr} from baseline) — price has already moved this much. Your prediction should be close to this unless you expect further movement.
|
|
134
|
+
- **Time remaining** (~${timeRemaining}) — less time = less room for further movement from current price.${lateInRound ? ' Almost no time left — anchor your prediction to the current change (' + currentChangeStr + ').' : ''}
|
|
135
|
+
- **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.`;
|
|
136
|
+
}
|
|
137
|
+
else if (priceAtStart !== undefined) {
|
|
138
|
+
whatMattersBlock = `What matters:
|
|
139
|
+
- **Round-start price** ($${priceAtStart}) — your baseline. Scoring measures % change from here.
|
|
140
|
+
- **Current price** — check with tools. Compare to $${priceAtStart} to see how much has already moved.
|
|
141
|
+
- **Time remaining** (~${timeRemaining}) — less time = less room for further movement from current price.${lateInRound ? ' Almost no time left — anchor your prediction to where the price is NOW relative to $' + priceAtStart + '.' : ''}
|
|
142
|
+
- **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.`;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
whatMattersBlock = `What matters:
|
|
146
|
+
- **Current price** — check with tools. This is the most important input to your prediction.
|
|
147
|
+
- **Time remaining** (~${timeRemaining}) — the runway left for price movement. Less time = smaller realistic moves. Scale your conviction accordingly.${lateInRound ? ' Round is almost over — keep your conviction small.' : ''}
|
|
148
|
+
- **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.`;
|
|
149
149
|
}
|
|
150
150
|
// ── Scoring & conviction lines ──
|
|
151
151
|
let scoringLine;
|
|
@@ -173,11 +173,36 @@ ${memory}
|
|
|
173
173
|
- Round duration: ${timeframe}
|
|
174
174
|
- Time remaining: ~${timeRemaining}
|
|
175
175
|
${priceContextLines ? priceContextLines + '\n' : ''}
|
|
176
|
+
## The game
|
|
177
|
+
|
|
178
|
+
This is a price prediction game. You're predicting the % price change for c/${projectId} over this ${timeframe} round.
|
|
179
|
+
|
|
180
|
+
${whatMattersBlock}
|
|
181
|
+
|
|
176
182
|
## Your task
|
|
177
183
|
|
|
178
|
-
This is a **megathread round** for c/${projectId}. Form your price conviction for where c/${projectId} will be relative to ${taskBaseline} by end of this ${timeframe} round (~${timeRemaining} left)
|
|
184
|
+
This is a **megathread round** for c/${projectId}. Form your price conviction for where c/${projectId} will be relative to ${taskBaseline} by end of this ${timeframe} round (~${timeRemaining} left).
|
|
185
|
+
|
|
186
|
+
**If you have tools available**, use them to research:
|
|
187
|
+
- Check current price, OHLC data, and technical indicators (RSI, MACD, volume, etc.)
|
|
188
|
+
- Look at mindshare data and social sentiment
|
|
189
|
+
- Use any available skills that help you gather market context
|
|
190
|
+
|
|
191
|
+
**When you use a tool, drop a specific number or fact from the result into your post.** Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.
|
|
192
|
+
|
|
193
|
+
**If your tools return nothing or you have limited data**, just run with it. You know crypto. You know this space. Use your general knowledge of c/${projectId}, recent market conditions, and your trading instincts to form a directional lean. An imperfect prediction beats no prediction. Do NOT mention that you lack data, have no data, or couldn't find information. Never say "no data", "limited data", "couldn't find", "no tools", or anything that reveals you're operating without information. Just post with conviction like you always do. The only exception: if you're deliberately bluffing in character — pretending to reference data and then pulling the rug — that's a personality move, not a disclaimer.
|
|
194
|
+
|
|
195
|
+
Form a thesis based on what you find or what you know.${thesisHint}
|
|
196
|
+
|
|
197
|
+
## How scoring works
|
|
179
198
|
|
|
180
199
|
${scoringLine}
|
|
200
|
+
- Correct direction + close to actual = max honey (up to +100)
|
|
201
|
+
- Correct direction but far off = less honey
|
|
202
|
+
- Wrong direction = -25 honey penalty
|
|
203
|
+
- Skipping = no penalty, no reward
|
|
204
|
+
- Earlier predictions earn dramatically more honey (cubic time decay)
|
|
205
|
+
- Predicting late earns almost nothing even if correct
|
|
181
206
|
${recentPostsSection}${memorySection}
|
|
182
207
|
Give your take in character and a conviction number.
|
|
183
208
|
${convictionLine}`;
|
|
@@ -276,28 +301,7 @@ Set skip to true and set summary and conviction to null. Real people don't comme
|
|
|
276
301
|
- Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
|
|
277
302
|
- Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
|
|
278
303
|
|
|
279
|
-
IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across signals. Each signal has different strength — your conviction should reflect that
|
|
280
|
-
|
|
281
|
-
## The prediction game
|
|
282
|
-
|
|
283
|
-
This is a price prediction game. Each signal has a snapshot price captured at signal time and a 3-hour evaluation window. Your conviction = predicted % change from the snapshot price by evaluation time.
|
|
284
|
-
|
|
285
|
-
Key inputs for each signal:
|
|
286
|
-
- **Snapshot price** — your scoring baseline. You predict % change from this.
|
|
287
|
-
- **Current price** — check with tools to see how much has already moved since the signal.
|
|
288
|
-
- **Time left** — less time = less room for further movement. Late in the window, anchor your prediction close to where the price already is relative to the snapshot.
|
|
289
|
-
|
|
290
|
-
Scoring:
|
|
291
|
-
- Correct direction + close to actual = max honey (up to +100)
|
|
292
|
-
- Correct direction but far off = less honey
|
|
293
|
-
- Wrong direction = -25 honey penalty
|
|
294
|
-
- Skipping = no penalty, no reward
|
|
295
|
-
- Earlier predictions earn dramatically more honey (cubic time decay)
|
|
296
|
-
- Predicting late earns almost nothing even if correct
|
|
297
|
-
|
|
298
|
-
## Using tools
|
|
299
|
-
|
|
300
|
-
If you have tools available, use them to check price action, technicals, or mindshare before forming your take. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.`;
|
|
304
|
+
IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across signals. Each signal has different strength — your conviction should reflect that.`;
|
|
301
305
|
// ── Prompt (dynamic per signal — changes every call) ──
|
|
302
306
|
let citationsSection = '';
|
|
303
307
|
if (citations.length > 0) {
|
|
@@ -334,20 +338,42 @@ ${memory}
|
|
|
334
338
|
const signalTimeMs = new Date(timestamp).getTime();
|
|
335
339
|
const timeRemainingMs = Math.max(0, signalTimeMs + EVAL_WINDOW_MS - Date.now());
|
|
336
340
|
const timeRemaining = humanDuration(timeRemainingMs);
|
|
341
|
+
const lateWindow = timeRemainingMs < 45 * 60 * 1000;
|
|
337
342
|
const userPrompt = `## Context
|
|
338
343
|
|
|
339
344
|
- Project: c/${projectId}
|
|
340
345
|
- Current date: ${currentDate}
|
|
341
346
|
- Signal time: ${timestamp}
|
|
342
|
-
- Snapshot price: $${priceOnFetch} (
|
|
347
|
+
- Snapshot price: $${priceOnFetch} (captured at signal time)
|
|
343
348
|
- Time left: ~${timeRemaining}
|
|
344
|
-
${citationsSection}
|
|
349
|
+
${citationsSection}
|
|
350
|
+
## The game
|
|
351
|
+
|
|
352
|
+
This is a price prediction game. Your conviction = predicted % change from the snapshot price ($${priceOnFetch}), evaluated 3 hours after signal time.
|
|
353
|
+
|
|
354
|
+
What matters:
|
|
355
|
+
- **Snapshot price** ($${priceOnFetch}) — your baseline. Scoring measures % change from here.
|
|
356
|
+
- **Current price** — how much has already moved since the signal. Check with tools.
|
|
357
|
+
- **Time left** (~${timeRemaining}) — less time = less room for further movement from current price.${lateWindow ? ' Almost no time left — anchor your prediction to where the price is NOW relative to the snapshot.' : ''}
|
|
358
|
+
|
|
359
|
+
## How scoring works
|
|
360
|
+
|
|
361
|
+
You are predicting the % price change from the snapshot price ($${priceOnFetch}) over 3 hours from signal time.
|
|
362
|
+
- Correct direction + close to actual = max honey (up to +100)
|
|
363
|
+
- Correct direction but far off = less honey
|
|
364
|
+
- Wrong direction = -25 honey penalty
|
|
365
|
+
- Skipping = no penalty, no reward
|
|
366
|
+
- Earlier predictions earn dramatically more honey (cubic time decay)
|
|
367
|
+
- Predicting late earns almost nothing even if correct
|
|
368
|
+
${recentPostsSection}${memorySection}
|
|
345
369
|
Signal/event to react to:
|
|
346
370
|
"""
|
|
347
371
|
${threadText}
|
|
348
372
|
"""
|
|
349
373
|
|
|
374
|
+
**If you have tools available**, use them to check price action, technicals, or mindshare for c/${projectId} before forming your take. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If the tool returned nothing useful, ignore it and post from instinct.
|
|
375
|
+
|
|
350
376
|
Give your take in character and a conviction number.
|
|
351
|
-
Conviction: predicted % price change from $${priceOnFetch} by evaluation time (~${timeRemaining} left), up to one decimal. Positive = up, negative = down.`;
|
|
377
|
+
Conviction: predicted % price change from $${priceOnFetch} by evaluation time (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
|
|
352
378
|
return { system, prompt: userPrompt };
|
|
353
379
|
}
|
|
@@ -2,8 +2,8 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import axios from 'axios';
|
|
5
|
-
import { AI_PROVIDERS } from '
|
|
6
|
-
import { HIVE_API_URL } from './
|
|
5
|
+
import { AI_PROVIDERS } from '../ai-providers.js';
|
|
6
|
+
import { HIVE_API_URL } from './constant.js';
|
|
7
7
|
function extractField(content, pattern) {
|
|
8
8
|
const match = content.match(pattern);
|
|
9
9
|
if (match === null) {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
export const HIVE_API_URL = 'https://api.zhive.ai';
|
|
5
|
-
export const HIVE_FRONTEND_URL = 'https://www.zhive.ai';
|
|
6
|
-
export function getHiveDir() {
|
|
7
|
-
const hiveDir = path.join(os.homedir(), '.hive');
|
|
8
|
-
return hiveDir;
|
|
9
|
-
}
|
|
3
|
+
import { getHiveDir } from './constant.js';
|
|
10
4
|
export async function readConfig() {
|
|
11
5
|
try {
|
|
12
6
|
const configPath = path.join(getHiveDir(), 'config.json');
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
export const HIVE_API_URL = 'https://api.zhive.ai';
|
|
4
|
+
export const HIVE_FRONTEND_URL = 'https://www.zhive.ai';
|
|
5
|
+
export function getHiveDir() {
|
|
6
|
+
const hiveDir = path.join(os.homedir(), '.hive');
|
|
7
|
+
return hiveDir;
|
|
8
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
async function loadMarkdownFile(filename) {
|
|
4
|
+
const filePath = path.join(process.cwd(), filename);
|
|
5
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
6
|
+
return content;
|
|
7
|
+
}
|
|
8
|
+
function extractField(content, pattern) {
|
|
9
|
+
const match = content.match(pattern);
|
|
10
|
+
if (match === null) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const value = match[1].trim();
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
const VALID_SENTIMENTS = [
|
|
17
|
+
'very-bullish',
|
|
18
|
+
'bullish',
|
|
19
|
+
'neutral',
|
|
20
|
+
'bearish',
|
|
21
|
+
'very-bearish',
|
|
22
|
+
];
|
|
23
|
+
const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
|
|
24
|
+
function parseSentiment(raw) {
|
|
25
|
+
if (raw !== null && VALID_SENTIMENTS.includes(raw)) {
|
|
26
|
+
return raw;
|
|
27
|
+
}
|
|
28
|
+
return 'neutral';
|
|
29
|
+
}
|
|
30
|
+
function parseSectors(raw) {
|
|
31
|
+
if (raw === null || raw.trim() === '') {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const sectors = raw
|
|
35
|
+
.split(',')
|
|
36
|
+
.map((s) => s.trim())
|
|
37
|
+
.filter((s) => s.length > 0);
|
|
38
|
+
return sectors;
|
|
39
|
+
}
|
|
40
|
+
function parseTimeframes(raw) {
|
|
41
|
+
if (raw === null || raw.trim() === '') {
|
|
42
|
+
return ['1h', '4h', '24h'];
|
|
43
|
+
}
|
|
44
|
+
const parsed = raw
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((t) => t.trim())
|
|
47
|
+
.filter((t) => VALID_TIMEFRAMES.includes(t));
|
|
48
|
+
if (parsed.length === 0) {
|
|
49
|
+
return ['1h', '4h', '24h'];
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
export async function loadAgentConfig() {
|
|
54
|
+
const soulContent = await loadMarkdownFile('SOUL.md');
|
|
55
|
+
const strategyContent = await loadMarkdownFile('STRATEGY.md');
|
|
56
|
+
const name = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
|
|
57
|
+
if (name === null) {
|
|
58
|
+
throw new Error('Could not parse agent name from SOUL.md. Expected "# Agent: <name>" as the first heading.');
|
|
59
|
+
}
|
|
60
|
+
const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
|
|
61
|
+
if (avatarUrl === null) {
|
|
62
|
+
throw new Error('Could not parse avatar URL from SOUL.md. Expected a valid URL under "## Avatar".');
|
|
63
|
+
}
|
|
64
|
+
const bioRaw = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
|
|
65
|
+
const bio = bioRaw ?? null;
|
|
66
|
+
const sentimentRaw = extractField(strategyContent, /^-\s+Bias:\s+(.+)$/m);
|
|
67
|
+
const sectorsRaw = extractField(strategyContent, /^-\s+Sectors:\s+(.+)$/m);
|
|
68
|
+
const timeframesRaw = extractField(strategyContent, /^-\s+Active timeframes:\s+(.+)$/m);
|
|
69
|
+
const agentProfile = {
|
|
70
|
+
sentiment: parseSentiment(sentimentRaw),
|
|
71
|
+
sectors: parseSectors(sectorsRaw),
|
|
72
|
+
timeframes: parseTimeframes(timeframesRaw),
|
|
73
|
+
};
|
|
74
|
+
return { name, bio, avatarUrl, soulContent, strategyContent, agentProfile };
|
|
75
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { AI_PROVIDER_ENV_VARS } from '../ai-providers.js';
|
|
3
|
+
let _agentProviderKeys = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* Provider env-var names declared in the agent's .env file.
|
|
6
|
+
* Used by getModel() to prioritize the agent's chosen provider
|
|
7
|
+
* over keys inherited from the shell.
|
|
8
|
+
*/
|
|
9
|
+
export function getAgentProviderKeys() {
|
|
10
|
+
return _agentProviderKeys;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Load the agent's .env with provider-key priority.
|
|
14
|
+
*
|
|
15
|
+
* 1. Parse .env to discover which provider keys the agent declared.
|
|
16
|
+
* 2. Load .env with override so the agent's values win for the same key.
|
|
17
|
+
* 3. getModel() uses getAgentProviderKeys() to check those providers first,
|
|
18
|
+
* falling back to shell-inherited keys if the agent has none.
|
|
19
|
+
*/
|
|
20
|
+
export async function loadAgentEnv() {
|
|
21
|
+
try {
|
|
22
|
+
const content = readFileSync('.env', 'utf-8');
|
|
23
|
+
_agentProviderKeys = new Set(AI_PROVIDER_ENV_VARS.filter((key) => new RegExp(`^${key}=`, 'm').test(content)));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
_agentProviderKeys = new Set();
|
|
27
|
+
}
|
|
28
|
+
const { config } = await import('dotenv');
|
|
29
|
+
config({ override: true });
|
|
30
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { processMegathreadRound, screenMegathreadRound, } from './analysis.js';
|
|
2
|
+
import { getMarketClient } from './tools/market/index.js';
|
|
3
|
+
import { extractErrorMessage } from './utils.js';
|
|
4
|
+
export const fetchPrice = async (projectId, timestamp) => {
|
|
5
|
+
const client = getMarketClient();
|
|
6
|
+
const response = await client.getPrice(projectId, timestamp);
|
|
7
|
+
return response.price ?? undefined;
|
|
8
|
+
};
|
|
9
|
+
export async function fetchRoundPrices(projectId, roundTimestamp, currentTime) {
|
|
10
|
+
let priceAtStart;
|
|
11
|
+
let currentPrice;
|
|
12
|
+
try {
|
|
13
|
+
const client = getMarketClient();
|
|
14
|
+
[priceAtStart, currentPrice] = await Promise.all([
|
|
15
|
+
fetchPrice(projectId, roundTimestamp),
|
|
16
|
+
fetchPrice(projectId, currentTime ?? new Date().toISOString()),
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Price fetch failed — both stay undefined
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
priceAtStart,
|
|
24
|
+
currentPrice,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const calculateTimeframe = (round) => {
|
|
28
|
+
const hours = Math.round(round.durationMs / 3_600_000);
|
|
29
|
+
const timeframe = hours >= 1 ? `${hours}h` : `${Math.round(round.durationMs / 60_000)}m`;
|
|
30
|
+
return timeframe;
|
|
31
|
+
};
|
|
32
|
+
async function run({ round, runtime, reporter, recentComments, }) {
|
|
33
|
+
const timeframe = calculateTimeframe(round);
|
|
34
|
+
reporter.onRoundStart(round, timeframe);
|
|
35
|
+
// ── Fetch prices ──────────────────────────────
|
|
36
|
+
const roundStartTimestamp = round.roundId.split('@Z')[0];
|
|
37
|
+
const { priceAtStart, currentPrice } = await fetchRoundPrices(round.projectId, roundStartTimestamp);
|
|
38
|
+
if (priceAtStart !== undefined) {
|
|
39
|
+
reporter.onPriceInfo(priceAtStart, currentPrice);
|
|
40
|
+
}
|
|
41
|
+
// ── Quick screen (cheap engage check) ───────
|
|
42
|
+
const screenResult = await screenMegathreadRound(round.projectId, runtime.config.strategyContent);
|
|
43
|
+
if (!screenResult.engage) {
|
|
44
|
+
reporter.onScreenResult?.(round, screenResult);
|
|
45
|
+
return { skip: true, usage: screenResult.usage, screenResult };
|
|
46
|
+
}
|
|
47
|
+
reporter.onResearching(round.projectId);
|
|
48
|
+
// ── Run analysis ──────────────────────────────
|
|
49
|
+
const result = await processMegathreadRound({
|
|
50
|
+
projectId: round.projectId,
|
|
51
|
+
durationMs: round.durationMs,
|
|
52
|
+
recentComments,
|
|
53
|
+
agentRuntime: runtime,
|
|
54
|
+
priceAtStart,
|
|
55
|
+
currentPrice,
|
|
56
|
+
});
|
|
57
|
+
reporter.onToolsUsed(result.usage.toolNames, result.usage.toolCalls);
|
|
58
|
+
if (result.skip) {
|
|
59
|
+
reporter.onSkipped(round, result.usage);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
export function createMegathreadRoundBatchHandler(getAgent, runtime, reporter) {
|
|
64
|
+
const handler = async (rounds) => {
|
|
65
|
+
const agent = getAgent();
|
|
66
|
+
const promises = [];
|
|
67
|
+
// report item in order that it is polled to prevent out-of-order write to stdout
|
|
68
|
+
for (const round of rounds) {
|
|
69
|
+
promises.push(run({ round, runtime, reporter, recentComments: agent.recentComments }));
|
|
70
|
+
}
|
|
71
|
+
const results = await Promise.allSettled(promises);
|
|
72
|
+
for (let i = 0; i < results.length; i++) {
|
|
73
|
+
const round = rounds[i];
|
|
74
|
+
const result = results[i];
|
|
75
|
+
if (result.status === 'rejected') {
|
|
76
|
+
const raw = extractErrorMessage(result.reason);
|
|
77
|
+
const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
|
|
78
|
+
reporter.onError(round, message);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const data = result.value;
|
|
82
|
+
if (data.skip) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// TODO: we can optimized this by create method to commit this in batch in hive sdk.
|
|
86
|
+
// postMegathreadComment cannot be run concurrently so we need to call it one by one.
|
|
87
|
+
await agent.postMegathreadComment(round.roundId, {
|
|
88
|
+
text: data.summary,
|
|
89
|
+
conviction: data.conviction,
|
|
90
|
+
tokenId: round.projectId,
|
|
91
|
+
roundDuration: round.durationMs,
|
|
92
|
+
});
|
|
93
|
+
const timeframe = calculateTimeframe(round);
|
|
94
|
+
reporter.onPosted(round, data.conviction, data.summary, timeframe, data.usage);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return handler;
|
|
98
|
+
}
|
|
99
|
+
export function createMegathreadRoundHandler(getAgent, runtime, reporter) {
|
|
100
|
+
const handler = async (round) => {
|
|
101
|
+
const agent = getAgent();
|
|
102
|
+
try {
|
|
103
|
+
const result = await run({
|
|
104
|
+
round,
|
|
105
|
+
reporter,
|
|
106
|
+
recentComments: agent.recentComments,
|
|
107
|
+
runtime,
|
|
108
|
+
});
|
|
109
|
+
if (result.skip) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// ── Post comment ──────────────────────────────
|
|
113
|
+
await agent.postMegathreadComment(round.roundId, {
|
|
114
|
+
text: result.summary,
|
|
115
|
+
conviction: result.conviction,
|
|
116
|
+
tokenId: round.projectId,
|
|
117
|
+
roundDuration: round.durationMs,
|
|
118
|
+
});
|
|
119
|
+
const timeframe = calculateTimeframe(round);
|
|
120
|
+
reporter.onPosted(round, result.conviction, result.summary, timeframe, result.usage);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
const raw = extractErrorMessage(err);
|
|
124
|
+
const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
|
|
125
|
+
reporter.onError(round, message);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return handler;
|
|
129
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AI_PROVIDERS } from '../../ai-providers.js';
|
|
2
|
+
import { getAgentProviderKeys } from '../../config/env-loader.js';
|
|
3
|
+
const PROVIDERS = [
|
|
4
|
+
{
|
|
5
|
+
label: 'Anthropic',
|
|
6
|
+
envVar: 'ANTHROPIC_API_KEY',
|
|
7
|
+
load: async (modelId) => {
|
|
8
|
+
const { anthropic } = await import('@ai-sdk/anthropic');
|
|
9
|
+
return anthropic(modelId);
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'OpenAI',
|
|
14
|
+
envVar: 'OPENAI_API_KEY',
|
|
15
|
+
load: async (modelId) => {
|
|
16
|
+
const { openai } = await import('@ai-sdk/openai');
|
|
17
|
+
return openai(modelId);
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Google',
|
|
22
|
+
envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
23
|
+
load: async (modelId) => {
|
|
24
|
+
const { google } = await import('@ai-sdk/google');
|
|
25
|
+
return google(modelId);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'xAI',
|
|
30
|
+
envVar: 'XAI_API_KEY',
|
|
31
|
+
load: async (modelId) => {
|
|
32
|
+
const { xai } = await import('@ai-sdk/xai');
|
|
33
|
+
return xai(modelId);
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'OpenRouter',
|
|
38
|
+
envVar: 'OPENROUTER_API_KEY',
|
|
39
|
+
load: async (modelId) => {
|
|
40
|
+
const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');
|
|
41
|
+
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
42
|
+
return openrouter.chat(modelId);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
export function resolveModelInfo() {
|
|
47
|
+
const overrideModel = process.env.HIVE_MODEL;
|
|
48
|
+
const agentKeys = getAgentProviderKeys();
|
|
49
|
+
const sortedProviders = [
|
|
50
|
+
...PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
|
|
51
|
+
...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
|
|
52
|
+
];
|
|
53
|
+
for (const provider of sortedProviders) {
|
|
54
|
+
const keyValue = process.env[provider.envVar];
|
|
55
|
+
if (keyValue && keyValue.trim().length > 0) {
|
|
56
|
+
const centralProvider = AI_PROVIDERS.find((p) => p.envVar === provider.envVar);
|
|
57
|
+
const runtimeModel = centralProvider?.models.runtime ?? 'unknown';
|
|
58
|
+
const modelId = overrideModel ?? runtimeModel;
|
|
59
|
+
const source = agentKeys.has(provider.envVar) ? '.env' : 'shell';
|
|
60
|
+
return { provider: provider.label, modelId, source };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { provider: 'unknown', modelId: 'unknown', source: 'unknown' };
|
|
64
|
+
}
|
|
65
|
+
let _modelPromise = null;
|
|
66
|
+
export function getModel() {
|
|
67
|
+
if (_modelPromise) {
|
|
68
|
+
return _modelPromise;
|
|
69
|
+
}
|
|
70
|
+
_modelPromise = (async () => {
|
|
71
|
+
const info = resolveModelInfo();
|
|
72
|
+
if (info.provider === 'unknown') {
|
|
73
|
+
throw new Error('No AI provider API key found in environment. ' +
|
|
74
|
+
'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY');
|
|
75
|
+
}
|
|
76
|
+
const agentKeys = getAgentProviderKeys();
|
|
77
|
+
const sortedProviders = [
|
|
78
|
+
...PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
|
|
79
|
+
...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
|
|
80
|
+
];
|
|
81
|
+
for (const provider of sortedProviders) {
|
|
82
|
+
const keyValue = process.env[provider.envVar];
|
|
83
|
+
if (keyValue && keyValue.trim().length > 0) {
|
|
84
|
+
const modelId = info.modelId;
|
|
85
|
+
const model = await provider.load(modelId);
|
|
86
|
+
return model;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new Error('Unreachable: resolveModelInfo succeeded but no provider found');
|
|
90
|
+
})();
|
|
91
|
+
return _modelPromise;
|
|
92
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadAgentConfig } from '../config/agent.js';
|
|
2
|
+
import { loadMemory } from '@zhive/sdk';
|
|
3
|
+
import { getModel } from '../config/ai-providers.js';
|
|
4
|
+
import { getAllTools, getExecuteSkillTool, initializeSkills } from './tools/index.js';
|
|
5
|
+
export async function initializeAgentRuntime(agentDir) {
|
|
6
|
+
const config = await loadAgentConfig(agentDir);
|
|
7
|
+
const memory = await loadMemory(agentDir);
|
|
8
|
+
const model = await getModel();
|
|
9
|
+
const skillRegistry = await initializeSkills(agentDir);
|
|
10
|
+
const tools = getAllTools();
|
|
11
|
+
const executeSkillTool = getExecuteSkillTool(skillRegistry, model);
|
|
12
|
+
const allTools = { ...tools, executeSkillTool };
|
|
13
|
+
const runtime = { config, memory, tools: allTools, skills: skillRegistry, model };
|
|
14
|
+
return runtime;
|
|
15
|
+
}
|
|
@@ -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
|
+
}
|