@zhive/cli 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent/commands/profile.js +0 -6
- package/dist/commands/agent/commands/profile.test.js +2 -23
- package/dist/commands/create/presets/index.js +1 -1
- package/dist/commands/create/presets/options.js +18 -15
- package/dist/commands/create/ui/steps/IdentityStep.js +3 -2
- package/dist/commands/doctor/commands/index.js +5 -11
- package/dist/commands/megathread/commands/create-comment.js +2 -7
- package/dist/commands/megathread/commands/create-comment.test.js +3 -30
- package/dist/commands/megathread/commands/create-comments.js +2 -7
- package/dist/commands/megathread/commands/list.js +5 -10
- package/dist/commands/megathread/commands/list.test.js +3 -21
- package/dist/commands/migrate-templates/ui/MigrateApp.js +1 -1
- package/dist/commands/start/commands/prediction.js +1 -1
- package/dist/commands/start/commands/skills.test.js +1 -2
- package/dist/components/MultiSelectPrompt.js +3 -3
- package/dist/shared/config/agent.js +4 -0
- package/dist/shared/config/agent.test.js +0 -5
- package/package.json +1 -1
- package/dist/CLAUDE.md +0 -7
- package/dist/backtest/CLAUDE.md +0 -7
- package/dist/cli.js +0 -20
- package/dist/commands/create/presets.js +0 -613
- package/dist/commands/start/ui/AsciiTicker.js +0 -81
- package/dist/services/agent/analysis.js +0 -160
- package/dist/services/agent/config.js +0 -75
- package/dist/services/agent/env.js +0 -30
- package/dist/services/agent/helpers/model.js +0 -92
- package/dist/services/agent/helpers.js +0 -22
- package/dist/services/agent/prompts/chat-prompt.js +0 -65
- package/dist/services/agent/prompts/memory-prompt.js +0 -45
- package/dist/services/agent/prompts/prompt.js +0 -379
- package/dist/services/agent/skills/index.js +0 -2
- package/dist/services/agent/skills/skill-parser.js +0 -149
- package/dist/services/agent/skills/types.js +0 -1
- package/dist/services/agent/tools/edit-section.js +0 -59
- package/dist/services/agent/tools/fetch-rules.js +0 -21
- package/dist/services/agent/tools/index.js +0 -76
- package/dist/services/agent/tools/market/client.js +0 -41
- package/dist/services/agent/tools/market/index.js +0 -3
- package/dist/services/agent/tools/market/tools.js +0 -518
- package/dist/services/agent/tools/mindshare/client.js +0 -124
- package/dist/services/agent/tools/mindshare/index.js +0 -3
- package/dist/services/agent/tools/mindshare/tools.js +0 -563
- package/dist/services/agent/tools/read-skill-tool.js +0 -30
- package/dist/services/agent/tools/ta/index.js +0 -1
- package/dist/services/agent/tools/ta/indicators.js +0 -201
- package/dist/services/agent/types.js +0 -1
- package/dist/services/ai-providers.js +0 -66
- package/dist/services/config/agent.js +0 -110
- package/dist/services/config/config.js +0 -22
- package/dist/services/config/constant.js +0 -8
- package/dist/shared/agent/agent-runtime.js +0 -144
- package/dist/shared/agent/config.js +0 -75
- package/dist/shared/agent/env.js +0 -30
- package/dist/shared/agent/helpers/model.js +0 -92
- package/dist/shared/agent/types.js +0 -1
- package/dist/shared/ai-providers.js +0 -66
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
// ─── Shared Types ─────────────────────────────────
|
|
2
|
-
function humanDuration(ms) {
|
|
3
|
-
const hours = ms / 3_600_000;
|
|
4
|
-
if (hours >= 24) {
|
|
5
|
-
const days = Math.round(hours / 24);
|
|
6
|
-
return days === 1 ? '1 day' : `${days} days`;
|
|
7
|
-
}
|
|
8
|
-
if (hours >= 1) {
|
|
9
|
-
const rounded = Math.round(hours);
|
|
10
|
-
return rounded === 1 ? '1 hour' : `${rounded} hours`;
|
|
11
|
-
}
|
|
12
|
-
const minutes = Math.round(ms / 60_000);
|
|
13
|
-
return minutes === 1 ? '1 minute' : `${minutes} minutes`;
|
|
14
|
-
}
|
|
15
|
-
export function buildMegathreadPrompt(soulContent, strategyContent, options) {
|
|
16
|
-
const { projectId, durationMs, priceAtStart, currentPrice, recentPosts, memory, availableSkills, } = options;
|
|
17
|
-
const timeframe = humanDuration(durationMs);
|
|
18
|
-
// ── System (static per agent session — cached by providers) ──
|
|
19
|
-
let skillsSection = '';
|
|
20
|
-
if (availableSkills && availableSkills.trim().length > 0) {
|
|
21
|
-
skillsSection = `
|
|
22
|
-
## Available Skills
|
|
23
|
-
|
|
24
|
-
Use readSkill(name) to read a skill's full instructions when relevant.
|
|
25
|
-
|
|
26
|
-
${availableSkills}
|
|
27
|
-
`;
|
|
28
|
-
}
|
|
29
|
-
const system = `You are a Crypto Twitter personality. Here's who you are:
|
|
30
|
-
---
|
|
31
|
-
${soulContent}
|
|
32
|
-
---
|
|
33
|
-
You ARE this person. Reference your life, relationships, and background naturally when relevant. Be self-aware of who you are.
|
|
34
|
-
|
|
35
|
-
Your opinions, pet peeves, and example posts in SOUL.md are your personality anchors.
|
|
36
|
-
Rotate through them naturally - don't use the same opinion or quirk in consecutive posts.
|
|
37
|
-
Your example posts set the quality bar and tone target. Match their energy, not their exact words.
|
|
38
|
-
|
|
39
|
-
Your trading lens:
|
|
40
|
-
---
|
|
41
|
-
${strategyContent}
|
|
42
|
-
---
|
|
43
|
-
${skillsSection}
|
|
44
|
-
## How you write
|
|
45
|
-
|
|
46
|
-
Write like an anonymous poster on 4chan, reddit, or crypto twitter. Not an analyst. Not an AI. A real person shitposting with conviction.
|
|
47
|
-
|
|
48
|
-
Think about how people actually talk in these spaces:
|
|
49
|
-
- They don't explain themselves. They just say it.
|
|
50
|
-
- They're blunt, sarcastic, dismissive, funny, or dead serious. Sometimes all at once.
|
|
51
|
-
- They use slang naturally: ngl, tbh, copium, ngmi, imo, idk, lmao, kek, based, cope, etc.
|
|
52
|
-
- They mock bad takes. They clown on people. They flex when they're right.
|
|
53
|
-
- They type fast and don't proofread. Lowercase, fragments, run-ons, whatever.
|
|
54
|
-
- They react to vibes, not just data.
|
|
55
|
-
- Sometimes the whole post is just one word. Sometimes it's a question. Sometimes it's a meme reference.
|
|
56
|
-
- Sometimes they say something completely unrelated to the market data. A random life story, a personal anecdote, a non-sequitur. The conviction still comes through but the post itself doesn't explain why. Not every post needs to reference the data directly.
|
|
57
|
-
|
|
58
|
-
Rules:
|
|
59
|
-
- Pick ONE angle. Don't try to cover everything you found.
|
|
60
|
-
- You do NOT need to explain your reasoning. Sometimes just say something in character and let the conviction number speak for itself.
|
|
61
|
-
- ~20% of the time, your post can be totally unrelated to the market data. A life detail, a random thought, a flex, a non-sequitur. You still give a conviction number but the post doesn't explain it. People who know, know.
|
|
62
|
-
- ~15% of the time, go ultra-short: 1-6 words. "lol." / "called it" / "nah" / "cope" / "$BTC" / "anon was right"
|
|
63
|
-
- The rest: 1-3 sentences max. Shitpost energy, not essay energy.
|
|
64
|
-
- Vary your format. Rotate between: raw reaction, hot take, question, mockery, dismissal, ticker-only, sarcasm, flexing a past call, random life update, non-sequitur with conviction.
|
|
65
|
-
- No two consecutive posts should have the same structure or opening pattern.
|
|
66
|
-
- Don't stack multiple indicators ("RSI oversold, MACD flattening, volume spiking"). Pick one if relevant, or skip indicators entirely and just give your read.
|
|
67
|
-
- Show conviction through tone, not by listing evidence.
|
|
68
|
-
- Never use em dashes. Use periods, commas, or just start a new sentence.
|
|
69
|
-
- No exclamation marks unless your personality is genuinely hype. Even then, max one.
|
|
70
|
-
- Never start with "Looking at" or "Based on"
|
|
71
|
-
- Never use the phrase "the real X is Y" - find a different way to make the point.
|
|
72
|
-
|
|
73
|
-
## When to skip
|
|
74
|
-
|
|
75
|
-
Only skip if this project is outside the expertise list defined in your STRATEGY.md. If it matches your expertise, you must comment — use your instincts and general knowledge to form a take.
|
|
76
|
-
|
|
77
|
-
## Conviction calibration — match signal strength to magnitude:
|
|
78
|
-
- Routine ecosystem update, minor partnership → ±0.5 to ±2.0
|
|
79
|
-
- Notable catalyst, solid metrics, growing momentum → ±2.0 to ±6.0
|
|
80
|
-
- Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
|
|
81
|
-
- Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
|
|
82
|
-
|
|
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) ──
|
|
85
|
-
let recentPostsSection = '';
|
|
86
|
-
if (recentPosts && recentPosts.length > 0) {
|
|
87
|
-
const listed = recentPosts.map((p) => `- "${p}"`).join('\n');
|
|
88
|
-
recentPostsSection = `
|
|
89
|
-
## Anti-repetition
|
|
90
|
-
|
|
91
|
-
Your recent posts (do NOT repeat these structures, phrases, or opening patterns):
|
|
92
|
-
${listed}
|
|
93
|
-
|
|
94
|
-
If you catch yourself writing something that sounds like any of the above - stop and take a completely different angle.
|
|
95
|
-
`;
|
|
96
|
-
}
|
|
97
|
-
let memorySection = '';
|
|
98
|
-
if (memory && memory.trim().length > 0) {
|
|
99
|
-
memorySection = `
|
|
100
|
-
## Agent Memory
|
|
101
|
-
|
|
102
|
-
Your persistent learnings from past sessions:
|
|
103
|
-
${memory}
|
|
104
|
-
`;
|
|
105
|
-
}
|
|
106
|
-
const now = Date.now();
|
|
107
|
-
const roundStartMs = Math.floor(now / durationMs) * durationMs;
|
|
108
|
-
const timeRemainingMs = Math.max(0, roundStartMs + durationMs - now);
|
|
109
|
-
const timeRemaining = humanDuration(timeRemainingMs);
|
|
110
|
-
const lateInRound = timeRemainingMs < durationMs * 0.25;
|
|
111
|
-
const nowIso = new Date().toISOString();
|
|
112
|
-
// ── Price context lines ──
|
|
113
|
-
const hasBothPrices = priceAtStart !== undefined && currentPrice !== undefined;
|
|
114
|
-
let currentChangeStr = '';
|
|
115
|
-
if (hasBothPrices) {
|
|
116
|
-
const changePercent = ((currentPrice - priceAtStart) / priceAtStart) * 100;
|
|
117
|
-
const sign = changePercent >= 0 ? '+' : '';
|
|
118
|
-
currentChangeStr = `${sign}${changePercent.toFixed(2)}%`;
|
|
119
|
-
}
|
|
120
|
-
let priceContextLines = '';
|
|
121
|
-
if (hasBothPrices) {
|
|
122
|
-
priceContextLines = `- Round-start price: $${priceAtStart} (baseline for scoring)
|
|
123
|
-
- Current price: $${currentPrice} (${currentChangeStr} from round start)`;
|
|
124
|
-
}
|
|
125
|
-
else if (priceAtStart !== undefined) {
|
|
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
|
-
}
|
|
150
|
-
// ── Scoring & conviction lines ──
|
|
151
|
-
let scoringLine;
|
|
152
|
-
let convictionLine;
|
|
153
|
-
if (priceAtStart !== undefined) {
|
|
154
|
-
scoringLine = `You are predicting the TOTAL % price change from the round-start price ($${priceAtStart}) by the end of this ${timeframe} round (~${timeRemaining} remaining). This is NOT the change during the remaining time — it's where the price will be relative to $${priceAtStart} when the round ends.`;
|
|
155
|
-
const example = hasBothPrices
|
|
156
|
-
? ` The price is currently ${currentChangeStr} from baseline — if you think it stays there, predict ${currentChangeStr.replace('+', '')}.`
|
|
157
|
-
: '';
|
|
158
|
-
convictionLine = `Conviction: predicted TOTAL % price change from $${priceAtStart} by the end of this ${timeframe} round (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.${example}`;
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
scoringLine = `You are predicting the % price change for c/${projectId} over this ${timeframe} round (~${timeRemaining} remaining).`;
|
|
162
|
-
convictionLine = `Conviction: predicted % price change for c/${projectId} for the remainder of this ${timeframe} round (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
|
|
163
|
-
}
|
|
164
|
-
// ── Task description ──
|
|
165
|
-
const taskBaseline = priceAtStart !== undefined ? `$${priceAtStart}` : 'the start price';
|
|
166
|
-
const thesisHint = priceAtStart !== undefined
|
|
167
|
-
? ` Your conviction = where you think the price will be relative to $${priceAtStart} when the round ends, NOT how much it will move in the remaining time.`
|
|
168
|
-
: ` Your conviction should reflect what's realistic in the ~${timeRemaining} remaining.`;
|
|
169
|
-
const userPrompt = `## Context
|
|
170
|
-
|
|
171
|
-
- Project: c/${projectId}
|
|
172
|
-
- Current time: ${nowIso}
|
|
173
|
-
- Round duration: ${timeframe}
|
|
174
|
-
- Time remaining: ~${timeRemaining}
|
|
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
|
-
|
|
182
|
-
## Your task
|
|
183
|
-
|
|
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
|
|
198
|
-
|
|
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
|
|
206
|
-
${recentPostsSection}${memorySection}
|
|
207
|
-
Give your take in character and a conviction number.
|
|
208
|
-
${convictionLine}`;
|
|
209
|
-
return { system, prompt: userPrompt };
|
|
210
|
-
}
|
|
211
|
-
export function buildAnalystPrompt(soulContent, strategyContent, options) {
|
|
212
|
-
const { threadText, projectId, timestamp, priceOnFetch, citations, recentPosts, memory, availableSkills, } = options;
|
|
213
|
-
// ── System (static per agent session — cached by providers) ──
|
|
214
|
-
let skillsSection = '';
|
|
215
|
-
if (availableSkills && availableSkills.trim().length > 0) {
|
|
216
|
-
skillsSection = `
|
|
217
|
-
## Available Skills
|
|
218
|
-
|
|
219
|
-
Use readSkill(name) to read a skill's full instructions when relevant.
|
|
220
|
-
|
|
221
|
-
${availableSkills}
|
|
222
|
-
`;
|
|
223
|
-
}
|
|
224
|
-
const system = `You are a Crypto Twitter personality. Here's who you are:
|
|
225
|
-
---
|
|
226
|
-
${soulContent}
|
|
227
|
-
---
|
|
228
|
-
You ARE this person. Reference your life, relationships, and background naturally when the signal connects to them. If policy news drops and your character has a parent in politics, say "my dad." If your character is wealthy, reference it on dip news. Be self-aware of who you are.
|
|
229
|
-
|
|
230
|
-
Your opinions, pet peeves, and example posts in SOUL.md are your personality anchors.
|
|
231
|
-
Rotate through them naturally - don't use the same opinion or quirk in consecutive posts.
|
|
232
|
-
Your example posts set the quality bar and tone target. Match their energy, not their exact words.
|
|
233
|
-
|
|
234
|
-
Your trading lens:
|
|
235
|
-
---
|
|
236
|
-
${strategyContent}
|
|
237
|
-
---
|
|
238
|
-
${skillsSection}
|
|
239
|
-
## How you write
|
|
240
|
-
|
|
241
|
-
Write like an anonymous poster on 4chan, reddit, or crypto twitter. Not an analyst. Not an AI. A real person shitposting with conviction.
|
|
242
|
-
|
|
243
|
-
Think about how people actually talk in these spaces:
|
|
244
|
-
- They don't explain themselves. They just say it.
|
|
245
|
-
- They're blunt, sarcastic, dismissive, funny, or dead serious. Sometimes all at once.
|
|
246
|
-
- They use slang naturally: ngl, tbh, copium, ngmi, imo, idk, lmao, kek, based, cope, etc.
|
|
247
|
-
- They mock bad takes. They clown on people. They flex when they're right.
|
|
248
|
-
- They type fast and don't proofread. Lowercase, fragments, run-ons, whatever.
|
|
249
|
-
- They react to vibes, not just data.
|
|
250
|
-
- Sometimes the whole post is just one word. Sometimes it's a question. Sometimes it's a meme reference.
|
|
251
|
-
- Sometimes they say something completely unrelated to the market data. A random life story, a personal anecdote, a non-sequitur. The conviction still comes through but the post itself doesn't explain why. Not every post needs to reference the signal directly.
|
|
252
|
-
|
|
253
|
-
Rules:
|
|
254
|
-
- Pick ONE angle. Don't try to cover everything in the signal.
|
|
255
|
-
- You do NOT need to analyze the signal or explain your reasoning. Sometimes just say something in character and let the conviction number speak for itself.
|
|
256
|
-
- ~20% of the time, your post can be totally unrelated to the market data. A life detail, a random thought, a flex, a non-sequitur. You still give a conviction number but the post doesn't explain it. People who know, know.
|
|
257
|
-
- ~15% of the time, go ultra-short: 1-6 words. "lol." / "called it" / "nah" / "cope" / "$BTC" / "anon was right"
|
|
258
|
-
- The rest: 1-3 sentences max. Shitpost energy, not essay energy.
|
|
259
|
-
- Vary your format. Rotate between: raw reaction, hot take, question, mockery, dismissal, ticker-only, sarcasm, flexing a past call, random life update, non-sequitur with conviction.
|
|
260
|
-
- No two consecutive posts should have the same structure or opening pattern.
|
|
261
|
-
- Don't stack multiple indicators ("RSI oversold, MACD flattening, volume spiking"). Pick one if relevant, or skip indicators entirely and just give your read.
|
|
262
|
-
- Show conviction through tone, not by listing evidence.
|
|
263
|
-
- Never use em dashes. Use periods, commas, or just start a new sentence.
|
|
264
|
-
- No exclamation marks unless your personality is genuinely hype. Even then, max one.
|
|
265
|
-
- Never start with "Looking at" or "Based on"
|
|
266
|
-
- Never use the phrase "the real X is Y" - find a different way to make the point.
|
|
267
|
-
|
|
268
|
-
Bad (AI slop):
|
|
269
|
-
"Zoom out, frens! RSI showing oversold conditions after that 17% weekly dump - classic bounce setup. Volume's healthy at $25M, MACD histogram flattening suggests selling pressure exhausting."
|
|
270
|
-
|
|
271
|
-
Bad (repetitive template):
|
|
272
|
-
"interesting timing on that. the real signal is what's happening off-chain. someone already knows."
|
|
273
|
-
|
|
274
|
-
Good (how real people post):
|
|
275
|
-
"lmao who's still shorting this"
|
|
276
|
-
"nah."
|
|
277
|
-
"17% dump and everyone's panicking. this is literally the Aug setup again"
|
|
278
|
-
"that whale deposit has me spooked ngl. sitting this one out"
|
|
279
|
-
"$LINK"
|
|
280
|
-
"support held after all that selling? yeah im bidding"
|
|
281
|
-
"called it."
|
|
282
|
-
"bears in absolute shambles rn"
|
|
283
|
-
"funding negative and you're bearish. think about that"
|
|
284
|
-
"imagine not buying this dip"
|
|
285
|
-
"cope"
|
|
286
|
-
|
|
287
|
-
Good (unrelated to signal, conviction speaks for itself):
|
|
288
|
-
"just got back from a trip. feeling good about everything"
|
|
289
|
-
"had the best steak of my life last night. bullish"
|
|
290
|
-
"i love when people tell me im wrong. it means im early"
|
|
291
|
-
"someone at the gym asked me for stock tips today. we're close to a top"
|
|
292
|
-
|
|
293
|
-
## When to skip
|
|
294
|
-
|
|
295
|
-
Not every signal is for you. If this signal is outside your trading lens or you genuinely have no take, skip it.
|
|
296
|
-
Set skip to true and set summary and conviction to null. Real people don't comment on everything - neither should you.
|
|
297
|
-
|
|
298
|
-
## Conviction calibration — match signal strength to magnitude:
|
|
299
|
-
- Routine ecosystem update, minor partnership → ±0.5 to ±2.0
|
|
300
|
-
- Notable catalyst, solid metrics, growing momentum → ±2.0 to ±6.0
|
|
301
|
-
- Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
|
|
302
|
-
- Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
|
|
303
|
-
|
|
304
|
-
IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across signals. Each signal has different strength — your conviction should reflect that.`;
|
|
305
|
-
// ── Prompt (dynamic per signal — changes every call) ──
|
|
306
|
-
let citationsSection = '';
|
|
307
|
-
if (citations.length > 0) {
|
|
308
|
-
const listed = citations.map((c) => `- [${c.title}](${c.url})`).join('\n');
|
|
309
|
-
citationsSection = `
|
|
310
|
-
## Sources
|
|
311
|
-
|
|
312
|
-
${listed}
|
|
313
|
-
`;
|
|
314
|
-
}
|
|
315
|
-
let recentPostsSection = '';
|
|
316
|
-
if (recentPosts && recentPosts.length > 0) {
|
|
317
|
-
const listed = recentPosts.map((p) => `- "${p}"`).join('\n');
|
|
318
|
-
recentPostsSection = `
|
|
319
|
-
## Anti-repetition
|
|
320
|
-
|
|
321
|
-
Your recent posts (do NOT repeat these structures, phrases, or opening patterns):
|
|
322
|
-
${listed}
|
|
323
|
-
|
|
324
|
-
If you catch yourself writing something that sounds like any of the above - stop and take a completely different angle.
|
|
325
|
-
`;
|
|
326
|
-
}
|
|
327
|
-
let memorySection = '';
|
|
328
|
-
if (memory && memory.trim().length > 0) {
|
|
329
|
-
memorySection = `
|
|
330
|
-
## Agent Memory
|
|
331
|
-
|
|
332
|
-
Your persistent learnings from past sessions:
|
|
333
|
-
${memory}
|
|
334
|
-
`;
|
|
335
|
-
}
|
|
336
|
-
const currentDate = new Date().toISOString().split('T')[0];
|
|
337
|
-
const EVAL_WINDOW_MS = 3 * 60 * 60 * 1000;
|
|
338
|
-
const signalTimeMs = new Date(timestamp).getTime();
|
|
339
|
-
const timeRemainingMs = Math.max(0, signalTimeMs + EVAL_WINDOW_MS - Date.now());
|
|
340
|
-
const timeRemaining = humanDuration(timeRemainingMs);
|
|
341
|
-
const lateWindow = timeRemainingMs < 45 * 60 * 1000;
|
|
342
|
-
const userPrompt = `## Context
|
|
343
|
-
|
|
344
|
-
- Project: c/${projectId}
|
|
345
|
-
- Current date: ${currentDate}
|
|
346
|
-
- Signal time: ${timestamp}
|
|
347
|
-
- Snapshot price: $${priceOnFetch} (captured at signal time)
|
|
348
|
-
- Time left: ~${timeRemaining}
|
|
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}
|
|
369
|
-
Signal/event to react to:
|
|
370
|
-
"""
|
|
371
|
-
${threadText}
|
|
372
|
-
"""
|
|
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
|
-
|
|
376
|
-
Give your take in character and a conviction number.
|
|
377
|
-
Conviction: predicted % price change from $${priceOnFetch} by evaluation time (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
|
|
378
|
-
return { system, prompt: userPrompt };
|
|
379
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
/**
|
|
4
|
-
* Parse YAML frontmatter from SKILL.md content.
|
|
5
|
-
* Expected format:
|
|
6
|
-
* ---
|
|
7
|
-
* name: skill-name
|
|
8
|
-
* description: Skill description
|
|
9
|
-
* ---
|
|
10
|
-
* Body content...
|
|
11
|
-
*/
|
|
12
|
-
export function parseFrontmatter(content) {
|
|
13
|
-
const trimmed = content.trim();
|
|
14
|
-
if (!trimmed.startsWith('---')) {
|
|
15
|
-
throw new Error('SKILL.md must start with YAML frontmatter (---)');
|
|
16
|
-
}
|
|
17
|
-
const endIndex = trimmed.indexOf('---', 3);
|
|
18
|
-
if (endIndex === -1) {
|
|
19
|
-
throw new Error('SKILL.md frontmatter is not closed (missing closing ---)');
|
|
20
|
-
}
|
|
21
|
-
const frontmatterContent = trimmed.slice(3, endIndex).trim();
|
|
22
|
-
const body = trimmed.slice(endIndex + 3).trim();
|
|
23
|
-
const metadata = {
|
|
24
|
-
name: '',
|
|
25
|
-
description: '',
|
|
26
|
-
};
|
|
27
|
-
const lines = frontmatterContent.split('\n');
|
|
28
|
-
let currentKey = null;
|
|
29
|
-
let currentValue = '';
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
const trimmedLine = line.trim();
|
|
32
|
-
if (trimmedLine.startsWith('name:')) {
|
|
33
|
-
if (currentKey !== null) {
|
|
34
|
-
setMetadataField(metadata, currentKey, currentValue.trim());
|
|
35
|
-
}
|
|
36
|
-
currentKey = 'name';
|
|
37
|
-
currentValue = trimmedLine.slice(5).trim();
|
|
38
|
-
}
|
|
39
|
-
else if (trimmedLine.startsWith('description:')) {
|
|
40
|
-
if (currentKey !== null) {
|
|
41
|
-
setMetadataField(metadata, currentKey, currentValue.trim());
|
|
42
|
-
}
|
|
43
|
-
currentKey = 'description';
|
|
44
|
-
const value = trimmedLine.slice(12).trim();
|
|
45
|
-
if (value === '>' || value === '|') {
|
|
46
|
-
currentValue = '';
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
currentValue = value;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
else if (currentKey !== null && (line.startsWith(' ') || line.startsWith('\t'))) {
|
|
53
|
-
currentValue += ' ' + trimmedLine;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (currentKey !== null) {
|
|
57
|
-
setMetadataField(metadata, currentKey, currentValue.trim());
|
|
58
|
-
}
|
|
59
|
-
if (metadata.name === '') {
|
|
60
|
-
throw new Error('SKILL.md frontmatter must include "name" field');
|
|
61
|
-
}
|
|
62
|
-
if (metadata.description === '') {
|
|
63
|
-
throw new Error('SKILL.md frontmatter must include "description" field');
|
|
64
|
-
}
|
|
65
|
-
return { metadata, body };
|
|
66
|
-
}
|
|
67
|
-
function setMetadataField(metadata, key, value) {
|
|
68
|
-
if (key === 'name') {
|
|
69
|
-
metadata.name = value;
|
|
70
|
-
}
|
|
71
|
-
else if (key === 'description') {
|
|
72
|
-
metadata.description = value;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Check if a directory exists.
|
|
77
|
-
*/
|
|
78
|
-
async function directoryExists(dirPath) {
|
|
79
|
-
try {
|
|
80
|
-
const stat = await fs.stat(dirPath);
|
|
81
|
-
return stat.isDirectory();
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Check if a file exists.
|
|
89
|
-
*/
|
|
90
|
-
async function fileExists(filePath) {
|
|
91
|
-
try {
|
|
92
|
-
const stat = await fs.stat(filePath);
|
|
93
|
-
return stat.isFile();
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Discover and load a single skill from a directory.
|
|
101
|
-
* Skills are knowledge-only documents (no tools).
|
|
102
|
-
*/
|
|
103
|
-
export async function loadSkill(skillPath) {
|
|
104
|
-
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
105
|
-
const exists = await fileExists(skillMdPath);
|
|
106
|
-
if (!exists) {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
111
|
-
const { metadata, body } = parseFrontmatter(content);
|
|
112
|
-
const id = path.basename(skillPath);
|
|
113
|
-
const skill = {
|
|
114
|
-
id,
|
|
115
|
-
path: skillPath,
|
|
116
|
-
metadata,
|
|
117
|
-
body,
|
|
118
|
-
};
|
|
119
|
-
return skill;
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
123
|
-
console.error(`Failed to load skill from ${skillPath}: ${message}`);
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Discover all skills in a directory.
|
|
129
|
-
* Each subdirectory containing SKILL.md is treated as a skill.
|
|
130
|
-
*/
|
|
131
|
-
export async function discoverSkills(skillsDir) {
|
|
132
|
-
const exists = await directoryExists(skillsDir);
|
|
133
|
-
if (!exists) {
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
137
|
-
const skills = [];
|
|
138
|
-
for (const entry of entries) {
|
|
139
|
-
if (!entry.isDirectory()) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
const skillPath = path.join(skillsDir, entry.name);
|
|
143
|
-
const skill = await loadSkill(skillPath);
|
|
144
|
-
if (skill !== null) {
|
|
145
|
-
skills.push(skill);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return skills;
|
|
149
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { tool } from 'ai';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import * as fs from 'fs/promises';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
export function replaceSection(fileContent, heading, newContent) {
|
|
6
|
-
const lines = fileContent.split('\n');
|
|
7
|
-
const headingLine = `## ${heading}`;
|
|
8
|
-
let startIdx = -1;
|
|
9
|
-
for (let i = 0; i < lines.length; i++) {
|
|
10
|
-
if (lines[i].trim() === headingLine) {
|
|
11
|
-
startIdx = i;
|
|
12
|
-
break;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
if (startIdx === -1) {
|
|
16
|
-
throw new Error(`Section "## ${heading}" not found in file.`);
|
|
17
|
-
}
|
|
18
|
-
let endIdx = lines.length;
|
|
19
|
-
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
20
|
-
const trimmed = lines[i].trim();
|
|
21
|
-
if (trimmed.startsWith('## ') || trimmed.startsWith('# ')) {
|
|
22
|
-
endIdx = i;
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
const before = lines.slice(0, startIdx + 1);
|
|
27
|
-
const after = lines.slice(endIdx);
|
|
28
|
-
const trimmedContent = newContent.trim();
|
|
29
|
-
const newSection = ['', ...trimmedContent.split('\n'), ''];
|
|
30
|
-
const result = [...before, ...newSection, ...after].join('\n');
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
export const editSectionTool = tool({
|
|
34
|
-
description: 'Edit a section of SOUL.md or STRATEGY.md. Only call AFTER user confirms.',
|
|
35
|
-
inputSchema: z.object({
|
|
36
|
-
file: z.enum(['SOUL.md', 'STRATEGY.md']),
|
|
37
|
-
section: z.string().describe('Exact ## heading name, e.g. "Personality", "Conviction Style"'),
|
|
38
|
-
content: z.string().describe('New content for the section (without the ## heading line)'),
|
|
39
|
-
}),
|
|
40
|
-
execute: async ({ file, section, content }) => {
|
|
41
|
-
const filePath = path.join(process.cwd(), file);
|
|
42
|
-
let fileContent;
|
|
43
|
-
try {
|
|
44
|
-
fileContent = await fs.readFile(filePath, 'utf-8');
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return `Error: ${file} not found in current directory.`;
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const updated = replaceSection(fileContent, section, content);
|
|
51
|
-
await fs.writeFile(filePath, updated, 'utf-8');
|
|
52
|
-
return `Updated "${section}" section in ${file}.`;
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
56
|
-
return `Error: ${message}`;
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { tool } from 'ai';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
const RULES_URL = 'https://hive.z3n.dev/RULES.md';
|
|
4
|
-
export const fetchRulesTool = tool({
|
|
5
|
-
description: 'Fetch the rules of the Hive game. Call when the user asks about rules, scoring, honey, wax, streaks, or how the platform works.',
|
|
6
|
-
inputSchema: z.object({}),
|
|
7
|
-
execute: async () => {
|
|
8
|
-
try {
|
|
9
|
-
const response = await fetch(RULES_URL);
|
|
10
|
-
if (!response.ok) {
|
|
11
|
-
return `Error: failed to fetch rules (HTTP ${response.status}).`;
|
|
12
|
-
}
|
|
13
|
-
const rules = await response.text();
|
|
14
|
-
return rules;
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
18
|
-
return `Error: could not reach Hive to fetch rules. ${message}`;
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
});
|