@zhive/cli 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +5 -5
  2. package/dist/CLAUDE.md +7 -0
  3. package/dist/backtest/CLAUDE.md +7 -0
  4. package/dist/cli.js +20 -0
  5. package/dist/commands/agent/commands/index.js +7 -0
  6. package/dist/commands/agent/commands/profile.js +40 -0
  7. package/dist/commands/agent/commands/profile.test.js +137 -0
  8. package/dist/commands/create/commands/index.js +10 -5
  9. package/dist/commands/list/commands/index.js +8 -3
  10. package/dist/commands/megathread/commands/create-comment.js +99 -0
  11. package/dist/commands/megathread/commands/create-comment.test.js +480 -0
  12. package/dist/commands/megathread/commands/index.js +9 -0
  13. package/dist/commands/megathread/commands/list.js +102 -0
  14. package/dist/commands/megathread/commands/list.test.js +206 -0
  15. package/dist/commands/migrate-templates/commands/index.js +9 -4
  16. package/dist/commands/run/commands/index.js +17 -12
  17. package/dist/commands/run/run-headless.js +2 -1
  18. package/dist/commands/start/commands/index.js +37 -32
  19. package/dist/commands/start/hooks/useAgent.js +2 -1
  20. package/dist/commands/start/services/backtest/runner.js +1 -1
  21. package/dist/commands/start-all/commands/index.js +22 -17
  22. package/dist/index.js +26 -57
  23. package/dist/{agent → services/agent}/analysis.js +5 -5
  24. package/dist/{load-agent-env.js → services/agent/env.js} +1 -1
  25. package/dist/{agent → services/agent/helpers}/model.js +2 -2
  26. package/dist/{agent → services/agent/prompts}/memory-prompt.js +20 -22
  27. package/dist/{agent → services/agent/prompts}/prompt.js +80 -54
  28. package/dist/{agent → services/agent}/tools/market/client.js +1 -1
  29. package/dist/{agent → services/agent}/tools/mindshare/client.js +1 -1
  30. package/dist/{agents.js → services/config/agent.js} +2 -2
  31. package/dist/{config.js → services/config/config.js} +1 -7
  32. package/dist/services/config/constant.js +8 -0
  33. package/dist/shared/agent/analysis.js +10 -13
  34. package/dist/shared/agent/config.js +75 -0
  35. package/dist/shared/agent/env.js +30 -0
  36. package/dist/shared/agent/handler.js +129 -0
  37. package/dist/shared/agent/helpers/model.js +92 -0
  38. package/dist/shared/agent/prompts/prompt.js +1 -1
  39. package/dist/shared/agent/runtime.js +15 -0
  40. package/dist/shared/ai-providers.js +66 -0
  41. package/dist/shared/config/agent.js +19 -0
  42. package/dist/shared/config/agent.test.js +115 -0
  43. package/dist/shared/config/ai-providers.js +27 -21
  44. package/package.json +4 -3
  45. package/dist/agent/app.js +0 -122
  46. package/dist/agent/commands/registry.js +0 -12
  47. package/dist/agent/components/AsciiTicker.js +0 -81
  48. package/dist/agent/components/CommandInput.js +0 -65
  49. package/dist/agent/components/HoneycombBoot.js +0 -291
  50. package/dist/agent/components/Spinner.js +0 -37
  51. package/dist/agent/hooks/useAgent.js +0 -480
  52. package/dist/agent/objects.js +0 -1
  53. package/dist/agent/process-lifecycle.js +0 -18
  54. package/dist/agent/run-headless.js +0 -189
  55. package/dist/agent/theme.js +0 -41
  56. package/dist/avatar.js +0 -34
  57. package/dist/backtest/default-backtest-data.js +0 -200
  58. package/dist/backtest/fetch.js +0 -41
  59. package/dist/backtest/import.js +0 -106
  60. package/dist/backtest/index.js +0 -10
  61. package/dist/backtest/results.js +0 -113
  62. package/dist/backtest/runner.js +0 -134
  63. package/dist/backtest/storage.js +0 -11
  64. package/dist/backtest/types.js +0 -1
  65. package/dist/commands/install.js +0 -50
  66. package/dist/commands/start/ui/PollText.js +0 -23
  67. package/dist/commands/start/ui/PredictionsPanel.js +0 -88
  68. package/dist/commands/start/ui/SpinnerContext.js +0 -20
  69. package/dist/components/InputGuard.js +0 -6
  70. package/dist/components/stdout-spinner.js +0 -48
  71. package/dist/create/CreateApp.js +0 -153
  72. package/dist/create/ai-generate.js +0 -147
  73. package/dist/create/generate.js +0 -73
  74. package/dist/create/steps/ApiKeyStep.js +0 -97
  75. package/dist/create/steps/AvatarStep.js +0 -16
  76. package/dist/create/steps/BioStep.js +0 -14
  77. package/dist/create/steps/DoneStep.js +0 -14
  78. package/dist/create/steps/IdentityStep.js +0 -163
  79. package/dist/create/steps/NameStep.js +0 -71
  80. package/dist/create/steps/ScaffoldStep.js +0 -58
  81. package/dist/create/steps/SoulStep.js +0 -58
  82. package/dist/create/steps/StrategyStep.js +0 -58
  83. package/dist/create/validate-api-key.js +0 -47
  84. package/dist/create/welcome.js +0 -304
  85. package/dist/list/ListApp.js +0 -79
  86. package/dist/migrate-templates/MigrateApp.js +0 -131
  87. package/dist/migrate-templates/migrate.js +0 -86
  88. package/dist/presets.js +0 -613
  89. package/dist/start/AgentProcessManager.js +0 -98
  90. package/dist/start/Dashboard.js +0 -92
  91. package/dist/start/SelectAgentApp.js +0 -81
  92. package/dist/start/StartApp.js +0 -189
  93. package/dist/start/patch-headless.js +0 -101
  94. package/dist/start/patch-managed-mode.js +0 -142
  95. package/dist/start/start-command.js +0 -24
  96. package/dist/theme.js +0 -54
  97. /package/dist/{agent → services/agent}/config.js +0 -0
  98. /package/dist/{agent → services/agent}/helpers.js +0 -0
  99. /package/dist/{agent → services/agent/prompts}/chat-prompt.js +0 -0
  100. /package/dist/{agent → services/agent}/skills/index.js +0 -0
  101. /package/dist/{agent → services/agent}/skills/skill-parser.js +0 -0
  102. /package/dist/{agent → services/agent}/skills/types.js +0 -0
  103. /package/dist/{agent → services/agent/tools}/edit-section.js +0 -0
  104. /package/dist/{agent → services/agent/tools}/fetch-rules.js +0 -0
  105. /package/dist/{agent → services/agent}/tools/index.js +0 -0
  106. /package/dist/{agent → services/agent}/tools/market/index.js +0 -0
  107. /package/dist/{agent → services/agent}/tools/market/tools.js +0 -0
  108. /package/dist/{agent → services/agent}/tools/mindshare/index.js +0 -0
  109. /package/dist/{agent → services/agent}/tools/mindshare/tools.js +0 -0
  110. /package/dist/{agent → services/agent}/tools/read-skill-tool.js +0 -0
  111. /package/dist/{agent → services/agent}/tools/ta/index.js +0 -0
  112. /package/dist/{agent → services/agent}/tools/ta/indicators.js +0 -0
  113. /package/dist/{agent → services/agent}/types.js +0 -0
  114. /package/dist/{ai-providers.js → services/ai-providers.js} +0 -0
@@ -0,0 +1,66 @@
1
+ export const AI_PROVIDERS = [
2
+ {
3
+ id: 'openai',
4
+ label: 'OpenAI',
5
+ package: '@ai-sdk/openai',
6
+ envVar: 'OPENAI_API_KEY',
7
+ models: { validation: 'gpt-4o-mini', generation: 'gpt-5-mini', runtime: 'gpt-5-mini' },
8
+ },
9
+ {
10
+ id: 'anthropic',
11
+ label: 'Anthropic',
12
+ package: '@ai-sdk/anthropic',
13
+ envVar: 'ANTHROPIC_API_KEY',
14
+ models: {
15
+ validation: 'claude-haiku-4-5-20251001',
16
+ generation: 'claude-haiku-4-5',
17
+ runtime: 'claude-haiku-4-5',
18
+ },
19
+ },
20
+ {
21
+ id: 'google',
22
+ label: 'Google',
23
+ package: '@ai-sdk/google',
24
+ envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
25
+ models: {
26
+ validation: 'gemini-2.0-flash',
27
+ generation: 'gemini-3-flash-preview',
28
+ runtime: 'gemini-3-flash-preview',
29
+ },
30
+ },
31
+ {
32
+ id: 'xai',
33
+ label: 'xAI',
34
+ package: '@ai-sdk/xai',
35
+ envVar: 'XAI_API_KEY',
36
+ models: {
37
+ validation: 'grok-2',
38
+ generation: 'grok-4-1-fast-reasoning',
39
+ runtime: 'grok-4-1-fast-reasoning',
40
+ },
41
+ },
42
+ {
43
+ id: 'openrouter',
44
+ label: 'OpenRouter',
45
+ package: '@openrouter/ai-sdk-provider',
46
+ envVar: 'OPENROUTER_API_KEY',
47
+ models: {
48
+ validation: 'openai/gpt-4o-mini',
49
+ generation: 'openai/gpt-5.1-mini',
50
+ runtime: 'openai/gpt-5.1-mini',
51
+ },
52
+ },
53
+ ];
54
+ /**
55
+ * All env-var names used by AI providers.
56
+ * Used to clear shell-inherited keys before loading an agent's .env,
57
+ * so only the agent's chosen provider is active.
58
+ */
59
+ export const AI_PROVIDER_ENV_VARS = AI_PROVIDERS.map((p) => p.envVar);
60
+ export function getProvider(id) {
61
+ const provider = AI_PROVIDERS.find((p) => p.id === id);
62
+ if (!provider) {
63
+ throw new Error(`Unknown AI provider: ${id}`);
64
+ }
65
+ return provider;
66
+ }
@@ -1,3 +1,4 @@
1
+ import { loadCredentials, } from '@zhive/sdk';
1
2
  import axios from 'axios';
2
3
  import fsExtra from 'fs-extra';
3
4
  import * as fs from 'fs/promises';
@@ -174,3 +175,21 @@ function parseTimeframes(raw) {
174
175
  }
175
176
  return parsed;
176
177
  }
178
+ export async function findAgentByName(name) {
179
+ const agents = await scanAgents();
180
+ const agent = agents.find((a) => a.name === name);
181
+ if (!agent) {
182
+ return null;
183
+ }
184
+ return agent;
185
+ }
186
+ export function getCredentialsPath(agentDir, agentName) {
187
+ const sanitized = agentName.replace(/[^a-zA-Z0-9-_]/g, '-');
188
+ const credPath = path.join(agentDir, `hive-${sanitized}.json`);
189
+ return credPath;
190
+ }
191
+ export async function loadAgentCredentials(agentDir, agentName) {
192
+ const credPath = getCredentialsPath(agentDir, agentName);
193
+ const credentials = await loadCredentials(credPath);
194
+ return credentials;
195
+ }
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { fileURLToPath } from 'node:url';
3
+ import * as path from 'node:path';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const FIXTURES_DIR = path.join(__dirname, '../../../__fixtures__/mock-hive');
6
+ vi.mock('./constant.js', () => ({
7
+ getHiveDir: vi.fn(() => FIXTURES_DIR),
8
+ HIVE_API_URL: 'http://localhost:6969',
9
+ }));
10
+ vi.mock('./ai-providers.js', () => ({
11
+ AI_PROVIDERS: [
12
+ { label: 'OpenAI', package: '@ai-sdk/openai', envVar: 'OPENAI_API_KEY' },
13
+ { label: 'Anthropic', package: '@ai-sdk/anthropic', envVar: 'ANTHROPIC_API_KEY' },
14
+ ],
15
+ }));
16
+ vi.mock('@zhive/sdk', () => ({
17
+ loadCredentials: vi.fn(),
18
+ }));
19
+ import { loadCredentials } from '@zhive/sdk';
20
+ import { findAgentByName, getCredentialsPath, loadAgentCredentials, scanAgents, } from './agent.js';
21
+ const mockLoadCredentials = loadCredentials;
22
+ describe('getCredentialsPath', () => {
23
+ it('constructs correct path with simple agent name', () => {
24
+ const result = getCredentialsPath('/mock/.zhive/agents/my-agent', 'my-agent');
25
+ expect(result).toBe('/mock/.zhive/agents/my-agent/hive-my-agent.json');
26
+ });
27
+ it('sanitizes special characters in agent name', () => {
28
+ const result = getCredentialsPath('/mock/.zhive/agents/my-agent', 'my@agent!test');
29
+ expect(result).toBe('/mock/.zhive/agents/my-agent/hive-my-agent-test.json');
30
+ });
31
+ it('allows underscores and hyphens in agent name', () => {
32
+ const result = getCredentialsPath('/mock/.zhive/agents/test_agent', 'test_agent-v2');
33
+ expect(result).toBe('/mock/.zhive/agents/test_agent/hive-test_agent-v2.json');
34
+ });
35
+ });
36
+ describe('loadAgentCredentials', () => {
37
+ beforeEach(() => {
38
+ vi.clearAllMocks();
39
+ });
40
+ it('delegates to loadCredentials with correct path', async () => {
41
+ const mockCredentials = { apiKey: 'test-api-key' };
42
+ mockLoadCredentials.mockResolvedValue(mockCredentials);
43
+ const result = await loadAgentCredentials('/mock/.zhive/agents/test-agent', 'test-agent');
44
+ expect(mockLoadCredentials).toHaveBeenCalledWith('/mock/.zhive/agents/test-agent/hive-test-agent.json');
45
+ expect(result).toEqual(mockCredentials);
46
+ });
47
+ it('returns null when loadCredentials returns null', async () => {
48
+ mockLoadCredentials.mockResolvedValue(null);
49
+ const result = await loadAgentCredentials('/mock/.zhive/agents/test-agent', 'test-agent');
50
+ expect(result).toBeNull();
51
+ });
52
+ });
53
+ describe('scanAgents', () => {
54
+ beforeEach(() => {
55
+ vi.clearAllMocks();
56
+ });
57
+ it('discovers all valid agents from fixtures directory', async () => {
58
+ const result = await scanAgents();
59
+ expect(result).toHaveLength(3);
60
+ const names = result.map((a) => a.name).sort();
61
+ expect(names).toEqual(['agent-no-skills', 'empty-agent', 'test-agent']);
62
+ });
63
+ it('loads agent config with correct properties', async () => {
64
+ const result = await scanAgents();
65
+ const testAgent = result.find((a) => a.name === 'test-agent');
66
+ expect(testAgent).toBeDefined();
67
+ expect(testAgent?.bio).toBe('Test agent for CLI testing');
68
+ expect(testAgent?.avatarUrl).toBe('https://example.com/avatar.png');
69
+ expect(testAgent?.agentProfile.sentiment).toBe('bullish');
70
+ expect(testAgent?.agentProfile.timeframes).toEqual(['1h', '4h']);
71
+ expect(testAgent?.agentProfile.sectors).toEqual(['defi', 'gaming']);
72
+ });
73
+ it('loads agent with different sentiment', async () => {
74
+ const result = await scanAgents();
75
+ const bearishAgent = result.find((a) => a.name === 'agent-no-skills');
76
+ expect(bearishAgent).toBeDefined();
77
+ expect(bearishAgent?.agentProfile.sentiment).toBe('bearish');
78
+ expect(bearishAgent?.agentProfile.timeframes).toEqual(['1h']);
79
+ expect(bearishAgent?.agentProfile.sectors).toEqual(['infrastructure']);
80
+ });
81
+ it('handles agent with empty sectors', async () => {
82
+ const result = await scanAgents();
83
+ const emptyAgent = result.find((a) => a.name === 'empty-agent');
84
+ expect(emptyAgent).toBeDefined();
85
+ expect(emptyAgent?.agentProfile.sentiment).toBe('neutral');
86
+ expect(emptyAgent?.agentProfile.sectors).toEqual([]);
87
+ });
88
+ });
89
+ describe('findAgentByName', () => {
90
+ beforeEach(() => {
91
+ vi.clearAllMocks();
92
+ });
93
+ it('returns null when agent name does not match', async () => {
94
+ const result = await findAgentByName('non-existent-agent');
95
+ expect(result).toBeNull();
96
+ });
97
+ it('returns agent when name matches', async () => {
98
+ const result = await findAgentByName('test-agent');
99
+ expect(result).not.toBeNull();
100
+ expect(result?.name).toBe('test-agent');
101
+ expect(result?.dir).toBe(path.join(FIXTURES_DIR, 'agents', 'test-agent'));
102
+ });
103
+ it('finds empty-agent by name', async () => {
104
+ const result = await findAgentByName('empty-agent');
105
+ expect(result).not.toBeNull();
106
+ expect(result?.name).toBe('empty-agent');
107
+ expect(result?.bio).toBe('Empty agent with no skills for testing');
108
+ });
109
+ it('finds agent-no-skills by name', async () => {
110
+ const result = await findAgentByName('agent-no-skills');
111
+ expect(result).not.toBeNull();
112
+ expect(result?.name).toBe('agent-no-skills');
113
+ expect(result?.bio).toBe('Agent without skills directory for testing');
114
+ });
115
+ });
@@ -127,30 +127,36 @@ export function resolveModelInfo() {
127
127
  }
128
128
  return { provider: 'unknown', modelId: 'unknown', source: 'unknown' };
129
129
  }
130
+ async function _loadModelForTier(tier) {
131
+ const agentKeys = getAgentProviderKeys();
132
+ const sortedProviders = [
133
+ ...AI_PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
134
+ ...AI_PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
135
+ ];
136
+ for (const provider of sortedProviders) {
137
+ const keyValue = process.env[provider.envVar];
138
+ if (keyValue && keyValue.trim().length > 0) {
139
+ const overrideModel = process.env.HIVE_MODEL;
140
+ const modelId = tier === 'runtime' && overrideModel ? overrideModel : provider.models[tier];
141
+ const model = await provider.load(modelId);
142
+ return model;
143
+ }
144
+ }
145
+ throw new Error('No AI provider API key found in environment. ' +
146
+ 'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY');
147
+ }
148
+ let _screenModelPromise = null;
149
+ export function getScreenModel() {
150
+ if (_screenModelPromise) {
151
+ return _screenModelPromise;
152
+ }
153
+ _screenModelPromise = _loadModelForTier('validation');
154
+ return _screenModelPromise;
155
+ }
130
156
  export function getModel() {
131
157
  if (_modelPromise) {
132
158
  return _modelPromise;
133
159
  }
134
- _modelPromise = (async () => {
135
- const info = resolveModelInfo();
136
- if (info.provider === 'unknown') {
137
- throw new Error('No AI provider API key found in environment. ' +
138
- 'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY');
139
- }
140
- const agentKeys = getAgentProviderKeys();
141
- const sortedProviders = [
142
- ...AI_PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
143
- ...AI_PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
144
- ];
145
- for (const provider of sortedProviders) {
146
- const keyValue = process.env[provider.envVar];
147
- if (keyValue && keyValue.trim().length > 0) {
148
- const modelId = info.modelId;
149
- const model = await provider.load(modelId);
150
- return model;
151
- }
152
- }
153
- throw new Error('Unreachable: resolveModelInfo succeeded but no provider found');
154
- })();
160
+ _modelPromise = _loadModelForTier('runtime');
155
161
  return _modelPromise;
156
162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhive/cli",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for bootstrapping zHive AI Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "build": "tsc -p tsconfig.build.json",
20
- "dev": "tsx src/index.tsx",
20
+ "dev": "tsx src/index.ts",
21
21
  "start": "node dist/index.js",
22
22
  "deploy": "node scripts/deploy.cjs",
23
23
  "analyze": "tsx src/sandbox/analyze.ts",
@@ -30,11 +30,12 @@
30
30
  "@ai-sdk/google": "^3.0.0",
31
31
  "@ai-sdk/openai": "^3.0.25",
32
32
  "@ai-sdk/xai": "^3.0.0",
33
- "@zhive/sdk": "^0.5.2",
34
33
  "@openrouter/ai-sdk-provider": "^0.4.0",
34
+ "@zhive/sdk": "^0.5.2",
35
35
  "ai": "^6.0.71",
36
36
  "axios": "^1.6.0",
37
37
  "chalk": "^5.3.0",
38
+ "commander": "^14.0.3",
38
39
  "dotenv": "^16.0.0",
39
40
  "fs-extra": "^11.2.0",
40
41
  "ink": "^5.1.0",
package/dist/agent/app.js DELETED
@@ -1,122 +0,0 @@
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
- }
@@ -1,12 +0,0 @@
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
- }
@@ -1,81 +0,0 @@
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
- }
@@ -1,65 +0,0 @@
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
- }