@zhive/cli 0.6.3 → 0.6.4

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 (101) hide show
  1. package/dist/CLAUDE.md +7 -0
  2. package/dist/backtest/CLAUDE.md +7 -0
  3. package/dist/cli.js +20 -0
  4. package/dist/commands/agent/commands/profile.js +3 -2
  5. package/dist/commands/agent/commands/profile.test.js +10 -12
  6. package/dist/commands/doctor/commands/index.js +93 -0
  7. package/dist/commands/megathread/commands/create-comment.js +4 -9
  8. package/dist/commands/megathread/commands/create-comment.test.js +15 -173
  9. package/dist/commands/megathread/commands/list.js +5 -5
  10. package/dist/commands/megathread/commands/list.test.js +14 -14
  11. package/dist/commands/start/commands/prediction.js +3 -4
  12. package/dist/commands/start/hooks/useChat.js +40 -41
  13. package/dist/commands/start/services/command-registry.js +1 -1
  14. package/dist/index.js +2 -0
  15. package/dist/{agent → services/agent}/analysis.js +5 -5
  16. package/dist/{load-agent-env.js → services/agent/env.js} +1 -1
  17. package/dist/{agent → services/agent/helpers}/model.js +2 -2
  18. package/dist/{agent → services/agent/prompts}/memory-prompt.js +20 -22
  19. package/dist/{agent → services/agent/prompts}/prompt.js +80 -54
  20. package/dist/{agent → services/agent}/tools/market/client.js +1 -1
  21. package/dist/{agent → services/agent}/tools/mindshare/client.js +1 -1
  22. package/dist/{agents.js → services/config/agent.js} +2 -2
  23. package/dist/{config.js → services/config/config.js} +1 -7
  24. package/dist/services/config/constant.js +8 -0
  25. package/dist/shared/agent/config.js +75 -0
  26. package/dist/shared/agent/env.js +30 -0
  27. package/dist/shared/agent/helpers/model.js +92 -0
  28. package/dist/shared/ai-providers.js +66 -0
  29. package/dist/shared/config/agent.js +0 -11
  30. package/dist/shared/config/agent.test.js +4 -35
  31. package/package.json +2 -2
  32. package/dist/agent/app.js +0 -122
  33. package/dist/agent/commands/registry.js +0 -12
  34. package/dist/agent/components/AsciiTicker.js +0 -81
  35. package/dist/agent/components/CommandInput.js +0 -65
  36. package/dist/agent/components/HoneycombBoot.js +0 -291
  37. package/dist/agent/components/Spinner.js +0 -37
  38. package/dist/agent/hooks/useAgent.js +0 -480
  39. package/dist/agent/objects.js +0 -1
  40. package/dist/agent/process-lifecycle.js +0 -18
  41. package/dist/agent/run-headless.js +0 -189
  42. package/dist/agent/theme.js +0 -41
  43. package/dist/avatar.js +0 -34
  44. package/dist/backtest/default-backtest-data.js +0 -200
  45. package/dist/backtest/fetch.js +0 -41
  46. package/dist/backtest/import.js +0 -106
  47. package/dist/backtest/index.js +0 -10
  48. package/dist/backtest/results.js +0 -113
  49. package/dist/backtest/runner.js +0 -134
  50. package/dist/backtest/storage.js +0 -11
  51. package/dist/backtest/types.js +0 -1
  52. package/dist/commands/install.js +0 -50
  53. package/dist/commands/start/ui/PollText.js +0 -23
  54. package/dist/commands/start/ui/PredictionsPanel.js +0 -88
  55. package/dist/commands/start/ui/SpinnerContext.js +0 -20
  56. package/dist/components/InputGuard.js +0 -6
  57. package/dist/components/stdout-spinner.js +0 -48
  58. package/dist/create/CreateApp.js +0 -153
  59. package/dist/create/ai-generate.js +0 -147
  60. package/dist/create/generate.js +0 -73
  61. package/dist/create/steps/ApiKeyStep.js +0 -97
  62. package/dist/create/steps/AvatarStep.js +0 -16
  63. package/dist/create/steps/BioStep.js +0 -14
  64. package/dist/create/steps/DoneStep.js +0 -14
  65. package/dist/create/steps/IdentityStep.js +0 -163
  66. package/dist/create/steps/NameStep.js +0 -71
  67. package/dist/create/steps/ScaffoldStep.js +0 -58
  68. package/dist/create/steps/SoulStep.js +0 -58
  69. package/dist/create/steps/StrategyStep.js +0 -58
  70. package/dist/create/validate-api-key.js +0 -47
  71. package/dist/create/welcome.js +0 -304
  72. package/dist/list/ListApp.js +0 -79
  73. package/dist/migrate-templates/MigrateApp.js +0 -131
  74. package/dist/migrate-templates/migrate.js +0 -86
  75. package/dist/presets.js +0 -613
  76. package/dist/start/AgentProcessManager.js +0 -98
  77. package/dist/start/Dashboard.js +0 -92
  78. package/dist/start/SelectAgentApp.js +0 -81
  79. package/dist/start/StartApp.js +0 -189
  80. package/dist/start/patch-headless.js +0 -101
  81. package/dist/start/patch-managed-mode.js +0 -142
  82. package/dist/start/start-command.js +0 -24
  83. package/dist/theme.js +0 -54
  84. /package/dist/{agent → services/agent}/config.js +0 -0
  85. /package/dist/{agent → services/agent}/helpers.js +0 -0
  86. /package/dist/{agent → services/agent/prompts}/chat-prompt.js +0 -0
  87. /package/dist/{agent → services/agent}/skills/index.js +0 -0
  88. /package/dist/{agent → services/agent}/skills/skill-parser.js +0 -0
  89. /package/dist/{agent → services/agent}/skills/types.js +0 -0
  90. /package/dist/{agent → services/agent/tools}/edit-section.js +0 -0
  91. /package/dist/{agent → services/agent/tools}/fetch-rules.js +0 -0
  92. /package/dist/{agent → services/agent}/tools/index.js +0 -0
  93. /package/dist/{agent → services/agent}/tools/market/index.js +0 -0
  94. /package/dist/{agent → services/agent}/tools/market/tools.js +0 -0
  95. /package/dist/{agent → services/agent}/tools/mindshare/index.js +0 -0
  96. /package/dist/{agent → services/agent}/tools/mindshare/tools.js +0 -0
  97. /package/dist/{agent → services/agent}/tools/read-skill-tool.js +0 -0
  98. /package/dist/{agent → services/agent}/tools/ta/index.js +0 -0
  99. /package/dist/{agent → services/agent}/tools/ta/indicators.js +0 -0
  100. /package/dist/{agent → services/agent}/types.js +0 -0
  101. /package/dist/{ai-providers.js → services/ai-providers.js} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
- import { HiveClient, durationMsToTimeframe } from '@zhive/sdk';
2
+ import { HiveClient, durationMsToTimeframe, loadConfig } from '@zhive/sdk';
3
3
  import { styled, symbols, border } from '../../shared/theme.js';
4
4
  import { HIVE_API_URL } from '../../../shared/config/constant.js';
5
- import { findAgentByName, loadAgentCredentials, scanAgents } from '../../../shared/config/agent.js';
5
+ import { findAgentByName, scanAgents } from '../../../shared/config/agent.js';
6
6
  import z from 'zod';
7
7
  import { printZodError } from '../../shared/ validation.js';
8
8
  const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
@@ -50,12 +50,12 @@ export function createMegathreadListCommand() {
50
50
  }
51
51
  process.exit(1);
52
52
  }
53
- const credentials = await loadAgentCredentials(agentConfig.dir, agentConfig.name);
54
- if (!credentials?.apiKey) {
53
+ const config = await loadConfig(agentConfig.dir);
54
+ if (!config?.apiKey) {
55
55
  console.error(styled.red(`${symbols.cross} No credentials found for agent "${agentName}". The agent may need to be registered first.`));
56
56
  process.exit(1);
57
57
  }
58
- const client = new HiveClient(HIVE_API_URL, credentials.apiKey);
58
+ const client = new HiveClient(HIVE_API_URL, config.apiKey);
59
59
  try {
60
60
  const rounds = await client.getUnpredictedRounds(timeframes);
61
61
  console.log('');
@@ -17,7 +17,7 @@ vi.mock('@zhive/sdk', async () => {
17
17
  HiveClient: vi.fn().mockImplementation(() => ({
18
18
  getUnpredictedRounds: vi.fn(),
19
19
  })),
20
- loadCredentials: vi.fn(),
20
+ loadConfig: vi.fn(),
21
21
  TIMEFRAME_DURATION_MS: {
22
22
  H1: 3600000,
23
23
  H4: 14400000,
@@ -30,10 +30,10 @@ vi.mock('@zhive/sdk', async () => {
30
30
  },
31
31
  };
32
32
  });
33
- import { HiveClient, loadCredentials } from '@zhive/sdk';
33
+ import { HiveClient, loadConfig } from '@zhive/sdk';
34
34
  import { createMegathreadListCommand } from './list.js';
35
35
  const MockHiveClient = HiveClient;
36
- const mockLoadCredentials = loadCredentials;
36
+ const mockLoadConfig = loadConfig;
37
37
  function createMockActiveRound(overrides = {}) {
38
38
  return {
39
39
  roundId: 'round-123',
@@ -88,21 +88,21 @@ describe('createMegathreadListCommand', () => {
88
88
  expect(consoleErrorOutput.length).toBeGreaterThan(0);
89
89
  });
90
90
  it('accepts valid timeframe values', async () => {
91
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
91
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
92
92
  mockGetUnpredictedRounds.mockResolvedValue([]);
93
93
  const command = createMegathreadListCommand();
94
94
  await command.parseAsync(['--agent', 'test-agent', '--timeframe', '1h,4h'], { from: 'user' });
95
95
  expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(['1h', '4h']);
96
96
  });
97
97
  it('accepts single valid timeframe', async () => {
98
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
98
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
99
99
  mockGetUnpredictedRounds.mockResolvedValue([]);
100
100
  const command = createMegathreadListCommand();
101
101
  await command.parseAsync(['--agent', 'test-agent', '--timeframe', '24h'], { from: 'user' });
102
102
  expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(['24h']);
103
103
  });
104
104
  it('passes undefined when no timeframe filter specified', async () => {
105
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
105
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
106
106
  mockGetUnpredictedRounds.mockResolvedValue([]);
107
107
  const command = createMegathreadListCommand();
108
108
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -122,13 +122,13 @@ describe('createMegathreadListCommand', () => {
122
122
  });
123
123
  describe('credentials validation', () => {
124
124
  it('shows error when credentials are missing', async () => {
125
- mockLoadCredentials.mockResolvedValue(null);
125
+ mockLoadConfig.mockResolvedValue(null);
126
126
  const command = createMegathreadListCommand();
127
127
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
128
128
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found for agent "test-agent"');
129
129
  });
130
130
  it('shows error when credentials have no API key', async () => {
131
- mockLoadCredentials.mockResolvedValue({ apiKey: null });
131
+ mockLoadConfig.mockResolvedValue({ apiKey: null });
132
132
  const command = createMegathreadListCommand();
133
133
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
134
134
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found');
@@ -136,7 +136,7 @@ describe('createMegathreadListCommand', () => {
136
136
  });
137
137
  describe('rounds display', () => {
138
138
  it('shows message when no unpredicted rounds available', async () => {
139
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
139
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
140
140
  mockGetUnpredictedRounds.mockResolvedValue([]);
141
141
  const command = createMegathreadListCommand();
142
142
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -149,7 +149,7 @@ describe('createMegathreadListCommand', () => {
149
149
  createMockActiveRound({ roundId: 'round-1', projectId: 'bitcoin', durationMs: 3600000 }),
150
150
  createMockActiveRound({ roundId: 'round-2', projectId: 'ethereum', durationMs: 14400000 }),
151
151
  ];
152
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
152
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
153
153
  mockGetUnpredictedRounds.mockResolvedValue(mockRounds);
154
154
  const command = createMegathreadListCommand();
155
155
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -168,7 +168,7 @@ describe('createMegathreadListCommand', () => {
168
168
  const mockRounds = [
169
169
  createMockActiveRound({ roundId: 'round-1', projectId: 'bitcoin', durationMs: 7200000 }),
170
170
  ];
171
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
171
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
172
172
  mockGetUnpredictedRounds.mockResolvedValue(mockRounds);
173
173
  const command = createMegathreadListCommand();
174
174
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -178,7 +178,7 @@ describe('createMegathreadListCommand', () => {
178
178
  });
179
179
  describe('API error handling', () => {
180
180
  it('shows error when API call fails', async () => {
181
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
181
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
182
182
  mockGetUnpredictedRounds.mockRejectedValue(new Error('Network error'));
183
183
  const command = createMegathreadListCommand();
184
184
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
@@ -186,7 +186,7 @@ describe('createMegathreadListCommand', () => {
186
186
  expect(consoleErrorOutput.join('\n')).toContain('Network error');
187
187
  });
188
188
  it('handles non-Error exceptions', async () => {
189
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
189
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
190
190
  mockGetUnpredictedRounds.mockRejectedValue('String error');
191
191
  const command = createMegathreadListCommand();
192
192
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
@@ -196,7 +196,7 @@ describe('createMegathreadListCommand', () => {
196
196
  });
197
197
  describe('works with different fixture agents', () => {
198
198
  it('works with empty-agent', async () => {
199
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
199
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
200
200
  mockGetUnpredictedRounds.mockResolvedValue([]);
201
201
  const command = createMegathreadListCommand();
202
202
  await command.parseAsync(['--agent', 'empty-agent'], { from: 'user' });
@@ -1,4 +1,4 @@
1
- import { credentialsPath, loadCredentials } from '@zhive/sdk';
1
+ import { loadConfig } from '@zhive/sdk';
2
2
  import { HIVE_API_URL } from '../../../shared/config/constant.js';
3
3
  import { extractErrorMessage } from '../../../shared/agent/utils.js';
4
4
  import { styled } from '../../shared/theme.js';
@@ -50,13 +50,12 @@ function getOutcomeStr(pred) {
50
50
  return `[WIN] +${pred.honey.toFixed(2)} Honey`;
51
51
  }
52
52
  if (pred.wax > 0) {
53
- return `[WIN] +${pred.wax.toFixed(2)} Wax`;
53
+ return `[LOSS] +${pred.wax.toFixed(2)} Wax`;
54
54
  }
55
55
  return '[LOSS]';
56
56
  }
57
57
  export async function predictionSlashCommand(agentName, callbacks) {
58
- const filePath = credentialsPath(agentName);
59
- const credentials = await loadCredentials(filePath);
58
+ const credentials = await loadConfig();
60
59
  if (!credentials?.apiKey) {
61
60
  callbacks.onError?.('Agent not registered yet. Wait for agent to start.');
62
61
  return;
@@ -7,7 +7,6 @@ import { fetchRulesTool } from '../../../shared/agent/tools/fetch-rules.js';
7
7
  import { extractErrorMessage } from '../../../shared/agent/utils.js';
8
8
  import { loadAgentConfig } from '../../../shared/config/agent.js';
9
9
  import { getModel } from '../../../shared/config/ai-providers.js';
10
- import { backtestSlashCommand } from '../commands/backtest.js';
11
10
  import { predictionSlashCommand } from '../commands/prediction.js';
12
11
  import { skillsSlashCommand } from '../commands/skills.js';
13
12
  import { SLASH_COMMANDS } from '../services/command-registry.js';
@@ -73,46 +72,46 @@ export function useChat(agentName) {
73
72
  const memoryOutput = memoryRef.current || 'No memory stored yet.';
74
73
  addChatActivity({ type: 'chat-agent', text: memoryOutput });
75
74
  },
76
- '/backtest': async () => {
77
- const config = {
78
- agentPath: process.cwd(),
79
- soulContent: soulContentRef.current,
80
- strategyContent: strategyContentRef.current,
81
- agentName: agentName,
82
- };
83
- await backtestSlashCommand(parts.slice(1), config, {
84
- onFetchStart: (numThreads) => {
85
- addChatActivity({
86
- type: 'chat-agent',
87
- text: `Fetching ${numThreads} resolved threads...`,
88
- });
89
- },
90
- onFetchError: (error) => {
91
- addChatActivity({
92
- type: 'chat-agent',
93
- text: `API fetch failed (${error}), falling back to default dataset...`,
94
- });
95
- addChatActivity({
96
- type: 'chat-agent',
97
- text: 'Starting backtest against default dataset...',
98
- });
99
- },
100
- onThreadStart: (index, total, thread) => {
101
- addChatActivity({
102
- type: 'chat-agent',
103
- text: `Processing ${index + 1}/${total}: ${thread.project_name}...`,
104
- });
105
- },
106
- onBacktestSuccess: (report) => {
107
- addChatActivity({ type: 'chat-agent', text: report });
108
- sessionMessagesRef.current.push({ role: 'assistant', content: report });
109
- },
110
- onBacktestError: (err) => {
111
- const errMessage = extractErrorMessage(err);
112
- addChatActivity({ type: 'chat-error', text: `Backtest failed: ${errMessage}` });
113
- },
114
- });
115
- },
75
+ // '/backtest': async () => {
76
+ // const config: RunnerConfig = {
77
+ // agentPath: process.cwd(),
78
+ // soulContent: soulContentRef.current,
79
+ // strategyContent: strategyContentRef.current,
80
+ // agentName: agentName,
81
+ // };
82
+ // await backtestSlashCommand(parts.slice(1), config, {
83
+ // onFetchStart: (numThreads) => {
84
+ // addChatActivity({
85
+ // type: 'chat-agent',
86
+ // text: `Fetching ${numThreads} resolved threads...`,
87
+ // });
88
+ // },
89
+ // onFetchError: (error: string) => {
90
+ // addChatActivity({
91
+ // type: 'chat-agent',
92
+ // text: `API fetch failed (${error}), falling back to default dataset...`,
93
+ // });
94
+ // addChatActivity({
95
+ // type: 'chat-agent',
96
+ // text: 'Starting backtest against default dataset...',
97
+ // });
98
+ // },
99
+ // onThreadStart: (index, total, thread) => {
100
+ // addChatActivity({
101
+ // type: 'chat-agent',
102
+ // text: `Processing ${index + 1}/${total}: ${thread.project_name}...`,
103
+ // });
104
+ // },
105
+ // onBacktestSuccess: (report: string) => {
106
+ // addChatActivity({ type: 'chat-agent', text: report });
107
+ // sessionMessagesRef.current.push({ role: 'assistant', content: report });
108
+ // },
109
+ // onBacktestError: (err: unknown) => {
110
+ // const errMessage = extractErrorMessage(err);
111
+ // addChatActivity({ type: 'chat-error', text: `Backtest failed: ${errMessage}` });
112
+ // },
113
+ // });
114
+ // },
116
115
  '/prediction': async () => {
117
116
  await predictionSlashCommand(agentName, {
118
117
  onFetchStart: () => {
@@ -3,7 +3,7 @@ export const SLASH_COMMANDS = [
3
3
  { name: '/help', description: 'Show available commands' },
4
4
  { name: '/clear', description: 'Clear chat history' },
5
5
  { name: '/memory', description: 'Show current memory state' },
6
- { name: '/backtest', description: 'Run agent against test set (/backtest <num> fetches from API)' },
6
+ // { name: '/backtest', description: 'Run agent against test set (/backtest <num> fetches from API)' },
7
7
  { name: '/prediction', description: 'Show your last 10 resolved predictions' },
8
8
  ];
9
9
  export function filterCommands(prefix) {
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { createStartCommand } from './commands/start/commands/index.js';
9
9
  import { createStartAllCommand } from './commands/start-all/commands/index.js';
10
10
  import { createRunCommand } from './commands/run/commands/index.js';
11
11
  import { createMigrateTemplatesCommand } from './commands/migrate-templates/commands/index.js';
12
+ import { createDoctorCommand } from './commands/doctor/commands/index.js';
12
13
  const require = createRequire(import.meta.url);
13
14
  const packageJson = require('../package.json');
14
15
  const program = new Command();
@@ -21,6 +22,7 @@ program.addCommand(createStartCommand());
21
22
  program.addCommand(createStartAllCommand());
22
23
  program.addCommand(createRunCommand());
23
24
  program.addCommand(createMigrateTemplatesCommand());
25
+ program.addCommand(createDoctorCommand());
24
26
  // Show help with exit code 0 when no arguments provided
25
27
  const args = process.argv.slice(2);
26
28
  if (args.length === 0) {
@@ -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
  }
@@ -1,3 +1,4 @@
1
+ // ─── Shared Types ─────────────────────────────────
1
2
  function humanDuration(ms) {
2
3
  const hours = ms / 3_600_000;
3
4
  if (hours >= 24) {
@@ -79,32 +80,8 @@ Only skip if this project is outside the expertise list defined in your STRATEGY
79
80
  - Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
80
81
  - Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
81
82
 
82
- IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across rounds. Each round has different context — your conviction should reflect that.
83
-
84
- ## The prediction game
85
-
86
- This is a price prediction game for megathread rounds. Each round has a project, a duration, and a round-start baseline price. Your conviction = predicted TOTAL % change from the round-start price by end of the round.
87
-
88
- Key inputs for each round:
89
- - **Round-start price** — your scoring baseline. You predict % change from this.
90
- - **Current price** — how much has already moved from baseline. Your prediction should account for movement that already happened.
91
- - **Time remaining** — less time = less room for further movement. Late in the round, anchor your prediction close to where the price already is relative to baseline.
92
- - **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.
93
-
94
- Scoring:
95
- - Correct direction + close to actual = max honey (up to +100)
96
- - Correct direction but far off = less honey
97
- - Wrong direction = -25 honey penalty
98
- - Skipping = no penalty, no reward
99
- - Earlier predictions earn dramatically more honey (cubic time decay)
100
- - Predicting late earns almost nothing even if correct
101
-
102
- ## Using tools
103
-
104
- If you have tools available, use them to research current price, OHLC data, technical indicators, mindshare data, and social sentiment. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.
105
-
106
- If your tools return nothing or you have limited data, just run with it. You know crypto. You know this space. Use your general knowledge, recent market conditions, and your trading instincts to form a directional lean. An imperfect prediction beats no prediction. Do NOT mention that you lack data. Never say "no data", "limited data", "couldn't find", "no tools", or anything that reveals you're operating without information. Just post with conviction like you always do. The only exception: if you're deliberately bluffing in character, that's a personality move, not a disclaimer.`;
107
- // ── Prompt (dynamic per round — changes every call) ──
83
+ IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across rounds. Each round has different context — your conviction should reflect that.`;
84
+ // ── Prompt (dynamic per signal — changes every call) ──
108
85
  let recentPostsSection = '';
109
86
  if (recentPosts && recentPosts.length > 0) {
110
87
  const listed = recentPosts.map((p) => `- "${p}"`).join('\n');
@@ -130,6 +107,7 @@ ${memory}
130
107
  const roundStartMs = Math.floor(now / durationMs) * durationMs;
131
108
  const timeRemainingMs = Math.max(0, roundStartMs + durationMs - now);
132
109
  const timeRemaining = humanDuration(timeRemainingMs);
110
+ const lateInRound = timeRemainingMs < durationMs * 0.25;
133
111
  const nowIso = new Date().toISOString();
134
112
  // ── Price context lines ──
135
113
  const hasBothPrices = priceAtStart !== undefined && currentPrice !== undefined;
@@ -141,11 +119,33 @@ ${memory}
141
119
  }
142
120
  let priceContextLines = '';
143
121
  if (hasBothPrices) {
144
- priceContextLines = `- Round-start price: $${priceAtStart} (scoring baseline)
122
+ priceContextLines = `- Round-start price: $${priceAtStart} (baseline for scoring)
145
123
  - Current price: $${currentPrice} (${currentChangeStr} from round start)`;
146
124
  }
147
125
  else if (priceAtStart !== undefined) {
148
- priceContextLines = `- Round-start price: $${priceAtStart} (scoring baseline)`;
126
+ priceContextLines = `- Round-start price: $${priceAtStart} (baseline for scoring)`;
127
+ }
128
+ // ── What-matters block ──
129
+ let whatMattersBlock;
130
+ if (hasBothPrices) {
131
+ whatMattersBlock = `What matters:
132
+ - **Round-start price** ($${priceAtStart}) — your baseline. Scoring measures % change from here.
133
+ - **Current price** ($${currentPrice}, ${currentChangeStr} from baseline) — price has already moved this much. Your prediction should be close to this unless you expect further movement.
134
+ - **Time remaining** (~${timeRemaining}) — less time = less room for further movement from current price.${lateInRound ? ' Almost no time left — anchor your prediction to the current change (' + currentChangeStr + ').' : ''}
135
+ - **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.`;
136
+ }
137
+ else if (priceAtStart !== undefined) {
138
+ whatMattersBlock = `What matters:
139
+ - **Round-start price** ($${priceAtStart}) — your baseline. Scoring measures % change from here.
140
+ - **Current price** — check with tools. Compare to $${priceAtStart} to see how much has already moved.
141
+ - **Time remaining** (~${timeRemaining}) — less time = less room for further movement from current price.${lateInRound ? ' Almost no time left — anchor your prediction to where the price is NOW relative to $' + priceAtStart + '.' : ''}
142
+ - **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.`;
143
+ }
144
+ else {
145
+ whatMattersBlock = `What matters:
146
+ - **Current price** — check with tools. This is the most important input to your prediction.
147
+ - **Time remaining** (~${timeRemaining}) — the runway left for price movement. Less time = smaller realistic moves. Scale your conviction accordingly.${lateInRound ? ' Round is almost over — keep your conviction small.' : ''}
148
+ - **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.`;
149
149
  }
150
150
  // ── Scoring & conviction lines ──
151
151
  let scoringLine;
@@ -173,11 +173,36 @@ ${memory}
173
173
  - Round duration: ${timeframe}
174
174
  - Time remaining: ~${timeRemaining}
175
175
  ${priceContextLines ? priceContextLines + '\n' : ''}
176
+ ## The game
177
+
178
+ This is a price prediction game. You're predicting the % price change for c/${projectId} over this ${timeframe} round.
179
+
180
+ ${whatMattersBlock}
181
+
176
182
  ## Your task
177
183
 
178
- This is a **megathread round** for c/${projectId}. Form your price conviction for where c/${projectId} will be relative to ${taskBaseline} by end of this ${timeframe} round (~${timeRemaining} left).${thesisHint}
184
+ This is a **megathread round** for c/${projectId}. Form your price conviction for where c/${projectId} will be relative to ${taskBaseline} by end of this ${timeframe} round (~${timeRemaining} left).
185
+
186
+ **If you have tools available**, use them to research:
187
+ - Check current price, OHLC data, and technical indicators (RSI, MACD, volume, etc.)
188
+ - Look at mindshare data and social sentiment
189
+ - Use any available skills that help you gather market context
190
+
191
+ **When you use a tool, drop a specific number or fact from the result into your post.** Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.
192
+
193
+ **If your tools return nothing or you have limited data**, just run with it. You know crypto. You know this space. Use your general knowledge of c/${projectId}, recent market conditions, and your trading instincts to form a directional lean. An imperfect prediction beats no prediction. Do NOT mention that you lack data, have no data, or couldn't find information. Never say "no data", "limited data", "couldn't find", "no tools", or anything that reveals you're operating without information. Just post with conviction like you always do. The only exception: if you're deliberately bluffing in character — pretending to reference data and then pulling the rug — that's a personality move, not a disclaimer.
194
+
195
+ Form a thesis based on what you find or what you know.${thesisHint}
196
+
197
+ ## How scoring works
179
198
 
180
199
  ${scoringLine}
200
+ - Correct direction + close to actual = max honey (up to +100)
201
+ - Correct direction but far off = less honey
202
+ - Wrong direction = -25 honey penalty
203
+ - Skipping = no penalty, no reward
204
+ - Earlier predictions earn dramatically more honey (cubic time decay)
205
+ - Predicting late earns almost nothing even if correct
181
206
  ${recentPostsSection}${memorySection}
182
207
  Give your take in character and a conviction number.
183
208
  ${convictionLine}`;
@@ -276,28 +301,7 @@ Set skip to true and set summary and conviction to null. Real people don't comme
276
301
  - Major protocol upgrade, big institutional entry, trend reversal → ±6.0 to ±12.0
277
302
  - Black swan, regulatory bombshell, massive exploit → ±12.0 to ±25.0
278
303
 
279
- IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across signals. Each signal has different strength — your conviction should reflect that.
280
-
281
- ## The prediction game
282
-
283
- This is a price prediction game. Each signal has a snapshot price captured at signal time and a 3-hour evaluation window. Your conviction = predicted % change from the snapshot price by evaluation time.
284
-
285
- Key inputs for each signal:
286
- - **Snapshot price** — your scoring baseline. You predict % change from this.
287
- - **Current price** — check with tools to see how much has already moved since the signal.
288
- - **Time left** — less time = less room for further movement. Late in the window, anchor your prediction close to where the price already is relative to the snapshot.
289
-
290
- Scoring:
291
- - Correct direction + close to actual = max honey (up to +100)
292
- - Correct direction but far off = less honey
293
- - Wrong direction = -25 honey penalty
294
- - Skipping = no penalty, no reward
295
- - Earlier predictions earn dramatically more honey (cubic time decay)
296
- - Predicting late earns almost nothing even if correct
297
-
298
- ## Using tools
299
-
300
- If you have tools available, use them to check price action, technicals, or mindshare before forming your take. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.`;
304
+ IMPORTANT: Vary your conviction numbers. Do NOT reuse the same number across signals. Each signal has different strength — your conviction should reflect that.`;
301
305
  // ── Prompt (dynamic per signal — changes every call) ──
302
306
  let citationsSection = '';
303
307
  if (citations.length > 0) {
@@ -334,20 +338,42 @@ ${memory}
334
338
  const signalTimeMs = new Date(timestamp).getTime();
335
339
  const timeRemainingMs = Math.max(0, signalTimeMs + EVAL_WINDOW_MS - Date.now());
336
340
  const timeRemaining = humanDuration(timeRemainingMs);
341
+ const lateWindow = timeRemainingMs < 45 * 60 * 1000;
337
342
  const userPrompt = `## Context
338
343
 
339
344
  - Project: c/${projectId}
340
345
  - Current date: ${currentDate}
341
346
  - Signal time: ${timestamp}
342
- - Snapshot price: $${priceOnFetch} (scoring baseline)
347
+ - Snapshot price: $${priceOnFetch} (captured at signal time)
343
348
  - Time left: ~${timeRemaining}
344
- ${citationsSection}${recentPostsSection}${memorySection}
349
+ ${citationsSection}
350
+ ## The game
351
+
352
+ This is a price prediction game. Your conviction = predicted % change from the snapshot price ($${priceOnFetch}), evaluated 3 hours after signal time.
353
+
354
+ What matters:
355
+ - **Snapshot price** ($${priceOnFetch}) — your baseline. Scoring measures % change from here.
356
+ - **Current price** — how much has already moved since the signal. Check with tools.
357
+ - **Time left** (~${timeRemaining}) — less time = less room for further movement from current price.${lateWindow ? ' Almost no time left — anchor your prediction to where the price is NOW relative to the snapshot.' : ''}
358
+
359
+ ## How scoring works
360
+
361
+ You are predicting the % price change from the snapshot price ($${priceOnFetch}) over 3 hours from signal time.
362
+ - Correct direction + close to actual = max honey (up to +100)
363
+ - Correct direction but far off = less honey
364
+ - Wrong direction = -25 honey penalty
365
+ - Skipping = no penalty, no reward
366
+ - Earlier predictions earn dramatically more honey (cubic time decay)
367
+ - Predicting late earns almost nothing even if correct
368
+ ${recentPostsSection}${memorySection}
345
369
  Signal/event to react to:
346
370
  """
347
371
  ${threadText}
348
372
  """
349
373
 
374
+ **If you have tools available**, use them to check price action, technicals, or mindshare for c/${projectId} before forming your take. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If the tool returned nothing useful, ignore it and post from instinct.
375
+
350
376
  Give your take in character and a conviction number.
351
- Conviction: predicted % price change from $${priceOnFetch} by evaluation time (~${timeRemaining} left), up to one decimal. Positive = up, negative = down.`;
377
+ Conviction: predicted % price change from $${priceOnFetch} by evaluation time (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
352
378
  return { system, prompt: userPrompt };
353
379
  }
@@ -1,4 +1,4 @@
1
- import { HIVE_API_URL } from '../../../config.js';
1
+ import { HIVE_API_URL } from '../../../config/constant.js';
2
2
  /**
3
3
  * Client for the backend Market API.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { HIVE_API_URL } from '../../../config.js';
1
+ import { HIVE_API_URL } from '../../../config/constant.js';
2
2
  export class MindshareClient {
3
3
  constructor(baseUrl = HIVE_API_URL) {
4
4
  this._baseUrl = baseUrl;
@@ -2,8 +2,8 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import axios from 'axios';
5
- import { AI_PROVIDERS } from './ai-providers.js';
6
- import { HIVE_API_URL } from './config.js';
5
+ import { AI_PROVIDERS } from '../ai-providers.js';
6
+ import { HIVE_API_URL } from './constant.js';
7
7
  function extractField(content, pattern) {
8
8
  const match = content.match(pattern);
9
9
  if (match === null) {
@@ -1,12 +1,6 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
- import os from 'os';
4
- export const HIVE_API_URL = 'https://api.zhive.ai';
5
- export const HIVE_FRONTEND_URL = 'https://www.zhive.ai';
6
- export function getHiveDir() {
7
- const hiveDir = path.join(os.homedir(), '.hive');
8
- return hiveDir;
9
- }
3
+ import { getHiveDir } from './constant.js';
10
4
  export async function readConfig() {
11
5
  try {
12
6
  const configPath = path.join(getHiveDir(), 'config.json');