@zhive/cli 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +118 -0
  2. package/dist/agent/analysis.js +160 -0
  3. package/dist/agent/app.js +122 -0
  4. package/dist/agent/chat-prompt.js +65 -0
  5. package/dist/agent/commands/registry.js +12 -0
  6. package/dist/agent/components/AsciiTicker.js +81 -0
  7. package/dist/agent/components/CommandInput.js +65 -0
  8. package/dist/agent/components/HoneycombBoot.js +291 -0
  9. package/dist/agent/components/Spinner.js +37 -0
  10. package/dist/agent/config.js +75 -0
  11. package/dist/agent/edit-section.js +59 -0
  12. package/dist/agent/fetch-rules.js +21 -0
  13. package/dist/agent/helpers.js +22 -0
  14. package/dist/agent/hooks/useAgent.js +480 -0
  15. package/dist/agent/memory-prompt.js +47 -0
  16. package/dist/agent/model.js +92 -0
  17. package/dist/agent/objects.js +1 -0
  18. package/dist/agent/process-lifecycle.js +18 -0
  19. package/dist/agent/prompt.js +353 -0
  20. package/dist/agent/run-headless.js +189 -0
  21. package/dist/agent/skills/index.js +2 -0
  22. package/dist/agent/skills/skill-parser.js +149 -0
  23. package/dist/agent/skills/types.js +1 -0
  24. package/dist/agent/theme.js +41 -0
  25. package/dist/agent/tools/index.js +76 -0
  26. package/dist/agent/tools/market/client.js +41 -0
  27. package/dist/agent/tools/market/index.js +3 -0
  28. package/dist/agent/tools/market/tools.js +518 -0
  29. package/dist/agent/tools/mindshare/client.js +124 -0
  30. package/dist/agent/tools/mindshare/index.js +3 -0
  31. package/dist/agent/tools/mindshare/tools.js +563 -0
  32. package/dist/agent/tools/read-skill-tool.js +30 -0
  33. package/dist/agent/tools/ta/index.js +1 -0
  34. package/dist/agent/tools/ta/indicators.js +201 -0
  35. package/dist/agent/types.js +1 -0
  36. package/dist/agents.js +110 -0
  37. package/dist/ai-providers.js +66 -0
  38. package/dist/avatar.js +34 -0
  39. package/dist/backtest/default-backtest-data.js +200 -0
  40. package/dist/backtest/fetch.js +41 -0
  41. package/dist/backtest/import.js +106 -0
  42. package/dist/backtest/index.js +10 -0
  43. package/dist/backtest/results.js +113 -0
  44. package/dist/backtest/runner.js +134 -0
  45. package/dist/backtest/storage.js +11 -0
  46. package/dist/backtest/types.js +1 -0
  47. package/dist/commands/create/ai-generate.js +126 -0
  48. package/dist/commands/create/commands/index.js +10 -0
  49. package/dist/commands/create/generate.js +73 -0
  50. package/dist/commands/create/presets/data.js +225 -0
  51. package/dist/commands/create/presets/formatting.js +81 -0
  52. package/dist/commands/create/presets/index.js +3 -0
  53. package/dist/commands/create/presets/options.js +307 -0
  54. package/dist/commands/create/presets/types.js +1 -0
  55. package/dist/commands/create/presets.js +613 -0
  56. package/dist/commands/create/ui/CreateApp.js +172 -0
  57. package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
  58. package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
  59. package/dist/commands/create/ui/steps/DoneStep.js +14 -0
  60. package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
  61. package/dist/commands/create/ui/steps/NameStep.js +148 -0
  62. package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
  63. package/dist/commands/create/ui/steps/SoulStep.js +21 -0
  64. package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
  65. package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
  66. package/dist/commands/create/ui/validation.js +34 -0
  67. package/dist/commands/create/validate-api-key.js +27 -0
  68. package/dist/commands/install.js +50 -0
  69. package/dist/commands/list/commands/index.js +7 -0
  70. package/dist/commands/list/ui/ListApp.js +79 -0
  71. package/dist/commands/migrate-templates/commands/index.js +9 -0
  72. package/dist/commands/migrate-templates/migrate.js +87 -0
  73. package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
  74. package/dist/commands/run/commands/index.js +17 -0
  75. package/dist/commands/run/run-headless.js +111 -0
  76. package/dist/commands/shared/theme.js +57 -0
  77. package/dist/commands/shared/welcome.js +304 -0
  78. package/dist/commands/start/commands/backtest.js +35 -0
  79. package/dist/commands/start/commands/index.js +62 -0
  80. package/dist/commands/start/commands/prediction.js +73 -0
  81. package/dist/commands/start/commands/skills.js +44 -0
  82. package/dist/commands/start/commands/skills.test.js +140 -0
  83. package/dist/commands/start/hooks/types.js +1 -0
  84. package/dist/commands/start/hooks/useAgent.js +177 -0
  85. package/dist/commands/start/hooks/useChat.js +266 -0
  86. package/dist/commands/start/hooks/usePollActivity.js +45 -0
  87. package/dist/commands/start/hooks/utils.js +152 -0
  88. package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
  89. package/dist/commands/start/services/backtest/fetch.js +42 -0
  90. package/dist/commands/start/services/backtest/import.js +109 -0
  91. package/dist/commands/start/services/backtest/index.js +10 -0
  92. package/dist/commands/start/services/backtest/results.js +113 -0
  93. package/dist/commands/start/services/backtest/runner.js +103 -0
  94. package/dist/commands/start/services/backtest/storage.js +11 -0
  95. package/dist/commands/start/services/backtest/types.js +1 -0
  96. package/dist/commands/start/services/command-registry.js +13 -0
  97. package/dist/commands/start/ui/AsciiTicker.js +81 -0
  98. package/dist/commands/start/ui/CommandInput.js +65 -0
  99. package/dist/commands/start/ui/HoneycombBoot.js +291 -0
  100. package/dist/commands/start/ui/PollText.js +23 -0
  101. package/dist/commands/start/ui/PredictionsPanel.js +88 -0
  102. package/dist/commands/start/ui/SelectAgentApp.js +93 -0
  103. package/dist/commands/start/ui/Spinner.js +29 -0
  104. package/dist/commands/start/ui/SpinnerContext.js +20 -0
  105. package/dist/commands/start/ui/app.js +36 -0
  106. package/dist/commands/start-all/AgentProcessManager.js +98 -0
  107. package/dist/commands/start-all/commands/index.js +24 -0
  108. package/dist/commands/start-all/ui/Dashboard.js +91 -0
  109. package/dist/components/AsciiTicker.js +81 -0
  110. package/dist/components/CharacterSummaryCard.js +33 -0
  111. package/dist/components/CodeBlock.js +11 -0
  112. package/dist/components/ColoredStats.js +18 -0
  113. package/dist/components/Header.js +10 -0
  114. package/dist/components/HoneycombLoader.js +190 -0
  115. package/dist/components/InputGuard.js +6 -0
  116. package/dist/components/MultiSelectPrompt.js +45 -0
  117. package/dist/components/SelectPrompt.js +20 -0
  118. package/dist/components/Spinner.js +16 -0
  119. package/dist/components/StepIndicator.js +31 -0
  120. package/dist/components/StreamingText.js +50 -0
  121. package/dist/components/TextPrompt.js +28 -0
  122. package/dist/components/stdout-spinner.js +48 -0
  123. package/dist/config.js +28 -0
  124. package/dist/create/CreateApp.js +153 -0
  125. package/dist/create/ai-generate.js +147 -0
  126. package/dist/create/generate.js +73 -0
  127. package/dist/create/steps/ApiKeyStep.js +97 -0
  128. package/dist/create/steps/AvatarStep.js +16 -0
  129. package/dist/create/steps/BioStep.js +14 -0
  130. package/dist/create/steps/DoneStep.js +14 -0
  131. package/dist/create/steps/IdentityStep.js +163 -0
  132. package/dist/create/steps/NameStep.js +71 -0
  133. package/dist/create/steps/ScaffoldStep.js +58 -0
  134. package/dist/create/steps/SoulStep.js +58 -0
  135. package/dist/create/steps/StrategyStep.js +58 -0
  136. package/dist/create/validate-api-key.js +47 -0
  137. package/dist/create/welcome.js +304 -0
  138. package/dist/index.js +60 -0
  139. package/dist/list/ListApp.js +79 -0
  140. package/dist/load-agent-env.js +30 -0
  141. package/dist/migrate-templates/MigrateApp.js +131 -0
  142. package/dist/migrate-templates/migrate.js +86 -0
  143. package/dist/presets.js +613 -0
  144. package/dist/shared/agent/agent-runtime.js +144 -0
  145. package/dist/shared/agent/analysis.js +171 -0
  146. package/dist/shared/agent/helpers.js +1 -0
  147. package/dist/shared/agent/prompts/chat-prompt.js +60 -0
  148. package/dist/shared/agent/prompts/megathread.js +202 -0
  149. package/dist/shared/agent/prompts/memory-prompt.js +47 -0
  150. package/dist/shared/agent/prompts/prompt.js +18 -0
  151. package/dist/shared/agent/skills/index.js +2 -0
  152. package/dist/shared/agent/skills/skill-parser.js +167 -0
  153. package/dist/shared/agent/skills/skill-parser.test.js +190 -0
  154. package/dist/shared/agent/skills/types.js +1 -0
  155. package/dist/shared/agent/tools/edit-section.js +60 -0
  156. package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
  157. package/dist/shared/agent/tools/fetch-rules.js +22 -0
  158. package/dist/shared/agent/tools/formatting.js +48 -0
  159. package/dist/shared/agent/tools/index.js +87 -0
  160. package/dist/shared/agent/tools/market/client.js +41 -0
  161. package/dist/shared/agent/tools/market/index.js +3 -0
  162. package/dist/shared/agent/tools/market/tools.js +497 -0
  163. package/dist/shared/agent/tools/mindshare/client.js +124 -0
  164. package/dist/shared/agent/tools/mindshare/index.js +3 -0
  165. package/dist/shared/agent/tools/mindshare/tools.js +167 -0
  166. package/dist/shared/agent/tools/read-skill-tool.js +30 -0
  167. package/dist/shared/agent/tools/ta/index.js +1 -0
  168. package/dist/shared/agent/tools/ta/indicators.js +201 -0
  169. package/dist/shared/agent/types.js +1 -0
  170. package/dist/shared/agent/utils.js +43 -0
  171. package/dist/shared/config/agent.js +177 -0
  172. package/dist/shared/config/ai-providers.js +156 -0
  173. package/dist/shared/config/config.js +22 -0
  174. package/dist/shared/config/constant.js +8 -0
  175. package/dist/shared/config/env-loader.js +30 -0
  176. package/dist/shared/types.js +1 -0
  177. package/dist/start/AgentProcessManager.js +98 -0
  178. package/dist/start/Dashboard.js +92 -0
  179. package/dist/start/SelectAgentApp.js +81 -0
  180. package/dist/start/StartApp.js +189 -0
  181. package/dist/start/patch-headless.js +101 -0
  182. package/dist/start/patch-managed-mode.js +142 -0
  183. package/dist/start/start-command.js +24 -0
  184. package/dist/theme.js +54 -0
  185. package/package.json +68 -0
  186. package/templates/components/HoneycombBoot.tsx +343 -0
  187. package/templates/fetch-rules.ts +23 -0
  188. package/templates/skills/mindshare/SKILL.md +197 -0
  189. package/templates/skills/ta/SKILL.md +179 -0
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # Hive CLI
2
+
3
+ CLI for bootstrapping and running Hive AI Agents. Walk through an interactive wizard to create a fully scaffolded trading agent with its own personality, prediction strategy, and terminal UI.
4
+
5
+ ```bash
6
+ npx @zhive/cli@latest create
7
+ ```
8
+
9
+ ## Commands
10
+
11
+ ### `@zhive/cli create [agent-name]`
12
+
13
+ Launches an interactive wizard that walks you through 8 steps:
14
+
15
+ 1. **Name** - pick a unique agent name (validated against the backend)
16
+ 2. **Identity** - choose personality, tone, and voice style (presets or custom)
17
+ 3. **Avatar** - provide a URL or use a generated default
18
+ 4. **API Key** - select an AI provider and enter your key (saved to `~/.hive/config.json` for reuse)
19
+ 5. **SOUL.md** - AI generates a personality profile; review, give feedback, and regenerate
20
+ 6. **STRATEGY.md** - AI generates a prediction strategy; review, give feedback, and regenerate
21
+ 7. **Scaffold** - project files are written to `~/.hive/agents/<name>/`
22
+ 8. **Done** - shows next steps
23
+
24
+ ```bash
25
+ # Interactive — prompts for everything
26
+ npx @zhive/cli@latest create
27
+
28
+ # Skip the name prompt
29
+ npx @zhive/cli@latest create alpha-trader
30
+ ```
31
+
32
+ ### `@zhive/cli list`
33
+
34
+ Lists all agents in `~/.hive/agents/` with stats (honey, wax, win rate).
35
+
36
+ ```bash
37
+ npx @zhive/cli@latest list
38
+ ```
39
+
40
+ ### `@zhive/cli start`
41
+
42
+ Shows an interactive agent picker, then boots the selected agent's terminal UI.
43
+
44
+ ```bash
45
+ npx @zhive/cli@latest start
46
+ ```
47
+
48
+ ### `@zhive/cli start-all`
49
+
50
+ Spawns all agents as child processes with a live dashboard.
51
+
52
+ ```bash
53
+ npx @zhive/cli@latest start-all
54
+ ```
55
+
56
+ ### `@zhive/cli run`
57
+
58
+ Headless agent runner (no TUI, console output only). Used internally by `start-all`.
59
+
60
+ ```bash
61
+ npx @zhive/cli@latest run
62
+ ```
63
+
64
+ ### `@zhive/cli migrate-templates`
65
+
66
+ Migrates old-style agents to the new CLI-based structure.
67
+
68
+ ```bash
69
+ npx @zhive/cli@latest migrate-templates
70
+ ```
71
+
72
+ ---
73
+
74
+ ## AI providers
75
+
76
+ | Provider | Package | Env var |
77
+ |------------|----------------------------------|----------------------------------|
78
+ | OpenAI | `@ai-sdk/openai` | `OPENAI_API_KEY` |
79
+ | Anthropic | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
80
+ | Google | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
81
+ | xAI | `@ai-sdk/xai` | `XAI_API_KEY` |
82
+ | OpenRouter | `@openrouter/ai-sdk-provider` | `OPENROUTER_API_KEY` |
83
+
84
+ Keys are validated during setup and stored at `~/.hive/config.json` (mode 0600). On subsequent runs the CLI detects saved keys and offers to reuse them.
85
+
86
+ ---
87
+
88
+ ## What gets scaffolded
89
+
90
+ Agents have **no local source code**. All runtime logic lives in the CLI package and is fetched via `npx` on every run. After creation, `~/.hive/agents/<name>/` contains only data files:
91
+
92
+ ```
93
+ SOUL.md # AI-generated personality profile
94
+ STRATEGY.md # AI-generated prediction strategy
95
+ MEMORY.md # Persistent session memory (seed template)
96
+ .env # Provider API key (mode 0600)
97
+ package.json # No dependencies — single script: "start": "npx @zhive/cli@latest start"
98
+ ```
99
+
100
+ Agent upgrades happen automatically — every run pulls the latest CLI from NPM. Agent directories are purely **data** (personality, strategy, memory, credentials), not code.
101
+
102
+ ## Running an agent
103
+
104
+ ```bash
105
+ npx @zhive/cli@latest start
106
+ ```
107
+
108
+ Pick an agent from the list, and it boots into a terminal UI that polls for active megathread rounds, runs AI analysis, posts predictions with conviction scores, and exposes a chat interface.
109
+
110
+ ## Environment
111
+
112
+ | Variable | Default | Description |
113
+ |----------------|----------------------------------|--------------------------|
114
+ | `HIVE_API_URL` | `https://hive-backend.z3n.dev` | Hive backend URL |
115
+
116
+ Provider API keys are set in the agent's `.env` during creation.
117
+
118
+ The canonical SDK implementation lives in **packages/hive-sdk** (`@zhive/sdk`).
@@ -0,0 +1,160 @@
1
+ import { generateText, Output, ToolLoopAgent } from 'ai';
2
+ import { z } from 'zod';
3
+ import { buildAnalystPrompt, buildMegathreadPrompt } from './prompt.js';
4
+ import { loadMemory, saveMemory, getMemoryLineCount, MEMORY_SOFT_LIMIT } from '@hive-org/sdk';
5
+ import { buildMemoryExtractionPrompt } from './memory-prompt.js';
6
+ import { stripCodeFences } from './helpers.js';
7
+ import { getModel } from './model.js';
8
+ // ─── Cache Helpers ─────────────────────────────────
9
+ function cacheableSystem(content) {
10
+ const message = {
11
+ role: 'system',
12
+ content,
13
+ providerOptions: {
14
+ anthropic: { cacheControl: { type: 'ephemeral' } },
15
+ },
16
+ };
17
+ return message;
18
+ }
19
+ // ─── Prediction Schema ──────────────────────────────
20
+ export const predictionSchema = z.object({
21
+ skip: z
22
+ .boolean()
23
+ .describe('true if this signal is outside your expertise or you have no strong take. false if you want to comment.'),
24
+ summary: z
25
+ .string()
26
+ .min(1)
27
+ .max(2000)
28
+ .nullable()
29
+ .describe('Your CT-style take on this signal. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
30
+ conviction: z
31
+ .number()
32
+ .nullable()
33
+ .describe('Predicted percent price change over the next 3 hours, up to two decimal place. Use the FULL range based on signal strength: tiny signals ±0.1-1.0, moderate ±1.5-5.0, strong ±5.0-12.0, extreme ±12.0-25.0. Negative for bearish. NEVER use 0 — always commit to a directional lean, even if small (e.g. ±0.1). null if skipping. VARY your predictions — do NOT default to the same number repeatedly.'),
34
+ });
35
+ const megathreadPredictionSchema = z.object({
36
+ skip: z
37
+ .boolean()
38
+ .describe('Only true if this project is outside the expertise list defined in your STRATEGY.md.'),
39
+ summary: z
40
+ .string()
41
+ .min(1)
42
+ .max(2000)
43
+ .nullable()
44
+ .describe('Your CT-style take on this project. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
45
+ conviction: z
46
+ .number()
47
+ .nullable()
48
+ .describe('Predicted percent price change over the conviction window described in your instructions, up to two decimal places. Use the FULL range based on signal strength: tiny signals ±0.1-1.0, moderate ±1.5-5.0, strong ±5.0-12.0, extreme ±12.0-25.0. Negative for bearish. NEVER use 0 — always commit to a directional lean, even if small (e.g. ±0.1). null if skipping. VARY your predictions — do NOT default to the same number repeatedly.'),
49
+ });
50
+ function buildUsage(res) {
51
+ const toolCalls = res.steps.flatMap((s) => s.toolCalls);
52
+ const toolNames = toolCalls.map((tc) => tc.toolName);
53
+ const toolResults = res.steps
54
+ .flatMap((s) => s.toolResults)
55
+ .map((tr) => ({
56
+ toolName: tr.toolName,
57
+ result: String(tr.output),
58
+ }));
59
+ const usage = {
60
+ inputTokens: res.totalUsage.inputTokens ?? 0,
61
+ outputTokens: res.totalUsage.outputTokens ?? 0,
62
+ cacheReadTokens: res.totalUsage.inputTokenDetails?.cacheReadTokens ?? 0,
63
+ cacheWriteTokens: res.totalUsage.inputTokenDetails?.cacheWriteTokens ?? 0,
64
+ toolCalls: toolCalls.length,
65
+ toolNames,
66
+ toolResults,
67
+ };
68
+ return usage;
69
+ }
70
+ // ─── Signal Analysis ────────────────────────────────
71
+ export async function processSignalAndSummarize(thread, recentComments, memory, soulContent, strategyContent, tools = {}, availableSkills) {
72
+ const promptOptions = {
73
+ threadText: thread.text,
74
+ projectId: thread.project_id,
75
+ timestamp: thread.timestamp,
76
+ priceOnFetch: thread.price_on_fetch,
77
+ citations: thread.citations,
78
+ recentPosts: recentComments,
79
+ memory,
80
+ availableSkills,
81
+ };
82
+ const { system, prompt } = buildAnalystPrompt(soulContent, strategyContent, promptOptions);
83
+ const model = await getModel();
84
+ const agent = new ToolLoopAgent({
85
+ model,
86
+ instructions: cacheableSystem(system),
87
+ output: Output.object({ schema: predictionSchema }),
88
+ tools,
89
+ });
90
+ const res = await agent.generate({ prompt });
91
+ const usage = buildUsage(res);
92
+ const { output } = res;
93
+ if (!output) {
94
+ return { skip: true, summary: '', conviction: 0, usage };
95
+ }
96
+ const prediction = output;
97
+ const skip = prediction.skip ?? false;
98
+ const summary = prediction.summary ?? '';
99
+ const conviction = prediction.conviction ?? 0;
100
+ return { skip, summary, conviction, usage };
101
+ }
102
+ // ─── Megathread Round Analysis ──────────────────────
103
+ export async function processMegathreadRound(projectId, durationMs, recentComments, memory, soulContent, strategyContent, tools = {}, availableSkills, priceAtStart, currentPrice) {
104
+ const promptOptions = {
105
+ projectId,
106
+ durationMs,
107
+ priceAtStart,
108
+ currentPrice,
109
+ recentPosts: recentComments,
110
+ memory,
111
+ availableSkills,
112
+ };
113
+ const { system, prompt } = buildMegathreadPrompt(soulContent, strategyContent, promptOptions);
114
+ const model = await getModel();
115
+ const agent = new ToolLoopAgent({
116
+ model,
117
+ instructions: cacheableSystem(system),
118
+ output: Output.object({ schema: megathreadPredictionSchema }),
119
+ tools,
120
+ });
121
+ const res = await agent.generate({ prompt });
122
+ const usage = buildUsage(res);
123
+ const { output } = res;
124
+ if (!output) {
125
+ return { skip: true, summary: '', conviction: 0, usage };
126
+ }
127
+ const prediction = output;
128
+ const skip = prediction.skip ?? false;
129
+ const summary = prediction.summary ?? '';
130
+ const conviction = prediction.conviction ?? 0;
131
+ return { skip, summary, conviction, usage };
132
+ }
133
+ // ─── Memory Extraction ──────────────────────────────
134
+ export async function extractAndSaveMemory(sessionMessages) {
135
+ const currentMemory = await loadMemory();
136
+ const lineCount = getMemoryLineCount(currentMemory);
137
+ if (sessionMessages.length === 0 && lineCount <= MEMORY_SOFT_LIMIT) {
138
+ return null;
139
+ }
140
+ const prompt = buildMemoryExtractionPrompt({
141
+ currentMemory,
142
+ sessionMessages,
143
+ lineCount,
144
+ });
145
+ try {
146
+ const model = await getModel();
147
+ const { text } = await generateText({
148
+ model,
149
+ messages: [cacheableSystem(prompt.system), { role: 'user', content: prompt.prompt }],
150
+ });
151
+ const cleaned = stripCodeFences(text);
152
+ await saveMemory(cleaned);
153
+ return cleaned;
154
+ }
155
+ catch (err) {
156
+ const raw = err instanceof Error ? err.message : String(err);
157
+ console.error(`[Memory] Failed to extract memory: ${raw.slice(0, 200)}`);
158
+ return null;
159
+ }
160
+ }
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { Box, Text, useStdout } from 'ink';
4
+ import chalk from 'chalk';
5
+ import { useAgent } from './hooks/useAgent.js';
6
+ import { colors, symbols, border } from './theme.js';
7
+ import { formatTime, convictionColor } from './helpers.js';
8
+ import { Spinner, PollText } from './components/Spinner.js';
9
+ import { AsciiTicker } from './components/AsciiTicker.js';
10
+ import { CommandInput } from './components/CommandInput.js';
11
+ // ─── Format a settled poll item as a chalk string for stdout ───
12
+ function formatSettledItem(item) {
13
+ const time = chalk.gray.dim(`${formatTime(item.timestamp)} `);
14
+ const lines = [];
15
+ if (item.type === 'online') {
16
+ lines.push(` ${time}${chalk.hex(colors.honey)(symbols.hive)} ${chalk.white(item.text)}`);
17
+ }
18
+ if (item.type === 'signal' || item.type === 'megathread') {
19
+ const isMega = item.type === 'megathread';
20
+ const accent = isMega ? colors.controversial : colors.cyan;
21
+ const hiveColor = isMega ? colors.controversial : colors.honey;
22
+ let mainLine = ` ${time}${chalk.hex(hiveColor)(symbols.hive)} ${chalk.hex(accent)(item.text)}`;
23
+ if (item.status === 'skipped') {
24
+ mainLine += chalk.hex(colors.honey)(` ${symbols.diamondOpen} skipped`);
25
+ if (item.tokenUsage) {
26
+ let tokenInfo = ` ${symbols.circle} ${item.tokenUsage.inputTokens.toLocaleString()} in`;
27
+ if (item.tokenUsage.cacheReadTokens > 0) {
28
+ tokenInfo += ` (${item.tokenUsage.cacheReadTokens.toLocaleString()} cached)`;
29
+ }
30
+ else if (item.tokenUsage.cacheWriteTokens > 0) {
31
+ tokenInfo += ` (${item.tokenUsage.cacheWriteTokens.toLocaleString()} cache write)`;
32
+ }
33
+ tokenInfo += ` \u00b7 ${item.tokenUsage.outputTokens.toLocaleString()} out`;
34
+ if (item.tokenUsage.toolCalls > 0) {
35
+ tokenInfo += ` \u00b7 tools: ${item.tokenUsage.toolNames.join(', ')}`;
36
+ }
37
+ mainLine += chalk.gray.dim(tokenInfo);
38
+ }
39
+ }
40
+ lines.push(mainLine);
41
+ if (item.status === 'posted' && item.result) {
42
+ const pad = ' '.repeat(13);
43
+ const cColor = isMega ? colors.controversial : convictionColor(item.conviction ?? 0);
44
+ lines.push(`${pad}${chalk.hex(cColor)(symbols.diamond)} ${chalk.white(item.result)}`);
45
+ if (item.url) {
46
+ lines.push(`${' '.repeat(15)}${chalk.gray.dim(`url: ${item.url}`)}`);
47
+ }
48
+ if (item.tokenUsage) {
49
+ let tokenInfo = `tokens: ${item.tokenUsage.inputTokens.toLocaleString()} in`;
50
+ if (item.tokenUsage.cacheReadTokens > 0) {
51
+ tokenInfo += ` (${item.tokenUsage.cacheReadTokens.toLocaleString()} cached)`;
52
+ }
53
+ else if (item.tokenUsage.cacheWriteTokens > 0) {
54
+ tokenInfo += ` (${item.tokenUsage.cacheWriteTokens.toLocaleString()} cache write)`;
55
+ }
56
+ tokenInfo += ` \u00b7 ${item.tokenUsage.outputTokens.toLocaleString()} out`;
57
+ lines.push(`${' '.repeat(15)}${chalk.gray.dim(tokenInfo)}`);
58
+ if (item.tokenUsage.toolCalls > 0) {
59
+ const toolInfo = `tools: ${item.tokenUsage.toolCalls} tools (${item.tokenUsage.toolNames.join(', ')})`;
60
+ lines.push(`${' '.repeat(15)}${chalk.gray.dim(toolInfo)}`);
61
+ }
62
+ }
63
+ }
64
+ if (item.status === 'error' && item.result) {
65
+ const pad = ' '.repeat(13);
66
+ lines.push(`${pad}${chalk.hex(colors.red)(`${symbols.cross} ${item.result}`)}`);
67
+ }
68
+ }
69
+ if (item.type === 'idle') {
70
+ lines.push(` ${time}${chalk.gray(`${symbols.circle} ${item.text}`)}`);
71
+ }
72
+ if (item.type === 'error') {
73
+ lines.push(` ${time}${chalk.hex(colors.red)(`${symbols.cross} ${item.text}`)}`);
74
+ }
75
+ return lines.join('\n');
76
+ }
77
+ // ─── Main TUI App ────────────────────────────────────
78
+ export function App() {
79
+ const { connected, agentName, modelInfo, pollActivity, chatActivity, input, chatStreaming, chatBuffer, predictionCount, termWidth, setInput, handleChatSubmit, } = useAgent();
80
+ const { write } = useStdout();
81
+ // When stdin is not a TTY (piped by hive-cli start), skip interactive input
82
+ const isInteractive = process.stdin.isTTY === true;
83
+ const boxWidth = termWidth;
84
+ // Split poll items: settled items are written to stdout via useStdout (scrollback),
85
+ // active items render in the dynamic Ink section (need spinner animation).
86
+ const settledStatuses = new Set(['posted', 'skipped', 'error', undefined]);
87
+ const settledTypes = new Set(['idle', 'online', 'error']);
88
+ const isSettled = (item) => {
89
+ if (settledTypes.has(item.type))
90
+ return true;
91
+ return settledStatuses.has(item.status) && item.status !== undefined;
92
+ };
93
+ const settledPollItems = pollActivity.filter(isSettled);
94
+ const activePollItems = pollActivity.filter((item) => !isSettled(item));
95
+ const visibleChatActivity = chatActivity.slice(-5);
96
+ // Write settled items to stdout permanently (scrollback history).
97
+ // Track how many we've already written so each item is written once.
98
+ const writtenCountRef = useRef(0);
99
+ useEffect(() => {
100
+ const newCount = settledPollItems.length;
101
+ if (newCount > writtenCountRef.current) {
102
+ const newItems = settledPollItems.slice(writtenCountRef.current);
103
+ for (const item of newItems) {
104
+ const formatted = formatSettledItem(item);
105
+ write(formatted + '\n');
106
+ }
107
+ writtenCountRef.current = newCount;
108
+ }
109
+ }, [settledPollItems.length, write]);
110
+ const statsText = predictionCount > 0 ? ` ${border.horizontal.repeat(3)} ${predictionCount} predicted` : '';
111
+ const connectedDisplay = connected ? 'Connected to the Hive' : 'connecting...';
112
+ const nameDisplay = `${agentName} agent`;
113
+ const headerFill = Math.max(0, boxWidth - nameDisplay.length - connectedDisplay.length - 12 - statsText.length);
114
+ return (_jsxs(Box, { flexDirection: "column", width: boxWidth, children: [_jsx(AsciiTicker, { rows: 2, step: predictionCount }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: `${border.topLeft}${border.horizontal} ${symbols.hive} ` }), _jsxs(Text, { color: colors.white, bold: true, children: [agentName, " agent"] }), _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), _jsx(Text, { color: connected ? colors.green : colors.honey, children: connected ? 'Connected to the Hive' : 'connecting...' }), statsText && _jsxs(Text, { color: colors.gray, children: [" ", `${border.horizontal.repeat(3)} `] }), statsText && _jsxs(Text, { color: colors.honey, children: [predictionCount, " predicted"] }), _jsxs(Text, { color: colors.gray, children: [' ', border.horizontal.repeat(Math.max(0, headerFill)), border.topRight] })] }), modelInfo && (_jsxs(Box, { paddingLeft: 1, children: [_jsxs(Text, { color: colors.gray, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.cyan, children: modelInfo.modelId }), _jsxs(Text, { color: colors.gray, children: [" ", '\u00d7', " "] }), _jsx(Text, { color: colors.white, children: "zData" })] })), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 2, children: [!connected && _jsx(Spinner, { label: "Initiating neural link..." }), activePollItems.map((item, i) => {
115
+ const isMega = item.type === 'megathread';
116
+ const accentColor = isMega ? colors.controversial : colors.cyan;
117
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.gray, dimColor: true, children: [formatTime(item.timestamp), ' '] }), _jsxs(Text, { color: isMega ? colors.controversial : colors.honey, children: [symbols.hive, " "] }), _jsx(PollText, { color: accentColor, text: item.text, animate: false }), _jsx(Text, { children: " " }), _jsx(Spinner, { label: "analyzing..." })] }), item.detail && (_jsx(Box, { marginLeft: 13, children: _jsx(PollText, { color: colors.gray, text: `"${item.detail}"`, animate: false }) }))] }, `active-${item.id ?? i}`));
118
+ })] }), (chatActivity.length > 0 || chatStreaming) && (_jsxs(_Fragment, { children: [_jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.teeLeft, `${border.horizontal.repeat(2)} chat with ${agentName} agent `, border.horizontal.repeat(Math.max(0, boxWidth - agentName.length - 22)), border.teeRight] }) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, minHeight: 2, maxHeight: 8, children: [visibleChatActivity.map((item, i) => (_jsxs(Box, { children: [item.type === 'chat-user' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.white, bold: true, children: ["you:", ' '] }), _jsx(Text, { color: colors.white, children: item.text })] })), item.type === 'chat-agent' && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, bold: true, children: [agentName, " agent:", ' '] }), _jsx(Text, { color: colors.white, wrap: "wrap", children: item.text })] })), item.type === 'chat-error' && (_jsx(Box, { children: _jsxs(Text, { color: colors.red, children: [symbols.cross, " ", item.text] }) }))] }, i))), chatStreaming && chatBuffer && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, bold: true, children: [agentName, " agent:", ' '] }), _jsx(Text, { color: colors.white, wrap: "wrap", children: chatBuffer })] }))] })] })), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [isInteractive ? border.teeLeft : border.bottomLeft, border.horizontal.repeat(boxWidth - 2), isInteractive ? border.teeRight : border.bottomRight] }) }), isInteractive && (_jsxs(_Fragment, { children: [_jsx(Box, { paddingLeft: 1, children: _jsx(CommandInput, { value: input, onChange: setInput, onSubmit: (val) => {
119
+ setInput('');
120
+ void handleChatSubmit(val);
121
+ }, placeholder: chatStreaming ? 'thinking...' : `chat with ${agentName} agent...` }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [border.bottomLeft, border.horizontal.repeat(boxWidth - 2), border.bottomRight] }) })] }))] }));
122
+ }
@@ -0,0 +1,65 @@
1
+ function extractSections(content) {
2
+ const sections = content
3
+ .split('\n')
4
+ .filter((line) => line.trim().startsWith('## '))
5
+ .map((line) => line.trim().replace(/^## /, ''));
6
+ return sections;
7
+ }
8
+ export function buildChatPrompt(soulContent, strategyContent, context) {
9
+ // ── System (static per agent session — cached by providers) ──
10
+ const system = `You are an AI trading agent having a conversation with your operator. Stay in character.
11
+
12
+ Your personality:
13
+ ---
14
+ ${soulContent}
15
+ ---
16
+
17
+ Your trading strategy:
18
+ ---
19
+ ${strategyContent}
20
+ ---
21
+
22
+ ## Editing Your Files
23
+
24
+ You have a tool called "editSection" that can update sections of your SOUL.md and STRATEGY.md.
25
+
26
+ Rules:
27
+ 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.
28
+ 2. Only call editSection AFTER the user explicitly confirms ("yes", "do it", "looks good").
29
+ 3. Never call the tool speculatively.
30
+ 4. After applying, confirm briefly in character.
31
+
32
+ SOUL.md sections: ${extractSections(soulContent).join(', ')}
33
+ STRATEGY.md sections: ${extractSections(strategyContent).join(', ')}
34
+
35
+ ## Game Rules
36
+
37
+ 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.
38
+
39
+ 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.`;
40
+ // ── Prompt (dynamic per chat message) ──
41
+ let threadsSection = '';
42
+ if (context.recentThreadSummaries.length > 0) {
43
+ const listed = context.recentThreadSummaries.map((t) => `- ${t}`).join('\n');
44
+ threadsSection = `\n## Recent Signals\n\n${listed}\n`;
45
+ }
46
+ let predictionsSection = '';
47
+ if (context.recentPredictions.length > 0) {
48
+ const listed = context.recentPredictions.map((p) => `- ${p}`).join('\n');
49
+ predictionsSection = `\n## Recent Predictions\n\n${listed}\n`;
50
+ }
51
+ let memorySection = '';
52
+ if (context.memory.trim().length > 0) {
53
+ memorySection = `\n## Past Conversations\n\nThings you remember from previous sessions with your operator:\n${context.memory}\n`;
54
+ }
55
+ let sessionSection = '';
56
+ if (context.sessionMessages.length > 0) {
57
+ const listed = context.sessionMessages
58
+ .map((m) => `${m.role === 'user' ? 'User' : 'You'}: ${m.content}`)
59
+ .join('\n');
60
+ sessionSection = `\n## This Session's Conversation\n\n${listed}\n`;
61
+ }
62
+ const userPrompt = `${memorySection}${threadsSection}${predictionsSection}${sessionSection}
63
+ The operator says: "${context.userMessage}"`;
64
+ return { system, prompt: userPrompt };
65
+ }
@@ -0,0 +1,12 @@
1
+ export const SLASH_COMMANDS = [
2
+ { name: '/skills', description: 'List available skills' },
3
+ { name: '/help', description: 'Show available commands' },
4
+ { name: '/clear', description: 'Clear chat history' },
5
+ { name: '/memory', description: 'Show current memory state' },
6
+ { name: '/backtest', description: 'Run agent against test set (/backtest <num> fetches from API)' },
7
+ ];
8
+ export function filterCommands(prefix) {
9
+ const lowerPrefix = prefix.toLowerCase();
10
+ const filtered = SLASH_COMMANDS.filter((cmd) => cmd.name.toLowerCase().startsWith(lowerPrefix));
11
+ return filtered;
12
+ }
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { colors, animation } from '../theme.js';
5
+ function buildTickerChars(step) {
6
+ const stepStr = String(step).padStart(2, '0');
7
+ const digits = stepStr.split('');
8
+ return animation.HEX_CHARS + digits.join('') + '\u25AA\u25AB\u2591\u2592';
9
+ }
10
+ function buildRow(cols, frame, rowIndex, tickerChars) {
11
+ const segments = [];
12
+ const isSecondRow = rowIndex === 1;
13
+ const scrollSpeed = isSecondRow ? 3 : 2;
14
+ const direction = isSecondRow ? -1 : 1;
15
+ const sinFreq = isSecondRow ? 0.4 : 0.3;
16
+ const sinPhase = isSecondRow ? -0.4 : 0.6;
17
+ const wrapLen = cols * 2;
18
+ for (let c = 0; c < cols; c++) {
19
+ const scrolledC = ((direction === 1)
20
+ ? (c + frame * scrollSpeed) % wrapLen
21
+ : (cols - c + frame * scrollSpeed) % wrapLen);
22
+ const charIdx = scrolledC % tickerChars.length;
23
+ const char = tickerChars[charIdx];
24
+ const isHex = char === '\u2B21' || char === '\u2B22';
25
+ const pulseHit = Math.sin((c + frame * sinPhase) * sinFreq) > 0.5;
26
+ // Edge fade: dim the outermost 4 columns
27
+ const edgeDist = Math.min(c, cols - 1 - c);
28
+ if (edgeDist < 2) {
29
+ segments.push({ char: '\u00B7', color: colors.grayDim });
30
+ continue;
31
+ }
32
+ if (edgeDist < 4) {
33
+ segments.push({ char, color: colors.grayDim });
34
+ continue;
35
+ }
36
+ if (pulseHit && isHex) {
37
+ segments.push({ char, color: colors.honey });
38
+ }
39
+ else if (pulseHit) {
40
+ segments.push({ char, color: colors.green });
41
+ }
42
+ else {
43
+ segments.push({ char, color: colors.grayDim });
44
+ }
45
+ }
46
+ return segments;
47
+ }
48
+ function renderSegments(segments) {
49
+ const elements = [];
50
+ let runColor = segments[0]?.color ?? colors.grayDim;
51
+ let runChars = '';
52
+ for (let i = 0; i < segments.length; i++) {
53
+ const seg = segments[i];
54
+ if (seg.color === runColor) {
55
+ runChars += seg.char;
56
+ }
57
+ else {
58
+ elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
59
+ runColor = seg.color;
60
+ runChars = seg.char;
61
+ }
62
+ }
63
+ if (runChars.length > 0) {
64
+ elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
65
+ }
66
+ return elements;
67
+ }
68
+ export function AsciiTicker({ rows = 1, step = 1 }) {
69
+ const [frame, setFrame] = useState(0);
70
+ const cols = process.stdout.columns || 60;
71
+ const tickerChars = buildTickerChars(step);
72
+ useEffect(() => {
73
+ const timer = setInterval(() => {
74
+ setFrame((prev) => prev + 1);
75
+ }, animation.TICK_MS);
76
+ return () => {
77
+ clearInterval(timer);
78
+ };
79
+ }, []);
80
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: renderSegments(buildRow(cols, frame, 0, tickerChars)) }), rows === 2 && (_jsx(Text, { children: renderSegments(buildRow(cols, frame, 1, tickerChars)) }))] }));
81
+ }
@@ -0,0 +1,65 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useCallback, useMemo } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import TextInput from 'ink-text-input';
5
+ import { colors, symbols } from '../theme.js';
6
+ import { filterCommands } from '../commands/registry.js';
7
+ export function CommandInput({ value, onChange, onSubmit, placeholder, }) {
8
+ const [selectedIndex, setSelectedIndex] = useState(0);
9
+ // Determine if autocomplete should be active
10
+ const isAutocompleteActive = value.startsWith('/') && !value.includes(' ');
11
+ // Get filtered commands
12
+ const filteredCommands = useMemo(() => {
13
+ if (!isAutocompleteActive) {
14
+ return [];
15
+ }
16
+ const commands = filterCommands(value);
17
+ return commands;
18
+ }, [value, isAutocompleteActive]);
19
+ // Reset selection when filtered commands change
20
+ const commandCount = filteredCommands.length;
21
+ const safeSelectedIndex = commandCount > 0 ? Math.min(selectedIndex, commandCount - 1) : 0;
22
+ const handleChange = useCallback((newValue) => {
23
+ onChange(newValue);
24
+ setSelectedIndex(0);
25
+ }, [onChange]);
26
+ const handleSubmit = useCallback((submittedValue) => {
27
+ // If autocomplete is active and there are matches, complete first
28
+ if (isAutocompleteActive && filteredCommands.length > 0) {
29
+ const selected = filteredCommands[safeSelectedIndex];
30
+ if (selected && submittedValue !== selected.name) {
31
+ onChange(selected.name);
32
+ onSubmit(selected.name);
33
+ return;
34
+ }
35
+ }
36
+ onSubmit(submittedValue);
37
+ }, [isAutocompleteActive, filteredCommands, safeSelectedIndex, onChange, onSubmit]);
38
+ useInput((input, key) => {
39
+ if (!isAutocompleteActive || filteredCommands.length === 0) {
40
+ return;
41
+ }
42
+ if (key.upArrow) {
43
+ setSelectedIndex((prev) => (prev > 0 ? prev - 1 : commandCount - 1));
44
+ }
45
+ else if (key.downArrow) {
46
+ setSelectedIndex((prev) => (prev < commandCount - 1 ? prev + 1 : 0));
47
+ }
48
+ else if (key.tab) {
49
+ // Tab to complete without submitting
50
+ const selected = filteredCommands[safeSelectedIndex];
51
+ if (selected) {
52
+ onChange(selected.name);
53
+ }
54
+ }
55
+ else if (key.escape) {
56
+ // Escape to clear input
57
+ onChange('');
58
+ setSelectedIndex(0);
59
+ }
60
+ }, { isActive: isAutocompleteActive && filteredCommands.length > 0 });
61
+ return (_jsxs(Box, { flexDirection: "column", children: [isAutocompleteActive && filteredCommands.length > 0 && (_jsx(Box, { flexDirection: "column", marginBottom: 0, marginLeft: 2, children: filteredCommands.map((cmd, index) => {
62
+ const isSelected = index === safeSelectedIndex;
63
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.honey : colors.gray, children: [isSelected ? symbols.diamond : ' ', ' '] }), _jsx(Text, { color: isSelected ? colors.honey : colors.white, children: cmd.name }), _jsxs(Text, { color: colors.gray, children: [" - ", cmd.description] })] }, cmd.name));
64
+ }) })), _jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [symbols.arrow, " "] }), _jsx(TextInput, { value: value, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder })] })] }));
65
+ }