@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,206 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } 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('../../../shared/config/constant.js', () => ({
7
+ getHiveDir: vi.fn(() => FIXTURES_DIR),
8
+ HIVE_API_URL: 'http://localhost:6969',
9
+ }));
10
+ vi.mock('../../../shared/config/ai-providers.js', () => ({
11
+ AI_PROVIDERS: [{ label: 'OpenAI', package: '@ai-sdk/openai', envVar: 'OPENAI_API_KEY' }],
12
+ }));
13
+ vi.mock('@zhive/sdk', async () => {
14
+ const actual = await vi.importActual('@zhive/sdk');
15
+ return {
16
+ ...actual,
17
+ HiveClient: vi.fn().mockImplementation(() => ({
18
+ getUnpredictedRounds: vi.fn(),
19
+ })),
20
+ loadCredentials: vi.fn(),
21
+ TIMEFRAME_DURATION_MS: {
22
+ H1: 3600000,
23
+ H4: 14400000,
24
+ H24: 86400000,
25
+ },
26
+ Timeframe: {
27
+ H1: 'H1',
28
+ H4: 'H4',
29
+ H24: 'H24',
30
+ },
31
+ };
32
+ });
33
+ import { HiveClient, loadCredentials } from '@zhive/sdk';
34
+ import { createMegathreadListCommand } from './list.js';
35
+ const MockHiveClient = HiveClient;
36
+ const mockLoadCredentials = loadCredentials;
37
+ function createMockActiveRound(overrides = {}) {
38
+ return {
39
+ roundId: 'round-123',
40
+ projectId: 'bitcoin',
41
+ durationMs: 3600000,
42
+ ...overrides,
43
+ };
44
+ }
45
+ describe('createMegathreadListCommand', () => {
46
+ let consoleLogSpy;
47
+ let consoleErrorSpy;
48
+ let processExitSpy;
49
+ let consoleOutput;
50
+ let consoleErrorOutput;
51
+ let mockGetUnpredictedRounds;
52
+ beforeEach(() => {
53
+ vi.clearAllMocks();
54
+ consoleOutput = [];
55
+ consoleErrorOutput = [];
56
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation((...args) => {
57
+ consoleOutput.push(args.join(' '));
58
+ });
59
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
60
+ consoleErrorOutput.push(args.join(' '));
61
+ });
62
+ processExitSpy = vi
63
+ .spyOn(process, 'exit')
64
+ .mockImplementation((code) => {
65
+ throw new Error(`process.exit(${code})`);
66
+ });
67
+ mockGetUnpredictedRounds = vi.fn();
68
+ MockHiveClient.mockImplementation(() => ({
69
+ getUnpredictedRounds: mockGetUnpredictedRounds,
70
+ }));
71
+ });
72
+ afterEach(() => {
73
+ consoleLogSpy.mockRestore();
74
+ consoleErrorSpy.mockRestore();
75
+ processExitSpy.mockRestore();
76
+ });
77
+ describe('timeframe validation', () => {
78
+ it('shows error for invalid timeframe value', async () => {
79
+ const command = createMegathreadListCommand();
80
+ await expect(command.parseAsync(['--agent', 'test-agent', '--timeframe', '2h'], { from: 'user' })).rejects.toThrow('process.exit(1)');
81
+ expect(consoleErrorOutput.join('\n')).toContain('Invalid timeframes: 2h');
82
+ expect(consoleErrorOutput.join('\n')).toContain('Valid values: 1h, 4h, 24h');
83
+ });
84
+ it('shows error for multiple invalid timeframes', async () => {
85
+ const command = createMegathreadListCommand();
86
+ await expect(command.parseAsync(['--agent', 'test-agent', '--timeframe', '2h,5h'], { from: 'user' })).rejects.toThrow('process.exit(1)');
87
+ expect(consoleErrorOutput.join('\n')).toContain('Invalid timeframes: 2h, 5h');
88
+ });
89
+ it('accepts valid timeframe values', async () => {
90
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
91
+ mockGetUnpredictedRounds.mockResolvedValue([]);
92
+ const command = createMegathreadListCommand();
93
+ await command.parseAsync(['--agent', 'test-agent', '--timeframe', '1h,4h'], { from: 'user' });
94
+ expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(['1h', '4h']);
95
+ });
96
+ it('accepts single valid timeframe', async () => {
97
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
98
+ mockGetUnpredictedRounds.mockResolvedValue([]);
99
+ const command = createMegathreadListCommand();
100
+ await command.parseAsync(['--agent', 'test-agent', '--timeframe', '24h'], { from: 'user' });
101
+ expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(['24h']);
102
+ });
103
+ it('passes undefined when no timeframe filter specified', async () => {
104
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
105
+ mockGetUnpredictedRounds.mockResolvedValue([]);
106
+ const command = createMegathreadListCommand();
107
+ await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
108
+ expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(undefined);
109
+ });
110
+ });
111
+ describe('agent validation', () => {
112
+ it('shows error when agent not found and lists available agents', async () => {
113
+ const command = createMegathreadListCommand();
114
+ await expect(command.parseAsync(['--agent', 'non-existent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
115
+ expect(consoleErrorOutput.join('\n')).toContain('Agent "non-existent" not found');
116
+ expect(consoleErrorOutput.join('\n')).toContain('Available agents:');
117
+ expect(consoleErrorOutput.join('\n')).toContain('test-agent');
118
+ expect(consoleErrorOutput.join('\n')).toContain('empty-agent');
119
+ expect(consoleErrorOutput.join('\n')).toContain('agent-no-skills');
120
+ });
121
+ });
122
+ describe('credentials validation', () => {
123
+ it('shows error when credentials are missing', async () => {
124
+ mockLoadCredentials.mockResolvedValue(null);
125
+ const command = createMegathreadListCommand();
126
+ await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
127
+ expect(consoleErrorOutput.join('\n')).toContain('No credentials found for agent "test-agent"');
128
+ });
129
+ it('shows error when credentials have no API key', async () => {
130
+ mockLoadCredentials.mockResolvedValue({ apiKey: null });
131
+ const command = createMegathreadListCommand();
132
+ await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
133
+ expect(consoleErrorOutput.join('\n')).toContain('No credentials found');
134
+ });
135
+ });
136
+ describe('rounds display', () => {
137
+ it('shows message when no unpredicted rounds available', async () => {
138
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
139
+ mockGetUnpredictedRounds.mockResolvedValue([]);
140
+ const command = createMegathreadListCommand();
141
+ await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
142
+ const output = consoleOutput.join('\n');
143
+ expect(output).toContain('Unpredicted Rounds for test-agent');
144
+ expect(output).toContain('No unpredicted rounds available');
145
+ });
146
+ it('displays rounds in table format', async () => {
147
+ const mockRounds = [
148
+ createMockActiveRound({ roundId: 'round-1', projectId: 'bitcoin', durationMs: 3600000 }),
149
+ createMockActiveRound({ roundId: 'round-2', projectId: 'ethereum', durationMs: 14400000 }),
150
+ ];
151
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
152
+ mockGetUnpredictedRounds.mockResolvedValue(mockRounds);
153
+ const command = createMegathreadListCommand();
154
+ await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
155
+ const output = consoleOutput.join('\n');
156
+ expect(output).toContain('Unpredicted Rounds for test-agent');
157
+ expect(output).toContain('Round ID');
158
+ expect(output).toContain('Token');
159
+ expect(output).toContain('Timeframe');
160
+ expect(output).toContain('round-1');
161
+ expect(output).toContain('bitcoin');
162
+ expect(output).toContain('round-2');
163
+ expect(output).toContain('ethereum');
164
+ expect(output).toContain('Total: 2 round(s)');
165
+ });
166
+ it('shows fallback duration when timeframe not recognized', async () => {
167
+ const mockRounds = [
168
+ createMockActiveRound({ roundId: 'round-1', projectId: 'bitcoin', durationMs: 7200000 }),
169
+ ];
170
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
171
+ mockGetUnpredictedRounds.mockResolvedValue(mockRounds);
172
+ const command = createMegathreadListCommand();
173
+ await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
174
+ const output = consoleOutput.join('\n');
175
+ expect(output).toContain('7200000ms');
176
+ });
177
+ });
178
+ describe('API error handling', () => {
179
+ it('shows error when API call fails', async () => {
180
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
181
+ mockGetUnpredictedRounds.mockRejectedValue(new Error('Network error'));
182
+ const command = createMegathreadListCommand();
183
+ await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
184
+ expect(consoleErrorOutput.join('\n')).toContain('Failed to fetch unpredicted rounds');
185
+ expect(consoleErrorOutput.join('\n')).toContain('Network error');
186
+ });
187
+ it('handles non-Error exceptions', async () => {
188
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
189
+ mockGetUnpredictedRounds.mockRejectedValue('String error');
190
+ const command = createMegathreadListCommand();
191
+ await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
192
+ expect(consoleErrorOutput.join('\n')).toContain('Failed to fetch unpredicted rounds');
193
+ expect(consoleErrorOutput.join('\n')).toContain('String error');
194
+ });
195
+ });
196
+ describe('works with different fixture agents', () => {
197
+ it('works with empty-agent', async () => {
198
+ mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
199
+ mockGetUnpredictedRounds.mockResolvedValue([]);
200
+ const command = createMegathreadListCommand();
201
+ await command.parseAsync(['--agent', 'empty-agent'], { from: 'user' });
202
+ const output = consoleOutput.join('\n');
203
+ expect(output).toContain('Unpredicted Rounds for empty-agent');
204
+ });
205
+ });
206
+ });
@@ -1,9 +1,14 @@
1
+ import { Command } from 'commander';
1
2
  import { render } from 'ink';
2
3
  import React from 'react';
3
4
  import { showWelcome } from '../../shared/welcome.js';
4
5
  import { MigrateApp } from '../ui/MigrateApp.js';
5
- export const migrateTemplateCommand = async () => {
6
- await showWelcome();
7
- const { waitUntilExit } = render(React.createElement(MigrateApp));
8
- await waitUntilExit();
6
+ export const createMigrateTemplatesCommand = () => {
7
+ return new Command('migrate-templates')
8
+ .description('Migrate old-style agents')
9
+ .action(async () => {
10
+ await showWelcome();
11
+ const { waitUntilExit } = render(React.createElement(MigrateApp));
12
+ await waitUntilExit();
13
+ });
9
14
  };
@@ -1,17 +1,22 @@
1
+ import { Command } from 'commander';
1
2
  import { access } from 'fs/promises';
2
3
  import { join } from 'path';
3
4
  import { runHeadless } from '../run-headless.js';
4
5
  import { loadAgentEnv } from '../../../shared/config/env-loader.js';
5
- export const runCommand = async () => {
6
- // Headless agent run — no TUI, just console output.
7
- // Used by start-all to spawn agents as child processes.
8
- const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
9
- .then(() => true)
10
- .catch(() => false);
11
- if (!isAgentDir) {
12
- console.error('Error: "run" must be called from an agent directory (with SOUL.md)');
13
- process.exit(1);
14
- }
15
- await loadAgentEnv();
16
- await runHeadless();
6
+ export const createRunCommand = () => {
7
+ return new Command('run')
8
+ .description('Run agent headless (no TUI, used by start-all)')
9
+ .action(async () => {
10
+ // Headless agent run — no TUI, just console output.
11
+ // Used by start-all to spawn agents as child processes.
12
+ const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
13
+ .then(() => true)
14
+ .catch(() => false);
15
+ if (!isAgentDir) {
16
+ console.error('Error: "run" must be called from an agent directory (with SOUL.md)');
17
+ process.exit(1);
18
+ }
19
+ await loadAgentEnv();
20
+ await runHeadless();
21
+ });
17
22
  };
@@ -1,8 +1,9 @@
1
1
  import { HiveAgent } from '@zhive/sdk';
2
- import { initializeAgentRuntime, createMegathreadRoundHandler, } from '../../shared/agent/agent-runtime.js';
3
2
  import { HIVE_API_URL, HIVE_FRONTEND_URL } from '../../shared/config/constant.js';
4
3
  import { resolveModelInfo } from '../../shared/config/ai-providers.js';
5
4
  import { formatTokenCount, formatTokenUsage } from '../../shared/agent/utils.js';
5
+ import { initializeAgentRuntime } from '../../shared/agent/runtime.js';
6
+ import { createMegathreadRoundHandler } from '../../shared/agent/handler.js';
6
7
  function formatUsageLine(usage) {
7
8
  const { input, output, tools } = formatTokenUsage(usage);
8
9
  const toolSuffix = tools !== null ? ` \u00b7 ${tools}` : '';
@@ -1,3 +1,4 @@
1
+ import { Command } from 'commander';
1
2
  import { render } from 'ink';
2
3
  import React from 'react';
3
4
  import { App } from '../ui/app.js';
@@ -6,43 +7,47 @@ import { showHoneycombBoot } from '../ui/HoneycombBoot.js';
6
7
  import chalk from 'chalk';
7
8
  import { symbols } from '../../shared/theme.js';
8
9
  import { loadAgentEnv } from '../../../shared/config/env-loader.js';
9
- export const startCommand = async () => {
10
- // Detect if cwd is an agent directory (has SOUL.md).
11
- // When called via agent's "npm start", cwd is the agent dir.
12
- const { access } = await import('fs/promises');
13
- const { join } = await import('path');
14
- const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
15
- .then(() => true)
16
- .catch(() => false);
17
- if (isAgentDir) {
18
- // Direct agent run — cwd is already the agent directory.
19
- await loadAgentEnv();
20
- setupProcessLifecycle();
21
- const { waitUntilExit } = render(React.createElement(App));
22
- await waitUntilExit();
23
- }
24
- else {
25
- // Interactive agent selection
26
- let selectedAgent = null;
27
- const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, {
28
- onSelect: (agent) => {
29
- selectedAgent = agent;
30
- },
31
- }));
32
- await waitForSelect();
33
- if (selectedAgent) {
34
- const picked = selectedAgent;
35
- await showHoneycombBoot(picked.name);
36
- // Clear screen + scrollback so boot animation and agent picker
37
- // don't appear when scrolling up in the agent TUI.
38
- process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
39
- process.chdir(picked.dir);
10
+ export const createStartCommand = () => {
11
+ return new Command('start')
12
+ .description('Start an agent (auto-detects agent dir)')
13
+ .action(async () => {
14
+ // Detect if cwd is an agent directory (has SOUL.md).
15
+ // When called via agent's "npm start", cwd is the agent dir.
16
+ const { access } = await import('fs/promises');
17
+ const { join } = await import('path');
18
+ const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
19
+ .then(() => true)
20
+ .catch(() => false);
21
+ if (isAgentDir) {
22
+ // Direct agent run cwd is already the agent directory.
40
23
  await loadAgentEnv();
41
24
  setupProcessLifecycle();
42
25
  const { waitUntilExit } = render(React.createElement(App));
43
26
  await waitUntilExit();
44
27
  }
45
- }
28
+ else {
29
+ // Interactive agent selection
30
+ let selectedAgent = null;
31
+ const { waitUntilExit: waitForSelect } = render(React.createElement(SelectAgentApp, {
32
+ onSelect: (agent) => {
33
+ selectedAgent = agent;
34
+ },
35
+ }));
36
+ await waitForSelect();
37
+ if (selectedAgent) {
38
+ const picked = selectedAgent;
39
+ await showHoneycombBoot(picked.name);
40
+ // Clear screen + scrollback so boot animation and agent picker
41
+ // don't appear when scrolling up in the agent TUI.
42
+ process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
43
+ process.chdir(picked.dir);
44
+ await loadAgentEnv();
45
+ setupProcessLifecycle();
46
+ const { waitUntilExit } = render(React.createElement(App));
47
+ await waitUntilExit();
48
+ }
49
+ }
50
+ });
46
51
  };
47
52
  const exitImmediately = (exitCode = 0) => {
48
53
  process.exit(exitCode);
@@ -1,11 +1,12 @@
1
1
  import { HiveAgent } from '@zhive/sdk';
2
2
  import { useEffect, useRef, useState } from 'react';
3
- import { createMegathreadRoundBatchHandler, createMegathreadRoundHandler, initializeAgentRuntime, } from '../../../shared/agent/agent-runtime.js';
4
3
  import { extractErrorMessage } from '../../../shared/agent/utils.js';
5
4
  import { resolveModelInfo } from '../../../shared/config/ai-providers.js';
6
5
  import { fetchBulkStats } from '../../../shared/config/agent.js';
7
6
  import { HIVE_API_URL } from '../../../shared/config/constant.js';
8
7
  import { usePollActivity } from './usePollActivity.js';
8
+ import { initializeAgentRuntime } from '../../../shared/agent/runtime.js';
9
+ import { createMegathreadRoundBatchHandler, createMegathreadRoundHandler, } from '../../../shared/agent/handler.js';
9
10
  const STATS_POLL_INTERVAL_MS = 5 * 60 * 1_000;
10
11
  export function useAgent() {
11
12
  const [connected, setConnected] = useState(false);
@@ -1,5 +1,5 @@
1
1
  import { processMegathreadRound } from '../../../../shared/agent/analysis.js';
2
- import { initializeAgentRuntime } from '../../../../shared/agent/agent-runtime.js';
2
+ import { initializeAgentRuntime } from '../../../../shared/agent/runtime.js';
3
3
  /**
4
4
  * Run a backtest against an agent configuration.
5
5
  */
@@ -1,3 +1,4 @@
1
+ import { Command } from 'commander';
1
2
  import React from 'react';
2
3
  import { render } from 'ink';
3
4
  import { showWelcome } from '../../shared/welcome.js';
@@ -5,20 +6,24 @@ import { styled, symbols } from '../../shared/theme.js';
5
6
  import { AgentProcessManager } from '../AgentProcessManager.js';
6
7
  import { Dashboard } from '../ui/Dashboard.js';
7
8
  import { fetchBulkStats, scanAgents, sortAgentsByHoney } from '../../../shared/config/agent.js';
8
- export async function startAllCommand() {
9
- // Run welcome animation and scan agents in parallel
10
- const results = await Promise.all([showWelcome(), scanAgents()]);
11
- const discovered = results[1];
12
- if (discovered.length === 0) {
13
- console.log(`\n ${styled.honey(symbols.hive)} ${styled.red('No agents found in ~/.zhive/agents/')}\n`);
14
- console.log(` ${styled.gray('Create agents with:')} ${styled.white('npx @zhive/cli@latest create')}\n`);
15
- return;
16
- }
17
- const names = discovered.map((a) => a.name);
18
- const statsMap = await fetchBulkStats(names);
19
- const sortedDiscovered = sortAgentsByHoney(discovered, statsMap);
20
- const manager = new AgentProcessManager();
21
- manager.spawnAll(sortedDiscovered);
22
- const { waitUntilExit } = render(React.createElement(Dashboard, { manager, statsMap }));
23
- await waitUntilExit();
24
- }
9
+ export const createStartAllCommand = () => {
10
+ return new Command('start-all')
11
+ .description('Start all agents')
12
+ .action(async () => {
13
+ // Run welcome animation and scan agents in parallel
14
+ const results = await Promise.all([showWelcome(), scanAgents()]);
15
+ const discovered = results[1];
16
+ if (discovered.length === 0) {
17
+ console.log(`\n ${styled.honey(symbols.hive)} ${styled.red('No agents found in ~/.zhive/agents/')}\n`);
18
+ console.log(` ${styled.gray('Create agents with:')} ${styled.white('npx @zhive/cli@latest create')}\n`);
19
+ return;
20
+ }
21
+ const names = discovered.map((a) => a.name);
22
+ const statsMap = await fetchBulkStats(names);
23
+ const sortedDiscovered = sortAgentsByHoney(discovered, statsMap);
24
+ const manager = new AgentProcessManager();
25
+ manager.spawnAll(sortedDiscovered);
26
+ const { waitUntilExit } = render(React.createElement(Dashboard, { manager, statsMap }));
27
+ await waitUntilExit();
28
+ });
29
+ };
package/dist/index.js CHANGED
@@ -1,60 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import { Command } from 'commander';
2
3
  import { createRequire } from 'module';
3
- import { createCommand } from './commands/create/commands/index.js';
4
- import { listCommand } from './commands/list/commands/index.js';
5
- import { migrateTemplateCommand } from './commands/migrate-templates/commands/index.js';
6
- import { runCommand } from './commands/run/commands/index.js';
7
- import { startAllCommand } from './commands/start-all/commands/index.js';
8
- import { startCommand } from './commands/start/commands/index.js';
4
+ import { createAgentCommand } from './commands/agent/commands/index.js';
5
+ import { createCreateCommand } from './commands/create/commands/index.js';
6
+ import { createListCommand } from './commands/list/commands/index.js';
7
+ import { createMegathreadCommand } from './commands/megathread/commands/index.js';
8
+ import { createStartCommand } from './commands/start/commands/index.js';
9
+ import { createStartAllCommand } from './commands/start-all/commands/index.js';
10
+ import { createRunCommand } from './commands/run/commands/index.js';
11
+ import { createMigrateTemplatesCommand } from './commands/migrate-templates/commands/index.js';
9
12
  const require = createRequire(import.meta.url);
10
- const pkg = require('../package.json');
11
- const HELP_TEXT = `@zhive/cli v${pkg.version}
12
-
13
- Usage:
14
- npx @zhive/cli@latest create [agent-name] Scaffold a new zHive agent
15
- npx @zhive/cli@latest list List existing agents
16
- npx @zhive/cli@latest start Start an agent (auto-detects agent dir)
17
- npx @zhive/cli@latest start-all Start all agents
18
- npx @zhive/cli@latest run Run agent headless (no TUI, used by start-all)
19
- npx @zhive/cli@latest migrate-templates Migrate old-style agents
20
- npx @zhive/cli@latest --help Show this help message
21
- npx @zhive/cli@latest --version Print version
22
-
23
- Examples:
24
- npx @zhive/cli@latest create alpha Creates ~/.zhive/agents/alpha/
25
- npx @zhive/cli@latest create Interactive setup
26
- npx @zhive/cli@latest list Show all agents
27
- npx @zhive/cli@latest start Pick an agent and run it
28
- npx @zhive/cli@latest start-all Launch all agents as child processes`;
29
- const command = process.argv[2];
30
- if (command === '--version' || command === '-v') {
31
- console.log(pkg.version);
32
- process.exit(0);
33
- }
34
- if (!command || command === '--help' || command === '-h') {
35
- console.log(HELP_TEXT);
36
- process.exit(0);
37
- }
38
- if (command === 'list') {
39
- await listCommand();
40
- }
41
- else if (command === 'start-all') {
42
- await startAllCommand();
43
- }
44
- else if (command === 'create') {
45
- await createCommand(process.argv);
46
- }
47
- else if (command === 'migrate-templates') {
48
- await migrateTemplateCommand();
49
- }
50
- else if (command === 'run') {
51
- await runCommand();
52
- }
53
- else if (command === 'start') {
54
- await startCommand();
55
- }
56
- else {
57
- console.error(`Unknown command: ${command}\n`);
58
- console.log(HELP_TEXT);
59
- process.exit(1);
60
- }
13
+ const packageJson = require('../package.json');
14
+ const program = new Command();
15
+ program.name('@zhive/cli').version(packageJson.version);
16
+ program.addCommand(createAgentCommand());
17
+ program.addCommand(createCreateCommand());
18
+ program.addCommand(createListCommand());
19
+ program.addCommand(createMegathreadCommand());
20
+ program.addCommand(createStartCommand());
21
+ program.addCommand(createStartAllCommand());
22
+ program.addCommand(createRunCommand());
23
+ program.addCommand(createMigrateTemplatesCommand());
24
+ // Show help with exit code 0 when no arguments provided
25
+ const args = process.argv.slice(2);
26
+ if (args.length === 0) {
27
+ program.help();
28
+ }
29
+ program.parse(process.argv);
@@ -1,10 +1,10 @@
1
1
  import { generateText, Output, ToolLoopAgent } from 'ai';
2
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';
3
+ import { buildAnalystPrompt, buildMegathreadPrompt } from './prompts/prompt.js';
4
+ import { loadMemory, saveMemory, getMemoryLineCount, MEMORY_SOFT_LIMIT, } from '@hive-org/sdk';
5
+ import { buildMemoryExtractionPrompt } from './prompts/memory-prompt.js';
6
6
  import { stripCodeFences } from './helpers.js';
7
- import { getModel } from './model.js';
7
+ import { getModel } from './helpers/model.js';
8
8
  // ─── Cache Helpers ─────────────────────────────────
9
9
  function cacheableSystem(content) {
10
10
  const message = {
@@ -146,7 +146,7 @@ export async function extractAndSaveMemory(sessionMessages) {
146
146
  const model = await getModel();
147
147
  const { text } = await generateText({
148
148
  model,
149
- messages: [cacheableSystem(prompt.system), { role: 'user', content: prompt.prompt }],
149
+ prompt,
150
150
  });
151
151
  const cleaned = stripCodeFences(text);
152
152
  await saveMemory(cleaned);
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from 'fs';
2
- import { AI_PROVIDER_ENV_VARS } from './ai-providers.js';
2
+ import { AI_PROVIDER_ENV_VARS } from '../ai-providers.js';
3
3
  let _agentProviderKeys = new Set();
4
4
  /**
5
5
  * Provider env-var names declared in the agent's .env file.
@@ -1,5 +1,5 @@
1
- import { AI_PROVIDERS } from '../ai-providers.js';
2
- import { getAgentProviderKeys } from '../load-agent-env.js';
1
+ import { AI_PROVIDERS } from '../../ai-providers.js';
2
+ import { getAgentProviderKeys } from '../env.js';
3
3
  const PROVIDERS = [
4
4
  {
5
5
  label: 'Anthropic',
@@ -1,9 +1,25 @@
1
1
  export function buildMemoryExtractionPrompt(context) {
2
2
  const { currentMemory, sessionMessages, lineCount } = context;
3
- // ── System (static — cached by providers) ──
4
- const system = `You are an AI trading agent's memory system. Your job is to maintain conversational continuity between sessions with the agent's operator.
3
+ let sessionSection = '';
4
+ if (sessionMessages.length > 0) {
5
+ const listed = sessionMessages
6
+ .map((m) => `${m.role === 'user' ? 'Operator' : 'Agent'}: ${m.content}`)
7
+ .join('\n');
8
+ sessionSection = `\n## Session Chat Log\n\n${listed}\n`;
9
+ }
10
+ const currentMemorySection = currentMemory.trim().length > 0
11
+ ? `\n## Current MEMORY.md\n\n\`\`\`markdown\n${currentMemory}\n\`\`\`\n`
12
+ : '\n## Current MEMORY.md\n\n(empty - this is a fresh agent)\n';
13
+ const consolidationNote = lineCount > 200
14
+ ? `\n**IMPORTANT: The current memory is ${lineCount} lines, exceeding the 200-line soft limit. Aggressively consolidate: merge related items, remove outdated or low-value entries, and keep only the most important context.**\n`
15
+ : '';
16
+ const prompt = `You are an AI trading agent's memory system. Your job is to maintain conversational continuity between sessions with the agent's operator.
17
+ ${currentMemorySection}${consolidationNote}
18
+ ## Session Activity
19
+ ${sessionSection}
20
+ ## Instructions
5
21
 
6
- Review the session data and update the agent's MEMORY.md file. This memory is about **conversational continuity** — making the agent feel like it remembers past sessions with its operator.
22
+ Review the session chat log above and update the agent's MEMORY.md file. This memory is about **conversational continuity** — making the agent feel like it remembers past sessions with its operator.
7
23
 
8
24
  Focus on extracting:
9
25
  1. **Topics discussed** — what subjects came up in conversation (e.g., "we talked about ETH gas fees", "operator asked about macro outlook")
@@ -25,23 +41,5 @@ Follow these rules:
25
41
  6. **Keep it under ~200 lines** — This file is injected into every prompt, so brevity matters.
26
42
 
27
43
  Output the complete updated MEMORY.md content. Start with \`# Memory\` as the top-level header. Output ONLY the markdown content, no code fences or explanation.`;
28
- // ── Prompt (dynamic — changes every call) ──
29
- let sessionSection = '';
30
- if (sessionMessages.length > 0) {
31
- const listed = sessionMessages
32
- .map((m) => `${m.role === 'user' ? 'Operator' : 'Agent'}: ${m.content}`)
33
- .join('\n');
34
- sessionSection = `\n## Session Chat Log\n\n${listed}\n`;
35
- }
36
- const currentMemorySection = currentMemory.trim().length > 0
37
- ? `\n## Current MEMORY.md\n\n\`\`\`markdown\n${currentMemory}\n\`\`\`\n`
38
- : '\n## Current MEMORY.md\n\n(empty - this is a fresh agent)\n';
39
- const consolidationNote = lineCount > 200
40
- ? `\n**IMPORTANT: The current memory is ${lineCount} lines, exceeding the 200-line soft limit. Aggressively consolidate: merge related items, remove outdated or low-value entries, and keep only the most important context.**\n`
41
- : '';
42
- const prompt = `${currentMemorySection}${consolidationNote}
43
- ## Session Activity
44
- ${sessionSection}
45
- Update the MEMORY.md based on the session activity above.`;
46
- return { system, prompt };
44
+ return prompt;
47
45
  }