@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.
- package/README.md +14 -0
- package/dist/__tests__/agent.test.d.ts +1 -0
- package/dist/__tests__/agent.test.js +138 -0
- package/dist/__tests__/apps.test.d.ts +1 -0
- package/dist/__tests__/apps.test.js +160 -0
- package/dist/__tests__/auth.test.d.ts +1 -0
- package/dist/__tests__/auth.test.js +94 -0
- package/dist/__tests__/bounty.test.d.ts +1 -0
- package/dist/__tests__/bounty.test.js +185 -0
- package/dist/__tests__/client.test.d.ts +1 -0
- package/dist/__tests__/client.test.js +106 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +99 -0
- package/dist/__tests__/dashboard-helpers.test.d.ts +1 -0
- package/dist/__tests__/dashboard-helpers.test.js +123 -0
- package/dist/__tests__/format.test.d.ts +1 -0
- package/dist/__tests__/format.test.js +144 -0
- package/dist/__tests__/helpers.d.ts +7 -0
- package/dist/__tests__/helpers.js +12 -0
- package/dist/__tests__/treasury.test.d.ts +1 -0
- package/dist/__tests__/treasury.test.js +122 -0
- package/dist/__tests__/websocket.test.d.ts +1 -0
- package/dist/__tests__/websocket.test.js +61 -0
- package/dist/__tests__/workspace.test.d.ts +1 -0
- package/dist/__tests__/workspace.test.js +105 -0
- package/dist/commands/agent.d.ts +0 -0
- package/dist/commands/agent.js +0 -0
- package/dist/commands/apps.d.ts +2 -0
- package/dist/commands/apps.js +174 -0
- package/dist/commands/auth.d.ts +0 -0
- package/dist/commands/auth.js +0 -0
- package/dist/commands/bounty.d.ts +0 -0
- package/dist/commands/bounty.js +8 -0
- package/dist/commands/dashboard.d.ts +31 -0
- package/dist/commands/dashboard.js +5 -0
- package/dist/commands/treasury.d.ts +0 -0
- package/dist/commands/treasury.js +2 -2
- package/dist/commands/webhook.d.ts +2 -0
- package/dist/commands/webhook.js +131 -0
- package/dist/commands/workspace.d.ts +0 -0
- package/dist/commands/workspace.js +160 -13
- package/dist/index.d.ts +0 -0
- package/dist/index.js +4 -0
- package/dist/utils/client.d.ts +1 -0
- package/dist/utils/client.js +3 -0
- package/dist/utils/config.d.ts +0 -0
- package/dist/utils/config.js +0 -0
- package/dist/utils/format.d.ts +0 -1
- package/dist/utils/format.js +2 -11
- package/dist/utils/websocket.d.ts +0 -0
- package/dist/utils/websocket.js +0 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -73,6 +73,20 @@ wen-market dashboard <workspace-id> # Interactive live dashboard with real
|
|
|
73
73
|
|
|
74
74
|
The dashboard shows a live view of all bounties, agent progress, treasury balances, and events in your terminal. Updates in real time via WebSocket.
|
|
75
75
|
|
|
76
|
+
### apps — Hosted App Management
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
wen-market apps list # List your hosted apps
|
|
80
|
+
wen-market apps list --status deployed # Filter by status
|
|
81
|
+
wen-market apps create --title "My App" --reward 50 --deadline 2026-06-01 # Create app + bounty
|
|
82
|
+
wen-market apps create --title "Vue App" --reward 100 --deadline 2026-06-01 --framework vue --visibility unlisted
|
|
83
|
+
wen-market apps status <id> # Show app details, URLs, deployment status
|
|
84
|
+
wen-market apps deployments <id> # List deployment history
|
|
85
|
+
wen-market apps download <id> # Download source.zip
|
|
86
|
+
wen-market apps export-github <id> --repo my-repo # Export source to GitHub
|
|
87
|
+
wen-market apps export-github <id> --repo my-repo --private # Export as private repo
|
|
88
|
+
```
|
|
89
|
+
|
|
76
90
|
## Configuration
|
|
77
91
|
|
|
78
92
|
Config is saved to `~/.wenrwa-marketplace/config.json` after `auth login`:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
// Mock websocket to prevent import issues
|
|
13
|
+
jest.mock('../utils/websocket', () => ({
|
|
14
|
+
connectWebSocket: jest.fn(),
|
|
15
|
+
formatEventPretty: jest.fn(),
|
|
16
|
+
}));
|
|
17
|
+
const agent_1 = require("../commands/agent");
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
chalk_1.default.level = 0;
|
|
20
|
+
});
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
async function runCommand(...args) {
|
|
25
|
+
await agent_1.agentCommand.parseAsync(args, { from: 'user' });
|
|
26
|
+
}
|
|
27
|
+
describe('agent list', () => {
|
|
28
|
+
it('renders a table of agents', async () => {
|
|
29
|
+
mockClient.get.mockResolvedValue({
|
|
30
|
+
agents: [
|
|
31
|
+
{ wallet_pubkey: 'agent-abc-12345678', name: 'CodeBot', weighted_reputation: 4.5, capabilities: ['code', 'test'], status: 'active' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
35
|
+
await runCommand('list');
|
|
36
|
+
expect(mockClient.get).toHaveBeenCalledWith(expect.stringContaining('/agents?'));
|
|
37
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
38
|
+
expect(output).toContain('CodeBot');
|
|
39
|
+
spy.mockRestore();
|
|
40
|
+
});
|
|
41
|
+
it('handles empty agents list', async () => {
|
|
42
|
+
mockClient.get.mockResolvedValue({ agents: [] });
|
|
43
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
44
|
+
await runCommand('list');
|
|
45
|
+
expect(spy).toHaveBeenCalledWith('No agents found.');
|
|
46
|
+
spy.mockRestore();
|
|
47
|
+
});
|
|
48
|
+
it('passes workspace filter', async () => {
|
|
49
|
+
mockClient.get.mockResolvedValue({ agents: [] });
|
|
50
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
51
|
+
await runCommand('list', '--workspace', 'ws1');
|
|
52
|
+
const url = mockClient.get.mock.calls[0][0];
|
|
53
|
+
expect(url).toContain('workspaceId=ws1');
|
|
54
|
+
spy.mockRestore();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('agent status', () => {
|
|
58
|
+
it('displays agent details', async () => {
|
|
59
|
+
mockClient.get.mockImplementation((path) => {
|
|
60
|
+
if (path.includes('/ratings'))
|
|
61
|
+
return Promise.resolve({ ratings: [] });
|
|
62
|
+
if (path.includes('/capabilities'))
|
|
63
|
+
return Promise.resolve({ scores: [] });
|
|
64
|
+
return Promise.resolve({
|
|
65
|
+
agent: {
|
|
66
|
+
wallet_pubkey: 'agent1', name: 'TestBot', description: 'A test bot',
|
|
67
|
+
capabilities: ['code'], status: 'active', weighted_reputation: 4.2,
|
|
68
|
+
completed_count: 10, failed_count: 1, created_at: '2024-01-01',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
73
|
+
await runCommand('status', 'agent1');
|
|
74
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
75
|
+
expect(output).toContain('TestBot');
|
|
76
|
+
expect(output).toContain('agent1');
|
|
77
|
+
spy.mockRestore();
|
|
78
|
+
});
|
|
79
|
+
it('renders ratings table when ratings exist', async () => {
|
|
80
|
+
mockClient.get.mockImplementation((path) => {
|
|
81
|
+
if (path.includes('/ratings'))
|
|
82
|
+
return Promise.resolve({
|
|
83
|
+
ratings: [{ quality_score: 5, speed_score: 4, communication_score: 5, review_text: 'Great work', created_at: new Date().toISOString() }],
|
|
84
|
+
});
|
|
85
|
+
if (path.includes('/capabilities'))
|
|
86
|
+
return Promise.resolve({ scores: [] });
|
|
87
|
+
return Promise.resolve({
|
|
88
|
+
agent: {
|
|
89
|
+
wallet_pubkey: 'a1', name: 'Bot', description: '', capabilities: [],
|
|
90
|
+
status: 'active', weighted_reputation: 4.5, completed_count: 5, failed_count: 0, created_at: '2024-01-01',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
95
|
+
await runCommand('status', 'a1');
|
|
96
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
97
|
+
expect(output).toContain('Ratings');
|
|
98
|
+
spy.mockRestore();
|
|
99
|
+
});
|
|
100
|
+
it('renders capability scores when they exist', async () => {
|
|
101
|
+
mockClient.get.mockImplementation((path) => {
|
|
102
|
+
if (path.includes('/ratings'))
|
|
103
|
+
return Promise.resolve({ ratings: [] });
|
|
104
|
+
if (path.includes('/capabilities'))
|
|
105
|
+
return Promise.resolve({
|
|
106
|
+
scores: [{ capability: 'code', avg_score: 4.8, rating_count: 12 }],
|
|
107
|
+
});
|
|
108
|
+
return Promise.resolve({
|
|
109
|
+
agent: {
|
|
110
|
+
wallet_pubkey: 'a1', name: 'Bot', description: '', capabilities: ['code'],
|
|
111
|
+
status: 'active', weighted_reputation: 4.5, completed_count: 5, failed_count: 0, created_at: '2024-01-01',
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
116
|
+
await runCommand('status', 'a1');
|
|
117
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
118
|
+
expect(output).toContain('Capability Scores');
|
|
119
|
+
spy.mockRestore();
|
|
120
|
+
});
|
|
121
|
+
it('handles missing ratings/capabilities gracefully', async () => {
|
|
122
|
+
mockClient.get.mockImplementation((path) => {
|
|
123
|
+
if (path.includes('/ratings'))
|
|
124
|
+
return Promise.reject(new Error('Not found'));
|
|
125
|
+
if (path.includes('/capabilities'))
|
|
126
|
+
return Promise.reject(new Error('Not found'));
|
|
127
|
+
return Promise.resolve({
|
|
128
|
+
agent: {
|
|
129
|
+
wallet_pubkey: 'a1', name: 'Bot', description: '', capabilities: [],
|
|
130
|
+
status: 'active', completed_count: 0, failed_count: 0, created_at: '2024-01-01',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
135
|
+
await expect(runCommand('status', 'a1')).resolves.toBeUndefined();
|
|
136
|
+
spy.mockRestore();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
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 apps_1 = require("../commands/apps");
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
chalk_1.default.level = 0;
|
|
15
|
+
});
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
async function runCommand(...args) {
|
|
20
|
+
await apps_1.appsCommand.parseAsync(args, { from: 'user' });
|
|
21
|
+
}
|
|
22
|
+
describe('apps list', () => {
|
|
23
|
+
it('renders a table of projects', async () => {
|
|
24
|
+
mockClient.get.mockResolvedValue({
|
|
25
|
+
projects: [
|
|
26
|
+
{
|
|
27
|
+
id: 'proj-abc-12345678',
|
|
28
|
+
title: 'My Landing Page',
|
|
29
|
+
slug: 'my-landing-page',
|
|
30
|
+
status: 'deployed',
|
|
31
|
+
deployed_url: 'https://my-landing-page.wenrwa.app',
|
|
32
|
+
preview_url: null,
|
|
33
|
+
created_at: new Date().toISOString(),
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
total: 1,
|
|
37
|
+
});
|
|
38
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
39
|
+
await runCommand('list');
|
|
40
|
+
expect(mockClient.get).toHaveBeenCalledWith(expect.stringContaining('/projects?'));
|
|
41
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('My Landing Page'));
|
|
42
|
+
spy.mockRestore();
|
|
43
|
+
});
|
|
44
|
+
it('passes status filter', async () => {
|
|
45
|
+
mockClient.get.mockResolvedValue({ projects: [], total: 0 });
|
|
46
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
47
|
+
await runCommand('list', '--status', 'deployed');
|
|
48
|
+
const url = mockClient.get.mock.calls[0][0];
|
|
49
|
+
expect(url).toContain('status=deployed');
|
|
50
|
+
spy.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
it('handles empty project list', async () => {
|
|
53
|
+
mockClient.get.mockResolvedValue({ projects: [] });
|
|
54
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
55
|
+
await runCommand('list');
|
|
56
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('No apps found'));
|
|
57
|
+
spy.mockRestore();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('apps create', () => {
|
|
61
|
+
it('creates a project and displays details', async () => {
|
|
62
|
+
mockClient.post.mockResolvedValue({
|
|
63
|
+
project: { id: 'proj-1', slug: 'cool-app', status: 'pending' },
|
|
64
|
+
bountyId: 'bounty-1',
|
|
65
|
+
});
|
|
66
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
67
|
+
await runCommand('create', '--title', 'Cool App', '--reward', '50', '--deadline', '2026-06-01');
|
|
68
|
+
expect(mockClient.post).toHaveBeenCalledWith('/projects', expect.objectContaining({
|
|
69
|
+
title: 'Cool App',
|
|
70
|
+
rewardAmount: '50000000',
|
|
71
|
+
framework: 'react',
|
|
72
|
+
visibility: 'public',
|
|
73
|
+
}));
|
|
74
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
75
|
+
expect(output).toContain('proj-1');
|
|
76
|
+
expect(output).toContain('bounty-1');
|
|
77
|
+
spy.mockRestore();
|
|
78
|
+
});
|
|
79
|
+
it('passes optional framework and visibility', async () => {
|
|
80
|
+
mockClient.post.mockResolvedValue({
|
|
81
|
+
project: { id: 'proj-2', slug: 'vue-app', status: 'pending' },
|
|
82
|
+
bountyId: 'bounty-2',
|
|
83
|
+
});
|
|
84
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
85
|
+
await runCommand('create', '--title', 'Vue App', '--reward', '100', '--deadline', '2026-06-01', '--framework', 'vue', '--visibility', 'unlisted');
|
|
86
|
+
expect(mockClient.post).toHaveBeenCalledWith('/projects', expect.objectContaining({
|
|
87
|
+
framework: 'vue',
|
|
88
|
+
visibility: 'unlisted',
|
|
89
|
+
}));
|
|
90
|
+
spy.mockRestore();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('apps status', () => {
|
|
94
|
+
it('displays project details', async () => {
|
|
95
|
+
mockClient.get.mockResolvedValue({
|
|
96
|
+
project: {
|
|
97
|
+
id: 'proj-1', title: 'My App', slug: 'my-app', status: 'deployed',
|
|
98
|
+
framework: 'react', visibility: 'public',
|
|
99
|
+
preview_url: 'https://preview.pages.dev',
|
|
100
|
+
deployed_url: 'https://my-app.wenrwa.app',
|
|
101
|
+
created_at: '2026-01-15', deployed_at: '2026-01-16',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
105
|
+
await runCommand('status', 'proj-1');
|
|
106
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
107
|
+
expect(output).toContain('My App');
|
|
108
|
+
expect(output).toContain('my-app.wenrwa.app');
|
|
109
|
+
expect(output).toContain('react');
|
|
110
|
+
spy.mockRestore();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('apps deployments', () => {
|
|
114
|
+
it('renders deployment history table', async () => {
|
|
115
|
+
mockClient.get.mockResolvedValue({
|
|
116
|
+
deployments: [
|
|
117
|
+
{
|
|
118
|
+
id: 'dep-abc-12345678',
|
|
119
|
+
status: 'deployed',
|
|
120
|
+
deployed_url: 'https://my-app.wenrwa.app',
|
|
121
|
+
initiated_by: 'poster-wallet-1234567890',
|
|
122
|
+
created_at: new Date().toISOString(),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
127
|
+
await runCommand('deployments', 'proj-1');
|
|
128
|
+
expect(mockClient.get).toHaveBeenCalledWith('/projects/proj-1/deployments');
|
|
129
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
130
|
+
expect(output).toContain('my-app.wenrwa.app');
|
|
131
|
+
spy.mockRestore();
|
|
132
|
+
});
|
|
133
|
+
it('handles empty deployments', async () => {
|
|
134
|
+
mockClient.get.mockResolvedValue({ deployments: [] });
|
|
135
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
136
|
+
await runCommand('deployments', 'proj-1');
|
|
137
|
+
expect(spy).toHaveBeenCalledWith('No deployments yet.');
|
|
138
|
+
spy.mockRestore();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('apps export-github', () => {
|
|
142
|
+
it('exports to GitHub and shows URL', async () => {
|
|
143
|
+
mockClient.post.mockResolvedValue({ repoUrl: 'https://github.com/user/repo' });
|
|
144
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
145
|
+
await runCommand('export-github', 'proj-1', '--repo', 'my-repo');
|
|
146
|
+
expect(mockClient.post).toHaveBeenCalledWith('/projects/proj-1/export-github', {
|
|
147
|
+
repoName: 'my-repo',
|
|
148
|
+
isPrivate: false,
|
|
149
|
+
});
|
|
150
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('github.com/user/repo'));
|
|
151
|
+
spy.mockRestore();
|
|
152
|
+
});
|
|
153
|
+
it('shows GitHub OAuth prompt when not connected', async () => {
|
|
154
|
+
mockClient.post.mockRejectedValue(new Error('GitHub not connected'));
|
|
155
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
156
|
+
await runCommand('export-github', 'proj-1');
|
|
157
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('GitHub not connected'));
|
|
158
|
+
spy.mockRestore();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
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 mockLoadConfig = jest.fn();
|
|
8
|
+
const mockSaveConfig = jest.fn();
|
|
9
|
+
const mockGetApiUrl = jest.fn().mockReturnValue('http://test:3001/api/marketplace');
|
|
10
|
+
jest.mock('../utils/config', () => ({
|
|
11
|
+
loadConfig: (...args) => mockLoadConfig(...args),
|
|
12
|
+
saveConfig: (...args) => mockSaveConfig(...args),
|
|
13
|
+
getApiUrl: (...args) => mockGetApiUrl(...args),
|
|
14
|
+
}));
|
|
15
|
+
const auth_1 = require("../commands/auth");
|
|
16
|
+
let mockFetch;
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
chalk_1.default.level = 0;
|
|
19
|
+
});
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
mockFetch = jest.fn();
|
|
23
|
+
global.fetch = mockFetch;
|
|
24
|
+
});
|
|
25
|
+
async function runCommand(...args) {
|
|
26
|
+
await auth_1.authCommand.parseAsync(args, { from: 'user' });
|
|
27
|
+
}
|
|
28
|
+
describe('auth login', () => {
|
|
29
|
+
it('verifies agent and saves config', async () => {
|
|
30
|
+
mockFetch.mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
|
31
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
32
|
+
await runCommand('login', '--api-key', 'my-wallet-key');
|
|
33
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/agents/my-wallet-key'));
|
|
34
|
+
expect(mockSaveConfig).toHaveBeenCalledWith({
|
|
35
|
+
apiKey: 'my-wallet-key',
|
|
36
|
+
apiUrl: 'http://test:3001/api/marketplace',
|
|
37
|
+
});
|
|
38
|
+
spy.mockRestore();
|
|
39
|
+
});
|
|
40
|
+
it('saves config even when fetch fails', async () => {
|
|
41
|
+
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
42
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
43
|
+
await runCommand('login', '--api-key', 'offline-key');
|
|
44
|
+
expect(mockSaveConfig).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'offline-key' }));
|
|
45
|
+
spy.mockRestore();
|
|
46
|
+
});
|
|
47
|
+
it('saves config when agent not found (non-ok response)', async () => {
|
|
48
|
+
mockFetch.mockResolvedValue({ ok: false });
|
|
49
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
50
|
+
await runCommand('login', '--api-key', 'new-user');
|
|
51
|
+
expect(mockSaveConfig).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'new-user' }));
|
|
52
|
+
spy.mockRestore();
|
|
53
|
+
});
|
|
54
|
+
it('uses custom api-url when provided', async () => {
|
|
55
|
+
mockFetch.mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
|
56
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
57
|
+
await runCommand('login', '--api-key', 'key', '--api-url', 'http://custom:4000');
|
|
58
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('http://custom:4000'));
|
|
59
|
+
expect(mockSaveConfig).toHaveBeenCalledWith(expect.objectContaining({ apiUrl: 'http://custom:4000' }));
|
|
60
|
+
spy.mockRestore();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('auth whoami', () => {
|
|
64
|
+
it('displays current session when logged in', async () => {
|
|
65
|
+
mockLoadConfig.mockReturnValue({ apiKey: 'my-key', apiUrl: 'http://test' });
|
|
66
|
+
mockFetch.mockResolvedValue({
|
|
67
|
+
ok: true,
|
|
68
|
+
json: () => Promise.resolve({ agent: { name: 'TestAgent', status: 'active', weighted_reputation: 4.5 } }),
|
|
69
|
+
});
|
|
70
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
71
|
+
await runCommand('whoami');
|
|
72
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
73
|
+
expect(output).toContain('my-key');
|
|
74
|
+
expect(output).toContain('TestAgent');
|
|
75
|
+
spy.mockRestore();
|
|
76
|
+
});
|
|
77
|
+
it('exits when not logged in', async () => {
|
|
78
|
+
mockLoadConfig.mockReturnValue(null);
|
|
79
|
+
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
80
|
+
const errSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
81
|
+
// Commander wraps action errors
|
|
82
|
+
await runCommand('whoami').catch(() => { });
|
|
83
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
84
|
+
exitSpy.mockRestore();
|
|
85
|
+
errSpy.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
it('handles API errors gracefully', async () => {
|
|
88
|
+
mockLoadConfig.mockReturnValue({ apiKey: 'key', apiUrl: 'http://test' });
|
|
89
|
+
mockFetch.mockRejectedValue(new Error('Connection refused'));
|
|
90
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
91
|
+
await runCommand('whoami');
|
|
92
|
+
spy.mockRestore();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,185 @@
|
|
|
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 bounty_1 = require("../commands/bounty");
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
chalk_1.default.level = 0;
|
|
15
|
+
});
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
async function runCommand(...args) {
|
|
20
|
+
await bounty_1.bountyCommand.parseAsync(args, { from: 'user' });
|
|
21
|
+
}
|
|
22
|
+
describe('bounty list', () => {
|
|
23
|
+
it('renders a table of bounties', async () => {
|
|
24
|
+
mockClient.get.mockResolvedValue({
|
|
25
|
+
bounties: [
|
|
26
|
+
{ id: 'bounty-abc-12345678', title: 'Build API endpoint', status: 'open', reward_amount: '1000000', reward_symbol: 'USDC', assignee_wallet: null, deadline: null },
|
|
27
|
+
],
|
|
28
|
+
total: 1,
|
|
29
|
+
});
|
|
30
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
31
|
+
await runCommand('list');
|
|
32
|
+
expect(mockClient.get).toHaveBeenCalledWith(expect.stringContaining('/bounties?'));
|
|
33
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('Build API'));
|
|
34
|
+
spy.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
it('passes workspace and status filters', async () => {
|
|
37
|
+
mockClient.get.mockResolvedValue({ bounties: [], total: 0 });
|
|
38
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
39
|
+
await runCommand('list', '--workspace', 'ws1', '--status', 'open');
|
|
40
|
+
const url = mockClient.get.mock.calls[0][0];
|
|
41
|
+
expect(url).toContain('workspaceId=ws1');
|
|
42
|
+
expect(url).toContain('status=open');
|
|
43
|
+
spy.mockRestore();
|
|
44
|
+
});
|
|
45
|
+
it('handles empty bounties list', async () => {
|
|
46
|
+
mockClient.get.mockResolvedValue({ bounties: [] });
|
|
47
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
48
|
+
await runCommand('list');
|
|
49
|
+
expect(spy).toHaveBeenCalledWith('No bounties found.');
|
|
50
|
+
spy.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('bounty status', () => {
|
|
54
|
+
it('displays bounty details', async () => {
|
|
55
|
+
mockClient.get.mockImplementation((path) => {
|
|
56
|
+
if (path.includes('/bids'))
|
|
57
|
+
return Promise.resolve({ bids: [] });
|
|
58
|
+
return Promise.resolve({
|
|
59
|
+
bounty: {
|
|
60
|
+
id: 'b1', title: 'Test Bounty', status: 'open', category: 'code',
|
|
61
|
+
reward_amount: '500000', reward_symbol: 'USDC', poster_wallet: 'poster123',
|
|
62
|
+
assignee_wallet: null, assignment_mode: 'bid', verification_mode: 'auto',
|
|
63
|
+
deadline: null, max_retries: 3, retry_count: 0, created_at: '2024-01-01',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
68
|
+
await runCommand('status', 'b1');
|
|
69
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('Test Bounty'));
|
|
70
|
+
spy.mockRestore();
|
|
71
|
+
});
|
|
72
|
+
it('handles bids fetch failure gracefully', async () => {
|
|
73
|
+
mockClient.get.mockImplementation((path) => {
|
|
74
|
+
if (path.includes('/bids'))
|
|
75
|
+
return Promise.reject(new Error('Not found'));
|
|
76
|
+
return Promise.resolve({
|
|
77
|
+
bounty: {
|
|
78
|
+
id: 'b1', title: 'Test', status: 'open', category: 'code',
|
|
79
|
+
reward_amount: '100', reward_symbol: 'USDC', poster_wallet: 'p1',
|
|
80
|
+
assignee_wallet: null, assignment_mode: 'bid', verification_mode: 'auto',
|
|
81
|
+
deadline: null, created_at: '2024-01-01',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
86
|
+
await expect(runCommand('status', 'b1')).resolves.toBeUndefined();
|
|
87
|
+
spy.mockRestore();
|
|
88
|
+
});
|
|
89
|
+
it('renders bids table when bids exist', async () => {
|
|
90
|
+
mockClient.get.mockImplementation((path) => {
|
|
91
|
+
if (path.includes('/bids'))
|
|
92
|
+
return Promise.resolve({
|
|
93
|
+
bids: [{ agent_wallet: 'agent123456789012', amount: '50', status: 'pending', message: 'I can do it' }],
|
|
94
|
+
});
|
|
95
|
+
return Promise.resolve({
|
|
96
|
+
bounty: {
|
|
97
|
+
id: 'b1', title: 'Test', status: 'open', category: 'code',
|
|
98
|
+
reward_amount: '100', reward_symbol: 'USDC', poster_wallet: 'p1',
|
|
99
|
+
assignee_wallet: null, assignment_mode: 'bid', verification_mode: 'auto',
|
|
100
|
+
deadline: null, created_at: '2024-01-01',
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
105
|
+
await runCommand('status', 'b1');
|
|
106
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
107
|
+
expect(output).toContain('Bids');
|
|
108
|
+
spy.mockRestore();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('bounty progress', () => {
|
|
112
|
+
it('renders progress entries', async () => {
|
|
113
|
+
mockClient.get.mockResolvedValue({
|
|
114
|
+
progress: [
|
|
115
|
+
{ percentage: 50, message: 'Half done', created_at: new Date().toISOString() },
|
|
116
|
+
{ percentage: 100, message: 'Complete', created_at: new Date().toISOString() },
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
120
|
+
await runCommand('progress', 'b1');
|
|
121
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
122
|
+
expect(output).toContain('Progress');
|
|
123
|
+
spy.mockRestore();
|
|
124
|
+
});
|
|
125
|
+
it('handles empty progress', async () => {
|
|
126
|
+
mockClient.get.mockResolvedValue({ progress: [] });
|
|
127
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
128
|
+
await runCommand('progress', 'b1');
|
|
129
|
+
expect(spy).toHaveBeenCalledWith('No progress reported yet.');
|
|
130
|
+
spy.mockRestore();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('bounty messages', () => {
|
|
134
|
+
it('renders message thread', async () => {
|
|
135
|
+
mockClient.get.mockResolvedValue({
|
|
136
|
+
messages: [
|
|
137
|
+
{ sender_wallet: 'sender1234567890', message_type: 'question', content: 'How?', created_at: new Date().toISOString() },
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
141
|
+
await runCommand('messages', 'b1');
|
|
142
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
143
|
+
expect(output).toContain('Messages');
|
|
144
|
+
spy.mockRestore();
|
|
145
|
+
});
|
|
146
|
+
it('handles empty messages', async () => {
|
|
147
|
+
mockClient.get.mockResolvedValue({ messages: [] });
|
|
148
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
149
|
+
await runCommand('messages', 'b1');
|
|
150
|
+
expect(spy).toHaveBeenCalledWith('No messages yet.');
|
|
151
|
+
spy.mockRestore();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('bounty verification', () => {
|
|
155
|
+
it('renders verification results with PASSED', async () => {
|
|
156
|
+
mockClient.get.mockResolvedValue({
|
|
157
|
+
results: [{ strategy: 'auto', passed: true, details: 'All tests pass' }],
|
|
158
|
+
});
|
|
159
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
160
|
+
await runCommand('verification', 'b1');
|
|
161
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
162
|
+
expect(output).toContain('PASSED');
|
|
163
|
+
spy.mockRestore();
|
|
164
|
+
});
|
|
165
|
+
it('renders verification results with FAILED', async () => {
|
|
166
|
+
mockClient.get.mockResolvedValue({
|
|
167
|
+
results: [
|
|
168
|
+
{ strategy: 'auto', passed: true, details: 'ok' },
|
|
169
|
+
{ strategy: 'manual', passed: false, error: 'Did not meet spec' },
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
173
|
+
await runCommand('verification', 'b1');
|
|
174
|
+
const output = spy.mock.calls.map(c => c[0]).join('\n');
|
|
175
|
+
expect(output).toContain('FAILED');
|
|
176
|
+
spy.mockRestore();
|
|
177
|
+
});
|
|
178
|
+
it('handles empty verification', async () => {
|
|
179
|
+
mockClient.get.mockResolvedValue({ results: [] });
|
|
180
|
+
const spy = jest.spyOn(console, 'log').mockImplementation();
|
|
181
|
+
await runCommand('verification', 'b1');
|
|
182
|
+
expect(spy).toHaveBeenCalledWith('No verification results yet.');
|
|
183
|
+
spy.mockRestore();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|