@hive-org/cli 0.0.6 → 0.0.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.
Files changed (47) hide show
  1. package/dist/agent/analysis.js +78 -0
  2. package/dist/agent/app.js +32 -0
  3. package/dist/agent/chat-prompt.js +63 -0
  4. package/dist/agent/components/AsciiTicker.js +81 -0
  5. package/dist/agent/components/HoneycombBoot.js +270 -0
  6. package/dist/agent/components/Spinner.js +37 -0
  7. package/dist/agent/config.js +52 -0
  8. package/dist/agent/edit-section.js +59 -0
  9. package/dist/agent/fetch-rules.js +21 -0
  10. package/dist/agent/helpers.js +22 -0
  11. package/dist/agent/hooks/useAgent.js +269 -0
  12. package/{templates/memory-prompt.ts → dist/agent/memory-prompt.js} +17 -32
  13. package/dist/agent/model.js +63 -0
  14. package/dist/agent/objects.js +1 -0
  15. package/dist/agent/process-lifecycle.js +56 -0
  16. package/{templates/prompt.ts → dist/agent/prompt.js} +18 -47
  17. package/dist/agent/theme.js +37 -0
  18. package/dist/agent/types.js +1 -0
  19. package/dist/agents.js +30 -21
  20. package/dist/ai-providers.js +0 -13
  21. package/dist/create/generate.js +10 -120
  22. package/dist/index.js +27 -4
  23. package/dist/migrate-templates/MigrateApp.js +131 -0
  24. package/dist/migrate-templates/migrate.js +86 -0
  25. package/dist/start/AgentProcessManager.js +131 -0
  26. package/dist/start/Dashboard.js +88 -0
  27. package/dist/start/patch-headless.js +101 -0
  28. package/dist/start/patch-managed-mode.js +142 -0
  29. package/dist/start/start-command.js +22 -0
  30. package/package.json +6 -5
  31. package/templates/analysis.ts +0 -103
  32. package/templates/chat-prompt.ts +0 -94
  33. package/templates/components/AsciiTicker.tsx +0 -113
  34. package/templates/components/HoneycombBoot.tsx +0 -348
  35. package/templates/components/Spinner.tsx +0 -64
  36. package/templates/edit-section.ts +0 -64
  37. package/templates/fetch-rules.ts +0 -23
  38. package/templates/helpers.ts +0 -22
  39. package/templates/hive/agent.ts +0 -2
  40. package/templates/hive/config.ts +0 -96
  41. package/templates/hive/memory.ts +0 -1
  42. package/templates/hive/objects.ts +0 -26
  43. package/templates/hooks/useAgent.ts +0 -337
  44. package/templates/index.tsx +0 -257
  45. package/templates/process-lifecycle.ts +0 -66
  46. package/templates/theme.ts +0 -40
  47. package/templates/types.ts +0 -23
@@ -1,94 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
-
4
- export interface ChatMessage {
5
- role: 'user' | 'assistant';
6
- content: string;
7
- }
8
-
9
- export interface ChatContext {
10
- recentThreadSummaries: string[];
11
- recentPredictions: string[];
12
- sessionMessages: ChatMessage[];
13
- memory: string;
14
- userMessage: string;
15
- }
16
-
17
- async function loadMarkdownFile(filename: string): Promise<string> {
18
- const filePath = path.join(process.cwd(), filename);
19
- const content = await fs.readFile(filePath, 'utf-8');
20
- return content;
21
- }
22
-
23
- function extractSections(content: string): string[] {
24
- const sections = content
25
- .split('\n')
26
- .filter((line) => line.trim().startsWith('## '))
27
- .map((line) => line.trim().replace(/^## /, ''));
28
- return sections;
29
- }
30
-
31
- export async function buildChatPrompt(context: ChatContext): Promise<string> {
32
- const soulContent = await loadMarkdownFile('SOUL.md');
33
- const strategyContent = await loadMarkdownFile('STRATEGY.md');
34
-
35
- let threadsSection = '';
36
- if (context.recentThreadSummaries.length > 0) {
37
- const listed = context.recentThreadSummaries.map((t) => `- ${t}`).join('\n');
38
- threadsSection = `\n## Recent Signals\n\n${listed}\n`;
39
- }
40
-
41
- let predictionsSection = '';
42
- if (context.recentPredictions.length > 0) {
43
- const listed = context.recentPredictions.map((p) => `- ${p}`).join('\n');
44
- predictionsSection = `\n## Recent Predictions\n\n${listed}\n`;
45
- }
46
-
47
- let memorySection = '';
48
- if (context.memory.trim().length > 0) {
49
- memorySection = `\n## Past Conversations\n\nThings you remember from previous sessions with your operator:\n${context.memory}\n`;
50
- }
51
-
52
- let sessionSection = '';
53
- if (context.sessionMessages.length > 0) {
54
- const listed = context.sessionMessages
55
- .map((m) => `${m.role === 'user' ? 'User' : 'You'}: ${m.content}`)
56
- .join('\n');
57
- sessionSection = `\n## This Session's Conversation\n\n${listed}\n`;
58
- }
59
-
60
- const prompt = `You are an AI trading agent having a conversation with your operator. Stay in character.
61
-
62
- Your personality:
63
- ---
64
- ${soulContent}
65
- ---
66
-
67
- Your trading strategy:
68
- ---
69
- ${strategyContent}
70
- ---
71
- ${memorySection}${threadsSection}${predictionsSection}${sessionSection}
72
- The operator says: "${context.userMessage}"
73
-
74
- ## Editing Your Files
75
-
76
- You have a tool called "editSection" that can update sections of your SOUL.md and STRATEGY.md.
77
-
78
- Rules:
79
- 1. When the user asks to change your personality or strategy, FIRST propose the change — show them what the new section content would look like.
80
- 2. Only call editSection AFTER the user explicitly confirms ("yes", "do it", "looks good").
81
- 3. Never call the tool speculatively.
82
- 4. After applying, confirm briefly in character.
83
-
84
- SOUL.md sections: ${extractSections(soulContent).join(', ')}
85
- STRATEGY.md sections: ${extractSections(strategyContent).join(', ')}
86
-
87
- ## Game Rules
88
-
89
- You have a tool called "fetchRules" that fetches the official Hive game rules. Call it when the user asks about rules, scoring, honey, wax, streaks, or how the platform works. Summarize the rules in your own voice — don't dump the raw markdown.
90
-
91
- Respond in character. Be helpful about your decisions and reasoning when asked, but maintain your personality voice. Keep responses concise (1-4 sentences unless a detailed explanation is specifically requested). When proposing edits, you may use longer responses to show the full preview.`;
92
-
93
- return prompt;
94
- }
@@ -1,113 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { colors, animation } from '../theme';
4
-
5
- interface AsciiTickerProps {
6
- rows?: 1 | 2;
7
- step?: number;
8
- }
9
-
10
- interface Segment {
11
- char: string;
12
- color: string;
13
- }
14
-
15
- function buildTickerChars(step: number): string {
16
- const stepStr = String(step).padStart(2, '0');
17
- const digits = stepStr.split('');
18
- return animation.HEX_CHARS + digits.join('') + '\u25AA\u25AB\u2591\u2592';
19
- }
20
-
21
- function buildRow(cols: number, frame: number, rowIndex: number, tickerChars: string): Segment[] {
22
- const segments: Segment[] = [];
23
- const isSecondRow = rowIndex === 1;
24
- const scrollSpeed = isSecondRow ? 3 : 2;
25
- const direction = isSecondRow ? -1 : 1;
26
- const sinFreq = isSecondRow ? 0.4 : 0.3;
27
- const sinPhase = isSecondRow ? -0.4 : 0.6;
28
- const wrapLen = cols * 2;
29
-
30
- for (let c = 0; c < cols; c++) {
31
- const scrolledC = ((direction === 1)
32
- ? (c + frame * scrollSpeed) % wrapLen
33
- : (cols - c + frame * scrollSpeed) % wrapLen);
34
-
35
- const charIdx = scrolledC % tickerChars.length;
36
- const char = tickerChars[charIdx];
37
- const isHex = char === '\u2B21' || char === '\u2B22';
38
- const pulseHit = Math.sin((c + frame * sinPhase) * sinFreq) > 0.5;
39
-
40
- // Edge fade: dim the outermost 4 columns
41
- const edgeDist = Math.min(c, cols - 1 - c);
42
- if (edgeDist < 2) {
43
- segments.push({ char: '\u00B7', color: colors.grayDim });
44
- continue;
45
- }
46
- if (edgeDist < 4) {
47
- segments.push({ char, color: colors.grayDim });
48
- continue;
49
- }
50
-
51
- if (pulseHit && isHex) {
52
- segments.push({ char, color: colors.honey });
53
- } else if (pulseHit) {
54
- segments.push({ char, color: colors.green });
55
- } else {
56
- segments.push({ char, color: colors.grayDim });
57
- }
58
- }
59
-
60
- return segments;
61
- }
62
-
63
- function renderSegments(segments: Segment[]): React.ReactElement[] {
64
- const elements: React.ReactElement[] = [];
65
- let runColor = segments[0]?.color ?? colors.grayDim;
66
- let runChars = '';
67
-
68
- for (let i = 0; i < segments.length; i++) {
69
- const seg = segments[i];
70
- if (seg.color === runColor) {
71
- runChars += seg.char;
72
- } else {
73
- elements.push(
74
- <Text key={`${elements.length}`} color={runColor}>{runChars}</Text>,
75
- );
76
- runColor = seg.color;
77
- runChars = seg.char;
78
- }
79
- }
80
-
81
- if (runChars.length > 0) {
82
- elements.push(
83
- <Text key={`${elements.length}`} color={runColor}>{runChars}</Text>,
84
- );
85
- }
86
-
87
- return elements;
88
- }
89
-
90
- export function AsciiTicker({ rows = 1, step = 1 }: AsciiTickerProps): React.ReactElement {
91
- const [frame, setFrame] = useState(0);
92
- const cols = process.stdout.columns || 60;
93
- const tickerChars = buildTickerChars(step);
94
-
95
- useEffect(() => {
96
- const timer = setInterval(() => {
97
- setFrame((prev) => prev + 1);
98
- }, animation.TICK_MS);
99
-
100
- return () => {
101
- clearInterval(timer);
102
- };
103
- }, []);
104
-
105
- return (
106
- <Box flexDirection="column">
107
- <Text>{renderSegments(buildRow(cols, frame, 0, tickerChars))}</Text>
108
- {rows === 2 && (
109
- <Text>{renderSegments(buildRow(cols, frame, 1, tickerChars))}</Text>
110
- )}
111
- </Box>
112
- );
113
- }
@@ -1,348 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { colors, animation } from '../theme';
4
-
5
- const BOOT_TOTAL_FRAMES = 58;
6
- const BOOT_FRAME_MS = 80;
7
- const NUM_BEES = 4;
8
- const NUM_STREAMS = 5;
9
- const SCRAMBLE_CHARS = '\u2B21\u2B22\u25C6\u25C7\u2591\u2592!@#$%01';
10
-
11
- const BOOT_MESSAGES = [
12
- { prefix: '\u2B21', text: 'Initializing {name} agent...', frame: 30 },
13
- { prefix: '\u25C6', text: 'Loading personality matrix...', frame: 36 },
14
- { prefix: '\u25C7', text: 'Connecting to the hive...', frame: 42 },
15
- { prefix: '\u2713', text: 'Neural link established', frame: 48 },
16
- ];
17
-
18
- // ─── Private types ───────────────────────────────────
19
-
20
- interface Bee {
21
- r: number;
22
- c: number;
23
- vr: number;
24
- vc: number;
25
- }
26
-
27
- interface Pulse {
28
- r: number;
29
- c: number;
30
- ttl: number;
31
- color: string;
32
- }
33
-
34
- interface Cell {
35
- char: string;
36
- color: string;
37
- }
38
-
39
- // ─── Private helpers ─────────────────────────────────
40
-
41
- function isHexEdge(r: number, c: number): boolean {
42
- const rowInHex = ((r % animation.HEX_H) + animation.HEX_H) % animation.HEX_H;
43
- const isOddHex = Math.floor(r / animation.HEX_H) % 2 === 1;
44
- const colOffset = isOddHex ? animation.HEX_W / 2 : 0;
45
- const colInHex = (((c - colOffset) % animation.HEX_W) + animation.HEX_W) % animation.HEX_W;
46
-
47
- if (rowInHex === 0 || rowInHex === animation.HEX_H - 1) {
48
- return colInHex >= 2 && colInHex <= 5;
49
- }
50
- if (rowInHex === 1 || rowInHex === 2) {
51
- return colInHex === 1 || colInHex === 6;
52
- }
53
- return false;
54
- }
55
-
56
- function compressRow(row: Cell[]): { text: string; color: string }[] {
57
- if (row.length === 0) return [];
58
- const segments: { text: string; color: string }[] = [];
59
- let current = { text: row[0].char, color: row[0].color };
60
- for (let i = 1; i < row.length; i++) {
61
- if (row[i].color === current.color) {
62
- current.text += row[i].char;
63
- } else {
64
- segments.push(current);
65
- current = { text: row[i].char, color: row[i].color };
66
- }
67
- }
68
- segments.push(current);
69
- return segments;
70
- }
71
-
72
- function initBees(rows: number, cols: number): Bee[] {
73
- const bees: Bee[] = [];
74
- for (let i = 0; i < NUM_BEES; i++) {
75
- bees.push({
76
- r: Math.floor(Math.random() * rows),
77
- c: Math.floor(Math.random() * cols),
78
- vr: Math.random() > 0.5 ? 1 : -1,
79
- vc: Math.random() > 0.5 ? 1 : -1,
80
- });
81
- }
82
- return bees;
83
- }
84
-
85
- function initStreamCols(cols: number): number[] {
86
- const streamCols: number[] = [];
87
- const spacing = Math.floor(cols / (NUM_STREAMS + 1));
88
- for (let i = 1; i <= NUM_STREAMS; i++) {
89
- streamCols.push(spacing * i);
90
- }
91
- return streamCols;
92
- }
93
-
94
- // ─── Grid builder ────────────────────────────────────
95
-
96
- function buildHoneycombGrid(
97
- cols: number,
98
- rows: number,
99
- frame: number,
100
- agentName: string,
101
- bees: Bee[],
102
- streamCols: number[],
103
- pulses: Pulse[],
104
- ): Cell[][] {
105
- const centerR = Math.floor(rows / 2) - 2;
106
- const centerC = Math.floor(cols / 2);
107
-
108
- // Initialize empty grid
109
- const grid: Cell[][] = [];
110
- for (let r = 0; r < rows; r++) {
111
- const row: Cell[] = [];
112
- for (let c = 0; c < cols; c++) {
113
- row.push({ char: ' ', color: colors.grayDim });
114
- }
115
- grid.push(row);
116
- }
117
-
118
- // ── Layer 1: Hex skeleton base ──
119
- for (let r = 0; r < rows; r++) {
120
- for (let c = 0; c < cols; c++) {
121
- if (isHexEdge(r, c)) {
122
- grid[r][c] = { char: '\u00B7', color: colors.grayDim };
123
- }
124
- }
125
- }
126
-
127
- // ── Layer 2: Scanning wave ──
128
- const scanRow = frame % (rows + 6);
129
- for (let r = 0; r < rows; r++) {
130
- for (let c = 0; c < cols; c++) {
131
- if (!isHexEdge(r, c)) continue;
132
- const dist = Math.abs(r - scanRow);
133
- if (dist === 0) {
134
- grid[r][c] = { char: '\u2B22', color: colors.honey };
135
- } else if (dist <= 1) {
136
- grid[r][c] = { char: '\u2B21', color: colors.honey };
137
- }
138
- }
139
- }
140
-
141
- // ── Layer 3: Vertical data streams ──
142
- if (frame >= 8) {
143
- const streamPhase = frame - 8;
144
- for (const sc of streamCols) {
145
- if (sc >= cols) continue;
146
- for (let r = 0; r < rows; r++) {
147
- const streamOffset = (streamPhase * 2 + sc) % (rows * 3);
148
- const streamDist = (((r - streamOffset) % rows) + rows) % rows;
149
- if (streamDist < 6) {
150
- const charIdx = (frame + r) % animation.DATA_CHARS.length;
151
- const streamChar = animation.DATA_CHARS[charIdx];
152
- let streamColor = colors.grayDim;
153
- if (streamDist === 0) {
154
- streamColor = colors.white;
155
- } else if (streamDist < 3) {
156
- streamColor = colors.green;
157
- }
158
- grid[r][sc] = { char: streamChar, color: streamColor };
159
- }
160
- }
161
- }
162
- }
163
-
164
- // ── Layer 4: Pulse overlay ──
165
- for (const pulse of pulses) {
166
- if (pulse.r >= 0 && pulse.r < rows && pulse.c >= 0 && pulse.c < cols) {
167
- const brightness = pulse.ttl / 8;
168
- const cell = grid[pulse.r][pulse.c];
169
- if (cell.char === '\u00B7' || cell.char === ' ') {
170
- grid[pulse.r][pulse.c] = {
171
- char: brightness > 0.5 ? '\u2B21' : '\u00B7',
172
- color: pulse.color,
173
- };
174
- }
175
- }
176
- }
177
-
178
- // ── Layer 5: Bee overlay ──
179
- for (const bee of bees) {
180
- const br = Math.max(0, Math.min(rows - 1, Math.round(bee.r)));
181
- const bc = Math.max(0, Math.min(cols - 1, Math.round(bee.c)));
182
- grid[br][bc] = { char: '\u25C6', color: colors.honey };
183
- }
184
-
185
- // ── Layer 6: Agent name with scramble→reveal ──
186
- if (frame >= 22) {
187
- const nameText = `\u2B21 ${agentName} agent \u2B21`;
188
- const nameStart = Math.max(0, centerC - Math.floor(nameText.length / 2));
189
- const scrambleProgress = Math.min(1, (frame - 22) / 8);
190
-
191
- // Clear space around the name
192
- for (let c = nameStart - 2; c < nameStart + nameText.length + 2 && c < cols; c++) {
193
- if (c >= 0) {
194
- grid[centerR][c] = { char: ' ', color: colors.grayDim };
195
- if (centerR - 1 >= 0) grid[centerR - 1][c] = { char: ' ', color: colors.grayDim };
196
- if (centerR + 1 < rows) grid[centerR + 1][c] = { char: ' ', color: colors.grayDim };
197
- }
198
- }
199
-
200
- // Top/bottom border lines around name
201
- for (let c = nameStart; c < nameStart + nameText.length && c < cols; c++) {
202
- if (centerR - 1 >= 0) grid[centerR - 1][c] = { char: '\u2500', color: colors.honey };
203
- if (centerR + 1 < rows) grid[centerR + 1][c] = { char: '\u2500', color: colors.honey };
204
- }
205
-
206
- // Name text with scramble effect
207
- for (let i = 0; i < nameText.length; i++) {
208
- const c = nameStart + i;
209
- if (c >= cols) break;
210
-
211
- const charThreshold = i / nameText.length;
212
- if (charThreshold <= scrambleProgress) {
213
- grid[centerR][c] = { char: nameText[i], color: colors.honey };
214
- } else {
215
- const scrambleIdx = Math.floor(Math.random() * SCRAMBLE_CHARS.length);
216
- grid[centerR][c] = { char: SCRAMBLE_CHARS[scrambleIdx], color: colors.gray };
217
- }
218
- }
219
- }
220
-
221
- // ── Layer 7: Boot messages with typewriter ──
222
- const msgStartRow = centerR + 4;
223
- for (let idx = 0; idx < BOOT_MESSAGES.length; idx++) {
224
- const msg = BOOT_MESSAGES[idx];
225
- if (frame < msg.frame) continue;
226
- const r = msgStartRow + idx;
227
- if (r >= rows) continue;
228
-
229
- const fullText = `${msg.prefix} ${msg.text.replace('{name}', agentName)}`;
230
- const msgStart = Math.max(0, centerC - Math.floor(fullText.length / 2));
231
- const visibleChars = Math.min(fullText.length, (frame - msg.frame) * 3);
232
-
233
- for (let i = 0; i < visibleChars; i++) {
234
- const c = msgStart + i;
235
- if (c >= cols) break;
236
-
237
- const isCheckmark = msg.prefix === '\u2713';
238
- grid[r][c] = { char: fullText[i], color: isCheckmark ? colors.green : colors.honey };
239
- }
240
- }
241
-
242
- return grid;
243
- }
244
-
245
- // ─── Component ───────────────────────────────────────
246
-
247
- interface HoneycombBootProps {
248
- agentName: string;
249
- width: number;
250
- onComplete: () => void;
251
- }
252
-
253
- export function HoneycombBoot({ agentName, width, onComplete }: HoneycombBootProps): React.ReactElement {
254
- const [frame, setFrame] = useState(0);
255
- const heightRef = useRef(process.stdout.rows || 24);
256
- const completedRef = useRef(false);
257
- const frameRef = useRef(0);
258
- const beesRef = useRef<Bee[]>(initBees(heightRef.current, width));
259
- const streamColsRef = useRef<number[]>(initStreamCols(width));
260
- const pulsesRef = useRef<Pulse[]>([]);
261
-
262
- useEffect(() => {
263
- const tick = setInterval(() => {
264
- const h = heightRef.current;
265
- const f = frameRef.current;
266
-
267
- // Advance bees every other frame
268
- if (beesRef.current.length > 0 && f % 2 === 0) {
269
- for (const bee of beesRef.current) {
270
- bee.r += bee.vr;
271
- bee.c += bee.vc;
272
- if (bee.r <= 0 || bee.r >= h - 1) {
273
- bee.vr *= -1;
274
- bee.r = Math.max(0, Math.min(h - 1, bee.r));
275
- }
276
- if (bee.c <= 0 || bee.c >= width - 1) {
277
- bee.vc *= -1;
278
- bee.c = Math.max(0, Math.min(width - 1, bee.c));
279
- }
280
- if (Math.random() > 0.3) {
281
- bee.vc = Math.random() > 0.5 ? 1 : -1;
282
- }
283
- }
284
- }
285
-
286
- // Spawn pulses every 4 frames
287
- if (f % 4 === 0) {
288
- const currentPulses = pulsesRef.current;
289
- for (let i = 0; i < 3; i++) {
290
- const pr = Math.floor(Math.random() * h);
291
- const pc = Math.floor(Math.random() * width);
292
- if (isHexEdge(pr, pc)) {
293
- const pulseColors = [colors.green, colors.red, colors.honey];
294
- const color = pulseColors[Math.floor(Math.random() * pulseColors.length)];
295
- currentPulses.push({ r: pr, c: pc, ttl: 8, color });
296
- }
297
- }
298
- pulsesRef.current = currentPulses
299
- .filter((p) => p.ttl > 0)
300
- .map((p) => ({ ...p, ttl: p.ttl - 1 }));
301
- }
302
-
303
- frameRef.current = f + 1;
304
- setFrame(frameRef.current);
305
-
306
- if (frameRef.current >= BOOT_TOTAL_FRAMES && !completedRef.current) {
307
- completedRef.current = true;
308
- clearInterval(tick);
309
- setTimeout(onComplete, 300);
310
- }
311
- }, BOOT_FRAME_MS);
312
-
313
- return () => {
314
- clearInterval(tick);
315
- };
316
- }, [onComplete, width]);
317
-
318
- const height = heightRef.current;
319
- const clampedFrame = Math.min(frame, BOOT_TOTAL_FRAMES);
320
- const grid = buildHoneycombGrid(
321
- width,
322
- height,
323
- clampedFrame,
324
- agentName,
325
- beesRef.current,
326
- streamColsRef.current,
327
- pulsesRef.current,
328
- );
329
-
330
- return (
331
- <Box flexDirection="column" height={height}>
332
- {grid.map((row, r) => {
333
- const segments = compressRow(row);
334
- return (
335
- <Box key={r} width={width}>
336
- <Text>
337
- {segments.map((seg, i) => (
338
- <Text key={i} color={seg.color}>
339
- {seg.text}
340
- </Text>
341
- ))}
342
- </Text>
343
- </Box>
344
- );
345
- })}
346
- </Box>
347
- );
348
- }
@@ -1,64 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Text } from 'ink';
3
- import { colors } from '../theme';
4
-
5
- const SPINNER_FRAMES = ['\u25D0', '\u25D3', '\u25D1', '\u25D2'];
6
-
7
- export function Spinner({ label }: { label: string }): React.ReactElement {
8
- const [frame, setFrame] = useState(0);
9
-
10
- useEffect(() => {
11
- const timer = setInterval(() => {
12
- setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
13
- }, 120);
14
- return () => {
15
- clearInterval(timer);
16
- };
17
- }, []);
18
-
19
- return (
20
- <Text>
21
- <Text color={colors.honey}>{SPINNER_FRAMES[frame]}</Text>
22
- <Text color="gray"> {label}</Text>
23
- </Text>
24
- );
25
- }
26
-
27
- export function TypewriterText({
28
- text,
29
- color,
30
- speed = 25,
31
- }: {
32
- text: string;
33
- color: string;
34
- speed?: number;
35
- }): React.ReactElement {
36
- const [visible, setVisible] = useState(0);
37
-
38
- useEffect(() => {
39
- if (visible >= text.length) return;
40
- const timer = setTimeout(() => {
41
- setVisible((prev) => Math.min(prev + 2, text.length));
42
- }, speed);
43
- return () => {
44
- clearTimeout(timer);
45
- };
46
- }, [visible, text, speed]);
47
-
48
- return <Text color={color}>{text.slice(0, visible)}</Text>;
49
- }
50
-
51
- export function PollText({
52
- text,
53
- color,
54
- animate,
55
- }: {
56
- text: string;
57
- color: string;
58
- animate: boolean;
59
- }): React.ReactElement {
60
- if (animate) {
61
- return <TypewriterText text={text} color={color} />;
62
- }
63
- return <Text color={color}>{text}</Text>;
64
- }
@@ -1,64 +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
-
6
- export function replaceSection(fileContent: string, heading: string, newContent: string): string {
7
- const lines = fileContent.split('\n');
8
- const headingLine = `## ${heading}`;
9
-
10
- let startIdx = -1;
11
- for (let i = 0; i < lines.length; i++) {
12
- if (lines[i].trim() === headingLine) {
13
- startIdx = i;
14
- break;
15
- }
16
- }
17
-
18
- if (startIdx === -1) {
19
- throw new Error(`Section "## ${heading}" not found in file.`);
20
- }
21
-
22
- let endIdx = lines.length;
23
- for (let i = startIdx + 1; i < lines.length; i++) {
24
- const trimmed = lines[i].trim();
25
- if (trimmed.startsWith('## ') || trimmed.startsWith('# ')) {
26
- endIdx = i;
27
- break;
28
- }
29
- }
30
-
31
- const before = lines.slice(0, startIdx + 1);
32
- const after = lines.slice(endIdx);
33
- const trimmedContent = newContent.trim();
34
- const newSection = ['', ...trimmedContent.split('\n'), ''];
35
-
36
- const result = [...before, ...newSection, ...after].join('\n');
37
- return result;
38
- }
39
-
40
- export const editSectionTool = tool({
41
- description: 'Edit a section of SOUL.md or STRATEGY.md. Only call AFTER user confirms.',
42
- inputSchema: z.object({
43
- file: z.enum(['SOUL.md', 'STRATEGY.md']),
44
- section: z.string().describe('Exact ## heading name, e.g. "Personality", "Conviction Style"'),
45
- content: z.string().describe('New content for the section (without the ## heading line)'),
46
- }),
47
- execute: async ({ file, section, content }) => {
48
- const filePath = path.join(process.cwd(), file);
49
- let fileContent: string;
50
- try {
51
- fileContent = await fs.readFile(filePath, 'utf-8');
52
- } catch {
53
- return `Error: ${file} not found in current directory.`;
54
- }
55
- try {
56
- const updated = replaceSection(fileContent, section, content);
57
- await fs.writeFile(filePath, updated, 'utf-8');
58
- return `Updated "${section}" section in ${file}.`;
59
- } catch (err: unknown) {
60
- const message = err instanceof Error ? err.message : String(err);
61
- return `Error: ${message}`;
62
- }
63
- },
64
- });
@@ -1,23 +0,0 @@
1
- import { tool } from 'ai';
2
- import { z } from 'zod';
3
-
4
- const RULES_URL = 'https://hive.z3n.dev/RULES.md';
5
-
6
- export const fetchRulesTool = tool({
7
- description:
8
- 'Fetch the rules of the Hive game. Call when the user asks about rules, scoring, honey, wax, streaks, or how the platform works.',
9
- inputSchema: z.object({}),
10
- execute: async () => {
11
- try {
12
- const response = await fetch(RULES_URL);
13
- if (!response.ok) {
14
- return `Error: failed to fetch rules (HTTP ${response.status}).`;
15
- }
16
- const rules = await response.text();
17
- return rules;
18
- } catch (err: unknown) {
19
- const message = err instanceof Error ? err.message : String(err);
20
- return `Error: could not reach Hive to fetch rules. ${message}`;
21
- }
22
- },
23
- });