@taskhunt/cli 0.1.2 → 0.1.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=commands.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/commands.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,248 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { Command } from 'commander';
3
+ // Mock api and spinner before importing commands
4
+ vi.mock('../api.js', () => ({
5
+ api: {
6
+ get: vi.fn(),
7
+ post: vi.fn(),
8
+ patch: vi.fn(),
9
+ delete: vi.fn(),
10
+ },
11
+ sseStream: vi.fn(),
12
+ }));
13
+ vi.mock('../spinner.js', () => ({
14
+ withSpinner: vi.fn((_msg, fn) => fn()),
15
+ }));
16
+ vi.mock('../config.js', () => ({
17
+ loadConfig: vi.fn(() => ({ apiKey: 'th_live_test', apiUrl: 'https://api.example.com', agentId: 'agent-1' })),
18
+ saveConfig: vi.fn(),
19
+ deleteConfig: vi.fn(),
20
+ getApiUrl: vi.fn(() => 'https://api.example.com'),
21
+ getApiKey: vi.fn(() => 'th_live_test'),
22
+ requireApiKey: vi.fn(() => 'th_live_test'),
23
+ }));
24
+ const { api } = await import('../api.js');
25
+ const { createAuthCommand } = await import('../commands/auth.js');
26
+ const { createTasksCommand } = await import('../commands/tasks.js');
27
+ const { createAgentsCommand } = await import('../commands/agents.js');
28
+ // Helper: run a command and capture stdout/stderr
29
+ async function runCmd(cmd, args) {
30
+ const stdout = [];
31
+ const stderr = [];
32
+ const origLog = console.log;
33
+ const origErr = console.error;
34
+ const origExit = process.exit;
35
+ let exitCode = null;
36
+ console.log = (...a) => stdout.push(a.join(' '));
37
+ console.error = (...a) => stderr.push(a.join(' '));
38
+ process.exit = ((code) => { exitCode = code ?? 0; throw new Error(`exit:${code}`); });
39
+ try {
40
+ await cmd.parseAsync(['node', 'taskhunt', ...args]);
41
+ }
42
+ catch (e) {
43
+ if (!(e instanceof Error && e.message.startsWith('exit:')))
44
+ throw e;
45
+ }
46
+ finally {
47
+ console.log = origLog;
48
+ console.error = origErr;
49
+ process.exit = origExit;
50
+ }
51
+ return { stdout: stdout.join('\n'), stderr: stderr.join('\n'), exitCode };
52
+ }
53
+ // ─── CLI entry: command routing ───────────────────────────────────────────────
54
+ describe('CLI: command routing', () => {
55
+ it('registers auth, tasks, agents, agent commands', () => {
56
+ const program = new Command();
57
+ program.addCommand(createAuthCommand());
58
+ program.addCommand(createTasksCommand());
59
+ program.addCommand(createAgentsCommand());
60
+ program.addCommand(createAgentsCommand().name('agent'));
61
+ const names = program.commands.map(c => c.name());
62
+ expect(names).toContain('auth');
63
+ expect(names).toContain('tasks');
64
+ expect(names).toContain('agents');
65
+ expect(names).toContain('agent');
66
+ });
67
+ it('agents and agent commands expose same subcommands', () => {
68
+ const agentsSub = createAgentsCommand().commands.map(c => c.name());
69
+ const agentSub = createAgentsCommand().name('agent').commands.map(c => c.name());
70
+ expect(agentsSub).toEqual(agentSub);
71
+ expect(agentsSub).toContain('register');
72
+ expect(agentsSub).toContain('whoami');
73
+ expect(agentsSub).toContain('update');
74
+ });
75
+ });
76
+ // ─── auth commands ────────────────────────────────────────────────────────────
77
+ describe('auth whoami', () => {
78
+ beforeEach(() => vi.clearAllMocks());
79
+ it('calls /auth/me and prints participant info', async () => {
80
+ vi.mocked(api.get).mockResolvedValue({
81
+ participant: { id: 'p1', displayName: 'Alice', type: 'agent', status: 'active', roles: ['worker'] },
82
+ });
83
+ const { stdout } = await runCmd(createAuthCommand(), ['whoami']);
84
+ expect(api.get).toHaveBeenCalledWith('/auth/me');
85
+ expect(stdout).toContain('Alice');
86
+ });
87
+ it('prints json with --format json', async () => {
88
+ vi.mocked(api.get).mockResolvedValue({
89
+ participant: { id: 'p1', displayName: 'Alice', type: 'agent', status: 'active', roles: [] },
90
+ });
91
+ const { stdout } = await runCmd(createAuthCommand(), ['whoami', '--format', 'json']);
92
+ expect(stdout).toContain('"displayName"');
93
+ });
94
+ });
95
+ describe('auth logout', () => {
96
+ it('calls deleteConfig', async () => {
97
+ const { deleteConfig } = await import('../config.js');
98
+ await runCmd(createAuthCommand(), ['logout']);
99
+ expect(vi.mocked(deleteConfig)).toHaveBeenCalled();
100
+ });
101
+ });
102
+ // ─── tasks commands ───────────────────────────────────────────────────────────
103
+ describe('tasks list', () => {
104
+ beforeEach(() => vi.clearAllMocks());
105
+ it('calls GET /tasks and prints table', async () => {
106
+ vi.mocked(api.get).mockResolvedValue([
107
+ { id: 'aaaabbbb1234', title: 'Test task', status: 'open', category: 'data', budgetValue: '50', budgetCurrency: 'USD', createdAt: new Date().toISOString() },
108
+ ]);
109
+ const { stdout } = await runCmd(createTasksCommand(), ['list']);
110
+ expect(api.get).toHaveBeenCalledWith('/tasks');
111
+ expect(stdout).toContain('Test task');
112
+ });
113
+ it('passes status filter', async () => {
114
+ vi.mocked(api.get).mockResolvedValue([]);
115
+ await runCmd(createTasksCommand(), ['list', '--status', 'open']);
116
+ expect(api.get).toHaveBeenCalledWith('/tasks?status=open');
117
+ });
118
+ it('passes category filter', async () => {
119
+ vi.mocked(api.get).mockResolvedValue([]);
120
+ await runCmd(createTasksCommand(), ['list', '--category', 'data']);
121
+ expect(api.get).toHaveBeenCalledWith('/tasks?category=data');
122
+ });
123
+ it('passes min-budget filter', async () => {
124
+ vi.mocked(api.get).mockResolvedValue([]);
125
+ await runCmd(createTasksCommand(), ['list', '--min-budget', '100']);
126
+ expect(api.get).toHaveBeenCalledWith('/tasks?min_budget=100');
127
+ });
128
+ it('prints empty message when no tasks', async () => {
129
+ vi.mocked(api.get).mockResolvedValue([]);
130
+ const { stdout } = await runCmd(createTasksCommand(), ['list']);
131
+ expect(stdout).toContain('No tasks found');
132
+ });
133
+ it('prints json with --format json', async () => {
134
+ vi.mocked(api.get).mockResolvedValue([{ id: 'x', title: 'T', status: 'open', category: 'c', budgetValue: '1', budgetCurrency: 'USD', createdAt: '' }]);
135
+ const { stdout } = await runCmd(createTasksCommand(), ['list', '--format', 'json']);
136
+ expect(stdout).toContain('"id"');
137
+ });
138
+ });
139
+ describe('tasks get', () => {
140
+ beforeEach(() => vi.clearAllMocks());
141
+ it('calls GET /tasks/:id', async () => {
142
+ vi.mocked(api.get).mockResolvedValue({
143
+ id: 'task-1', title: 'My task', status: 'open', category: 'data',
144
+ budgetValue: '100', budgetCurrency: 'USD', description: 'desc',
145
+ posterId: 'p1', bidMode: 'fixed', createdAt: '', updatedAt: '',
146
+ });
147
+ const { stdout } = await runCmd(createTasksCommand(), ['get', 'task-1']);
148
+ expect(api.get).toHaveBeenCalledWith('/tasks/task-1');
149
+ expect(stdout).toContain('My task');
150
+ });
151
+ });
152
+ describe('tasks search', () => {
153
+ beforeEach(() => vi.clearAllMocks());
154
+ it('calls GET /tasks with query param', async () => {
155
+ vi.mocked(api.get).mockResolvedValue([]);
156
+ await runCmd(createTasksCommand(), ['search', 'residential ip']);
157
+ expect(api.get).toHaveBeenCalledWith('/tasks?q=residential%20ip');
158
+ });
159
+ });
160
+ describe('tasks claim', () => {
161
+ beforeEach(() => vi.clearAllMocks());
162
+ it('calls POST /tasks/:id/claim', async () => {
163
+ vi.mocked(api.post).mockResolvedValue({ taskId: 't1', assigneeId: 'a1', status: 'in_progress' });
164
+ const { stdout } = await runCmd(createTasksCommand(), ['claim', 'task-1']);
165
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/claim');
166
+ expect(stdout).toContain('Task claimed');
167
+ });
168
+ });
169
+ describe('tasks propose', () => {
170
+ beforeEach(() => vi.clearAllMocks());
171
+ it('calls POST /tasks/:id/proposals with correct body', async () => {
172
+ vi.mocked(api.post).mockResolvedValue({ id: 'prop-1', taskId: 't1', status: 'pending' });
173
+ await runCmd(createTasksCommand(), ['propose', 'task-1', '--approach', 'I can do it', '--price', '50', '--time', '60']);
174
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/proposals', {
175
+ approach: 'I can do it',
176
+ priceValue: '50',
177
+ priceCurrency: 'USD',
178
+ estimatedTime: 60,
179
+ });
180
+ });
181
+ });
182
+ describe('tasks checkpoint', () => {
183
+ beforeEach(() => vi.clearAllMocks());
184
+ it('calls POST /tasks/:id/checkpoint', async () => {
185
+ vi.mocked(api.post).mockResolvedValue({ id: 'cp1', taskId: 't1', label: 'Step 1 done' });
186
+ const { stdout } = await runCmd(createTasksCommand(), ['checkpoint', 'task-1', '--label', 'Step 1 done']);
187
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/checkpoint', { label: 'Step 1 done' });
188
+ expect(stdout).toContain('Step 1 done');
189
+ });
190
+ });
191
+ describe('tasks publish', () => {
192
+ beforeEach(() => vi.clearAllMocks());
193
+ it('calls POST /tasks/:id/publish', async () => {
194
+ vi.mocked(api.post).mockResolvedValue({ id: 't1', status: 'open' });
195
+ await runCmd(createTasksCommand(), ['publish', 'task-1']);
196
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/publish');
197
+ });
198
+ });
199
+ describe('tasks stake', () => {
200
+ beforeEach(() => vi.clearAllMocks());
201
+ it('calls POST /tasks/:id/stake', async () => {
202
+ vi.mocked(api.post).mockResolvedValue({ id: 't1', status: 'in_progress' });
203
+ await runCmd(createTasksCommand(), ['stake', 'task-1']);
204
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/stake');
205
+ });
206
+ });
207
+ describe('tasks accept-proposal', () => {
208
+ beforeEach(() => vi.clearAllMocks());
209
+ it('calls POST /tasks/:taskId/proposals/:proposalId/accept', async () => {
210
+ vi.mocked(api.post).mockResolvedValue({ id: 't1', status: 'assigned' });
211
+ await runCmd(createTasksCommand(), ['accept-proposal', 'task-1', 'prop-1']);
212
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/proposals/prop-1/accept');
213
+ });
214
+ });
215
+ describe('tasks review', () => {
216
+ beforeEach(() => vi.clearAllMocks());
217
+ it('calls POST /submissions/:id/review with verdict', async () => {
218
+ vi.mocked(api.post).mockResolvedValue({ id: 'r1', verdict: 'APPROVED' });
219
+ const { stdout } = await runCmd(createTasksCommand(), ['review', 'sub-1', '--verdict', 'APPROVED', '--comment', 'Looks good', '--score', '9']);
220
+ expect(api.post).toHaveBeenCalledWith('/submissions/sub-1/review', {
221
+ verdict: 'APPROVED',
222
+ comment: 'Looks good',
223
+ overallScore: 9,
224
+ });
225
+ expect(stdout).toContain('APPROVED');
226
+ });
227
+ });
228
+ // ─── agents commands ──────────────────────────────────────────────────────────
229
+ describe('agents whoami', () => {
230
+ beforeEach(() => vi.clearAllMocks());
231
+ it('calls GET /agents/:agentId and prints info', async () => {
232
+ vi.mocked(api.get).mockResolvedValue({
233
+ participant: { id: 'p1', displayName: 'Bot', type: 'agent', status: 'active' },
234
+ agent: { participantId: 'p1', agentFramework: 'claude', modelProvider: null, modelName: null, capabilities: [], verified: false, maxConcurrent: 3 },
235
+ reputation: {},
236
+ });
237
+ const { stdout } = await runCmd(createAgentsCommand(), ['whoami']);
238
+ expect(api.get).toHaveBeenCalledWith('/agents/agent-1');
239
+ expect(stdout).toContain('Bot');
240
+ });
241
+ it('exits with error when no agentId configured', async () => {
242
+ const { loadConfig } = await import('../config.js');
243
+ vi.mocked(loadConfig).mockReturnValueOnce(null);
244
+ const { exitCode } = await runCmd(createAgentsCommand(), ['whoami']);
245
+ expect(exitCode).toBe(1);
246
+ });
247
+ });
248
+ //# sourceMappingURL=commands.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.test.js","sourceRoot":"","sources":["../../src/__tests__/commands.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,iDAAiD;AACjD,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1B,GAAG,EAAE;QACH,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACZ,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;KAChB;IACD,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;CACnB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,IAAY,EAAE,EAAiB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;CAC9D,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7B,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,yBAAyB,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5G,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;IACnB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;IACrB,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,yBAAyB,CAAC;IACjD,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC;IACtC,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC;CAC3C,CAAC,CAAC,CAAC;AAEJ,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;AAC1C,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;AAClE,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;AACpE,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;AAEtE,kDAAkD;AAClD,KAAK,UAAU,MAAM,CAAC,GAAY,EAAE,IAAc;IAChD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAE9B,IAAI,QAAQ,GAAkB,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,GAAG,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAU,CAAC;IAExG,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YAAE,MAAM,CAAC,CAAC;IACtE,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC;QACtB,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC;QACxB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAC1B,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;AAC5E,CAAC;AAED,iFAAiF;AAEjF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAExD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACjF,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACxC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC;YACnC,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE;SACpG,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC;YACnC,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;SAC5F,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QACrF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QAClC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACtD,MAAM,MAAM,CAAC,iBAAiB,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC;YACnC,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE;SAC5J,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACpC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACtC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,sBAAsB,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,uBAAuB,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACvJ,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACpC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC;YACnC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;YAChE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM;YAC9D,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE;SAC/D,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;QACtD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,2BAA2B,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;QACjG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC3E,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAC;QAC7D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACzF,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QACxH,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,yBAAyB,EAAE;YAC/D,QAAQ,EAAE,aAAa;YACvB,UAAU,EAAE,IAAI;YAChB,aAAa,EAAE,KAAK;YACpB,aAAa,EAAE,EAAE;SAClB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QACzF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;QAC1G,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QAC5F,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACpE,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,uBAAuB,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;QAC3E,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACxE,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC5E,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,uCAAuC,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACzE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/I,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,2BAA2B,EAAE;YACjE,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,YAAY;YACrB,YAAY,EAAE,CAAC;SAChB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;IAErC,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC;YACnC,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;YAC9E,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAAE;YACnJ,UAAU,EAAE,EAAE;SACf,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACpD,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACrE,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ // Use a temp dir for config isolation
6
+ const testHome = join(tmpdir(), 'taskhunt-test-' + process.pid);
7
+ vi.stubEnv('HOME', testHome);
8
+ vi.stubEnv('TASKHUNT_API_KEY', '');
9
+ vi.stubEnv('TASKHUNT_API_URL', '');
10
+ // Must import AFTER env stub
11
+ const { loadConfig, saveConfig, deleteConfig, getApiUrl, getApiKey } = await import('../config.js');
12
+ describe('config', () => {
13
+ beforeEach(() => {
14
+ mkdirSync(join(testHome, '.taskhunt'), { recursive: true });
15
+ deleteConfig();
16
+ });
17
+ afterEach(() => {
18
+ rmSync(testHome, { recursive: true, force: true });
19
+ });
20
+ it('loadConfig returns null when no config file', () => {
21
+ expect(loadConfig()).toBeNull();
22
+ });
23
+ it('saveConfig + loadConfig round-trips', () => {
24
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://api.example.com' });
25
+ const cfg = loadConfig();
26
+ expect(cfg?.apiKey).toBe('th_live_abc');
27
+ expect(cfg?.apiUrl).toBe('https://api.example.com');
28
+ });
29
+ it('saveConfig preserves optional fields', () => {
30
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://api.example.com', participantId: 'p1', agentId: 'a1' });
31
+ const cfg = loadConfig();
32
+ expect(cfg?.participantId).toBe('p1');
33
+ expect(cfg?.agentId).toBe('a1');
34
+ });
35
+ it('deleteConfig removes the file', () => {
36
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://api.example.com' });
37
+ deleteConfig();
38
+ expect(loadConfig()).toBeNull();
39
+ });
40
+ it('deleteConfig is safe when no file exists', () => {
41
+ expect(() => deleteConfig()).not.toThrow();
42
+ });
43
+ it('getApiUrl falls back to config when env var is empty', () => {
44
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://custom.api.com' });
45
+ expect(getApiUrl()).toBe('https://custom.api.com');
46
+ });
47
+ it('getApiKey falls back to config when env var is empty', () => {
48
+ saveConfig({ apiKey: 'th_live_xyz', apiUrl: 'https://api.example.com' });
49
+ expect(getApiKey()).toBe('th_live_xyz');
50
+ });
51
+ });
52
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA,qBAAa,QAAS,SAAQ,KAAK;aAEf,IAAI,EAAE,MAAM;aAEZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFjC,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAKpD;AAmCD,eAAO,MAAM,GAAG;UACR,CAAC,QAAQ,MAAM;WACd,CAAC,QAAQ,MAAM,SAAS,OAAO;YAC9B,CAAC,QAAQ,MAAM,SAAS,OAAO;aAC9B,CAAC,QAAQ,MAAM;CACzB,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAMxF"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAQA,qBAAa,QAAS,SAAQ,KAAK;aAEf,IAAI,EAAE,MAAM;aAEZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFjC,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAKpD;AAmCD,eAAO,MAAM,GAAG;UACR,CAAC,QAAQ,MAAM;WACd,CAAC,QAAQ,MAAM,SAAS,OAAO;YAC9B,CAAC,QAAQ,MAAM,SAAS,OAAO;aAC9B,CAAC,QAAQ,MAAM;CACzB,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAMxF"}
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGvD,MAAM,OAAO,QAAS,SAAQ,KAAK;IAEf;IAEA;IAHlB,YACkB,IAAY,EAC5B,OAAe,EACC,OAAiC;QAEjD,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAQ;QAEZ,YAAO,GAAP,OAAO,CAA0B;QAGjD,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED,KAAK,UAAU,OAAO,CACpB,MAAc,EACd,IAAY,EACZ,IAAc;IAEd,MAAM,GAAG,GAAG,GAAG,SAAS,EAAE,UAAU,IAAI,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAE/B,yDAAyD;IACzD,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAChF,MAAM,OAAO,GAA2B,QAAQ;QAC9C,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE;QACvD,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,MAAM,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC1E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAC/C,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM;QACN,OAAO;QACP,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC5D,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAmB,CAAC;IAElD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,GAAG,EAAE,CAAI,IAAY,EAAE,EAAE,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,CAAC;IACjD,IAAI,EAAE,CAAI,IAAY,EAAE,IAAc,EAAE,EAAE,CAAC,OAAO,CAAI,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;IACzE,KAAK,EAAE,CAAI,IAAY,EAAE,IAAc,EAAE,EAAE,CAAC,OAAO,CAAI,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;IAC3E,MAAM,EAAE,CAAI,IAAY,EAAE,EAAE,CAAC,OAAO,CAAI,QAAQ,EAAE,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,OAAO;QACL,GAAG,EAAE,GAAG,SAAS,EAAE,UAAU,IAAI,EAAE;QACnC,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,MAAM,EAAE,EAAE;KACjD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQvD,MAAM,OAAO,QAAS,SAAQ,KAAK;IAEf;IAEA;IAHlB,YACkB,IAAY,EAC5B,OAAe,EACC,OAAiC;QAEjD,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAQ;QAEZ,YAAO,GAAP,OAAO,CAA0B;QAGjD,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED,KAAK,UAAU,OAAO,CACpB,MAAc,EACd,IAAY,EACZ,IAAc;IAEd,MAAM,GAAG,GAAG,GAAG,SAAS,EAAE,UAAU,IAAI,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAE/B,yDAAyD;IACzD,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAChF,MAAM,OAAO,GAA2B,QAAQ;QAC9C,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE;QACvD,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,MAAM,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC1E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAC/C,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM;QACN,OAAO;QACP,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC5D,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAmB,CAAC;IAElD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,GAAG,EAAE,CAAI,IAAY,EAAE,EAAE,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,CAAC;IACjD,IAAI,EAAE,CAAI,IAAY,EAAE,IAAc,EAAE,EAAE,CAAC,OAAO,CAAI,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;IACzE,KAAK,EAAE,CAAI,IAAY,EAAE,IAAc,EAAE,EAAE,CAAC,OAAO,CAAI,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;IAC3E,MAAM,EAAE,CAAI,IAAY,EAAE,EAAE,CAAC,OAAO,CAAI,QAAQ,EAAE,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,OAAO;QACL,GAAG,EAAE,GAAG,SAAS,EAAE,UAAU,IAAI,EAAE;QACnC,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,MAAM,EAAE,EAAE;KACjD,CAAC;AACJ,CAAC"}
package/dist/config.js CHANGED
@@ -4,10 +4,10 @@ import { join } from 'node:path';
4
4
  const CONFIG_DIR = join(homedir(), '.taskhunt');
5
5
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
6
6
  export function getApiUrl() {
7
- return process.env['TASKHUNT_API_URL'] ?? loadConfig()?.apiUrl ?? 'https://api.taskhunt.ai';
7
+ return process.env['TASKHUNT_API_URL'] || loadConfig()?.apiUrl || 'https://api.taskhunt.ai';
8
8
  }
9
9
  export function getApiKey() {
10
- return process.env['TASKHUNT_API_KEY'] ?? loadConfig()?.apiKey;
10
+ return process.env['TASKHUNT_API_KEY'] || loadConfig()?.apiKey;
11
11
  }
12
12
  export function requireApiKey() {
13
13
  const key = getApiKey();
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ program
11
11
  program.addCommand(createAuthCommand());
12
12
  program.addCommand(createTasksCommand());
13
13
  program.addCommand(createAgentsCommand());
14
- program.addCommand(createAgentsCommand().name('agent').alias('agent'));
14
+ program.addCommand(createAgentsCommand().name('agent'));
15
15
  program.parseAsync().catch((err) => {
16
16
  console.error(err.message);
17
17
  process.exit(1);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAEvE,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAExD,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskhunt/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "taskhunt": "./dist/index.js"
@@ -11,7 +11,8 @@
11
11
  "build": "tsc",
12
12
  "dev": "tsc --watch",
13
13
  "lint": "tsc --noEmit",
14
- "prepare": "pnpm build"
14
+ "prepare": "pnpm build",
15
+ "test": "vitest run"
15
16
  },
16
17
  "dependencies": {
17
18
  "chalk": "^5.6.2",
@@ -21,6 +22,7 @@
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^22.0.0",
24
- "typescript": "^5.7.0"
25
+ "typescript": "^5.7.0",
26
+ "vitest": "^4.1.0"
25
27
  }
26
28
  }
@@ -0,0 +1,292 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { Command } from 'commander';
3
+
4
+ // Mock api and spinner before importing commands
5
+ vi.mock('../api.js', () => ({
6
+ api: {
7
+ get: vi.fn(),
8
+ post: vi.fn(),
9
+ patch: vi.fn(),
10
+ delete: vi.fn(),
11
+ },
12
+ sseStream: vi.fn(),
13
+ }));
14
+
15
+ vi.mock('../spinner.js', () => ({
16
+ withSpinner: vi.fn((_msg: string, fn: () => unknown) => fn()),
17
+ }));
18
+
19
+ vi.mock('../config.js', () => ({
20
+ loadConfig: vi.fn(() => ({ apiKey: 'th_live_test', apiUrl: 'https://api.example.com', agentId: 'agent-1' })),
21
+ saveConfig: vi.fn(),
22
+ deleteConfig: vi.fn(),
23
+ getApiUrl: vi.fn(() => 'https://api.example.com'),
24
+ getApiKey: vi.fn(() => 'th_live_test'),
25
+ requireApiKey: vi.fn(() => 'th_live_test'),
26
+ }));
27
+
28
+ const { api } = await import('../api.js');
29
+ const { createAuthCommand } = await import('../commands/auth.js');
30
+ const { createTasksCommand } = await import('../commands/tasks.js');
31
+ const { createAgentsCommand } = await import('../commands/agents.js');
32
+
33
+ // Helper: run a command and capture stdout/stderr
34
+ async function runCmd(cmd: Command, args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number | null }> {
35
+ const stdout: string[] = [];
36
+ const stderr: string[] = [];
37
+ const origLog = console.log;
38
+ const origErr = console.error;
39
+ const origExit = process.exit;
40
+
41
+ let exitCode: number | null = null;
42
+
43
+ console.log = (...a) => stdout.push(a.join(' '));
44
+ console.error = (...a) => stderr.push(a.join(' '));
45
+ process.exit = ((code?: number) => { exitCode = code ?? 0; throw new Error(`exit:${code}`); }) as never;
46
+
47
+ try {
48
+ await cmd.parseAsync(['node', 'taskhunt', ...args]);
49
+ } catch (e) {
50
+ if (!(e instanceof Error && e.message.startsWith('exit:'))) throw e;
51
+ } finally {
52
+ console.log = origLog;
53
+ console.error = origErr;
54
+ process.exit = origExit;
55
+ }
56
+
57
+ return { stdout: stdout.join('\n'), stderr: stderr.join('\n'), exitCode };
58
+ }
59
+
60
+ // ─── CLI entry: command routing ───────────────────────────────────────────────
61
+
62
+ describe('CLI: command routing', () => {
63
+ it('registers auth, tasks, agents, agent commands', () => {
64
+ const program = new Command();
65
+ program.addCommand(createAuthCommand());
66
+ program.addCommand(createTasksCommand());
67
+ program.addCommand(createAgentsCommand());
68
+ program.addCommand(createAgentsCommand().name('agent'));
69
+
70
+ const names = program.commands.map(c => c.name());
71
+ expect(names).toContain('auth');
72
+ expect(names).toContain('tasks');
73
+ expect(names).toContain('agents');
74
+ expect(names).toContain('agent');
75
+ });
76
+
77
+ it('agents and agent commands expose same subcommands', () => {
78
+ const agentsSub = createAgentsCommand().commands.map(c => c.name());
79
+ const agentSub = createAgentsCommand().name('agent').commands.map(c => c.name());
80
+ expect(agentsSub).toEqual(agentSub);
81
+ expect(agentsSub).toContain('register');
82
+ expect(agentsSub).toContain('whoami');
83
+ expect(agentsSub).toContain('update');
84
+ });
85
+ });
86
+
87
+ // ─── auth commands ────────────────────────────────────────────────────────────
88
+
89
+ describe('auth whoami', () => {
90
+ beforeEach(() => vi.clearAllMocks());
91
+
92
+ it('calls /auth/me and prints participant info', async () => {
93
+ vi.mocked(api.get).mockResolvedValue({
94
+ participant: { id: 'p1', displayName: 'Alice', type: 'agent', status: 'active', roles: ['worker'] },
95
+ });
96
+ const { stdout } = await runCmd(createAuthCommand(), ['whoami']);
97
+ expect(api.get).toHaveBeenCalledWith('/auth/me');
98
+ expect(stdout).toContain('Alice');
99
+ });
100
+
101
+ it('prints json with --format json', async () => {
102
+ vi.mocked(api.get).mockResolvedValue({
103
+ participant: { id: 'p1', displayName: 'Alice', type: 'agent', status: 'active', roles: [] },
104
+ });
105
+ const { stdout } = await runCmd(createAuthCommand(), ['whoami', '--format', 'json']);
106
+ expect(stdout).toContain('"displayName"');
107
+ });
108
+ });
109
+
110
+ describe('auth logout', () => {
111
+ it('calls deleteConfig', async () => {
112
+ const { deleteConfig } = await import('../config.js');
113
+ await runCmd(createAuthCommand(), ['logout']);
114
+ expect(vi.mocked(deleteConfig)).toHaveBeenCalled();
115
+ });
116
+ });
117
+
118
+ // ─── tasks commands ───────────────────────────────────────────────────────────
119
+
120
+ describe('tasks list', () => {
121
+ beforeEach(() => vi.clearAllMocks());
122
+
123
+ it('calls GET /tasks and prints table', async () => {
124
+ vi.mocked(api.get).mockResolvedValue([
125
+ { id: 'aaaabbbb1234', title: 'Test task', status: 'open', category: 'data', budgetValue: '50', budgetCurrency: 'USD', createdAt: new Date().toISOString() },
126
+ ]);
127
+ const { stdout } = await runCmd(createTasksCommand(), ['list']);
128
+ expect(api.get).toHaveBeenCalledWith('/tasks');
129
+ expect(stdout).toContain('Test task');
130
+ });
131
+
132
+ it('passes status filter', async () => {
133
+ vi.mocked(api.get).mockResolvedValue([]);
134
+ await runCmd(createTasksCommand(), ['list', '--status', 'open']);
135
+ expect(api.get).toHaveBeenCalledWith('/tasks?status=open');
136
+ });
137
+
138
+ it('passes category filter', async () => {
139
+ vi.mocked(api.get).mockResolvedValue([]);
140
+ await runCmd(createTasksCommand(), ['list', '--category', 'data']);
141
+ expect(api.get).toHaveBeenCalledWith('/tasks?category=data');
142
+ });
143
+
144
+ it('passes min-budget filter', async () => {
145
+ vi.mocked(api.get).mockResolvedValue([]);
146
+ await runCmd(createTasksCommand(), ['list', '--min-budget', '100']);
147
+ expect(api.get).toHaveBeenCalledWith('/tasks?min_budget=100');
148
+ });
149
+
150
+ it('prints empty message when no tasks', async () => {
151
+ vi.mocked(api.get).mockResolvedValue([]);
152
+ const { stdout } = await runCmd(createTasksCommand(), ['list']);
153
+ expect(stdout).toContain('No tasks found');
154
+ });
155
+
156
+ it('prints json with --format json', async () => {
157
+ vi.mocked(api.get).mockResolvedValue([{ id: 'x', title: 'T', status: 'open', category: 'c', budgetValue: '1', budgetCurrency: 'USD', createdAt: '' }]);
158
+ const { stdout } = await runCmd(createTasksCommand(), ['list', '--format', 'json']);
159
+ expect(stdout).toContain('"id"');
160
+ });
161
+ });
162
+
163
+ describe('tasks get', () => {
164
+ beforeEach(() => vi.clearAllMocks());
165
+
166
+ it('calls GET /tasks/:id', async () => {
167
+ vi.mocked(api.get).mockResolvedValue({
168
+ id: 'task-1', title: 'My task', status: 'open', category: 'data',
169
+ budgetValue: '100', budgetCurrency: 'USD', description: 'desc',
170
+ posterId: 'p1', bidMode: 'fixed', createdAt: '', updatedAt: '',
171
+ });
172
+ const { stdout } = await runCmd(createTasksCommand(), ['get', 'task-1']);
173
+ expect(api.get).toHaveBeenCalledWith('/tasks/task-1');
174
+ expect(stdout).toContain('My task');
175
+ });
176
+ });
177
+
178
+ describe('tasks search', () => {
179
+ beforeEach(() => vi.clearAllMocks());
180
+
181
+ it('calls GET /tasks with query param', async () => {
182
+ vi.mocked(api.get).mockResolvedValue([]);
183
+ await runCmd(createTasksCommand(), ['search', 'residential ip']);
184
+ expect(api.get).toHaveBeenCalledWith('/tasks?q=residential%20ip');
185
+ });
186
+ });
187
+
188
+ describe('tasks claim', () => {
189
+ beforeEach(() => vi.clearAllMocks());
190
+
191
+ it('calls POST /tasks/:id/claim', async () => {
192
+ vi.mocked(api.post).mockResolvedValue({ taskId: 't1', assigneeId: 'a1', status: 'in_progress' });
193
+ const { stdout } = await runCmd(createTasksCommand(), ['claim', 'task-1']);
194
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/claim');
195
+ expect(stdout).toContain('Task claimed');
196
+ });
197
+ });
198
+
199
+ describe('tasks propose', () => {
200
+ beforeEach(() => vi.clearAllMocks());
201
+
202
+ it('calls POST /tasks/:id/proposals with correct body', async () => {
203
+ vi.mocked(api.post).mockResolvedValue({ id: 'prop-1', taskId: 't1', status: 'pending' });
204
+ await runCmd(createTasksCommand(), ['propose', 'task-1', '--approach', 'I can do it', '--price', '50', '--time', '60']);
205
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/proposals', {
206
+ approach: 'I can do it',
207
+ priceValue: '50',
208
+ priceCurrency: 'USD',
209
+ estimatedTime: 60,
210
+ });
211
+ });
212
+ });
213
+
214
+ describe('tasks checkpoint', () => {
215
+ beforeEach(() => vi.clearAllMocks());
216
+
217
+ it('calls POST /tasks/:id/checkpoint', async () => {
218
+ vi.mocked(api.post).mockResolvedValue({ id: 'cp1', taskId: 't1', label: 'Step 1 done' });
219
+ const { stdout } = await runCmd(createTasksCommand(), ['checkpoint', 'task-1', '--label', 'Step 1 done']);
220
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/checkpoint', { label: 'Step 1 done' });
221
+ expect(stdout).toContain('Step 1 done');
222
+ });
223
+ });
224
+
225
+ describe('tasks publish', () => {
226
+ beforeEach(() => vi.clearAllMocks());
227
+
228
+ it('calls POST /tasks/:id/publish', async () => {
229
+ vi.mocked(api.post).mockResolvedValue({ id: 't1', status: 'open' });
230
+ await runCmd(createTasksCommand(), ['publish', 'task-1']);
231
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/publish');
232
+ });
233
+ });
234
+
235
+ describe('tasks stake', () => {
236
+ beforeEach(() => vi.clearAllMocks());
237
+
238
+ it('calls POST /tasks/:id/stake', async () => {
239
+ vi.mocked(api.post).mockResolvedValue({ id: 't1', status: 'in_progress' });
240
+ await runCmd(createTasksCommand(), ['stake', 'task-1']);
241
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/stake');
242
+ });
243
+ });
244
+
245
+ describe('tasks accept-proposal', () => {
246
+ beforeEach(() => vi.clearAllMocks());
247
+
248
+ it('calls POST /tasks/:taskId/proposals/:proposalId/accept', async () => {
249
+ vi.mocked(api.post).mockResolvedValue({ id: 't1', status: 'assigned' });
250
+ await runCmd(createTasksCommand(), ['accept-proposal', 'task-1', 'prop-1']);
251
+ expect(api.post).toHaveBeenCalledWith('/tasks/task-1/proposals/prop-1/accept');
252
+ });
253
+ });
254
+
255
+ describe('tasks review', () => {
256
+ beforeEach(() => vi.clearAllMocks());
257
+
258
+ it('calls POST /submissions/:id/review with verdict', async () => {
259
+ vi.mocked(api.post).mockResolvedValue({ id: 'r1', verdict: 'APPROVED' });
260
+ const { stdout } = await runCmd(createTasksCommand(), ['review', 'sub-1', '--verdict', 'APPROVED', '--comment', 'Looks good', '--score', '9']);
261
+ expect(api.post).toHaveBeenCalledWith('/submissions/sub-1/review', {
262
+ verdict: 'APPROVED',
263
+ comment: 'Looks good',
264
+ overallScore: 9,
265
+ });
266
+ expect(stdout).toContain('APPROVED');
267
+ });
268
+ });
269
+
270
+ // ─── agents commands ──────────────────────────────────────────────────────────
271
+
272
+ describe('agents whoami', () => {
273
+ beforeEach(() => vi.clearAllMocks());
274
+
275
+ it('calls GET /agents/:agentId and prints info', async () => {
276
+ vi.mocked(api.get).mockResolvedValue({
277
+ participant: { id: 'p1', displayName: 'Bot', type: 'agent', status: 'active' },
278
+ agent: { participantId: 'p1', agentFramework: 'claude', modelProvider: null, modelName: null, capabilities: [], verified: false, maxConcurrent: 3 },
279
+ reputation: {},
280
+ });
281
+ const { stdout } = await runCmd(createAgentsCommand(), ['whoami']);
282
+ expect(api.get).toHaveBeenCalledWith('/agents/agent-1');
283
+ expect(stdout).toContain('Bot');
284
+ });
285
+
286
+ it('exits with error when no agentId configured', async () => {
287
+ const { loadConfig } = await import('../config.js');
288
+ vi.mocked(loadConfig).mockReturnValueOnce(null);
289
+ const { exitCode } = await runCmd(createAgentsCommand(), ['whoami']);
290
+ expect(exitCode).toBe(1);
291
+ });
292
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, rmSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+
6
+ // Use a temp dir for config isolation
7
+ const testHome = join(tmpdir(), 'taskhunt-test-' + process.pid);
8
+
9
+ vi.stubEnv('HOME', testHome);
10
+ vi.stubEnv('TASKHUNT_API_KEY', '');
11
+ vi.stubEnv('TASKHUNT_API_URL', '');
12
+
13
+ // Must import AFTER env stub
14
+ const { loadConfig, saveConfig, deleteConfig, getApiUrl, getApiKey } = await import('../config.js');
15
+
16
+ describe('config', () => {
17
+ beforeEach(() => {
18
+ mkdirSync(join(testHome, '.taskhunt'), { recursive: true });
19
+ deleteConfig();
20
+ });
21
+
22
+ afterEach(() => {
23
+ rmSync(testHome, { recursive: true, force: true });
24
+ });
25
+
26
+ it('loadConfig returns null when no config file', () => {
27
+ expect(loadConfig()).toBeNull();
28
+ });
29
+
30
+ it('saveConfig + loadConfig round-trips', () => {
31
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://api.example.com' });
32
+ const cfg = loadConfig();
33
+ expect(cfg?.apiKey).toBe('th_live_abc');
34
+ expect(cfg?.apiUrl).toBe('https://api.example.com');
35
+ });
36
+
37
+ it('saveConfig preserves optional fields', () => {
38
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://api.example.com', participantId: 'p1', agentId: 'a1' });
39
+ const cfg = loadConfig();
40
+ expect(cfg?.participantId).toBe('p1');
41
+ expect(cfg?.agentId).toBe('a1');
42
+ });
43
+
44
+ it('deleteConfig removes the file', () => {
45
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://api.example.com' });
46
+ deleteConfig();
47
+ expect(loadConfig()).toBeNull();
48
+ });
49
+
50
+ it('deleteConfig is safe when no file exists', () => {
51
+ expect(() => deleteConfig()).not.toThrow();
52
+ });
53
+
54
+ it('getApiUrl falls back to config when env var is empty', () => {
55
+ saveConfig({ apiKey: 'th_live_abc', apiUrl: 'https://custom.api.com' });
56
+ expect(getApiUrl()).toBe('https://custom.api.com');
57
+ });
58
+
59
+ it('getApiKey falls back to config when env var is empty', () => {
60
+ saveConfig({ apiKey: 'th_live_xyz', apiUrl: 'https://api.example.com' });
61
+ expect(getApiKey()).toBe('th_live_xyz');
62
+ });
63
+ });
package/src/api.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { getApiUrl, requireApiKey } from './config.js';
2
- import type { ApiResponse } from '@taskhunt/shared';
2
+
3
+ interface ApiResponse<T> {
4
+ success: boolean;
5
+ data: T;
6
+ error: { code: string; message: string; details?: Record<string, unknown> };
7
+ }
3
8
 
4
9
  export class ApiError extends Error {
5
10
  constructor(
package/src/config.ts CHANGED
@@ -13,11 +13,11 @@ const CONFIG_DIR = join(homedir(), '.taskhunt');
13
13
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
14
14
 
15
15
  export function getApiUrl(): string {
16
- return process.env['TASKHUNT_API_URL'] ?? loadConfig()?.apiUrl ?? 'https://api.taskhunt.ai';
16
+ return process.env['TASKHUNT_API_URL'] || loadConfig()?.apiUrl || 'https://api.taskhunt.ai';
17
17
  }
18
18
 
19
19
  export function getApiKey(): string | undefined {
20
- return process.env['TASKHUNT_API_KEY'] ?? loadConfig()?.apiKey;
20
+ return process.env['TASKHUNT_API_KEY'] || loadConfig()?.apiKey;
21
21
  }
22
22
 
23
23
  export function requireApiKey(): string {
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ program
15
15
  program.addCommand(createAuthCommand());
16
16
  program.addCommand(createTasksCommand());
17
17
  program.addCommand(createAgentsCommand());
18
- program.addCommand(createAgentsCommand().name('agent').alias('agent'));
18
+ program.addCommand(createAgentsCommand().name('agent'));
19
19
 
20
20
  program.parseAsync().catch((err: Error) => {
21
21
  console.error(err.message);
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: false,
6
+ environment: 'node',
7
+ include: ['src/__tests__/**/*.test.ts'],
8
+ },
9
+ });