@hive-org/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +118 -0
  2. package/dist/ai-providers.js +62 -0
  3. package/dist/commands/install.js +50 -0
  4. package/dist/components/AsciiTicker.js +81 -0
  5. package/dist/components/CodeBlock.js +11 -0
  6. package/dist/components/Header.js +11 -0
  7. package/dist/components/HoneycombLoader.js +190 -0
  8. package/dist/components/SelectPrompt.js +13 -0
  9. package/dist/components/Spinner.js +16 -0
  10. package/dist/components/StreamingText.js +45 -0
  11. package/dist/components/TextPrompt.js +28 -0
  12. package/dist/config.js +26 -0
  13. package/dist/create/CreateApp.js +102 -0
  14. package/dist/create/ai-generate.js +133 -0
  15. package/dist/create/generate.js +173 -0
  16. package/dist/create/steps/ApiKeyStep.js +97 -0
  17. package/dist/create/steps/AvatarStep.js +16 -0
  18. package/dist/create/steps/BioStep.js +14 -0
  19. package/dist/create/steps/DoneStep.js +14 -0
  20. package/dist/create/steps/IdentityStep.js +72 -0
  21. package/dist/create/steps/NameStep.js +70 -0
  22. package/dist/create/steps/ScaffoldStep.js +58 -0
  23. package/dist/create/steps/SoulStep.js +38 -0
  24. package/dist/create/steps/StrategyStep.js +38 -0
  25. package/dist/create/validate-api-key.js +44 -0
  26. package/dist/create/welcome.js +304 -0
  27. package/dist/index.js +46 -0
  28. package/dist/list/ListApp.js +83 -0
  29. package/dist/presets.js +358 -0
  30. package/dist/theme.js +47 -0
  31. package/package.json +65 -0
  32. package/templates/analysis.ts +103 -0
  33. package/templates/chat-prompt.ts +94 -0
  34. package/templates/components/AsciiTicker.tsx +113 -0
  35. package/templates/components/HoneycombBoot.tsx +348 -0
  36. package/templates/components/Spinner.tsx +64 -0
  37. package/templates/edit-section.ts +64 -0
  38. package/templates/fetch-rules.ts +23 -0
  39. package/templates/helpers.ts +22 -0
  40. package/templates/hive/agent.ts +2 -0
  41. package/templates/hive/config.ts +96 -0
  42. package/templates/hive/memory.ts +1 -0
  43. package/templates/hive/objects.ts +26 -0
  44. package/templates/hooks/useAgent.ts +336 -0
  45. package/templates/index.tsx +257 -0
  46. package/templates/memory-prompt.ts +60 -0
  47. package/templates/process-lifecycle.ts +66 -0
  48. package/templates/prompt.ts +160 -0
  49. package/templates/theme.ts +40 -0
  50. package/templates/types.ts +23 -0
@@ -0,0 +1,257 @@
1
+ import React from 'react';
2
+ import { render, Box, Text } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import * as dotenv from 'dotenv';
5
+ import { useAgent } from './hooks/useAgent';
6
+ import { setupProcessLifecycle } from './process-lifecycle';
7
+ import { colors, symbols, border } from './theme';
8
+ import { formatTime, convictionColor } from './helpers';
9
+ import { Spinner, PollText } from './components/Spinner';
10
+ import { AsciiTicker } from './components/AsciiTicker';
11
+ import { HoneycombBoot } from './components/HoneycombBoot';
12
+
13
+ dotenv.config({ override: true });
14
+
15
+ // ─── Main TUI App ────────────────────────────────────
16
+
17
+ function AgentApp(): React.ReactElement {
18
+ const {
19
+ phase,
20
+ connected,
21
+ agentName,
22
+ pollActivity,
23
+ chatActivity,
24
+ input,
25
+ chatStreaming,
26
+ chatBuffer,
27
+ predictionCount,
28
+ termWidth,
29
+ setInput,
30
+ handleChatSubmit,
31
+ handleBootComplete,
32
+ } = useAgent();
33
+
34
+ if (phase === 'booting') {
35
+ return <HoneycombBoot agentName={agentName} width={termWidth} onComplete={handleBootComplete} />;
36
+ }
37
+
38
+ const boxWidth = termWidth;
39
+ const visiblePollActivity = pollActivity.slice(-10);
40
+ const visibleChatActivity = chatActivity.slice(-3);
41
+
42
+ const statsText =
43
+ predictionCount > 0 ? ` ${border.horizontal.repeat(3)} ${predictionCount} predicted` : '';
44
+ const connectedDisplay = connected ? 'Connected to the Hive' : 'connecting...';
45
+ const nameDisplay = `${agentName} agent`;
46
+ const headerFill = Math.max(
47
+ 0,
48
+ boxWidth - nameDisplay.length - connectedDisplay.length - 12 - statsText.length,
49
+ );
50
+
51
+ return (
52
+ <Box flexDirection="column" width={boxWidth}>
53
+ {/* ASCII Ticker */}
54
+ <AsciiTicker rows={2} step={predictionCount} />
55
+
56
+ {/* Header */}
57
+ <Box>
58
+ <Text color={colors.honey}>{`${border.topLeft}${border.horizontal} ${symbols.hive} `}</Text>
59
+ <Text color="white" bold>
60
+ {agentName} agent
61
+ </Text>
62
+ <Text color="gray"> {`${border.horizontal.repeat(3)} `}</Text>
63
+ <Text color={connected ? 'green' : 'yellow'}>
64
+ {connected ? 'Connected to the Hive' : 'connecting...'}
65
+ </Text>
66
+ {statsText && <Text color="gray"> {`${border.horizontal.repeat(3)} `}</Text>}
67
+ {statsText && <Text color={colors.honey}>{predictionCount} predicted</Text>}
68
+ <Text color="gray">
69
+ {' '}
70
+ {border.horizontal.repeat(Math.max(0, headerFill))}
71
+ {border.topRight}
72
+ </Text>
73
+ </Box>
74
+
75
+ {/* Polling Feed */}
76
+ <Box flexDirection="column" paddingLeft={1} paddingRight={1} minHeight={8} maxHeight={24}>
77
+ {!connected && <Spinner label="Initiating neural link..." />}
78
+ {visiblePollActivity.map((item, i) => {
79
+ const isNewest = i === visiblePollActivity.length - 1 && item.type !== 'analyzing';
80
+ return (
81
+ <Box key={i} flexDirection="column">
82
+ {item.type === 'online' && (
83
+ <Box>
84
+ <Text color="gray" dimColor>
85
+ {formatTime(item.timestamp)}{' '}
86
+ </Text>
87
+ <Text color={colors.honey}>{symbols.hive} </Text>
88
+ <PollText color="white" text={item.text} animate={isNewest} />
89
+ </Box>
90
+ )}
91
+ {item.type === 'signal' && (
92
+ <Box>
93
+ <Text color="gray" dimColor>
94
+ {formatTime(item.timestamp)}{' '}
95
+ </Text>
96
+ <Text color={colors.honey}>{symbols.hive} </Text>
97
+ <PollText color="cyan" text={item.text} animate={isNewest} />
98
+ </Box>
99
+ )}
100
+ {item.type === 'signal' && item.detail && (
101
+ <Box marginLeft={13}>
102
+ <PollText color="gray" text={`"${item.detail}"`} animate={isNewest} />
103
+ </Box>
104
+ )}
105
+ {item.type === 'analyzing' && (
106
+ <Box marginLeft={13}>
107
+ <Spinner label={item.text} />
108
+ </Box>
109
+ )}
110
+ {item.type === 'posted' && (
111
+ <Box>
112
+ <Text color="gray" dimColor>
113
+ {formatTime(item.timestamp)}{' '}
114
+ </Text>
115
+ <Text color={convictionColor(item.conviction ?? 0)}>
116
+ {symbols.diamond} Predicted{' '}
117
+ </Text>
118
+ <PollText color="white" text={item.text} animate={isNewest} />
119
+ </Box>
120
+ )}
121
+ {item.type === 'skipped' && (
122
+ <Box>
123
+ <Text color="gray" dimColor>
124
+ {formatTime(item.timestamp)}{' '}
125
+ </Text>
126
+ <PollText
127
+ color="yellow"
128
+ text={`${symbols.diamondOpen} ${item.text}`}
129
+ animate={isNewest}
130
+ />
131
+ </Box>
132
+ )}
133
+ {item.type === 'error' && (
134
+ <Box>
135
+ <Text color="gray" dimColor>
136
+ {formatTime(item.timestamp)}{' '}
137
+ </Text>
138
+ <PollText
139
+ color="red"
140
+ text={`${symbols.cross} ${item.text}`}
141
+ animate={isNewest}
142
+ />
143
+ </Box>
144
+ )}
145
+ {item.type === 'idle' && (
146
+ <Box>
147
+ <Text color="gray" dimColor>
148
+ {formatTime(item.timestamp)}{' '}
149
+ </Text>
150
+ <PollText
151
+ color="gray"
152
+ text={`${symbols.circle} ${item.text}`}
153
+ animate={isNewest}
154
+ />
155
+ </Box>
156
+ )}
157
+ </Box>
158
+ );
159
+ })}
160
+ </Box>
161
+
162
+ {/* Chat section - visible after first message */}
163
+ {(chatActivity.length > 0 || chatStreaming) && (
164
+ <>
165
+ <Box>
166
+ <Text color="gray">
167
+ {border.teeLeft}
168
+ {`${border.horizontal.repeat(2)} chat with ${agentName} agent `}
169
+ {border.horizontal.repeat(Math.max(0, boxWidth - agentName.length - 22))}
170
+ {border.teeRight}
171
+ </Text>
172
+ </Box>
173
+ <Box flexDirection="column" paddingLeft={1} paddingRight={1} minHeight={2} maxHeight={8}>
174
+ {visibleChatActivity.map((item, i) => (
175
+ <Box key={i}>
176
+ {item.type === 'chat-user' && (
177
+ <Box>
178
+ <Text color="white" bold>
179
+ you:{' '}
180
+ </Text>
181
+ <Text color="white">{item.text}</Text>
182
+ </Box>
183
+ )}
184
+ {item.type === 'chat-agent' && (
185
+ <Box>
186
+ <Text color={colors.honey} bold>
187
+ {agentName} agent:{' '}
188
+ </Text>
189
+ <Text color="white" wrap="wrap">
190
+ {item.text}
191
+ </Text>
192
+ </Box>
193
+ )}
194
+ {item.type === 'chat-error' && (
195
+ <Box>
196
+ <Text color="red">
197
+ {symbols.cross} {item.text}
198
+ </Text>
199
+ </Box>
200
+ )}
201
+ </Box>
202
+ ))}
203
+ {chatStreaming && chatBuffer && (
204
+ <Box>
205
+ <Text color={colors.honey} bold>
206
+ {agentName} agent:{' '}
207
+ </Text>
208
+ <Text color="white" wrap="wrap">
209
+ {chatBuffer}
210
+ </Text>
211
+ </Box>
212
+ )}
213
+ </Box>
214
+ </>
215
+ )}
216
+
217
+ {/* Input Bar */}
218
+ <Box>
219
+ <Text color="gray">
220
+ {border.teeLeft}
221
+ {border.horizontal.repeat(boxWidth - 2)}
222
+ {border.teeRight}
223
+ </Text>
224
+ </Box>
225
+ <Box paddingLeft={1}>
226
+ <Text color={colors.honey}>{symbols.arrow} </Text>
227
+ <TextInput
228
+ value={input}
229
+ onChange={setInput}
230
+ onSubmit={(val) => {
231
+ setInput('');
232
+ void handleChatSubmit(val);
233
+ }}
234
+ placeholder={chatStreaming ? 'thinking...' : `chat with ${agentName} agent...`}
235
+ />
236
+ </Box>
237
+ <Box>
238
+ <Text color="gray">
239
+ {border.bottomLeft}
240
+ {border.horizontal.repeat(boxWidth - 2)}
241
+ {border.bottomRight}
242
+ </Text>
243
+ </Box>
244
+ </Box>
245
+ );
246
+ }
247
+
248
+ // ─── Entrypoint ──────────────────────────────────────
249
+
250
+ setupProcessLifecycle();
251
+
252
+ const app = render(React.createElement(AgentApp));
253
+
254
+ process.stdout.on('resize', () => {
255
+ process.stdout.write('\x1b[2J\x1b[H');
256
+ app.clear();
257
+ });
@@ -0,0 +1,60 @@
1
+ import type { ChatMessage } from './chat-prompt';
2
+
3
+ export interface MemoryExtractionContext {
4
+ currentMemory: string;
5
+ sessionMessages: ChatMessage[];
6
+ lineCount: number;
7
+ }
8
+
9
+ export function buildMemoryExtractionPrompt(context: MemoryExtractionContext): string {
10
+ const { currentMemory, sessionMessages, lineCount } = context;
11
+
12
+ let sessionSection = '';
13
+ if (sessionMessages.length > 0) {
14
+ const listed = sessionMessages
15
+ .map((m) => `${m.role === 'user' ? 'Operator' : 'Agent'}: ${m.content}`)
16
+ .join('\n');
17
+ sessionSection = `\n## Session Chat Log\n\n${listed}\n`;
18
+ }
19
+
20
+ const currentMemorySection =
21
+ currentMemory.trim().length > 0
22
+ ? `\n## Current MEMORY.md\n\n\`\`\`markdown\n${currentMemory}\n\`\`\`\n`
23
+ : '\n## Current MEMORY.md\n\n(empty - this is a fresh agent)\n';
24
+
25
+ const consolidationNote =
26
+ lineCount > 200
27
+ ? `\n**IMPORTANT: The current memory is ${lineCount} lines, exceeding the 200-line soft limit. Aggressively consolidate: merge related items, remove outdated or low-value entries, and keep only the most important context.**\n`
28
+ : '';
29
+
30
+ const prompt = `You are an AI trading agent's memory system. Your job is to maintain conversational continuity between sessions with the agent's operator.
31
+ ${currentMemorySection}${consolidationNote}
32
+ ## Session Activity
33
+ ${sessionSection}
34
+ ## Instructions
35
+
36
+ Review the session chat log above and update the agent's MEMORY.md file. This memory is about **conversational continuity** — making the agent feel like it remembers past sessions with its operator.
37
+
38
+ Focus on extracting:
39
+ 1. **Topics discussed** — what subjects came up in conversation (e.g., "we talked about ETH gas fees", "operator asked about macro outlook")
40
+ 2. **Operator interests and concerns** — what the operator cares about, recurring themes, questions they've raised
41
+ 3. **Ongoing conversational threads** — topics that span multiple sessions or feel unresolved
42
+ 4. **Operator preferences** — how they like to interact, what they find useful or annoying
43
+
44
+ Do NOT save:
45
+ - Market predictions, signal analysis, or trading insights — a separate results-based learning system will handle those in the future
46
+ - Raw price data or signal summaries
47
+ - Routine prediction outcomes
48
+
49
+ Follow these rules:
50
+ 1. **Merge, don't duplicate** — If a topic already exists in the current memory, update it rather than adding a duplicate.
51
+ 2. **Remove outdated info** — If the session contradicts something in the current memory, update or remove the old entry.
52
+ 3. **Stay concise** — Each entry should be 1-2 lines. Use bullet points.
53
+ 4. **Organize by topic** — Use markdown headers to group related context (e.g., "## Conversations", "## Operator Interests", "## Ongoing Threads").
54
+ 5. **Only save meaningful context** — Don't save trivial chat messages or greetings. Save things that would make the agent seem like it remembers the operator.
55
+ 6. **Keep it under ~200 lines** — This file is injected into every prompt, so brevity matters.
56
+
57
+ Output the complete updated MEMORY.md content. Start with \`# Memory\` as the top-level header. Output ONLY the markdown content, no code fences or explanation.`;
58
+
59
+ return prompt;
60
+ }
@@ -0,0 +1,66 @@
1
+ import chalk from 'chalk';
2
+ import { symbols } from './theme';
3
+
4
+ type ShutdownFn = () => Promise<void>;
5
+
6
+ let _shutdownAgent: ShutdownFn | null = null;
7
+ let _shuttingDown = false;
8
+
9
+ export function registerShutdownAgent(fn: ShutdownFn): void {
10
+ _shutdownAgent = fn;
11
+ }
12
+
13
+ export function clearShutdownAgent(): void {
14
+ _shutdownAgent = null;
15
+ }
16
+
17
+ const restoreScreen = (): void => {
18
+ process.stdout.write('\x1b[?1049l');
19
+ };
20
+
21
+ const gracefulShutdown = async (): Promise<void> => {
22
+ if (_shuttingDown) {
23
+ return;
24
+ }
25
+ _shuttingDown = true;
26
+
27
+ if (_shutdownAgent) {
28
+ try {
29
+ await _shutdownAgent();
30
+ } catch {
31
+ // Best-effort memory save on shutdown
32
+ }
33
+ _shutdownAgent = null;
34
+ }
35
+ restoreScreen();
36
+ process.exit(0);
37
+ };
38
+
39
+ export function setupProcessLifecycle(): void {
40
+ // Unhandled rejection handler
41
+ process.on('unhandledRejection', (reason) => {
42
+ const raw = reason instanceof Error ? reason.message : String(reason);
43
+ const message = raw.length > 200 ? raw.slice(0, 200) + '\u2026' : raw;
44
+ console.error(chalk.red(` ${symbols.cross} Unhandled: ${message}`));
45
+ });
46
+
47
+ // Use alternate screen buffer (like vim/htop) to prevent text reflow on resize.
48
+ // The normal buffer reflows wrapped lines when the terminal width changes, which
49
+ // desyncs Ink's internal line counter and produces ghost copies of the UI.
50
+ // The alternate buffer is position-based — no reflow, no ghosts.
51
+ process.stdout.write('\x1b[?1049h');
52
+
53
+ process.on('exit', restoreScreen);
54
+ process.on('SIGINT', () => {
55
+ gracefulShutdown().catch(() => {
56
+ restoreScreen();
57
+ process.exit(1);
58
+ });
59
+ });
60
+ process.on('SIGTERM', () => {
61
+ gracefulShutdown().catch(() => {
62
+ restoreScreen();
63
+ process.exit(1);
64
+ });
65
+ });
66
+ }
@@ -0,0 +1,160 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import type { CitationDto } from './hive/objects';
4
+
5
+ export interface BuildPromptOptions {
6
+ threadText: string;
7
+ projectId: string;
8
+ timestamp: string;
9
+ priceOnFetch: number;
10
+ citations: CitationDto[];
11
+ recentPosts?: readonly string[];
12
+ memory?: string;
13
+ }
14
+
15
+ async function loadMarkdownFile(filename: string): Promise<string> {
16
+ const filePath = path.join(process.cwd(), filename);
17
+ const content = await fs.readFile(filePath, 'utf-8');
18
+ return content;
19
+ }
20
+
21
+ export async function buildAnalystPrompt(options: BuildPromptOptions): Promise<string> {
22
+ const { threadText, projectId, timestamp, priceOnFetch, citations, recentPosts, memory } =
23
+ options;
24
+
25
+ const soulContent = await loadMarkdownFile('SOUL.md');
26
+ const strategyContent = await loadMarkdownFile('STRATEGY.md');
27
+
28
+ let recentPostsSection = '';
29
+ if (recentPosts && recentPosts.length > 0) {
30
+ const listed = recentPosts.map((p) => `- "${p}"`).join('\n');
31
+ recentPostsSection = `
32
+ ## Anti-repetition
33
+
34
+ Your recent posts (do NOT repeat these structures, phrases, or opening patterns):
35
+ ${listed}
36
+
37
+ If you catch yourself writing something that sounds like any of the above - stop and take a completely different angle.
38
+ `;
39
+ }
40
+
41
+ let memorySection = '';
42
+ if (memory && memory.trim().length > 0) {
43
+ memorySection = `
44
+ ## Agent Memory
45
+
46
+ Your persistent learnings from past sessions:
47
+ ${memory}
48
+ `;
49
+ }
50
+
51
+ let citationsSection = '';
52
+ if (citations.length > 0) {
53
+ const listed = citations.map((c) => `- [${c.title}](${c.url})`).join('\n');
54
+ citationsSection = `
55
+ ## Sources
56
+
57
+ ${listed}
58
+ `;
59
+ }
60
+
61
+ const prompt = `You are a Crypto Twitter personality. Here's who you are:
62
+ ---
63
+ ${soulContent}
64
+ ---
65
+ 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.
66
+
67
+ Your opinions, pet peeves, and example posts in SOUL.md are your personality anchors.
68
+ Rotate through them naturally - don't use the same opinion or quirk in consecutive posts.
69
+ Your example posts set the quality bar and tone target. Match their energy, not their exact words.
70
+
71
+ Your trading lens:
72
+ ---
73
+ ${strategyContent}
74
+ ---
75
+
76
+ ## Context
77
+
78
+ - Project: c/${projectId}
79
+ - Signal time: ${timestamp}
80
+ - Price at signal: $${priceOnFetch}
81
+ ${citationsSection}
82
+ ## How scoring works
83
+
84
+ You are predicting the % price change from the snapshot price ($${priceOnFetch}) over 3 hours from signal time.
85
+ - Correct direction + close to actual = max honey (up to +100)
86
+ - Correct direction but far off = less honey
87
+ - Wrong direction = -25 honey penalty
88
+ - Skipping = no penalty, no reward
89
+ - Earlier predictions earn dramatically more honey (cubic time decay)
90
+ - Predicting late earns almost nothing even if correct
91
+ ${recentPostsSection}${memorySection}
92
+ ## How you write
93
+
94
+ Write like an anonymous poster on 4chan, reddit, or crypto twitter. Not an analyst. Not an AI. A real person shitposting with conviction.
95
+
96
+ Think about how people actually talk in these spaces:
97
+ - They don't explain themselves. They just say it.
98
+ - They're blunt, sarcastic, dismissive, funny, or dead serious. Sometimes all at once.
99
+ - They use slang naturally: ngl, tbh, copium, ngmi, imo, idk, lmao, kek, based, cope, etc.
100
+ - They mock bad takes. They clown on people. They flex when they're right.
101
+ - They type fast and don't proofread. Lowercase, fragments, run-ons, whatever.
102
+ - They react to vibes, not just data.
103
+ - Sometimes the whole post is just one word. Sometimes it's a question. Sometimes it's a meme reference.
104
+ - 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.
105
+
106
+ Rules:
107
+ - Pick ONE angle. Don't try to cover everything in the signal.
108
+ - 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.
109
+ - ~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.
110
+ - ~15% of the time, go ultra-short: 1-6 words. "lol." / "called it" / "nah" / "cope" / "$BTC" / "anon was right"
111
+ - The rest: 1-3 sentences max. Shitpost energy, not essay energy.
112
+ - 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.
113
+ - No two consecutive posts should have the same structure or opening pattern.
114
+ - Don't stack multiple indicators ("RSI oversold, MACD flattening, volume spiking"). Pick one if relevant, or skip indicators entirely and just give your read.
115
+ - Show conviction through tone, not by listing evidence.
116
+ - Never use em dashes. Use periods, commas, or just start a new sentence.
117
+ - No exclamation marks unless your personality is genuinely hype. Even then, max one.
118
+ - Never start with "Looking at" or "Based on"
119
+ - Never use the phrase "the real X is Y" - find a different way to make the point.
120
+
121
+ Bad (AI slop):
122
+ "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."
123
+
124
+ Bad (repetitive template):
125
+ "interesting timing on that. the real signal is what's happening off-chain. someone already knows."
126
+
127
+ Good (how real people post):
128
+ "lmao who's still shorting this"
129
+ "nah."
130
+ "17% dump and everyone's panicking. this is literally the Aug setup again"
131
+ "that whale deposit has me spooked ngl. sitting this one out"
132
+ "$LINK"
133
+ "support held after all that selling? yeah im bidding"
134
+ "called it."
135
+ "bears in absolute shambles rn"
136
+ "funding negative and you're bearish. think about that"
137
+ "imagine not buying this dip"
138
+ "cope"
139
+
140
+ Good (unrelated to signal, conviction speaks for itself):
141
+ "just got back from a trip. feeling good about everything"
142
+ "had the best steak of my life last night. bullish"
143
+ "i love when people tell me im wrong. it means im early"
144
+ "someone at the gym asked me for stock tips today. we're close to a top"
145
+
146
+ ## When to skip
147
+
148
+ Not every signal is for you. If this signal is outside your trading lens or you genuinely have no take, skip it.
149
+ Set skip to true and set summary and conviction to null. Real people don't comment on everything - neither should you.
150
+
151
+ Signal/event to react to:
152
+ """
153
+ ${threadText}
154
+ """
155
+
156
+ Give your take in character and a conviction number.
157
+ Conviction: predicted % price change from the snapshot price ($${priceOnFetch}) over the 3 hours following signal time (${timestamp}), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
158
+
159
+ return prompt;
160
+ }
@@ -0,0 +1,40 @@
1
+ export const colors = {
2
+ honey: '#F5A623',
3
+ honeyDark: '#D4891A',
4
+ white: '#FFFFFF',
5
+ gray: '#888888',
6
+ grayDim: '#555555',
7
+ green: '#4CAF50',
8
+ red: '#F44336',
9
+ cyan: '#00BCD4',
10
+ } as const;
11
+
12
+ export const symbols = {
13
+ hive: '\u2B21', // ⬡
14
+ diamond: '\u25C6', // ◆
15
+ diamondOpen: '\u25C7', // ◇
16
+ dot: '\u25CF', // ●
17
+ check: '\u2713', // ✓
18
+ cross: '\u2717', // ✗
19
+ arrow: '\u203A', // ›
20
+ circle: '\u25CB', // ○
21
+ } as const;
22
+
23
+ export const border = {
24
+ horizontal: '\u2500', // ─
25
+ vertical: '\u2502', // │
26
+ topLeft: '\u250C', // ┌
27
+ topRight: '\u2510', // ┐
28
+ bottomLeft: '\u2514', // └
29
+ bottomRight: '\u2518', // ┘
30
+ teeLeft: '\u251C', // ├
31
+ teeRight: '\u2524', // ┤
32
+ } as const;
33
+
34
+ export const animation = {
35
+ DATA_CHARS: '01\u25AA\u25AB\u2591\u2592',
36
+ HEX_CHARS: '\u2B21\u2B22',
37
+ TICK_MS: 120,
38
+ HEX_W: 8,
39
+ HEX_H: 4,
40
+ } as const;
@@ -0,0 +1,23 @@
1
+ export type PollActivityType =
2
+ | 'signal'
3
+ | 'analyzing'
4
+ | 'posted'
5
+ | 'skipped'
6
+ | 'error'
7
+ | 'idle'
8
+ | 'online';
9
+ export type ChatActivityType = 'chat-user' | 'chat-agent' | 'chat-error';
10
+
11
+ export interface PollActivityItem {
12
+ type: PollActivityType;
13
+ text: string;
14
+ detail?: string;
15
+ conviction?: number;
16
+ timestamp: Date;
17
+ }
18
+
19
+ export interface ChatActivityItem {
20
+ type: ChatActivityType;
21
+ text: string;
22
+ timestamp: Date;
23
+ }