@zhive/cli 0.6.2 → 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.
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
+ import { findAgentByName, scanAgents } from '../../../shared/config/agent.js';
2
3
  import { styled, symbols } from '../../shared/theme.js';
3
- import { findAgentByName, loadAgentCredentials, scanAgents } from '../../../shared/config/agent.js';
4
+ import { loadConfig } from '@zhive/sdk';
4
5
  export const createAgentProfileCommand = () => {
5
6
  return new Command('profile')
6
7
  .description('Display agent profile information')
@@ -18,7 +19,7 @@ export const createAgentProfileCommand = () => {
18
19
  }
19
20
  process.exit(1);
20
21
  }
21
- const credentials = await loadAgentCredentials(agentConfig.dir, agentConfig.name);
22
+ const credentials = await loadConfig(agentConfig.dir);
22
23
  if (!credentials?.apiKey) {
23
24
  console.error(styled.red(`${symbols.cross} No credentials found for agent "${agentName}". The agent may need to be registered first.`));
24
25
  process.exit(1);
@@ -8,15 +8,13 @@ vi.mock('../../../shared/config/constant.js', () => ({
8
8
  HIVE_API_URL: 'http://localhost:6969',
9
9
  }));
10
10
  vi.mock('../../../shared/config/ai-providers.js', () => ({
11
- AI_PROVIDERS: [
12
- { label: 'OpenAI', package: '@ai-sdk/openai', envVar: 'OPENAI_API_KEY' },
13
- ],
11
+ AI_PROVIDERS: [{ label: 'OpenAI', package: '@ai-sdk/openai', envVar: 'OPENAI_API_KEY' }],
14
12
  }));
15
13
  vi.mock('@zhive/sdk', async () => {
16
14
  const actual = await vi.importActual('@zhive/sdk');
17
15
  return {
18
16
  ...actual,
19
- loadCredentials: vi.fn(),
17
+ loadConfig: vi.fn(),
20
18
  };
21
19
  });
22
20
  vi.mock('../../shared/theme.js', () => ({
@@ -34,9 +32,9 @@ vi.mock('../../shared/theme.js', () => ({
34
32
  hive: '⬡',
35
33
  },
36
34
  }));
37
- import { loadCredentials } from '@zhive/sdk';
35
+ import { loadConfig } from '@zhive/sdk';
38
36
  import { createAgentProfileCommand } from './profile.js';
39
- const mockLoadCredentials = loadCredentials;
37
+ const mockLoadConfig = loadConfig;
40
38
  describe('createAgentProfileCommand', () => {
41
39
  let consoleLogSpy;
42
40
  let consoleErrorSpy;
@@ -74,20 +72,20 @@ describe('createAgentProfileCommand', () => {
74
72
  expect(consoleErrorOutput.join('\n')).toContain('agent-no-skills');
75
73
  });
76
74
  it('shows error when credentials are missing', async () => {
77
- mockLoadCredentials.mockResolvedValue(null);
75
+ mockLoadConfig.mockResolvedValue(null);
78
76
  const command = createAgentProfileCommand();
79
77
  await expect(command.parseAsync(['test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
80
78
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found for agent "test-agent"');
81
79
  expect(consoleErrorOutput.join('\n')).toContain('may need to be registered first');
82
80
  });
83
81
  it('shows error when credentials have no API key', async () => {
84
- mockLoadCredentials.mockResolvedValue({ apiKey: null });
82
+ mockLoadConfig.mockResolvedValue({ apiKey: null });
85
83
  const command = createAgentProfileCommand();
86
84
  await expect(command.parseAsync(['test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
87
85
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found');
88
86
  });
89
87
  it('displays profile from local config', async () => {
90
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
88
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
91
89
  const command = createAgentProfileCommand();
92
90
  await command.parseAsync(['test-agent'], { from: 'user' });
93
91
  const output = consoleOutput.join('\n');
@@ -100,7 +98,7 @@ describe('createAgentProfileCommand', () => {
100
98
  expect(output).toContain('https://example.com/avatar.png');
101
99
  });
102
100
  it('displays profile settings section', async () => {
103
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
101
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
104
102
  const command = createAgentProfileCommand();
105
103
  await command.parseAsync(['test-agent'], { from: 'user' });
106
104
  const output = consoleOutput.join('\n');
@@ -113,7 +111,7 @@ describe('createAgentProfileCommand', () => {
113
111
  expect(output).toContain('defi, gaming');
114
112
  });
115
113
  it('handles agent with empty sectors', async () => {
116
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
114
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
117
115
  const command = createAgentProfileCommand();
118
116
  await command.parseAsync(['empty-agent'], { from: 'user' });
119
117
  const output = consoleOutput.join('\n');
@@ -126,7 +124,7 @@ describe('createAgentProfileCommand', () => {
126
124
  expect(sectorsLine).toContain('-');
127
125
  });
128
126
  it('works with different fixture agents', async () => {
129
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
127
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
130
128
  const command = createAgentProfileCommand();
131
129
  await command.parseAsync(['agent-no-skills'], { from: 'user' });
132
130
  const output = consoleOutput.join('\n');
@@ -0,0 +1,93 @@
1
+ import { Command } from 'commander';
2
+ import path from 'path';
3
+ import fsExtra from 'fs-extra';
4
+ import { HiveClient, loadConfig } from '@zhive/sdk';
5
+ import { styled, symbols } from '../../shared/theme.js';
6
+ import { getHiveDir, HIVE_API_URL } from '../../../shared/config/constant.js';
7
+ import { loadAgentConfig } from '../../../shared/config/agent.js';
8
+ async function checkAgent(agentDir, dirName) {
9
+ const result = {
10
+ dirName,
11
+ dirPath: agentDir,
12
+ name: null,
13
+ configError: null,
14
+ registrationStatus: 'skipped',
15
+ };
16
+ let configLoaded = false;
17
+ try {
18
+ const config = await loadAgentConfig(agentDir);
19
+ result.name = config.name;
20
+ configLoaded = true;
21
+ }
22
+ catch (err) {
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ result.configError = message;
25
+ }
26
+ if (!configLoaded) {
27
+ return result;
28
+ }
29
+ const credentials = await loadConfig(agentDir);
30
+ if (!credentials?.apiKey) {
31
+ result.registrationStatus = 'no-api-key';
32
+ return result;
33
+ }
34
+ try {
35
+ const client = new HiveClient(HIVE_API_URL, credentials.apiKey);
36
+ await client.getMe();
37
+ result.registrationStatus = 'registered';
38
+ }
39
+ catch {
40
+ result.registrationStatus = 'not-registered';
41
+ }
42
+ return result;
43
+ }
44
+ function printResult(result) {
45
+ const displayName = result.name ?? result.dirName;
46
+ console.log(` ${styled.whiteBold(`Agent: ${displayName}`)}`);
47
+ console.log(` ${styled.gray('Path:')} ${result.dirPath}`);
48
+ if (result.configError !== null) {
49
+ console.log(` ${styled.red(`${symbols.cross} Config error: ${result.configError}`)}`);
50
+ console.log(` ${styled.gray('- Registration: skipped (config failed)')}`);
51
+ }
52
+ else {
53
+ console.log(` ${styled.green(`${symbols.check} Config loaded successfully`)}`);
54
+ switch (result.registrationStatus) {
55
+ case 'registered':
56
+ console.log(` ${styled.green(`${symbols.check} Registered`)}`);
57
+ break;
58
+ case 'not-registered':
59
+ console.log(` ${styled.red(`${symbols.cross} Not registered`)}`);
60
+ break;
61
+ case 'no-api-key':
62
+ console.log(` ${styled.honey(`${symbols.diamond} No API key found`)}`);
63
+ break;
64
+ }
65
+ }
66
+ console.log('');
67
+ }
68
+ export const createDoctorCommand = () => {
69
+ return new Command('doctor').description('Check health of all local agents').action(async () => {
70
+ const agentsDir = path.join(getHiveDir(), 'agents');
71
+ const exists = await fsExtra.pathExists(agentsDir);
72
+ if (!exists) {
73
+ console.log('');
74
+ console.log(styled.red(`${symbols.cross} No agents directory found. Create an agent with: npx @zhive/cli@latest create`));
75
+ return;
76
+ }
77
+ const entries = await fsExtra.readdir(agentsDir, { withFileTypes: true });
78
+ const directories = entries.filter((entry) => entry.isDirectory());
79
+ if (directories.length === 0) {
80
+ console.log('');
81
+ console.log(styled.red(`${symbols.cross} No agents found. Create one with: npx @zhive/cli@latest create`));
82
+ return;
83
+ }
84
+ console.log('');
85
+ console.log(styled.honeyBold(`${symbols.hive} Agent Health Check`));
86
+ console.log('');
87
+ for (const entry of directories) {
88
+ const agentDir = path.join(agentsDir, entry.name);
89
+ const checkResult = await checkAgent(agentDir, entry.name);
90
+ printResult(checkResult);
91
+ }
92
+ });
93
+ };
@@ -1,17 +1,15 @@
1
1
  import { Command } from 'commander';
2
2
  import { z } from 'zod';
3
- import { HiveClient, TIMEFRAME_DURATION_MS } from '@zhive/sdk';
3
+ import { HiveClient, loadConfig } from '@zhive/sdk';
4
4
  import { styled, symbols } from '../../shared/theme.js';
5
5
  import { HIVE_API_URL } from '../../../shared/config/constant.js';
6
- import { findAgentByName, loadAgentCredentials, scanAgents } from '../../../shared/config/agent.js';
6
+ import { findAgentByName, scanAgents } from '../../../shared/config/agent.js';
7
7
  import { printZodError } from '../../shared/ validation.js';
8
8
  const CreateCommentOptionsSchema = z.object({
9
9
  agent: z.string().min(1),
10
10
  round: z.string().min(1),
11
11
  conviction: z.coerce.number().min(-100).max(100),
12
12
  text: z.string().min(1),
13
- token: z.string().min(1),
14
- timeframe: z.enum(['1h', '4h', '24h']),
15
13
  });
16
14
  export function createMegathreadCreateCommentCommand() {
17
15
  return new Command('create-comment')
@@ -20,15 +18,13 @@ export function createMegathreadCreateCommentCommand() {
20
18
  .requiredOption('--round <roundId>', 'Round ID to comment on')
21
19
  .requiredOption('--conviction <number>', 'Conviction score (-100 to 100)')
22
20
  .requiredOption('--text <text>', 'Comment text')
23
- .requiredOption('--token <tokenId>', 'Token/project ID')
24
- .requiredOption('--timeframe <tf>', 'Timeframe (1h, 4h, 24h)')
25
21
  .action(async (options) => {
26
22
  const parseResult = CreateCommentOptionsSchema.safeParse(options);
27
23
  if (!parseResult.success) {
28
24
  printZodError(parseResult);
29
25
  process.exit(1);
30
26
  }
31
- const { agent: agentName, round: roundId, conviction, text, token, timeframe, } = parseResult.data;
27
+ const { agent: agentName, round: roundId, conviction, text } = parseResult.data;
32
28
  const agentConfig = await findAgentByName(agentName);
33
29
  if (!agentConfig) {
34
30
  const agents = await scanAgents();
@@ -41,18 +37,15 @@ export function createMegathreadCreateCommentCommand() {
41
37
  }
42
38
  process.exit(1);
43
39
  }
44
- const credentials = await loadAgentCredentials(agentConfig.dir, agentConfig.name);
40
+ const credentials = await loadConfig(agentConfig.dir);
45
41
  if (!credentials?.apiKey) {
46
42
  console.error(styled.red(`${symbols.cross} No credentials found for agent "${agentName}". The agent may need to be registered first.`));
47
43
  process.exit(1);
48
44
  }
49
45
  const client = new HiveClient(HIVE_API_URL, credentials.apiKey);
50
- const duration = TIMEFRAME_DURATION_MS[timeframe];
51
46
  const payload = {
52
47
  text,
53
48
  conviction,
54
- tokenId: token,
55
- roundDuration: duration,
56
49
  };
57
50
  try {
58
51
  await client.postMegathreadComment(roundId, payload);
@@ -60,7 +53,6 @@ export function createMegathreadCreateCommentCommand() {
60
53
  console.log(styled.green(`${symbols.check} Comment posted successfully!`));
61
54
  console.log('');
62
55
  console.log(` ${styled.gray('Round:')} ${roundId}`);
63
- console.log(` ${styled.gray('Token:')} ${token}`);
64
56
  console.log(` ${styled.gray('Conviction:')} ${conviction >= 0 ? '+' : ''}${conviction.toFixed(1)}%`);
65
57
  console.log(` ${styled.gray('Text:')} ${text.length > 50 ? text.slice(0, 50) + '...' : text}`);
66
58
  console.log('');
@@ -8,9 +8,7 @@ vi.mock('../../../shared/config/constant.js', () => ({
8
8
  HIVE_API_URL: 'http://localhost:6969',
9
9
  }));
10
10
  vi.mock('../../../shared/config/ai-providers.js', () => ({
11
- AI_PROVIDERS: [
12
- { label: 'OpenAI', package: '@ai-sdk/openai', envVar: 'OPENAI_API_KEY' },
13
- ],
11
+ AI_PROVIDERS: [{ label: 'OpenAI', package: '@ai-sdk/openai', envVar: 'OPENAI_API_KEY' }],
14
12
  }));
15
13
  vi.mock('@zhive/sdk', async () => {
16
14
  const actual = await vi.importActual('@zhive/sdk');
@@ -19,7 +17,7 @@ vi.mock('@zhive/sdk', async () => {
19
17
  HiveClient: vi.fn().mockImplementation(() => ({
20
18
  postMegathreadComment: vi.fn(),
21
19
  })),
22
- loadCredentials: vi.fn(),
20
+ loadConfig: vi.fn(),
23
21
  };
24
22
  });
25
23
  vi.mock('../../shared/theme.js', () => ({
@@ -33,10 +31,10 @@ vi.mock('../../shared/theme.js', () => ({
33
31
  check: '✓',
34
32
  },
35
33
  }));
36
- import { HiveClient, loadCredentials } from '@zhive/sdk';
34
+ import { HiveClient, loadConfig } from '@zhive/sdk';
37
35
  import { createMegathreadCreateCommentCommand } from './create-comment.js';
38
36
  const MockHiveClient = HiveClient;
39
- const mockLoadCredentials = loadCredentials;
37
+ const mockLoadConfig = loadConfig;
40
38
  describe('createMegathreadCreateCommentCommand', () => {
41
39
  let consoleLogSpy;
42
40
  let consoleErrorSpy;
@@ -81,10 +79,6 @@ describe('createMegathreadCreateCommentCommand', () => {
81
79
  '150',
82
80
  '--text',
83
81
  'Test comment',
84
- '--token',
85
- 'bitcoin',
86
- '--timeframe',
87
- '1h',
88
82
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
89
83
  expect(consoleErrorOutput.join('\n')).toContain('conviction');
90
84
  expect(consoleErrorOutput.join('\n')).toContain('100');
@@ -100,10 +94,6 @@ describe('createMegathreadCreateCommentCommand', () => {
100
94
  '-150',
101
95
  '--text',
102
96
  'Test comment',
103
- '--token',
104
- 'bitcoin',
105
- '--timeframe',
106
- '1h',
107
97
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
108
98
  expect(consoleErrorOutput.join('\n')).toContain('conviction');
109
99
  expect(consoleErrorOutput.join('\n')).toContain('-100');
@@ -119,16 +109,12 @@ describe('createMegathreadCreateCommentCommand', () => {
119
109
  'abc',
120
110
  '--text',
121
111
  'Test comment',
122
- '--token',
123
- 'bitcoin',
124
- '--timeframe',
125
- '1h',
126
112
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
127
113
  expect(consoleErrorOutput.join('\n')).toContain('conviction');
128
114
  expect(consoleErrorOutput.join('\n')).toContain('number');
129
115
  });
130
116
  it('accepts valid conviction at upper boundary', async () => {
131
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
117
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
132
118
  mockPostMegathreadComment.mockResolvedValue(undefined);
133
119
  const command = createMegathreadCreateCommentCommand();
134
120
  await command.parseAsync([
@@ -140,20 +126,14 @@ describe('createMegathreadCreateCommentCommand', () => {
140
126
  '100',
141
127
  '--text',
142
128
  'Test comment',
143
- '--token',
144
- 'bitcoin',
145
- '--timeframe',
146
- '1h',
147
129
  ], { from: 'user' });
148
130
  expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
149
131
  text: 'Test comment',
150
132
  conviction: 100,
151
- tokenId: 'bitcoin',
152
- roundDuration: 3600000,
153
133
  });
154
134
  });
155
135
  it('accepts valid conviction at lower boundary', async () => {
156
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
136
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
157
137
  mockPostMegathreadComment.mockResolvedValue(undefined);
158
138
  const command = createMegathreadCreateCommentCommand();
159
139
  await command.parseAsync([
@@ -165,20 +145,14 @@ describe('createMegathreadCreateCommentCommand', () => {
165
145
  '-100',
166
146
  '--text',
167
147
  'Test comment',
168
- '--token',
169
- 'bitcoin',
170
- '--timeframe',
171
- '1h',
172
148
  ], { from: 'user' });
173
149
  expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
174
150
  text: 'Test comment',
175
151
  conviction: -100,
176
- tokenId: 'bitcoin',
177
- roundDuration: 3600000,
178
152
  });
179
153
  });
180
154
  it('accepts decimal conviction values', async () => {
181
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
155
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
182
156
  mockPostMegathreadComment.mockResolvedValue(undefined);
183
157
  const command = createMegathreadCreateCommentCommand();
184
158
  await command.parseAsync([
@@ -190,110 +164,10 @@ describe('createMegathreadCreateCommentCommand', () => {
190
164
  '25.5',
191
165
  '--text',
192
166
  'Test comment',
193
- '--token',
194
- 'bitcoin',
195
- '--timeframe',
196
- '1h',
197
167
  ], { from: 'user' });
198
168
  expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
199
169
  text: 'Test comment',
200
170
  conviction: 25.5,
201
- tokenId: 'bitcoin',
202
- roundDuration: 3600000,
203
- });
204
- });
205
- });
206
- describe('timeframe validation', () => {
207
- it('shows error for invalid timeframe value', async () => {
208
- const command = createMegathreadCreateCommentCommand();
209
- await expect(command.parseAsync([
210
- '--agent',
211
- 'test-agent',
212
- '--round',
213
- 'round-123',
214
- '--conviction',
215
- '50',
216
- '--text',
217
- 'Test comment',
218
- '--token',
219
- 'bitcoin',
220
- '--timeframe',
221
- '2h',
222
- ], { from: 'user' })).rejects.toThrow('process.exit(1)');
223
- });
224
- it('accepts 1h timeframe', async () => {
225
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
226
- mockPostMegathreadComment.mockResolvedValue(undefined);
227
- const command = createMegathreadCreateCommentCommand();
228
- await command.parseAsync([
229
- '--agent',
230
- 'test-agent',
231
- '--round',
232
- 'round-123',
233
- '--conviction',
234
- '50',
235
- '--text',
236
- 'Test comment',
237
- '--token',
238
- 'bitcoin',
239
- '--timeframe',
240
- '1h',
241
- ], { from: 'user' });
242
- expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
243
- text: 'Test comment',
244
- conviction: 50,
245
- tokenId: 'bitcoin',
246
- roundDuration: 3600000,
247
- });
248
- });
249
- it('accepts 4h timeframe', async () => {
250
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
251
- mockPostMegathreadComment.mockResolvedValue(undefined);
252
- const command = createMegathreadCreateCommentCommand();
253
- await command.parseAsync([
254
- '--agent',
255
- 'test-agent',
256
- '--round',
257
- 'round-123',
258
- '--conviction',
259
- '50',
260
- '--text',
261
- 'Test comment',
262
- '--token',
263
- 'bitcoin',
264
- '--timeframe',
265
- '4h',
266
- ], { from: 'user' });
267
- expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
268
- text: 'Test comment',
269
- conviction: 50,
270
- tokenId: 'bitcoin',
271
- roundDuration: 14400000,
272
- });
273
- });
274
- it('accepts 24h timeframe', async () => {
275
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
276
- mockPostMegathreadComment.mockResolvedValue(undefined);
277
- const command = createMegathreadCreateCommentCommand();
278
- await command.parseAsync([
279
- '--agent',
280
- 'test-agent',
281
- '--round',
282
- 'round-123',
283
- '--conviction',
284
- '50',
285
- '--text',
286
- 'Test comment',
287
- '--token',
288
- 'bitcoin',
289
- '--timeframe',
290
- '24h',
291
- ], { from: 'user' });
292
- expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
293
- text: 'Test comment',
294
- conviction: 50,
295
- tokenId: 'bitcoin',
296
- roundDuration: 86400000,
297
171
  });
298
172
  });
299
173
  });
@@ -309,10 +183,6 @@ describe('createMegathreadCreateCommentCommand', () => {
309
183
  '50',
310
184
  '--text',
311
185
  'Test comment',
312
- '--token',
313
- 'bitcoin',
314
- '--timeframe',
315
- '1h',
316
186
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
317
187
  expect(consoleErrorOutput.join('\n')).toContain('Agent "non-existent" not found');
318
188
  expect(consoleErrorOutput.join('\n')).toContain('Available agents:');
@@ -323,7 +193,7 @@ describe('createMegathreadCreateCommentCommand', () => {
323
193
  });
324
194
  describe('credentials validation', () => {
325
195
  it('shows error when credentials are missing', async () => {
326
- mockLoadCredentials.mockResolvedValue(null);
196
+ mockLoadConfig.mockResolvedValue(null);
327
197
  const command = createMegathreadCreateCommentCommand();
328
198
  await expect(command.parseAsync([
329
199
  '--agent',
@@ -334,15 +204,11 @@ describe('createMegathreadCreateCommentCommand', () => {
334
204
  '50',
335
205
  '--text',
336
206
  'Test comment',
337
- '--token',
338
- 'bitcoin',
339
- '--timeframe',
340
- '1h',
341
207
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
342
208
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found for agent "test-agent"');
343
209
  });
344
210
  it('shows error when credentials have no API key', async () => {
345
- mockLoadCredentials.mockResolvedValue({ apiKey: null });
211
+ mockLoadConfig.mockResolvedValue({ apiKey: null });
346
212
  const command = createMegathreadCreateCommentCommand();
347
213
  await expect(command.parseAsync([
348
214
  '--agent',
@@ -353,17 +219,13 @@ describe('createMegathreadCreateCommentCommand', () => {
353
219
  '50',
354
220
  '--text',
355
221
  'Test comment',
356
- '--token',
357
- 'bitcoin',
358
- '--timeframe',
359
- '1h',
360
222
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
361
223
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found');
362
224
  });
363
225
  });
364
226
  describe('successful comment posting', () => {
365
227
  it('posts comment and shows success message', async () => {
366
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
228
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
367
229
  mockPostMegathreadComment.mockResolvedValue(undefined);
368
230
  const command = createMegathreadCreateCommentCommand();
369
231
  await command.parseAsync([
@@ -375,26 +237,19 @@ describe('createMegathreadCreateCommentCommand', () => {
375
237
  '50',
376
238
  '--text',
377
239
  'Bullish on Bitcoin!',
378
- '--token',
379
- 'bitcoin',
380
- '--timeframe',
381
- '1h',
382
240
  ], { from: 'user' });
383
241
  expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
384
242
  text: 'Bullish on Bitcoin!',
385
243
  conviction: 50,
386
- tokenId: 'bitcoin',
387
- roundDuration: 3600000,
388
244
  });
389
245
  const output = consoleOutput.join('\n');
390
246
  expect(output).toContain('Comment posted successfully');
391
247
  expect(output).toContain('round-123');
392
- expect(output).toContain('bitcoin');
393
248
  expect(output).toContain('+50.0%');
394
249
  expect(output).toContain('Bullish on Bitcoin!');
395
250
  });
396
251
  it('formats negative conviction correctly', async () => {
397
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
252
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
398
253
  mockPostMegathreadComment.mockResolvedValue(undefined);
399
254
  const command = createMegathreadCreateCommentCommand();
400
255
  await command.parseAsync([
@@ -406,39 +261,20 @@ describe('createMegathreadCreateCommentCommand', () => {
406
261
  '-30',
407
262
  '--text',
408
263
  'Bearish outlook',
409
- '--token',
410
- 'ethereum',
411
- '--timeframe',
412
- '4h',
413
264
  ], { from: 'user' });
414
265
  const output = consoleOutput.join('\n');
415
266
  expect(output).toContain('-30.0%');
416
267
  });
417
268
  it('truncates long text in success message', async () => {
418
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
269
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
419
270
  mockPostMegathreadComment.mockResolvedValue(undefined);
420
271
  const longText = 'A'.repeat(100);
421
272
  const command = createMegathreadCreateCommentCommand();
422
- await command.parseAsync([
423
- '--agent',
424
- 'test-agent',
425
- '--round',
426
- 'round-123',
427
- '--conviction',
428
- '25',
429
- '--text',
430
- longText,
431
- '--token',
432
- 'bitcoin',
433
- '--timeframe',
434
- '1h',
435
- ], { from: 'user' });
273
+ await command.parseAsync(['--agent', 'test-agent', '--round', 'round-123', '--conviction', '25', '--text', longText], { from: 'user' });
436
274
  // Verify full text was sent to API
437
275
  expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
438
276
  text: longText,
439
277
  conviction: 25,
440
- tokenId: 'bitcoin',
441
- roundDuration: 3600000,
442
278
  });
443
279
  // Verify truncated display
444
280
  const output = consoleOutput.join('\n');
@@ -447,7 +283,7 @@ describe('createMegathreadCreateCommentCommand', () => {
447
283
  });
448
284
  describe('API error handling', () => {
449
285
  it('shows error when API call fails', async () => {
450
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
286
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
451
287
  mockPostMegathreadComment.mockRejectedValue(new Error('Network error'));
452
288
  const command = createMegathreadCreateCommentCommand();
453
289
  await expect(command.parseAsync([
@@ -459,16 +295,12 @@ describe('createMegathreadCreateCommentCommand', () => {
459
295
  '50',
460
296
  '--text',
461
297
  'Test comment',
462
- '--token',
463
- 'bitcoin',
464
- '--timeframe',
465
- '1h',
466
298
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
467
299
  expect(consoleErrorOutput.join('\n')).toContain('Failed to post comment');
468
300
  expect(consoleErrorOutput.join('\n')).toContain('Network error');
469
301
  });
470
302
  it('handles non-Error exceptions', async () => {
471
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
303
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
472
304
  mockPostMegathreadComment.mockRejectedValue('String error');
473
305
  const command = createMegathreadCreateCommentCommand();
474
306
  await expect(command.parseAsync([
@@ -480,10 +312,6 @@ describe('createMegathreadCreateCommentCommand', () => {
480
312
  '50',
481
313
  '--text',
482
314
  'Test comment',
483
- '--token',
484
- 'bitcoin',
485
- '--timeframe',
486
- '1h',
487
315
  ], { from: 'user' })).rejects.toThrow('process.exit(1)');
488
316
  expect(consoleErrorOutput.join('\n')).toContain('Failed to post comment');
489
317
  expect(consoleErrorOutput.join('\n')).toContain('String error');
@@ -491,7 +319,7 @@ describe('createMegathreadCreateCommentCommand', () => {
491
319
  });
492
320
  describe('works with different fixture agents', () => {
493
321
  it('works with empty-agent', async () => {
494
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
322
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
495
323
  mockPostMegathreadComment.mockResolvedValue(undefined);
496
324
  const command = createMegathreadCreateCommentCommand();
497
325
  await command.parseAsync([
@@ -503,10 +331,6 @@ describe('createMegathreadCreateCommentCommand', () => {
503
331
  '50',
504
332
  '--text',
505
333
  'Test comment',
506
- '--token',
507
- 'bitcoin',
508
- '--timeframe',
509
- '1h',
510
334
  ], { from: 'user' });
511
335
  const output = consoleOutput.join('\n');
512
336
  expect(output).toContain('Comment posted successfully');
@@ -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,15 +30,16 @@ 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',
40
40
  projectId: 'bitcoin',
41
41
  durationMs: 3600000,
42
+ snapTimeMs: Date.now(),
42
43
  priceAtStart: null,
43
44
  ...overrides,
44
45
  };
@@ -87,21 +88,21 @@ describe('createMegathreadListCommand', () => {
87
88
  expect(consoleErrorOutput.length).toBeGreaterThan(0);
88
89
  });
89
90
  it('accepts valid timeframe values', async () => {
90
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
91
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
91
92
  mockGetUnpredictedRounds.mockResolvedValue([]);
92
93
  const command = createMegathreadListCommand();
93
94
  await command.parseAsync(['--agent', 'test-agent', '--timeframe', '1h,4h'], { from: 'user' });
94
95
  expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(['1h', '4h']);
95
96
  });
96
97
  it('accepts single valid timeframe', async () => {
97
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
98
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
98
99
  mockGetUnpredictedRounds.mockResolvedValue([]);
99
100
  const command = createMegathreadListCommand();
100
101
  await command.parseAsync(['--agent', 'test-agent', '--timeframe', '24h'], { from: 'user' });
101
102
  expect(mockGetUnpredictedRounds).toHaveBeenCalledWith(['24h']);
102
103
  });
103
104
  it('passes undefined when no timeframe filter specified', async () => {
104
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
105
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
105
106
  mockGetUnpredictedRounds.mockResolvedValue([]);
106
107
  const command = createMegathreadListCommand();
107
108
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -121,13 +122,13 @@ describe('createMegathreadListCommand', () => {
121
122
  });
122
123
  describe('credentials validation', () => {
123
124
  it('shows error when credentials are missing', async () => {
124
- mockLoadCredentials.mockResolvedValue(null);
125
+ mockLoadConfig.mockResolvedValue(null);
125
126
  const command = createMegathreadListCommand();
126
127
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
127
128
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found for agent "test-agent"');
128
129
  });
129
130
  it('shows error when credentials have no API key', async () => {
130
- mockLoadCredentials.mockResolvedValue({ apiKey: null });
131
+ mockLoadConfig.mockResolvedValue({ apiKey: null });
131
132
  const command = createMegathreadListCommand();
132
133
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
133
134
  expect(consoleErrorOutput.join('\n')).toContain('No credentials found');
@@ -135,7 +136,7 @@ describe('createMegathreadListCommand', () => {
135
136
  });
136
137
  describe('rounds display', () => {
137
138
  it('shows message when no unpredicted rounds available', async () => {
138
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
139
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
139
140
  mockGetUnpredictedRounds.mockResolvedValue([]);
140
141
  const command = createMegathreadListCommand();
141
142
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -148,7 +149,7 @@ describe('createMegathreadListCommand', () => {
148
149
  createMockActiveRound({ roundId: 'round-1', projectId: 'bitcoin', durationMs: 3600000 }),
149
150
  createMockActiveRound({ roundId: 'round-2', projectId: 'ethereum', durationMs: 14400000 }),
150
151
  ];
151
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
152
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
152
153
  mockGetUnpredictedRounds.mockResolvedValue(mockRounds);
153
154
  const command = createMegathreadListCommand();
154
155
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -167,7 +168,7 @@ describe('createMegathreadListCommand', () => {
167
168
  const mockRounds = [
168
169
  createMockActiveRound({ roundId: 'round-1', projectId: 'bitcoin', durationMs: 7200000 }),
169
170
  ];
170
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
171
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
171
172
  mockGetUnpredictedRounds.mockResolvedValue(mockRounds);
172
173
  const command = createMegathreadListCommand();
173
174
  await command.parseAsync(['--agent', 'test-agent'], { from: 'user' });
@@ -177,7 +178,7 @@ describe('createMegathreadListCommand', () => {
177
178
  });
178
179
  describe('API error handling', () => {
179
180
  it('shows error when API call fails', async () => {
180
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
181
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
181
182
  mockGetUnpredictedRounds.mockRejectedValue(new Error('Network error'));
182
183
  const command = createMegathreadListCommand();
183
184
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
@@ -185,7 +186,7 @@ describe('createMegathreadListCommand', () => {
185
186
  expect(consoleErrorOutput.join('\n')).toContain('Network error');
186
187
  });
187
188
  it('handles non-Error exceptions', async () => {
188
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
189
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
189
190
  mockGetUnpredictedRounds.mockRejectedValue('String error');
190
191
  const command = createMegathreadListCommand();
191
192
  await expect(command.parseAsync(['--agent', 'test-agent'], { from: 'user' })).rejects.toThrow('process.exit(1)');
@@ -195,7 +196,7 @@ describe('createMegathreadListCommand', () => {
195
196
  });
196
197
  describe('works with different fixture agents', () => {
197
198
  it('works with empty-agent', async () => {
198
- mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
199
+ mockLoadConfig.mockResolvedValue({ apiKey: 'test-api-key' });
199
200
  mockGetUnpredictedRounds.mockResolvedValue([]);
200
201
  const command = createMegathreadListCommand();
201
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';
@@ -29,8 +29,7 @@ export function formatPredictions(predictions) {
29
29
  const conviction = `${sign}${pred.conviction.toFixed(1)}%`;
30
30
  const outcome = getOutcomeStr(pred);
31
31
  const date = new Date(pred.created_at).toLocaleDateString();
32
- const roundStart = pred.round_id.split('@Z')[0];
33
- const durationMs = new Date(pred.resolved_at).getTime() - new Date(roundStart).getTime();
32
+ const durationMs = pred.duration_ms ?? 0;
34
33
  const duration = { 3600000: '1h', 14400000: '4h', 86400000: '24h' }[durationMs] || '??';
35
34
  return { name: pred.project_id, duration, conviction, outcome, date };
36
35
  });
@@ -51,13 +50,12 @@ function getOutcomeStr(pred) {
51
50
  return `[WIN] +${pred.honey.toFixed(2)} Honey`;
52
51
  }
53
52
  if (pred.wax > 0) {
54
- return `[WIN] +${pred.wax.toFixed(2)} Wax`;
53
+ return `[LOSS] +${pred.wax.toFixed(2)} Wax`;
55
54
  }
56
55
  return '[LOSS]';
57
56
  }
58
57
  export async function predictionSlashCommand(agentName, callbacks) {
59
- const filePath = credentialsPath(agentName);
60
- const credentials = await loadCredentials(filePath);
58
+ const credentials = await loadConfig();
61
59
  if (!credentials?.apiKey) {
62
60
  callbacks.onError?.('Agent not registered yet. Wait for agent to start.');
63
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: () => {
@@ -43,7 +43,7 @@ const megathreadPostedActivityFormatter = {
43
43
  return `[${sign}${item.conviction.toFixed(2)}%] "${item.summary}"`;
44
44
  },
45
45
  getDetail(item) {
46
- return item.id.split('@')[0];
46
+ return item.timestamp.toISOString();
47
47
  },
48
48
  format(item) {
49
49
  const lines = [];
@@ -61,7 +61,7 @@ const megathreadErrorActivityFormatter = {
61
61
  return item.errorMessage;
62
62
  },
63
63
  getDetail(item) {
64
- return item.id.split('@')[0];
64
+ return item.timestamp.toISOString();
65
65
  },
66
66
  format(item) {
67
67
  const pad = ' '.repeat(13);
@@ -74,7 +74,7 @@ const megathreadActivityFormatter = {
74
74
  return `${projectTag} \u00B7 ${item.timeframe} round`;
75
75
  },
76
76
  getDetail(item) {
77
- return item.id.split('@')[0];
77
+ return item.timestamp.toISOString();
78
78
  },
79
79
  format(item) {
80
80
  const mainLine = ` ${time(item)}${chalk.hex(colors.controversial)(symbols.hive)} ${chalk.hex(colors.controversial)(this.getText(item))}`;
@@ -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) {
@@ -33,7 +33,7 @@ async function run({ round, runtime, reporter, recentComments, }) {
33
33
  const timeframe = calculateTimeframe(round);
34
34
  reporter.onRoundStart(round, timeframe);
35
35
  // ── Fetch prices ──────────────────────────────
36
- const roundStartTimestamp = round.roundId.split('@Z')[0];
36
+ const roundStartTimestamp = new Date(round.snapTimeMs).toISOString();
37
37
  const { priceAtStart, currentPrice } = await fetchRoundPrices(round.projectId, roundStartTimestamp);
38
38
  if (priceAtStart !== undefined) {
39
39
  reporter.onPriceInfo(priceAtStart, currentPrice);
@@ -87,8 +87,6 @@ export function createMegathreadRoundBatchHandler(getAgent, runtime, reporter) {
87
87
  await agent.postMegathreadComment(round.roundId, {
88
88
  text: data.summary,
89
89
  conviction: data.conviction,
90
- tokenId: round.projectId,
91
- roundDuration: round.durationMs,
92
90
  });
93
91
  const timeframe = calculateTimeframe(round);
94
92
  reporter.onPosted(round, data.conviction, data.summary, timeframe, data.usage);
@@ -113,8 +111,6 @@ export function createMegathreadRoundHandler(getAgent, runtime, reporter) {
113
111
  await agent.postMegathreadComment(round.roundId, {
114
112
  text: result.summary,
115
113
  conviction: result.conviction,
116
- tokenId: round.projectId,
117
- roundDuration: round.durationMs,
118
114
  });
119
115
  const timeframe = calculateTimeframe(round);
120
116
  reporter.onPosted(round, result.conviction, result.summary, timeframe, result.usage);
@@ -1,4 +1,3 @@
1
- import { loadCredentials, } from '@zhive/sdk';
2
1
  import axios from 'axios';
3
2
  import fsExtra from 'fs-extra';
4
3
  import * as fs from 'fs/promises';
@@ -183,13 +182,3 @@ export async function findAgentByName(name) {
183
182
  }
184
183
  return agent;
185
184
  }
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
- }
@@ -14,42 +14,11 @@ vi.mock('./ai-providers.js', () => ({
14
14
  ],
15
15
  }));
16
16
  vi.mock('@zhive/sdk', () => ({
17
- loadCredentials: vi.fn(),
17
+ loadConfig: vi.fn(),
18
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
- });
19
+ import { loadConfig } from '@zhive/sdk';
20
+ import { findAgentByName, scanAgents } from './agent.js';
21
+ const mockLoadConfig = loadConfig;
53
22
  describe('scanAgents', () => {
54
23
  beforeEach(() => {
55
24
  vi.clearAllMocks();
@@ -9,8 +9,8 @@ export function getHiveDir() {
9
9
  const pathToCheck = [
10
10
  path.join(homeDir, '.zhive'),
11
11
  path.join(homeDir, '.hive'), // legacy hive dir
12
- path.join(homeDir, '.openclaw', '.hive'),
13
- path.join(homeDir, '.openclaw', 'workspace', '.hive'),
12
+ path.join(homeDir, '.openclaw', '.zhive'),
13
+ path.join(homeDir, '.openclaw', 'workspace', '.zhive'),
14
14
  ];
15
15
  for (const p of pathToCheck) {
16
16
  if (fs.existsSync(p)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhive/cli",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "CLI for bootstrapping zHive AI Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,7 +31,7 @@
31
31
  "@ai-sdk/openai": "^3.0.25",
32
32
  "@ai-sdk/xai": "^3.0.0",
33
33
  "@openrouter/ai-sdk-provider": "^0.4.0",
34
- "@zhive/sdk": "^0.5.4",
34
+ "@zhive/sdk": "^0.5.6",
35
35
  "ai": "^6.0.71",
36
36
  "axios": "^1.6.0",
37
37
  "chalk": "^5.3.0",