@wenrwa/marketplace-cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +14 -0
  2. package/dist/__tests__/agent.test.d.ts +1 -0
  3. package/dist/__tests__/agent.test.js +138 -0
  4. package/dist/__tests__/apps.test.d.ts +1 -0
  5. package/dist/__tests__/apps.test.js +160 -0
  6. package/dist/__tests__/auth.test.d.ts +1 -0
  7. package/dist/__tests__/auth.test.js +94 -0
  8. package/dist/__tests__/bounty.test.d.ts +1 -0
  9. package/dist/__tests__/bounty.test.js +185 -0
  10. package/dist/__tests__/client.test.d.ts +1 -0
  11. package/dist/__tests__/client.test.js +106 -0
  12. package/dist/__tests__/config.test.d.ts +1 -0
  13. package/dist/__tests__/config.test.js +99 -0
  14. package/dist/__tests__/dashboard-helpers.test.d.ts +1 -0
  15. package/dist/__tests__/dashboard-helpers.test.js +123 -0
  16. package/dist/__tests__/format.test.d.ts +1 -0
  17. package/dist/__tests__/format.test.js +144 -0
  18. package/dist/__tests__/helpers.d.ts +7 -0
  19. package/dist/__tests__/helpers.js +12 -0
  20. package/dist/__tests__/treasury.test.d.ts +1 -0
  21. package/dist/__tests__/treasury.test.js +122 -0
  22. package/dist/__tests__/websocket.test.d.ts +1 -0
  23. package/dist/__tests__/websocket.test.js +61 -0
  24. package/dist/__tests__/workspace.test.d.ts +1 -0
  25. package/dist/__tests__/workspace.test.js +105 -0
  26. package/dist/commands/agent.d.ts +0 -0
  27. package/dist/commands/agent.js +0 -0
  28. package/dist/commands/apps.d.ts +2 -0
  29. package/dist/commands/apps.js +174 -0
  30. package/dist/commands/auth.d.ts +0 -0
  31. package/dist/commands/auth.js +0 -0
  32. package/dist/commands/bounty.d.ts +0 -0
  33. package/dist/commands/bounty.js +8 -0
  34. package/dist/commands/dashboard.d.ts +31 -0
  35. package/dist/commands/dashboard.js +5 -0
  36. package/dist/commands/treasury.d.ts +0 -0
  37. package/dist/commands/treasury.js +2 -2
  38. package/dist/commands/webhook.d.ts +2 -0
  39. package/dist/commands/webhook.js +131 -0
  40. package/dist/commands/workspace.d.ts +0 -0
  41. package/dist/commands/workspace.js +160 -13
  42. package/dist/index.d.ts +0 -0
  43. package/dist/index.js +4 -0
  44. package/dist/utils/client.d.ts +1 -0
  45. package/dist/utils/client.js +3 -0
  46. package/dist/utils/config.d.ts +0 -0
  47. package/dist/utils/config.js +0 -0
  48. package/dist/utils/format.d.ts +0 -1
  49. package/dist/utils/format.js +2 -11
  50. package/dist/utils/websocket.d.ts +0 -0
  51. package/dist/utils/websocket.js +0 -0
  52. package/package.json +5 -2
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock('../utils/config', () => ({
4
+ requireConfig: jest.fn().mockReturnValue({
5
+ apiUrl: 'http://test:3001/api/marketplace',
6
+ apiKey: 'test-wallet-key',
7
+ }),
8
+ }));
9
+ const client_1 = require("../utils/client");
10
+ let mockFetch;
11
+ beforeEach(() => {
12
+ mockFetch = jest.fn();
13
+ global.fetch = mockFetch;
14
+ // Reset singleton
15
+ client_1.getClient.__reset?.();
16
+ });
17
+ afterEach(() => {
18
+ jest.restoreAllMocks();
19
+ });
20
+ function okResponse(data) {
21
+ return Promise.resolve({
22
+ ok: true,
23
+ status: 200,
24
+ json: () => Promise.resolve(data),
25
+ });
26
+ }
27
+ function errorResponse(status, data) {
28
+ return Promise.resolve({
29
+ ok: false,
30
+ status,
31
+ json: () => Promise.resolve(data),
32
+ });
33
+ }
34
+ describe('ApiClient', () => {
35
+ it('reads apiUrl and apiKey from config', () => {
36
+ const client = new client_1.ApiClient();
37
+ expect(client.getApiUrl()).toBe('http://test:3001/api/marketplace');
38
+ expect(client.getApiKey()).toBe('test-wallet-key');
39
+ });
40
+ describe('get()', () => {
41
+ it('sends GET request with correct URL and headers', async () => {
42
+ mockFetch.mockReturnValue(okResponse({ success: true, items: [] }));
43
+ const client = new client_1.ApiClient();
44
+ await client.get('/bounties');
45
+ expect(mockFetch).toHaveBeenCalledWith('http://test:3001/api/marketplace/bounties', expect.objectContaining({
46
+ method: 'GET',
47
+ headers: expect.objectContaining({
48
+ 'X-Wallet-Pubkey': 'test-wallet-key',
49
+ }),
50
+ }));
51
+ });
52
+ it('returns parsed response data', async () => {
53
+ mockFetch.mockReturnValue(okResponse({ success: true, bounties: [{ id: '1' }] }));
54
+ const client = new client_1.ApiClient();
55
+ const result = await client.get('/bounties');
56
+ expect(result.bounties).toEqual([{ id: '1' }]);
57
+ });
58
+ it('throws on HTTP error', async () => {
59
+ mockFetch.mockReturnValue(errorResponse(404, { success: false, error: { code: 'NOT_FOUND', message: 'Not found' } }));
60
+ const client = new client_1.ApiClient();
61
+ await expect(client.get('/missing')).rejects.toThrow('Not found');
62
+ });
63
+ it('throws generic message when error has no message', async () => {
64
+ mockFetch.mockReturnValue(errorResponse(500, {}));
65
+ const client = new client_1.ApiClient();
66
+ await expect(client.get('/fail')).rejects.toThrow('HTTP 500');
67
+ });
68
+ });
69
+ describe('post()', () => {
70
+ it('sends POST with JSON body and Content-Type', async () => {
71
+ mockFetch.mockReturnValue(okResponse({ success: true }));
72
+ const client = new client_1.ApiClient();
73
+ await client.post('/bounties', { title: 'New bounty' });
74
+ expect(mockFetch).toHaveBeenCalledWith('http://test:3001/api/marketplace/bounties', expect.objectContaining({
75
+ method: 'POST',
76
+ headers: expect.objectContaining({ 'Content-Type': 'application/json' }),
77
+ body: JSON.stringify({ title: 'New bounty' }),
78
+ }));
79
+ });
80
+ it('sends POST without body when none provided', async () => {
81
+ mockFetch.mockReturnValue(okResponse({ success: true }));
82
+ const client = new client_1.ApiClient();
83
+ await client.post('/action');
84
+ expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ method: 'POST', body: undefined }));
85
+ });
86
+ });
87
+ describe('del()', () => {
88
+ it('sends DELETE request', async () => {
89
+ mockFetch.mockReturnValue(okResponse({ success: true }));
90
+ const client = new client_1.ApiClient();
91
+ await client.del('/bounties/123');
92
+ expect(mockFetch).toHaveBeenCalledWith('http://test:3001/api/marketplace/bounties/123', expect.objectContaining({ method: 'DELETE' }));
93
+ });
94
+ });
95
+ });
96
+ describe('getClient()', () => {
97
+ it('returns an ApiClient instance', () => {
98
+ const client = (0, client_1.getClient)();
99
+ expect(client).toBeInstanceOf(client_1.ApiClient);
100
+ });
101
+ it('returns the same instance on subsequent calls', () => {
102
+ const a = (0, client_1.getClient)();
103
+ const b = (0, client_1.getClient)();
104
+ expect(a).toBe(b);
105
+ });
106
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const config_1 = require("../utils/config");
38
+ jest.mock('fs');
39
+ const mockedFs = fs;
40
+ afterEach(() => {
41
+ jest.restoreAllMocks();
42
+ delete process.env.MARKETPLACE_API_URL;
43
+ });
44
+ describe('loadConfig()', () => {
45
+ it('returns parsed config when file exists', () => {
46
+ const config = { apiKey: 'test-key', apiUrl: 'http://localhost:3001/api/marketplace' };
47
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(config));
48
+ expect((0, config_1.loadConfig)()).toEqual(config);
49
+ });
50
+ it('returns null when file does not exist', () => {
51
+ mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
52
+ expect((0, config_1.loadConfig)()).toBeNull();
53
+ });
54
+ it('returns null on invalid JSON', () => {
55
+ mockedFs.readFileSync.mockReturnValue('not json{');
56
+ expect((0, config_1.loadConfig)()).toBeNull();
57
+ });
58
+ });
59
+ describe('saveConfig()', () => {
60
+ it('creates directory and writes file', () => {
61
+ mockedFs.mkdirSync.mockReturnValue(undefined);
62
+ mockedFs.writeFileSync.mockReturnValue(undefined);
63
+ const config = { apiKey: 'key', apiUrl: 'http://test' };
64
+ (0, config_1.saveConfig)(config);
65
+ expect(mockedFs.mkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true });
66
+ expect(mockedFs.writeFileSync).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('"apiKey": "key"'));
67
+ });
68
+ });
69
+ describe('requireConfig()', () => {
70
+ it('returns config when it exists', () => {
71
+ const config = { apiKey: 'key', apiUrl: 'http://test' };
72
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(config));
73
+ expect((0, config_1.requireConfig)()).toEqual(config);
74
+ });
75
+ it('exits process when config is missing', () => {
76
+ mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
77
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined);
78
+ const errSpy = jest.spyOn(console, 'error').mockImplementation();
79
+ (0, config_1.requireConfig)();
80
+ expect(exitSpy).toHaveBeenCalledWith(1);
81
+ exitSpy.mockRestore();
82
+ errSpy.mockRestore();
83
+ });
84
+ });
85
+ describe('getApiUrl()', () => {
86
+ it('prefers env var over config', () => {
87
+ process.env.MARKETPLACE_API_URL = 'http://env-url';
88
+ expect((0, config_1.getApiUrl)()).toBe('http://env-url');
89
+ });
90
+ it('falls back to config value', () => {
91
+ const config = { apiKey: 'key', apiUrl: 'http://config-url' };
92
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(config));
93
+ expect((0, config_1.getApiUrl)()).toBe('http://config-url');
94
+ });
95
+ it('falls back to default when no config or env', () => {
96
+ mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
97
+ expect((0, config_1.getApiUrl)()).toBe('http://localhost:3001/api/marketplace');
98
+ });
99
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chalk_1 = __importDefault(require("chalk"));
7
+ const dashboard_1 = require("../commands/dashboard");
8
+ beforeAll(() => {
9
+ chalk_1.default.level = 0;
10
+ });
11
+ function makeBounty(overrides = {}) {
12
+ return {
13
+ id: 'b1', title: 'Test', status: 'open', reward_amount: '100', reward_symbol: 'USDC',
14
+ ...overrides,
15
+ };
16
+ }
17
+ function makeState(bounties) {
18
+ return {
19
+ workspace: { id: 'ws1', name: 'Test', mode: 'open' },
20
+ bounties,
21
+ treasury: { balance: '0', total_funded: '0', total_paid: '0' },
22
+ events: [],
23
+ startTime: Date.now(),
24
+ };
25
+ }
26
+ describe('updateBountyFromEvent()', () => {
27
+ it('handles bounty:assigned', () => {
28
+ const state = makeState([makeBounty({ id: 'b1' })]);
29
+ const event = { event: 'bounty:assigned', data: { bountyId: 'b1', assigneeWallet: 'agent1' } };
30
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
31
+ expect(state.bounties[0].status).toBe('assigned');
32
+ expect(state.bounties[0].assignee_wallet).toBe('agent1');
33
+ });
34
+ it('handles bounty:progress', () => {
35
+ const state = makeState([makeBounty({ id: 'b1' })]);
36
+ const event = { event: 'bounty:progress', data: { bountyId: 'b1', percentage: 75 } };
37
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
38
+ expect(state.bounties[0].percentage).toBe(75);
39
+ });
40
+ it('handles bounty:submitted', () => {
41
+ const state = makeState([makeBounty({ id: 'b1', status: 'assigned' })]);
42
+ const event = { event: 'bounty:submitted', data: { bountyId: 'b1' } };
43
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
44
+ expect(state.bounties[0].status).toBe('submitted');
45
+ });
46
+ it('handles bounty:completed', () => {
47
+ const state = makeState([makeBounty({ id: 'b1' })]);
48
+ const event = { event: 'bounty:completed', data: { bountyId: 'b1' } };
49
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
50
+ expect(state.bounties[0].status).toBe('completed');
51
+ expect(state.bounties[0].percentage).toBe(100);
52
+ });
53
+ it('handles bounty:heartbeat', () => {
54
+ const state = makeState([makeBounty({ id: 'b1' })]);
55
+ const event = { event: 'bounty:heartbeat', data: { bountyId: 'b1' } };
56
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
57
+ expect(state.bounties[0].last_heartbeat).toBeDefined();
58
+ });
59
+ it('no-ops for unknown event', () => {
60
+ const state = makeState([makeBounty({ id: 'b1', status: 'open' })]);
61
+ const event = { event: 'unknown:event', data: { bountyId: 'b1' } };
62
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
63
+ expect(state.bounties[0].status).toBe('open');
64
+ });
65
+ it('no-ops when bountyId is missing', () => {
66
+ const state = makeState([makeBounty({ id: 'b1', status: 'open' })]);
67
+ const event = { event: 'bounty:completed', data: {} };
68
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
69
+ expect(state.bounties[0].status).toBe('open');
70
+ });
71
+ it('no-ops when bounty not found in state', () => {
72
+ const state = makeState([makeBounty({ id: 'b1', status: 'open' })]);
73
+ const event = { event: 'bounty:completed', data: { bountyId: 'b999' } };
74
+ (0, dashboard_1.updateBountyFromEvent)(state, event);
75
+ expect(state.bounties[0].status).toBe('open');
76
+ });
77
+ });
78
+ describe('pad()', () => {
79
+ it('left-aligns by default', () => {
80
+ const result = (0, dashboard_1.pad)('hi', 6);
81
+ expect(result).toBe('hi ');
82
+ });
83
+ it('right-aligns when right=true', () => {
84
+ const result = (0, dashboard_1.pad)('hi', 6, true);
85
+ expect(result).toBe(' hi');
86
+ });
87
+ it('handles strings already at target length', () => {
88
+ expect((0, dashboard_1.pad)('abc', 3)).toBe('abc');
89
+ });
90
+ });
91
+ describe('colorOverhead()', () => {
92
+ it('returns 0 for unknown status (no color applied)', () => {
93
+ // chalk.level=0 means no ANSI codes, overhead = 0
94
+ expect((0, dashboard_1.colorOverhead)('unknown')).toBe(0);
95
+ });
96
+ });
97
+ describe('heartbeatColor()', () => {
98
+ it('returns dash unchanged', () => {
99
+ expect((0, dashboard_1.heartbeatColor)('—')).toBe('—');
100
+ });
101
+ it('returns green for fresh heartbeat (seconds)', () => {
102
+ const result = (0, dashboard_1.heartbeatColor)('30s ago');
103
+ expect(result).toBe('30s ago'); // chalk.level=0
104
+ });
105
+ it('returns red for stale heartbeat (hours)', () => {
106
+ const result = (0, dashboard_1.heartbeatColor)('2h ago');
107
+ expect(result).toBe('2h ago'); // chalk.level=0
108
+ });
109
+ it('returns value for no-match pattern', () => {
110
+ expect((0, dashboard_1.heartbeatColor)('just now')).toBe('just now');
111
+ });
112
+ });
113
+ describe('formatUptime()', () => {
114
+ it('formats 0ms as "0h 0m"', () => {
115
+ expect((0, dashboard_1.formatUptime)(0)).toBe('0h 0m');
116
+ });
117
+ it('formats 90 minutes as "1h 30m"', () => {
118
+ expect((0, dashboard_1.formatUptime)(90 * 60 * 1000)).toBe('1h 30m');
119
+ });
120
+ it('formats 2 hours as "2h 0m"', () => {
121
+ expect((0, dashboard_1.formatUptime)(2 * 60 * 60 * 1000)).toBe('2h 0m');
122
+ });
123
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chalk_1 = __importDefault(require("chalk"));
7
+ const format_1 = require("../utils/format");
8
+ beforeAll(() => {
9
+ chalk_1.default.level = 0;
10
+ });
11
+ describe('statusColor()', () => {
12
+ it('returns green for completed/approved/done', () => {
13
+ expect((0, format_1.statusColor)('completed')).toBe('completed');
14
+ expect((0, format_1.statusColor)('approved')).toBe('approved');
15
+ expect((0, format_1.statusColor)('done')).toBe('done');
16
+ });
17
+ it('returns yellow for assigned/working/in_progress', () => {
18
+ expect((0, format_1.statusColor)('assigned')).toBe('assigned');
19
+ expect((0, format_1.statusColor)('working')).toBe('working');
20
+ expect((0, format_1.statusColor)('in_progress')).toBe('in_progress');
21
+ });
22
+ it('returns red for disputed/stale/failed', () => {
23
+ expect((0, format_1.statusColor)('disputed')).toBe('disputed');
24
+ expect((0, format_1.statusColor)('stale')).toBe('stale');
25
+ expect((0, format_1.statusColor)('failed')).toBe('failed');
26
+ });
27
+ it('returns gray for blocked', () => {
28
+ expect((0, format_1.statusColor)('blocked')).toBe('blocked');
29
+ });
30
+ it('returns cyan for open/pending', () => {
31
+ expect((0, format_1.statusColor)('open')).toBe('open');
32
+ expect((0, format_1.statusColor)('pending')).toBe('pending');
33
+ });
34
+ it('returns dim for expired/cancelled', () => {
35
+ expect((0, format_1.statusColor)('expired')).toBe('expired');
36
+ expect((0, format_1.statusColor)('cancelled')).toBe('cancelled');
37
+ });
38
+ it('returns raw string for unknown status', () => {
39
+ expect((0, format_1.statusColor)('mystery')).toBe('mystery');
40
+ });
41
+ });
42
+ describe('shorten()', () => {
43
+ it('shortens long strings', () => {
44
+ expect((0, format_1.shorten)('abcdefghijklmnop')).toBe('abcd...mnop');
45
+ });
46
+ it('returns short strings unchanged', () => {
47
+ expect((0, format_1.shorten)('short')).toBe('short');
48
+ });
49
+ it('returns null/undefined as-is', () => {
50
+ expect((0, format_1.shorten)(null)).toBe(null);
51
+ expect((0, format_1.shorten)(undefined)).toBe(undefined);
52
+ });
53
+ it('respects custom length', () => {
54
+ expect((0, format_1.shorten)('abcdefghij', 8)).toBe('abcd...ghij');
55
+ });
56
+ });
57
+ describe('formatUsdc()', () => {
58
+ it('converts micro-USDC to USDC', () => {
59
+ expect((0, format_1.formatUsdc)(1_000_000)).toBe('1.00');
60
+ expect((0, format_1.formatUsdc)('2500000')).toBe('2.50');
61
+ });
62
+ });
63
+ describe('formatReward()', () => {
64
+ it('formats USDC amounts by default', () => {
65
+ expect((0, format_1.formatReward)(1_000_000)).toBe('1.00 USDC');
66
+ });
67
+ it('formats with explicit USDC symbol', () => {
68
+ expect((0, format_1.formatReward)(1_000_000, 'USDC')).toBe('1.00 USDC');
69
+ });
70
+ it('formats with custom symbol using USDC decimals', () => {
71
+ expect((0, format_1.formatReward)(500_000, 'USDT')).toBe('0.50 USDT');
72
+ });
73
+ });
74
+ describe('formatTimestamp()', () => {
75
+ it('extracts HH:MM:SS from ISO timestamp', () => {
76
+ const result = (0, format_1.formatTimestamp)('2024-06-15T14:30:45.000Z');
77
+ expect(result).toBe('14:30:45');
78
+ });
79
+ });
80
+ describe('formatTimeAgo()', () => {
81
+ it('returns seconds for < 60s', () => {
82
+ const ts = new Date(Date.now() - 30_000).toISOString();
83
+ expect((0, format_1.formatTimeAgo)(ts)).toMatch(/^\d+s ago$/);
84
+ });
85
+ it('returns minutes for < 60m', () => {
86
+ const ts = new Date(Date.now() - 5 * 60_000).toISOString();
87
+ expect((0, format_1.formatTimeAgo)(ts)).toMatch(/^\d+m ago$/);
88
+ });
89
+ it('returns hours for < 24h', () => {
90
+ const ts = new Date(Date.now() - 3 * 3600_000).toISOString();
91
+ expect((0, format_1.formatTimeAgo)(ts)).toMatch(/^\d+h ago$/);
92
+ });
93
+ it('returns days for >= 24h', () => {
94
+ const ts = new Date(Date.now() - 2 * 86400_000).toISOString();
95
+ expect((0, format_1.formatTimeAgo)(ts)).toMatch(/^\d+d ago$/);
96
+ });
97
+ });
98
+ describe('makeTable()', () => {
99
+ it('returns a string containing headers and rows', () => {
100
+ const result = (0, format_1.makeTable)(['Name', 'Age'], [['Alice', '30'], ['Bob', '25']]);
101
+ expect(result).toContain('Name');
102
+ expect(result).toContain('Age');
103
+ expect(result).toContain('Alice');
104
+ expect(result).toContain('Bob');
105
+ });
106
+ });
107
+ describe('heading()', () => {
108
+ it('logs formatted heading', () => {
109
+ const spy = jest.spyOn(console, 'log').mockImplementation();
110
+ (0, format_1.heading)('My Section');
111
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('My Section'));
112
+ spy.mockRestore();
113
+ });
114
+ });
115
+ describe('success()', () => {
116
+ it('logs success message with + prefix', () => {
117
+ const spy = jest.spyOn(console, 'log').mockImplementation();
118
+ (0, format_1.success)('Done!');
119
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('+ Done!'));
120
+ spy.mockRestore();
121
+ });
122
+ });
123
+ describe('error()', () => {
124
+ it('logs error message with x prefix', () => {
125
+ const spy = jest.spyOn(console, 'error').mockImplementation();
126
+ (0, format_1.error)('Oops');
127
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('x Oops'));
128
+ spy.mockRestore();
129
+ });
130
+ });
131
+ describe('info()', () => {
132
+ it('logs info message with i prefix', () => {
133
+ const spy = jest.spyOn(console, 'log').mockImplementation();
134
+ (0, format_1.info)('Note');
135
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('i Note'));
136
+ spy.mockRestore();
137
+ });
138
+ });
139
+ describe('formatJson()', () => {
140
+ it('returns pretty-printed JSON', () => {
141
+ const result = (0, format_1.formatJson)({ a: 1, b: 'hello' });
142
+ expect(result).toBe('{\n "a": 1,\n "b": "hello"\n}');
143
+ });
144
+ });
@@ -0,0 +1,7 @@
1
+ export declare function createMockClient(): {
2
+ get: jest.Mock<any, any, any>;
3
+ post: jest.Mock<any, any, any>;
4
+ del: jest.Mock<any, any, any>;
5
+ getApiUrl: jest.Mock<any, any, any>;
6
+ getApiKey: jest.Mock<any, any, any>;
7
+ };
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMockClient = createMockClient;
4
+ function createMockClient() {
5
+ return {
6
+ get: jest.fn(),
7
+ post: jest.fn(),
8
+ del: jest.fn(),
9
+ getApiUrl: jest.fn().mockReturnValue('http://test:3001/api/marketplace'),
10
+ getApiKey: jest.fn().mockReturnValue('test-key'),
11
+ };
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chalk_1 = __importDefault(require("chalk"));
7
+ const helpers_1 = require("./helpers");
8
+ const mockClient = (0, helpers_1.createMockClient)();
9
+ jest.mock('../utils/client', () => ({
10
+ getClient: () => mockClient,
11
+ }));
12
+ const treasury_1 = require("../commands/treasury");
13
+ beforeAll(() => {
14
+ chalk_1.default.level = 0;
15
+ });
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ });
19
+ async function runCommand(...args) {
20
+ await treasury_1.treasuryCommand.parseAsync(args, { from: 'user' });
21
+ }
22
+ describe('treasury status', () => {
23
+ it('displays treasury balances', async () => {
24
+ mockClient.get.mockImplementation((path) => {
25
+ if (path.includes('/ledger'))
26
+ return Promise.resolve({ ledger: [] });
27
+ return Promise.resolve({ balance: '5000', total_funded: '10000', total_paid: '5000', agents: [] });
28
+ });
29
+ const spy = jest.spyOn(console, 'log').mockImplementation();
30
+ await runCommand('status', 'ws1');
31
+ const output = spy.mock.calls.map(c => c[0]).join('\n');
32
+ expect(output).toContain('5000');
33
+ expect(output).toContain('10000');
34
+ spy.mockRestore();
35
+ });
36
+ it('renders agent allocations when available', async () => {
37
+ mockClient.get.mockImplementation((path) => {
38
+ if (path.includes('/ledger'))
39
+ return Promise.resolve({ ledger: [] });
40
+ return Promise.resolve({
41
+ balance: '1000', total_funded: '2000', total_paid: '1000',
42
+ agents: [{ agent_wallet: 'agent-12345678901', allocated: '500', spent: '200', remaining: '300' }],
43
+ });
44
+ });
45
+ const spy = jest.spyOn(console, 'log').mockImplementation();
46
+ await runCommand('status', 'ws1');
47
+ const output = spy.mock.calls.map(c => c[0]).join('\n');
48
+ expect(output).toContain('Agent Allocations');
49
+ spy.mockRestore();
50
+ });
51
+ it('renders ledger when available', async () => {
52
+ mockClient.get.mockImplementation((path) => {
53
+ if (path.includes('/ledger'))
54
+ return Promise.resolve({
55
+ ledger: [{ type: 'fund', amount: '1000', agent_wallet: null, created_at: new Date().toISOString() }],
56
+ });
57
+ return Promise.resolve({ balance: '1000', total_funded: '1000', total_paid: '0', agents: [] });
58
+ });
59
+ const spy = jest.spyOn(console, 'log').mockImplementation();
60
+ await runCommand('status', 'ws1');
61
+ const output = spy.mock.calls.map(c => c[0]).join('\n');
62
+ expect(output).toContain('Ledger');
63
+ spy.mockRestore();
64
+ });
65
+ it('handles ledger fetch failure gracefully', async () => {
66
+ mockClient.get.mockImplementation((path) => {
67
+ if (path.includes('/ledger'))
68
+ return Promise.reject(new Error('Not found'));
69
+ return Promise.resolve({ balance: '100', agents: [] });
70
+ });
71
+ const spy = jest.spyOn(console, 'log').mockImplementation();
72
+ await expect(runCommand('status', 'ws1')).resolves.toBeUndefined();
73
+ spy.mockRestore();
74
+ });
75
+ });
76
+ describe('treasury fund', () => {
77
+ it('posts funding amount', async () => {
78
+ mockClient.post.mockResolvedValue({ success: true });
79
+ const spy = jest.spyOn(console, 'log').mockImplementation();
80
+ await runCommand('fund', 'ws1', '--amount', '5000');
81
+ expect(mockClient.post).toHaveBeenCalledWith('/workspaces/ws1/treasury/fund', { amount: '5000', txSignature: 'cli-funding' });
82
+ spy.mockRestore();
83
+ });
84
+ it('uses provided tx signature', async () => {
85
+ mockClient.post.mockResolvedValue({ success: true });
86
+ const spy = jest.spyOn(console, 'log').mockImplementation();
87
+ await runCommand('fund', 'ws1', '--amount', '1000', '--tx-signature', 'abc123');
88
+ expect(mockClient.post).toHaveBeenCalledWith('/workspaces/ws1/treasury/fund', { amount: '1000', txSignature: 'abc123' });
89
+ spy.mockRestore();
90
+ });
91
+ it('handles fund error', async () => {
92
+ mockClient.post.mockRejectedValue(new Error('Insufficient balance'));
93
+ const errSpy = jest.spyOn(console, 'error').mockImplementation();
94
+ await runCommand('fund', 'ws1', '--amount', '9999999');
95
+ expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('Insufficient balance'));
96
+ errSpy.mockRestore();
97
+ });
98
+ });
99
+ describe('treasury distribute', () => {
100
+ it('distributes to all workspace agents', async () => {
101
+ mockClient.get.mockResolvedValue({
102
+ workspace: { agent_wallets: ['agent1', 'agent2'] },
103
+ });
104
+ mockClient.post.mockResolvedValue({ success: true });
105
+ const spy = jest.spyOn(console, 'log').mockImplementation();
106
+ await runCommand('distribute', 'ws1', '--per-agent', '500');
107
+ expect(mockClient.post).toHaveBeenCalledWith('/workspaces/ws1/treasury/fund-agents', {
108
+ distributions: [
109
+ { agentWallet: 'agent1', amount: '500' },
110
+ { agentWallet: 'agent2', amount: '500' },
111
+ ],
112
+ });
113
+ spy.mockRestore();
114
+ });
115
+ it('handles empty agent list', async () => {
116
+ mockClient.get.mockResolvedValue({ workspace: { agent_wallets: [] } });
117
+ const errSpy = jest.spyOn(console, 'error').mockImplementation();
118
+ await runCommand('distribute', 'ws1', '--per-agent', '500');
119
+ expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('No agents'));
120
+ errSpy.mockRestore();
121
+ });
122
+ });
@@ -0,0 +1 @@
1
+ export {};