@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.
- package/dist/commands/agent/commands/profile.js +3 -2
- package/dist/commands/agent/commands/profile.test.js +10 -12
- package/dist/commands/doctor/commands/index.js +93 -0
- package/dist/commands/megathread/commands/create-comment.js +4 -12
- package/dist/commands/megathread/commands/create-comment.test.js +16 -192
- package/dist/commands/megathread/commands/list.js +5 -5
- package/dist/commands/megathread/commands/list.test.js +15 -14
- package/dist/commands/start/commands/prediction.js +4 -6
- package/dist/commands/start/hooks/useChat.js +40 -41
- package/dist/commands/start/hooks/utils.js +3 -3
- package/dist/commands/start/services/command-registry.js +1 -1
- package/dist/index.js +2 -0
- package/dist/shared/agent/handler.js +1 -5
- package/dist/shared/config/agent.js +0 -11
- package/dist/shared/config/agent.test.js +4 -35
- package/dist/shared/config/constant.js +2 -2
- package/package.json +2 -2
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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 {
|
|
35
|
+
import { loadConfig } from '@zhive/sdk';
|
|
38
36
|
import { createAgentProfileCommand } from './profile.js';
|
|
39
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
34
|
+
import { HiveClient, loadConfig } from '@zhive/sdk';
|
|
37
35
|
import { createMegathreadCreateCommentCommand } from './create-comment.js';
|
|
38
36
|
const MockHiveClient = HiveClient;
|
|
39
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
54
|
-
if (!
|
|
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,
|
|
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
|
-
|
|
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,
|
|
33
|
+
import { HiveClient, loadConfig } from '@zhive/sdk';
|
|
34
34
|
import { createMegathreadListCommand } from './list.js';
|
|
35
35
|
const MockHiveClient = HiveClient;
|
|
36
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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 `[
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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.
|
|
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))}`;
|
|
@@ -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.
|
|
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
|
-
|
|
17
|
+
loadConfig: vi.fn(),
|
|
18
18
|
}));
|
|
19
|
-
import {
|
|
20
|
-
import { findAgentByName,
|
|
21
|
-
const
|
|
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', '.
|
|
13
|
-
path.join(homeDir, '.openclaw', 'workspace', '.
|
|
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.
|
|
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.
|
|
34
|
+
"@zhive/sdk": "^0.5.6",
|
|
35
35
|
"ai": "^6.0.71",
|
|
36
36
|
"axios": "^1.6.0",
|
|
37
37
|
"chalk": "^5.3.0",
|