@zhive/cli 0.6.1 → 0.6.3
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.
- package/dist/commands/megathread/commands/create-comment.js +6 -34
- package/dist/commands/megathread/commands/create-comment.test.js +86 -69
- package/dist/commands/megathread/commands/list.js +30 -34
- package/dist/commands/megathread/commands/list.test.js +4 -3
- package/dist/commands/shared/ validation.js +5 -0
- package/dist/commands/start/commands/prediction.js +1 -2
- package/dist/commands/start/hooks/utils.js +3 -3
- package/dist/shared/agent/handler.js +1 -5
- package/dist/shared/config/constant.js +10 -6
- package/package.json +2 -2
|
@@ -4,37 +4,14 @@ import { HiveClient } from '@zhive/sdk';
|
|
|
4
4
|
import { styled, symbols } from '../../shared/theme.js';
|
|
5
5
|
import { HIVE_API_URL } from '../../../shared/config/constant.js';
|
|
6
6
|
import { findAgentByName, loadAgentCredentials, scanAgents } from '../../../shared/config/agent.js';
|
|
7
|
+
import { printZodError } from '../../shared/ validation.js';
|
|
7
8
|
const CreateCommentOptionsSchema = z.object({
|
|
8
9
|
agent: z.string().min(1),
|
|
9
10
|
round: z.string().min(1),
|
|
10
|
-
conviction: z.
|
|
11
|
-
const num = parseFloat(val);
|
|
12
|
-
if (isNaN(num)) {
|
|
13
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Must be a number. Got: ${val}` });
|
|
14
|
-
return z.NEVER;
|
|
15
|
-
}
|
|
16
|
-
if (num < -100 || num > 100) {
|
|
17
|
-
ctx.addIssue({
|
|
18
|
-
code: 'custom',
|
|
19
|
-
message: `Must be between -100 and 100. Got: ${val}`,
|
|
20
|
-
});
|
|
21
|
-
return z.NEVER;
|
|
22
|
-
}
|
|
23
|
-
return num;
|
|
24
|
-
}),
|
|
11
|
+
conviction: z.coerce.number().min(-100).max(100),
|
|
25
12
|
text: z.string().min(1),
|
|
26
13
|
token: z.string().min(1),
|
|
27
|
-
|
|
28
|
-
const num = parseInt(val, 10);
|
|
29
|
-
if (isNaN(num) || num <= 0) {
|
|
30
|
-
ctx.addIssue({
|
|
31
|
-
code: 'custom',
|
|
32
|
-
message: `Must be a positive number. Got: ${val}`,
|
|
33
|
-
});
|
|
34
|
-
return z.NEVER;
|
|
35
|
-
}
|
|
36
|
-
return num;
|
|
37
|
-
}),
|
|
14
|
+
timeframe: z.enum(['1h', '4h', '24h']),
|
|
38
15
|
});
|
|
39
16
|
export function createMegathreadCreateCommentCommand() {
|
|
40
17
|
return new Command('create-comment')
|
|
@@ -44,17 +21,14 @@ export function createMegathreadCreateCommentCommand() {
|
|
|
44
21
|
.requiredOption('--conviction <number>', 'Conviction score (-100 to 100)')
|
|
45
22
|
.requiredOption('--text <text>', 'Comment text')
|
|
46
23
|
.requiredOption('--token <tokenId>', 'Token/project ID')
|
|
47
|
-
.requiredOption('--
|
|
24
|
+
.requiredOption('--timeframe <tf>', 'Timeframe (1h, 4h, 24h)')
|
|
48
25
|
.action(async (options) => {
|
|
49
26
|
const parseResult = CreateCommentOptionsSchema.safeParse(options);
|
|
50
27
|
if (!parseResult.success) {
|
|
51
|
-
|
|
52
|
-
.map((e) => `${e.path.join('.')}: ${e.message}`)
|
|
53
|
-
.join(', ');
|
|
54
|
-
console.error(styled.red(`${symbols.cross} Validation error: ${errors}`));
|
|
28
|
+
printZodError(parseResult);
|
|
55
29
|
process.exit(1);
|
|
56
30
|
}
|
|
57
|
-
const { agent: agentName, round: roundId, conviction, text, token,
|
|
31
|
+
const { agent: agentName, round: roundId, conviction, text, token, timeframe, } = parseResult.data;
|
|
58
32
|
const agentConfig = await findAgentByName(agentName);
|
|
59
33
|
if (!agentConfig) {
|
|
60
34
|
const agents = await scanAgents();
|
|
@@ -76,8 +50,6 @@ export function createMegathreadCreateCommentCommand() {
|
|
|
76
50
|
const payload = {
|
|
77
51
|
text,
|
|
78
52
|
conviction,
|
|
79
|
-
tokenId: token,
|
|
80
|
-
roundDuration: duration,
|
|
81
53
|
};
|
|
82
54
|
try {
|
|
83
55
|
await client.postMegathreadComment(roundId, payload);
|
|
@@ -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');
|
|
@@ -83,11 +81,11 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
83
81
|
'Test comment',
|
|
84
82
|
'--token',
|
|
85
83
|
'bitcoin',
|
|
86
|
-
'--
|
|
87
|
-
'
|
|
84
|
+
'--timeframe',
|
|
85
|
+
'1h',
|
|
88
86
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
89
|
-
expect(consoleErrorOutput.join('\n')).toContain('conviction
|
|
90
|
-
expect(consoleErrorOutput.join('\n')).toContain('
|
|
87
|
+
expect(consoleErrorOutput.join('\n')).toContain('conviction');
|
|
88
|
+
expect(consoleErrorOutput.join('\n')).toContain('100');
|
|
91
89
|
});
|
|
92
90
|
it('shows error when conviction is too low', async () => {
|
|
93
91
|
const command = createMegathreadCreateCommentCommand();
|
|
@@ -102,11 +100,11 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
102
100
|
'Test comment',
|
|
103
101
|
'--token',
|
|
104
102
|
'bitcoin',
|
|
105
|
-
'--
|
|
106
|
-
'
|
|
103
|
+
'--timeframe',
|
|
104
|
+
'1h',
|
|
107
105
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
108
|
-
expect(consoleErrorOutput.join('\n')).toContain('conviction
|
|
109
|
-
expect(consoleErrorOutput.join('\n')).toContain('
|
|
106
|
+
expect(consoleErrorOutput.join('\n')).toContain('conviction');
|
|
107
|
+
expect(consoleErrorOutput.join('\n')).toContain('-100');
|
|
110
108
|
});
|
|
111
109
|
it('shows error when conviction is not a number', async () => {
|
|
112
110
|
const command = createMegathreadCreateCommentCommand();
|
|
@@ -121,11 +119,11 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
121
119
|
'Test comment',
|
|
122
120
|
'--token',
|
|
123
121
|
'bitcoin',
|
|
124
|
-
'--
|
|
125
|
-
'
|
|
122
|
+
'--timeframe',
|
|
123
|
+
'1h',
|
|
126
124
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
127
|
-
expect(consoleErrorOutput.join('\n')).toContain('conviction
|
|
128
|
-
expect(consoleErrorOutput.join('\n')).toContain('
|
|
125
|
+
expect(consoleErrorOutput.join('\n')).toContain('conviction');
|
|
126
|
+
expect(consoleErrorOutput.join('\n')).toContain('number');
|
|
129
127
|
});
|
|
130
128
|
it('accepts valid conviction at upper boundary', async () => {
|
|
131
129
|
mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
|
|
@@ -142,14 +140,12 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
142
140
|
'Test comment',
|
|
143
141
|
'--token',
|
|
144
142
|
'bitcoin',
|
|
145
|
-
'--
|
|
146
|
-
'
|
|
143
|
+
'--timeframe',
|
|
144
|
+
'1h',
|
|
147
145
|
], { from: 'user' });
|
|
148
146
|
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
149
147
|
text: 'Test comment',
|
|
150
148
|
conviction: 100,
|
|
151
|
-
tokenId: 'bitcoin',
|
|
152
|
-
roundDuration: 3600000,
|
|
153
149
|
});
|
|
154
150
|
});
|
|
155
151
|
it('accepts valid conviction at lower boundary', async () => {
|
|
@@ -167,14 +163,12 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
167
163
|
'Test comment',
|
|
168
164
|
'--token',
|
|
169
165
|
'bitcoin',
|
|
170
|
-
'--
|
|
171
|
-
'
|
|
166
|
+
'--timeframe',
|
|
167
|
+
'1h',
|
|
172
168
|
], { from: 'user' });
|
|
173
169
|
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
174
170
|
text: 'Test comment',
|
|
175
171
|
conviction: -100,
|
|
176
|
-
tokenId: 'bitcoin',
|
|
177
|
-
roundDuration: 3600000,
|
|
178
172
|
});
|
|
179
173
|
});
|
|
180
174
|
it('accepts decimal conviction values', async () => {
|
|
@@ -192,19 +186,17 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
192
186
|
'Test comment',
|
|
193
187
|
'--token',
|
|
194
188
|
'bitcoin',
|
|
195
|
-
'--
|
|
196
|
-
'
|
|
189
|
+
'--timeframe',
|
|
190
|
+
'1h',
|
|
197
191
|
], { from: 'user' });
|
|
198
192
|
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
199
193
|
text: 'Test comment',
|
|
200
194
|
conviction: 25.5,
|
|
201
|
-
tokenId: 'bitcoin',
|
|
202
|
-
roundDuration: 3600000,
|
|
203
195
|
});
|
|
204
196
|
});
|
|
205
197
|
});
|
|
206
|
-
describe('
|
|
207
|
-
it('shows error
|
|
198
|
+
describe('timeframe validation', () => {
|
|
199
|
+
it('shows error for invalid timeframe value', async () => {
|
|
208
200
|
const command = createMegathreadCreateCommentCommand();
|
|
209
201
|
await expect(command.parseAsync([
|
|
210
202
|
'--agent',
|
|
@@ -217,15 +209,15 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
217
209
|
'Test comment',
|
|
218
210
|
'--token',
|
|
219
211
|
'bitcoin',
|
|
220
|
-
'--
|
|
221
|
-
'
|
|
212
|
+
'--timeframe',
|
|
213
|
+
'2h',
|
|
222
214
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
223
|
-
expect(consoleErrorOutput.join('\n')).toContain('duration: Must be a positive number');
|
|
224
|
-
expect(consoleErrorOutput.join('\n')).toContain('Got: -1');
|
|
225
215
|
});
|
|
226
|
-
it('
|
|
216
|
+
it('accepts 1h timeframe', async () => {
|
|
217
|
+
mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
|
|
218
|
+
mockPostMegathreadComment.mockResolvedValue(undefined);
|
|
227
219
|
const command = createMegathreadCreateCommentCommand();
|
|
228
|
-
await
|
|
220
|
+
await command.parseAsync([
|
|
229
221
|
'--agent',
|
|
230
222
|
'test-agent',
|
|
231
223
|
'--round',
|
|
@@ -236,15 +228,19 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
236
228
|
'Test comment',
|
|
237
229
|
'--token',
|
|
238
230
|
'bitcoin',
|
|
239
|
-
'--
|
|
240
|
-
'
|
|
241
|
-
], { from: 'user' })
|
|
242
|
-
expect(
|
|
243
|
-
|
|
231
|
+
'--timeframe',
|
|
232
|
+
'1h',
|
|
233
|
+
], { from: 'user' });
|
|
234
|
+
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
235
|
+
text: 'Test comment',
|
|
236
|
+
conviction: 50,
|
|
237
|
+
});
|
|
244
238
|
});
|
|
245
|
-
it('
|
|
239
|
+
it('accepts 4h timeframe', async () => {
|
|
240
|
+
mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
|
|
241
|
+
mockPostMegathreadComment.mockResolvedValue(undefined);
|
|
246
242
|
const command = createMegathreadCreateCommentCommand();
|
|
247
|
-
await
|
|
243
|
+
await command.parseAsync([
|
|
248
244
|
'--agent',
|
|
249
245
|
'test-agent',
|
|
250
246
|
'--round',
|
|
@@ -255,11 +251,36 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
255
251
|
'Test comment',
|
|
256
252
|
'--token',
|
|
257
253
|
'bitcoin',
|
|
258
|
-
'--
|
|
259
|
-
'
|
|
260
|
-
], { from: 'user' })
|
|
261
|
-
expect(
|
|
262
|
-
|
|
254
|
+
'--timeframe',
|
|
255
|
+
'4h',
|
|
256
|
+
], { from: 'user' });
|
|
257
|
+
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
258
|
+
text: 'Test comment',
|
|
259
|
+
conviction: 50,
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
it('accepts 24h timeframe', async () => {
|
|
263
|
+
mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
|
|
264
|
+
mockPostMegathreadComment.mockResolvedValue(undefined);
|
|
265
|
+
const command = createMegathreadCreateCommentCommand();
|
|
266
|
+
await command.parseAsync([
|
|
267
|
+
'--agent',
|
|
268
|
+
'test-agent',
|
|
269
|
+
'--round',
|
|
270
|
+
'round-123',
|
|
271
|
+
'--conviction',
|
|
272
|
+
'50',
|
|
273
|
+
'--text',
|
|
274
|
+
'Test comment',
|
|
275
|
+
'--token',
|
|
276
|
+
'bitcoin',
|
|
277
|
+
'--timeframe',
|
|
278
|
+
'24h',
|
|
279
|
+
], { from: 'user' });
|
|
280
|
+
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
281
|
+
text: 'Test comment',
|
|
282
|
+
conviction: 50,
|
|
283
|
+
});
|
|
263
284
|
});
|
|
264
285
|
});
|
|
265
286
|
describe('agent validation', () => {
|
|
@@ -276,8 +297,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
276
297
|
'Test comment',
|
|
277
298
|
'--token',
|
|
278
299
|
'bitcoin',
|
|
279
|
-
'--
|
|
280
|
-
'
|
|
300
|
+
'--timeframe',
|
|
301
|
+
'1h',
|
|
281
302
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
282
303
|
expect(consoleErrorOutput.join('\n')).toContain('Agent "non-existent" not found');
|
|
283
304
|
expect(consoleErrorOutput.join('\n')).toContain('Available agents:');
|
|
@@ -301,8 +322,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
301
322
|
'Test comment',
|
|
302
323
|
'--token',
|
|
303
324
|
'bitcoin',
|
|
304
|
-
'--
|
|
305
|
-
'
|
|
325
|
+
'--timeframe',
|
|
326
|
+
'1h',
|
|
306
327
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
307
328
|
expect(consoleErrorOutput.join('\n')).toContain('No credentials found for agent "test-agent"');
|
|
308
329
|
});
|
|
@@ -320,8 +341,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
320
341
|
'Test comment',
|
|
321
342
|
'--token',
|
|
322
343
|
'bitcoin',
|
|
323
|
-
'--
|
|
324
|
-
'
|
|
344
|
+
'--timeframe',
|
|
345
|
+
'1h',
|
|
325
346
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
326
347
|
expect(consoleErrorOutput.join('\n')).toContain('No credentials found');
|
|
327
348
|
});
|
|
@@ -342,14 +363,12 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
342
363
|
'Bullish on Bitcoin!',
|
|
343
364
|
'--token',
|
|
344
365
|
'bitcoin',
|
|
345
|
-
'--
|
|
346
|
-
'
|
|
366
|
+
'--timeframe',
|
|
367
|
+
'1h',
|
|
347
368
|
], { from: 'user' });
|
|
348
369
|
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
349
370
|
text: 'Bullish on Bitcoin!',
|
|
350
371
|
conviction: 50,
|
|
351
|
-
tokenId: 'bitcoin',
|
|
352
|
-
roundDuration: 3600000,
|
|
353
372
|
});
|
|
354
373
|
const output = consoleOutput.join('\n');
|
|
355
374
|
expect(output).toContain('Comment posted successfully');
|
|
@@ -373,8 +392,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
373
392
|
'Bearish outlook',
|
|
374
393
|
'--token',
|
|
375
394
|
'ethereum',
|
|
376
|
-
'--
|
|
377
|
-
'
|
|
395
|
+
'--timeframe',
|
|
396
|
+
'4h',
|
|
378
397
|
], { from: 'user' });
|
|
379
398
|
const output = consoleOutput.join('\n');
|
|
380
399
|
expect(output).toContain('-30.0%');
|
|
@@ -395,15 +414,13 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
395
414
|
longText,
|
|
396
415
|
'--token',
|
|
397
416
|
'bitcoin',
|
|
398
|
-
'--
|
|
399
|
-
'
|
|
417
|
+
'--timeframe',
|
|
418
|
+
'1h',
|
|
400
419
|
], { from: 'user' });
|
|
401
420
|
// Verify full text was sent to API
|
|
402
421
|
expect(mockPostMegathreadComment).toHaveBeenCalledWith('round-123', {
|
|
403
422
|
text: longText,
|
|
404
423
|
conviction: 25,
|
|
405
|
-
tokenId: 'bitcoin',
|
|
406
|
-
roundDuration: 3600000,
|
|
407
424
|
});
|
|
408
425
|
// Verify truncated display
|
|
409
426
|
const output = consoleOutput.join('\n');
|
|
@@ -426,8 +443,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
426
443
|
'Test comment',
|
|
427
444
|
'--token',
|
|
428
445
|
'bitcoin',
|
|
429
|
-
'--
|
|
430
|
-
'
|
|
446
|
+
'--timeframe',
|
|
447
|
+
'1h',
|
|
431
448
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
432
449
|
expect(consoleErrorOutput.join('\n')).toContain('Failed to post comment');
|
|
433
450
|
expect(consoleErrorOutput.join('\n')).toContain('Network error');
|
|
@@ -447,8 +464,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
447
464
|
'Test comment',
|
|
448
465
|
'--token',
|
|
449
466
|
'bitcoin',
|
|
450
|
-
'--
|
|
451
|
-
'
|
|
467
|
+
'--timeframe',
|
|
468
|
+
'1h',
|
|
452
469
|
], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
453
470
|
expect(consoleErrorOutput.join('\n')).toContain('Failed to post comment');
|
|
454
471
|
expect(consoleErrorOutput.join('\n')).toContain('String error');
|
|
@@ -470,8 +487,8 @@ describe('createMegathreadCreateCommentCommand', () => {
|
|
|
470
487
|
'Test comment',
|
|
471
488
|
'--token',
|
|
472
489
|
'bitcoin',
|
|
473
|
-
'--
|
|
474
|
-
'
|
|
490
|
+
'--timeframe',
|
|
491
|
+
'1h',
|
|
475
492
|
], { from: 'user' });
|
|
476
493
|
const output = consoleOutput.join('\n');
|
|
477
494
|
expect(output).toContain('Comment posted successfully');
|
|
@@ -1,47 +1,43 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { HiveClient,
|
|
2
|
+
import { HiveClient, durationMsToTimeframe } 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
5
|
import { findAgentByName, loadAgentCredentials, scanAgents } from '../../../shared/config/agent.js';
|
|
6
|
+
import z from 'zod';
|
|
7
|
+
import { printZodError } from '../../shared/ validation.js';
|
|
6
8
|
const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
const ListMegathreadOptionsSchema = z.object({
|
|
10
|
+
agent: z.string(),
|
|
11
|
+
timeframe: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.transform((val, ctx) => {
|
|
15
|
+
if (!val)
|
|
16
|
+
return undefined;
|
|
17
|
+
const parsed = val.split(',').map((t) => t.trim());
|
|
18
|
+
const invalidParts = parsed.filter((t) => !VALID_TIMEFRAMES.includes(t));
|
|
19
|
+
if (invalidParts.length > 0) {
|
|
20
|
+
ctx.addIssue({
|
|
21
|
+
code: 'custom',
|
|
22
|
+
message: `Invalid. valid values are [${VALID_TIMEFRAMES.join(', ')}]`,
|
|
23
|
+
});
|
|
24
|
+
return z.NEVER;
|
|
25
|
+
}
|
|
26
|
+
return parsed;
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
25
29
|
export function createMegathreadListCommand() {
|
|
26
30
|
const program = new Command('list')
|
|
27
31
|
.description('List unpredicted megathread rounds of an agent')
|
|
28
32
|
.requiredOption('--agent <name>', 'Agent name')
|
|
29
33
|
.option('--timeframe <timeframes>', 'Filter by timeframes (comma-separated: 1h,4h,24h)')
|
|
30
34
|
.action(async (options) => {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (parsed === null) {
|
|
36
|
-
const invalidParts = timeframeOption
|
|
37
|
-
.split(',')
|
|
38
|
-
.map((t) => t.trim())
|
|
39
|
-
.filter((t) => !VALID_TIMEFRAMES.includes(t));
|
|
40
|
-
console.error(styled.red(`${symbols.cross} Invalid timeframes: ${invalidParts.join(', ')}. Valid values: 1h, 4h, 24h`));
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
timeframes = parsed;
|
|
35
|
+
const parseResult = ListMegathreadOptionsSchema.safeParse(options);
|
|
36
|
+
if (!parseResult.success) {
|
|
37
|
+
printZodError(parseResult);
|
|
38
|
+
process.exit(1);
|
|
44
39
|
}
|
|
40
|
+
const { agent: agentName, timeframe: timeframes } = parseResult.data;
|
|
45
41
|
const agentConfig = await findAgentByName(agentName);
|
|
46
42
|
if (!agentConfig) {
|
|
47
43
|
const agents = await scanAgents();
|
|
@@ -70,11 +66,11 @@ export function createMegathreadListCommand() {
|
|
|
70
66
|
console.log('');
|
|
71
67
|
return;
|
|
72
68
|
}
|
|
73
|
-
const headers = ['Round ID', 'Token', 'Timeframe'];
|
|
69
|
+
const headers = ['Round ID', 'Token', 'Timeframe', 'PriceAtStart'];
|
|
74
70
|
const rows = rounds.map((r) => {
|
|
75
71
|
const tf = durationMsToTimeframe(r.durationMs);
|
|
76
72
|
const timeframeStr = tf ?? `${r.durationMs}ms`;
|
|
77
|
-
return [r.roundId, r.projectId, timeframeStr];
|
|
73
|
+
return [r.roundId, r.projectId, timeframeStr, r.priceAtStart];
|
|
78
74
|
});
|
|
79
75
|
const colWidths = headers.map((h, i) => {
|
|
80
76
|
const dataMax = Math.max(...rows.map((row) => String(row[i]).length));
|
|
@@ -39,6 +39,8 @@ function createMockActiveRound(overrides = {}) {
|
|
|
39
39
|
roundId: 'round-123',
|
|
40
40
|
projectId: 'bitcoin',
|
|
41
41
|
durationMs: 3600000,
|
|
42
|
+
snapTimeMs: Date.now(),
|
|
43
|
+
priceAtStart: null,
|
|
42
44
|
...overrides,
|
|
43
45
|
};
|
|
44
46
|
}
|
|
@@ -78,13 +80,12 @@ describe('createMegathreadListCommand', () => {
|
|
|
78
80
|
it('shows error for invalid timeframe value', async () => {
|
|
79
81
|
const command = createMegathreadListCommand();
|
|
80
82
|
await expect(command.parseAsync(['--agent', 'test-agent', '--timeframe', '2h'], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
81
|
-
expect(consoleErrorOutput.
|
|
82
|
-
expect(consoleErrorOutput.join('\n')).toContain('Valid values: 1h, 4h, 24h');
|
|
83
|
+
expect(consoleErrorOutput.length).toBeGreaterThan(0);
|
|
83
84
|
});
|
|
84
85
|
it('shows error for multiple invalid timeframes', async () => {
|
|
85
86
|
const command = createMegathreadListCommand();
|
|
86
87
|
await expect(command.parseAsync(['--agent', 'test-agent', '--timeframe', '2h,5h'], { from: 'user' })).rejects.toThrow('process.exit(1)');
|
|
87
|
-
expect(consoleErrorOutput.
|
|
88
|
+
expect(consoleErrorOutput.length).toBeGreaterThan(0);
|
|
88
89
|
});
|
|
89
90
|
it('accepts valid timeframe values', async () => {
|
|
90
91
|
mockLoadCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
|
|
@@ -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
|
|
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
|
});
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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))}`;
|
|
@@ -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.
|
|
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);
|
|
@@ -6,12 +6,16 @@ export const HIVE_FRONTEND_URL = 'https://www.zhive.ai';
|
|
|
6
6
|
export function getHiveDir() {
|
|
7
7
|
const homeDir = os.homedir();
|
|
8
8
|
const zhiveDir = path.join(homeDir, '.zhive');
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const pathToCheck = [
|
|
10
|
+
path.join(homeDir, '.zhive'),
|
|
11
|
+
path.join(homeDir, '.hive'), // legacy hive dir
|
|
12
|
+
path.join(homeDir, '.openclaw', '.zhive'),
|
|
13
|
+
path.join(homeDir, '.openclaw', 'workspace', '.zhive'),
|
|
14
|
+
];
|
|
15
|
+
for (const p of pathToCheck) {
|
|
16
|
+
if (fs.existsSync(p)) {
|
|
17
|
+
return p;
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
return zhiveDir;
|
|
17
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhive/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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.
|
|
34
|
+
"@zhive/sdk": "^0.5.5",
|
|
35
35
|
"ai": "^6.0.71",
|
|
36
36
|
"axios": "^1.6.0",
|
|
37
37
|
"chalk": "^5.3.0",
|