rentman-cli 2.0.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/.env.example +39 -0
- package/CLI_PRODUCTION_ANALYSIS.md +510 -0
- package/IMPLEMENTATION_REPORT.md +245 -0
- package/README.md +72 -0
- package/SECURITY_FIXES_README.md +336 -0
- package/_DELETED_rentman_identity.json.bak +8 -0
- package/_backup_old_cli_20260208_130317/src/commands/init.js +118 -0
- package/_backup_old_cli_20260208_130317/src/commands/login-v2.js +62 -0
- package/_backup_old_cli_20260208_130317/src/commands/login.js +40 -0
- package/_backup_old_cli_20260208_130317/src/commands/post-mission.js +179 -0
- package/_backup_old_cli_20260208_130317/src/index.js +135 -0
- package/bin/rentman.js +7 -0
- package/gen_identity.js +23 -0
- package/migrate-identity.js +75 -0
- package/mission.json +21 -0
- package/package.json +37 -0
- package/src/commands/config.js +44 -0
- package/src/commands/guide.js +26 -0
- package/src/commands/init.js +147 -0
- package/src/commands/legal.js +78 -0
- package/src/commands/listen.js +88 -0
- package/src/commands/post-mission.js +202 -0
- package/src/commands/task.js +126 -0
- package/src/index.js +247 -0
- package/src/lib/api.js +120 -0
- package/src/lib/config.js +34 -0
- package/src/lib/crypto.js +80 -0
- package/src/lib/secure-config.js +118 -0
- package/test-integration.js +135 -0
- package/test_mission_v6.json +11 -0
- package/test_mission_v7.json +11 -0
- package/test_task.json +11 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const Ajv = require('ajv');
|
|
4
|
+
const { apiRequest } = require('../lib/api');
|
|
5
|
+
|
|
6
|
+
const taskCommand = new Command();
|
|
7
|
+
|
|
8
|
+
// JSON Schema for task validation
|
|
9
|
+
const taskSchema = {
|
|
10
|
+
type: 'object',
|
|
11
|
+
required: ['title', 'description', 'task_type', 'budget_amount'],
|
|
12
|
+
properties: {
|
|
13
|
+
title: { type: 'string', maxLength: 200 },
|
|
14
|
+
description: { type: 'string' },
|
|
15
|
+
task_type: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
enum: ['delivery', 'verification', 'repair', 'representation', 'creative', 'communication']
|
|
18
|
+
},
|
|
19
|
+
location: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
required: ['lat', 'lng'],
|
|
22
|
+
properties: {
|
|
23
|
+
lat: { type: 'number', minimum: -90, maximum: 90 },
|
|
24
|
+
lng: { type: 'number', minimum: -180, maximum: 180 },
|
|
25
|
+
address: { type: 'string' }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
budget_amount: { type: 'number', minimum: 1 },
|
|
29
|
+
required_skills: {
|
|
30
|
+
type: 'array',
|
|
31
|
+
items: { type: 'string' }
|
|
32
|
+
},
|
|
33
|
+
requirements: { type: 'object' }
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
taskCommand
|
|
38
|
+
.command('create <file>')
|
|
39
|
+
.description('Create a new task from JSON file')
|
|
40
|
+
.action(async (file) => {
|
|
41
|
+
try {
|
|
42
|
+
// Read and parse JSON file
|
|
43
|
+
if (!fs.existsSync(file)) {
|
|
44
|
+
console.error(`β File not found: ${file}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const taskData = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
49
|
+
|
|
50
|
+
// Validate schema
|
|
51
|
+
const ajv = new Ajv();
|
|
52
|
+
const validate = ajv.compile(taskSchema);
|
|
53
|
+
const valid = validate(taskData);
|
|
54
|
+
|
|
55
|
+
if (!valid) {
|
|
56
|
+
console.error('β Invalid task schema:');
|
|
57
|
+
console.error(validate.errors);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('π Creating task...');
|
|
62
|
+
console.log(JSON.stringify(taskData, null, 2));
|
|
63
|
+
|
|
64
|
+
// POST to API
|
|
65
|
+
const response = await apiRequest('/market-tasks', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
body: JSON.stringify(taskData),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (response.success) {
|
|
71
|
+
const taskId = response.data.id;
|
|
72
|
+
console.log(`β
Task Created: ${taskId}`);
|
|
73
|
+
console.log(`Status: ${response.data.status}`);
|
|
74
|
+
console.log(`Budget: $${response.data.budget_amount}`);
|
|
75
|
+
} else {
|
|
76
|
+
console.error('β Task creation failed');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('β Error:', error.message);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
taskCommand
|
|
86
|
+
.command('map')
|
|
87
|
+
.description('ASCII visualization of active tasks')
|
|
88
|
+
.action(async () => {
|
|
89
|
+
try {
|
|
90
|
+
const response = await apiRequest('/market-tasks?status=OPEN');
|
|
91
|
+
|
|
92
|
+
console.log('πΊοΈ ACTIVE TASKS MAP');
|
|
93
|
+
console.log('β'.repeat(50));
|
|
94
|
+
|
|
95
|
+
if (response.success && response.data.length > 0) {
|
|
96
|
+
response.data.forEach((task, i) => {
|
|
97
|
+
const icon = getTaskIcon(task.task_type);
|
|
98
|
+
const loc = task.location_address || 'π» Remote';
|
|
99
|
+
|
|
100
|
+
console.log(`${icon} ${task.title}`);
|
|
101
|
+
console.log(` π ${loc} | $${task.budget_amount} | ${task.status}`);
|
|
102
|
+
console.log(` ID: ${task.id}`);
|
|
103
|
+
console.log('');
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
console.log('No active tasks found.');
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('β Error:', error.message);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
function getTaskIcon(type) {
|
|
115
|
+
const icons = {
|
|
116
|
+
delivery: 'π¦',
|
|
117
|
+
verification: 'β
',
|
|
118
|
+
repair: 'π§',
|
|
119
|
+
representation: 'π€',
|
|
120
|
+
creative: 'π¨',
|
|
121
|
+
communication: 'π'
|
|
122
|
+
};
|
|
123
|
+
return icons[type] || 'π';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = taskCommand;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rentman CLI - Main Entry Point
|
|
3
|
+
* Secure, production-ready version
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
require('dotenv').config();
|
|
7
|
+
|
|
8
|
+
const { Command } = require('commander');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const updateNotifier = require('update-notifier');
|
|
11
|
+
const pkg = require('../package.json');
|
|
12
|
+
|
|
13
|
+
// Check for updates
|
|
14
|
+
updateNotifier({ pkg }).notify();
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('rentman')
|
|
20
|
+
.description('CLI tool for AI agents to hire humans via Rentman marketplace')
|
|
21
|
+
.version(pkg.version);
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// Core Commands
|
|
25
|
+
// ============================================
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('init')
|
|
29
|
+
.description('Initialize agent identity (KYA) and link to owner account')
|
|
30
|
+
.action(require('./commands/init-secure'));
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('post-mission [file]')
|
|
34
|
+
.description('Create a task in the marketplace (from JSON file or interactive)')
|
|
35
|
+
.alias('post')
|
|
36
|
+
.action(require('./commands/post-mission-secure'));
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command('listen <taskId>')
|
|
40
|
+
.description('Listen for real-time updates on a task')
|
|
41
|
+
.alias('watch')
|
|
42
|
+
.action(require('./commands/listen'));
|
|
43
|
+
|
|
44
|
+
program
|
|
45
|
+
.command('config <key> [value]')
|
|
46
|
+
.description('Get/set configuration values')
|
|
47
|
+
.action(require('./commands/config'));
|
|
48
|
+
|
|
49
|
+
program
|
|
50
|
+
.command('legal [type]')
|
|
51
|
+
.description('View legal documents (privacy, terms)')
|
|
52
|
+
.action(require('./commands/legal'));
|
|
53
|
+
|
|
54
|
+
program
|
|
55
|
+
.command('guide')
|
|
56
|
+
.description('Show the Rentman workflow guide')
|
|
57
|
+
.action(require('./commands/guide'));
|
|
58
|
+
|
|
59
|
+
// ============================================
|
|
60
|
+
// Task Management
|
|
61
|
+
// ============================================
|
|
62
|
+
|
|
63
|
+
program
|
|
64
|
+
.command('task:list')
|
|
65
|
+
.description('List available tasks in the marketplace')
|
|
66
|
+
.option('-s, --status <status>', 'Filter by status (open, in_progress, completed)')
|
|
67
|
+
.option('-t, --type <type>', 'Filter by task type')
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
try {
|
|
70
|
+
const { getTasks } = require('./lib/api');
|
|
71
|
+
const ora = (await import('ora')).default;
|
|
72
|
+
const Table = require('cli-table3');
|
|
73
|
+
|
|
74
|
+
const spinner = ora('Fetching tasks...').start();
|
|
75
|
+
|
|
76
|
+
const response = await getTasks({
|
|
77
|
+
status: options.status,
|
|
78
|
+
task_type: options.type,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
spinner.stop();
|
|
82
|
+
|
|
83
|
+
if (response.success && response.data && response.data.length > 0) {
|
|
84
|
+
const table = new Table({
|
|
85
|
+
head: [
|
|
86
|
+
chalk.cyan('ID'),
|
|
87
|
+
chalk.cyan('Type'),
|
|
88
|
+
chalk.cyan('Title'),
|
|
89
|
+
chalk.cyan('Budget'),
|
|
90
|
+
chalk.cyan('Status'),
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const icons = {
|
|
95
|
+
delivery: 'π¦',
|
|
96
|
+
verification: 'β
',
|
|
97
|
+
repair: 'π§',
|
|
98
|
+
representation: 'π€',
|
|
99
|
+
creative: 'π¨',
|
|
100
|
+
communication: 'π',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
response.data.forEach((task) => {
|
|
104
|
+
const icon = icons[task.task_type] || 'π';
|
|
105
|
+
table.push([
|
|
106
|
+
task.id.substring(0, 8) + '...',
|
|
107
|
+
`${icon} ${task.task_type}`,
|
|
108
|
+
task.title.substring(0, 30),
|
|
109
|
+
chalk.green(`$${task.budget_amount}`),
|
|
110
|
+
task.status,
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(table.toString());
|
|
115
|
+
console.log(
|
|
116
|
+
chalk.gray(`\nShowing ${response.data.length} tasks (Page ${response.pagination?.page || 1})`)
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
console.log(chalk.yellow('No tasks found.'));
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(chalk.red('β Error:'), error.message);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
program
|
|
128
|
+
.command('task:view <taskId>')
|
|
129
|
+
.description('View details of a specific task')
|
|
130
|
+
.action(async (taskId) => {
|
|
131
|
+
try {
|
|
132
|
+
const { getTask } = require('./lib/api');
|
|
133
|
+
const ora = (await import('ora')).default;
|
|
134
|
+
|
|
135
|
+
const spinner = ora('Fetching task...').start();
|
|
136
|
+
const response = await getTask(taskId);
|
|
137
|
+
spinner.stop();
|
|
138
|
+
|
|
139
|
+
const task = response.data;
|
|
140
|
+
|
|
141
|
+
console.log(chalk.bold.blue('\nπ Task Details\n'));
|
|
142
|
+
console.log(chalk.white('ID: ') + chalk.cyan(task.id));
|
|
143
|
+
console.log(chalk.white('Title: ') + chalk.cyan(task.title));
|
|
144
|
+
console.log(chalk.white('Type: ') + chalk.cyan(task.task_type));
|
|
145
|
+
console.log(chalk.white('Status: ') + chalk.cyan(task.status));
|
|
146
|
+
console.log(chalk.white('Budget: ') + chalk.green(`$${task.budget_amount} ${task.budget_currency}`));
|
|
147
|
+
console.log(chalk.white('Description:\n') + chalk.gray(task.description));
|
|
148
|
+
console.log('');
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(chalk.red('β Error:'), error.message);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ============================================
|
|
156
|
+
// Human Search
|
|
157
|
+
// ============================================
|
|
158
|
+
|
|
159
|
+
program
|
|
160
|
+
.command('humans:search')
|
|
161
|
+
.description('Search for qualified human operators')
|
|
162
|
+
.option('-s, --skills <skills>', 'Required skills (comma-separated)')
|
|
163
|
+
.option('-r, --min-reputation <score>', 'Minimum reputation score')
|
|
164
|
+
.action(async (options) => {
|
|
165
|
+
try {
|
|
166
|
+
const { searchHumans } = require('./lib/api');
|
|
167
|
+
const ora = (await import('ora')).default;
|
|
168
|
+
const Table = require('cli-table3');
|
|
169
|
+
|
|
170
|
+
const spinner = ora('Searching humans...').start();
|
|
171
|
+
|
|
172
|
+
const filters = {};
|
|
173
|
+
if (options.skills) filters.skills = options.skills.split(',');
|
|
174
|
+
if (options.minReputation) filters.min_reputation = parseInt(options.minReputation);
|
|
175
|
+
|
|
176
|
+
const response = await searchHumans(filters);
|
|
177
|
+
spinner.stop();
|
|
178
|
+
|
|
179
|
+
if (response.success && response.data && response.data.length > 0) {
|
|
180
|
+
const table = new Table({
|
|
181
|
+
head: [
|
|
182
|
+
chalk.cyan('ID'),
|
|
183
|
+
chalk.cyan('Reputation'),
|
|
184
|
+
chalk.cyan('Level'),
|
|
185
|
+
chalk.cyan('Tasks'),
|
|
186
|
+
chalk.cyan('Success Rate'),
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
response.data.forEach((human) => {
|
|
191
|
+
table.push([
|
|
192
|
+
human.id.substring(0, 8) + '...',
|
|
193
|
+
chalk.green(`${human.reputation_score}/100`),
|
|
194
|
+
`Lv ${human.level}`,
|
|
195
|
+
human.tasks_completed || 0,
|
|
196
|
+
`${human.success_rate || 0}%`,
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
console.log(table.toString());
|
|
201
|
+
} else {
|
|
202
|
+
console.log(chalk.yellow('No humans found matching criteria.'));
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(chalk.red('β Error:'), error.message);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// ============================================
|
|
211
|
+
// Utilities
|
|
212
|
+
// ============================================
|
|
213
|
+
|
|
214
|
+
program
|
|
215
|
+
.command('whoami')
|
|
216
|
+
.description('Show current agent identity')
|
|
217
|
+
.action(() => {
|
|
218
|
+
const { getIdentity, getConfigPath } = require('./lib/secure-config');
|
|
219
|
+
const identity = getIdentity();
|
|
220
|
+
|
|
221
|
+
if (!identity) {
|
|
222
|
+
console.log(chalk.yellow('β οΈ No identity found'));
|
|
223
|
+
console.log(chalk.gray('β Run: rentman init'));
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(chalk.bold.blue('\nπ€ Current Agent Identity\n'));
|
|
228
|
+
console.log(chalk.white('Agent ID: ') + chalk.cyan(identity.agent_id));
|
|
229
|
+
console.log(chalk.white('Public ID: ') + chalk.cyan(identity.public_agent_id || 'N/A'));
|
|
230
|
+
console.log(chalk.white('Public Key: ') + chalk.gray(identity.public_key?.substring(0, 20) + '...'));
|
|
231
|
+
console.log(chalk.white('Source: ') + chalk.cyan(identity.source));
|
|
232
|
+
console.log(chalk.white('Config Path: ') + chalk.gray(getConfigPath()));
|
|
233
|
+
console.log('');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ============================================
|
|
237
|
+
// Error Handling
|
|
238
|
+
// ============================================
|
|
239
|
+
|
|
240
|
+
program.on('command:*', () => {
|
|
241
|
+
console.error(chalk.red('\nβ Invalid command: %s'), program.args.join(' '));
|
|
242
|
+
console.log(chalk.yellow('β See --help for available commands'));
|
|
243
|
+
process.exit(1);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Parse arguments
|
|
247
|
+
program.parse();
|
package/src/lib/api.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for Agent Gateway
|
|
3
|
+
* All requests go through the gateway for proper authentication and audit
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fetch = require('node-fetch');
|
|
7
|
+
const { getApiConfig, getApiKey, getIdentity } = require('./secure-config');
|
|
8
|
+
const { generateNaclSignature } = require('./crypto');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Make authenticated request to Agent Gateway
|
|
12
|
+
* Supports both API Key and NACL signature authentication
|
|
13
|
+
*/
|
|
14
|
+
async function apiRequest(endpoint, options = {}) {
|
|
15
|
+
const config = getApiConfig();
|
|
16
|
+
const apiKey = getApiKey();
|
|
17
|
+
const identity = getIdentity();
|
|
18
|
+
|
|
19
|
+
// Build full URL
|
|
20
|
+
const url = `${config.gatewayUrl}/market${endpoint}`;
|
|
21
|
+
|
|
22
|
+
// Prepare headers
|
|
23
|
+
const headers = {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
...options.headers,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Authentication: Prefer NACL signature, fallback to API key
|
|
29
|
+
if (identity && identity.secret_key) {
|
|
30
|
+
// NACL Signature Authentication (most secure)
|
|
31
|
+
const method = options.method || 'GET';
|
|
32
|
+
const payload = options.body || {};
|
|
33
|
+
|
|
34
|
+
const signature = generateNaclSignature(
|
|
35
|
+
payload,
|
|
36
|
+
identity.secret_key,
|
|
37
|
+
method,
|
|
38
|
+
`/market${endpoint}`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
headers['x-agent-id'] = identity.agent_id;
|
|
42
|
+
headers['x-signature'] = `nacl:${signature}`;
|
|
43
|
+
} else if (apiKey) {
|
|
44
|
+
// API Key Authentication (fallback)
|
|
45
|
+
headers['x-api-key'] = apiKey;
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error('No authentication credentials found. Run: rentman init');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Make request
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
...options,
|
|
54
|
+
headers,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const error = new Error(data.error?.message || 'API request failed');
|
|
61
|
+
error.statusCode = response.status;
|
|
62
|
+
error.code = data.error?.code;
|
|
63
|
+
error.details = data.error?.details;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return data;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Network or parsing errors
|
|
70
|
+
if (!error.statusCode) {
|
|
71
|
+
throw new Error(`Network error: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Convenience methods
|
|
79
|
+
*/
|
|
80
|
+
async function getTasks(filters = {}) {
|
|
81
|
+
const params = new URLSearchParams(filters);
|
|
82
|
+
return apiRequest(`/tasks?${params}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function getTask(taskId) {
|
|
86
|
+
return apiRequest(`/tasks/${taskId}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function createTask(taskData) {
|
|
90
|
+
return apiRequest('/tasks', {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body: JSON.stringify(taskData),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function searchHumans(filters = {}) {
|
|
97
|
+
const params = new URLSearchParams(filters);
|
|
98
|
+
return apiRequest(`/humans?${params}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function hireHuman(taskId, humanId, offeredAmount, terms) {
|
|
102
|
+
return apiRequest('/hire', {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
task_id: taskId,
|
|
106
|
+
human_id: humanId,
|
|
107
|
+
offered_amount: offeredAmount,
|
|
108
|
+
terms,
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
apiRequest,
|
|
115
|
+
getTasks,
|
|
116
|
+
getTask,
|
|
117
|
+
createTask,
|
|
118
|
+
searchHumans,
|
|
119
|
+
hireHuman,
|
|
120
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.rentman');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
function ensureConfigDir() {
|
|
9
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
10
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getConfig() {
|
|
15
|
+
ensureConfigDir();
|
|
16
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
17
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function setConfig(key, value) {
|
|
23
|
+
ensureConfigDir();
|
|
24
|
+
const config = getConfig();
|
|
25
|
+
config[key] = value;
|
|
26
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getApiKey() {
|
|
30
|
+
const config = getConfig();
|
|
31
|
+
return config.apiKey;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { getConfig, setConfig, getApiKey };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic Utilities
|
|
3
|
+
* NACL signature generation for Agent Gateway authentication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const nacl = require('tweetnacl');
|
|
7
|
+
const naclUtil = require('tweetnacl-util');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate Ed25519 keypair
|
|
11
|
+
*/
|
|
12
|
+
function generateKeyPair() {
|
|
13
|
+
const keyPair = nacl.sign.keyPair();
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
publicKey: naclUtil.encodeBase64(keyPair.publicKey),
|
|
17
|
+
secretKey: naclUtil.encodeBase64(keyPair.secretKey),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate NACL signature for request authentication
|
|
23
|
+
* @param {Object} payload - Request payload (will be JSON stringified)
|
|
24
|
+
* @param {string} secretKeyBase64 - Base64 encoded secret key
|
|
25
|
+
* @param {string} method - HTTP method (GET, POST, etc.)
|
|
26
|
+
* @param {string} url - Request URL path
|
|
27
|
+
* @returns {string} Base64 encoded signature
|
|
28
|
+
*/
|
|
29
|
+
function generateNaclSignature(payload, secretKeyBase64, method = 'POST', url = '') {
|
|
30
|
+
try {
|
|
31
|
+
// Create message to sign: method + url + payload
|
|
32
|
+
const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
33
|
+
const message = `${method}:${url}:${payloadString}`;
|
|
34
|
+
|
|
35
|
+
// Convert to bytes
|
|
36
|
+
const messageBytes = naclUtil.decodeUTF8(message);
|
|
37
|
+
const secretKeyBytes = naclUtil.decodeBase64(secretKeyBase64);
|
|
38
|
+
|
|
39
|
+
// Sign message
|
|
40
|
+
const signature = nacl.sign.detached(messageBytes, secretKeyBytes);
|
|
41
|
+
|
|
42
|
+
// Return base64 encoded signature
|
|
43
|
+
return naclUtil.encodeBase64(signature);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Failed to generate signature: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Verify NACL signature (for testing)
|
|
51
|
+
*/
|
|
52
|
+
function verifyNaclSignature(payload, signatureBase64, publicKeyBase64, method = 'POST', url = '') {
|
|
53
|
+
try {
|
|
54
|
+
const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
55
|
+
const message = `${method}:${url}:${payloadString}`;
|
|
56
|
+
|
|
57
|
+
const messageBytes = naclUtil.decodeUTF8(message);
|
|
58
|
+
const signatureBytes = naclUtil.decodeBase64(signatureBase64);
|
|
59
|
+
const publicKeyBytes = naclUtil.decodeBase64(publicKeyBase64);
|
|
60
|
+
|
|
61
|
+
return nacl.sign.detached.verify(messageBytes, signatureBytes, publicKeyBytes);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate secure random string
|
|
69
|
+
*/
|
|
70
|
+
function generateRandomString(length = 32) {
|
|
71
|
+
const bytes = nacl.randomBytes(length);
|
|
72
|
+
return naclUtil.encodeBase64(bytes).slice(0, length);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
generateKeyPair,
|
|
77
|
+
generateNaclSignature,
|
|
78
|
+
verifyNaclSignature,
|
|
79
|
+
generateRandomString,
|
|
80
|
+
};
|