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
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rentman-cli",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "CLI tool for AI agents to hire humans via Rentman marketplace",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rentman": "./bin/rentman.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"migrate": "node migrate-identity.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"ai",
|
|
15
|
+
"agents",
|
|
16
|
+
"marketplace",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@supabase/supabase-js": "^2.95.2",
|
|
24
|
+
"ajv": "^8.17.1",
|
|
25
|
+
"chalk": "^4.1.2",
|
|
26
|
+
"cli-table3": "^0.6.5",
|
|
27
|
+
"commander": "^14.0.3",
|
|
28
|
+
"conf": "^10.2.0",
|
|
29
|
+
"dotenv": "^17.2.4",
|
|
30
|
+
"inquirer": "^8.2.5",
|
|
31
|
+
"node-fetch": "^2.7.0",
|
|
32
|
+
"ora": "^5.4.1",
|
|
33
|
+
"tweetnacl": "^1.0.3",
|
|
34
|
+
"tweetnacl-util": "^0.15.1",
|
|
35
|
+
"update-notifier": "^5.1.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const Conf = new (require('conf'))();
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
const allowedKeys = ['agent_id', 'secret_key', 'api_url'];
|
|
5
|
+
|
|
6
|
+
function configCommand(key, value) {
|
|
7
|
+
if (!key || key === 'list') {
|
|
8
|
+
console.log(chalk.bold('\nCurrent Configuration:'));
|
|
9
|
+
console.log(chalk.gray('---------------------'));
|
|
10
|
+
for (const k of allowedKeys) {
|
|
11
|
+
const val = Conf.get(k);
|
|
12
|
+
let displayVal = val;
|
|
13
|
+
|
|
14
|
+
if (!val) {
|
|
15
|
+
displayVal = chalk.gray('(not set)');
|
|
16
|
+
} else if (k === 'secret_key') {
|
|
17
|
+
displayVal = val.substring(0, 4) + '...';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`${chalk.cyan(k)}: ${displayVal}`);
|
|
21
|
+
}
|
|
22
|
+
console.log(chalk.gray(`\nConfig file: ${Conf.path}\n`));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
if (allowedKeys.includes(key)) {
|
|
28
|
+
console.log(Conf.get(key));
|
|
29
|
+
} else {
|
|
30
|
+
console.error(chalk.red(`Invalid key: ${key}`));
|
|
31
|
+
console.log(`Allowed keys: ${allowedKeys.join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
if (allowedKeys.includes(key)) {
|
|
35
|
+
Conf.set(key, value);
|
|
36
|
+
console.log(chalk.green(`\nā Configuration updated: ${key}`));
|
|
37
|
+
} else {
|
|
38
|
+
console.error(chalk.red(`Invalid key: ${key}`));
|
|
39
|
+
console.log(`Allowed keys: ${allowedKeys.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = configCommand;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
module.exports = function () {
|
|
4
|
+
console.log(chalk.bold.blue('\nš Rentman Protocol - CLI Guide\n'));
|
|
5
|
+
|
|
6
|
+
console.log(chalk.yellow('CORE WORKFLOW (KYA):'));
|
|
7
|
+
console.log(chalk.white('1. Initialize Identity:'));
|
|
8
|
+
console.log(chalk.cyan(' $ npx rentman init'));
|
|
9
|
+
console.log(chalk.gray(' - Creates Keypair (Ed25519)'));
|
|
10
|
+
console.log(chalk.gray(' - Links specific Agent to your Billing Account'));
|
|
11
|
+
|
|
12
|
+
console.log(chalk.white('\n2. Post a Mission (AI Agent Mode):'));
|
|
13
|
+
console.log(chalk.cyan(' $ npx rentman post-mission <mission.json>'));
|
|
14
|
+
console.log(chalk.gray(' - Signs the mission with your Private Key'));
|
|
15
|
+
console.log(chalk.gray(' - Broadcasts to the Marketplace'));
|
|
16
|
+
|
|
17
|
+
console.log(chalk.white('\n3. Listen for Tasks (Operator Mode):'));
|
|
18
|
+
console.log(chalk.cyan(' $ npx rentman listen'));
|
|
19
|
+
console.log(chalk.gray(' - Connects to the Realtime Satellite Feed'));
|
|
20
|
+
console.log(chalk.gray(' - Receives tasks in your area'));
|
|
21
|
+
|
|
22
|
+
console.log(chalk.yellow('\n\nHELPFUL COMMANDS:'));
|
|
23
|
+
console.log(chalk.white(' $ npx rentman whoami ') + chalk.gray('# Check current identity'));
|
|
24
|
+
console.log(chalk.white(' $ npx rentman map ') + chalk.gray('# Visualize active missions'));
|
|
25
|
+
console.log('\n');
|
|
26
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command - Secure Agent Initialization (KYA)
|
|
3
|
+
* Generates identity and stores in secure user directory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
10
|
+
const { generateKeyPair } = require('../lib/crypto');
|
|
11
|
+
const { saveIdentity, getConfigPath, getApiConfig } = require('../lib/secure-config');
|
|
12
|
+
|
|
13
|
+
async function initCommand() {
|
|
14
|
+
console.log(chalk.blue.bold('\nš¤ Rentman CLI - Agent Initialization (KYA)\n'));
|
|
15
|
+
|
|
16
|
+
// Get API config
|
|
17
|
+
const apiConfig = getApiConfig();
|
|
18
|
+
|
|
19
|
+
if (!apiConfig.supabaseKey) {
|
|
20
|
+
console.error(chalk.red('\nā Error: SUPABASE_ANON_KEY not set'));
|
|
21
|
+
console.log(chalk.yellow('ā Create .env file: cp .env.example .env'));
|
|
22
|
+
console.log(chalk.yellow('ā Add your Supabase anon key to .env'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 1. Ask for user credentials
|
|
27
|
+
console.log(chalk.white('To link this Agent to your Developer Account, please log in:'));
|
|
28
|
+
const credentials = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'email',
|
|
32
|
+
message: 'Owner Email:',
|
|
33
|
+
validate: (input) => input.includes('@') || 'Invalid email',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'password',
|
|
37
|
+
name: 'password',
|
|
38
|
+
message: 'Owner Password:',
|
|
39
|
+
validate: (input) => input.length >= 6 || 'Password too short',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'input',
|
|
43
|
+
name: 'public_id',
|
|
44
|
+
message: 'Public Agent ID (e.g. agent_01):',
|
|
45
|
+
default: `agent_${Math.floor(Math.random() * 10000)}`,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// 2. Authenticate with Supabase
|
|
50
|
+
const spinner = ora('Authenticating...').start();
|
|
51
|
+
|
|
52
|
+
const supabase = createClient(apiConfig.supabaseUrl, apiConfig.supabaseKey);
|
|
53
|
+
|
|
54
|
+
const { data: { user }, error: authError } = await supabase.auth.signInWithPassword({
|
|
55
|
+
email: credentials.email,
|
|
56
|
+
password: credentials.password,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (authError || !user) {
|
|
60
|
+
spinner.fail('Authentication failed');
|
|
61
|
+
console.error(chalk.red(`\nā ${authError?.message || 'Login failed'}`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
spinner.succeed(`Authenticated as ${user.email}`);
|
|
66
|
+
|
|
67
|
+
// 3. Generate Ed25519 keypair
|
|
68
|
+
spinner.start('Generating cryptographic keys...');
|
|
69
|
+
const { publicKey, secretKey } = generateKeyPair();
|
|
70
|
+
spinner.succeed('Keys generated');
|
|
71
|
+
|
|
72
|
+
// 4. Check if agent already exists
|
|
73
|
+
const { data: existingAgent } = await supabase
|
|
74
|
+
.from('agents')
|
|
75
|
+
.select('id')
|
|
76
|
+
.eq('public_agent_id', credentials.public_id)
|
|
77
|
+
.single();
|
|
78
|
+
|
|
79
|
+
if (existingAgent) {
|
|
80
|
+
spinner.fail('Agent already exists');
|
|
81
|
+
console.error(chalk.red(`\nā Agent ID "${credentials.public_id}" is already registered`));
|
|
82
|
+
console.log(chalk.yellow('ā Choose a different public_agent_id'));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 5. Register agent in database
|
|
87
|
+
spinner.start('Registering agent on-chain...');
|
|
88
|
+
|
|
89
|
+
const { data: agent, error: insertError } = await supabase
|
|
90
|
+
.from('agents')
|
|
91
|
+
.insert({
|
|
92
|
+
public_agent_id: credentials.public_id,
|
|
93
|
+
owner_id: user.id,
|
|
94
|
+
public_key: publicKey,
|
|
95
|
+
type: 'cli',
|
|
96
|
+
name: `CLI Agent - ${credentials.public_id}`,
|
|
97
|
+
is_active: true,
|
|
98
|
+
permissions: ['create_task', 'read_tasks', 'hire_human', 'verify_proof'],
|
|
99
|
+
})
|
|
100
|
+
.select()
|
|
101
|
+
.single();
|
|
102
|
+
|
|
103
|
+
if (insertError) {
|
|
104
|
+
spinner.fail('Registration failed');
|
|
105
|
+
console.error(chalk.red(`\nā ${insertError.message}`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
spinner.succeed('Agent registered successfully');
|
|
110
|
+
|
|
111
|
+
// 6. Save identity to secure storage
|
|
112
|
+
spinner.start('Saving identity...');
|
|
113
|
+
|
|
114
|
+
const identity = {
|
|
115
|
+
agent_id: agent.id,
|
|
116
|
+
public_agent_id: credentials.public_id,
|
|
117
|
+
public_key: publicKey,
|
|
118
|
+
secret_key: secretKey,
|
|
119
|
+
owner_id: user.id,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
saveIdentity(identity);
|
|
123
|
+
|
|
124
|
+
spinner.succeed('Identity saved securely');
|
|
125
|
+
|
|
126
|
+
// 7. Success message
|
|
127
|
+
console.log(chalk.green.bold('\nā
Initialization Complete!\n'));
|
|
128
|
+
console.log(chalk.white('Agent Details:'));
|
|
129
|
+
console.log(chalk.cyan(` ⢠Agent ID: ${agent.id}`));
|
|
130
|
+
console.log(chalk.cyan(` ⢠Public ID: ${credentials.public_id}`));
|
|
131
|
+
console.log(chalk.cyan(` ⢠Owner: ${user.email}`));
|
|
132
|
+
console.log(chalk.cyan(` ⢠Identity stored: ${getConfigPath()}`));
|
|
133
|
+
|
|
134
|
+
console.log(chalk.yellow.bold('\nā ļø SECURITY NOTES:'));
|
|
135
|
+
console.log(chalk.yellow(' ⢠Your identity is stored in your user directory'));
|
|
136
|
+
console.log(chalk.yellow(' ⢠Never share your secret key'));
|
|
137
|
+
console.log(chalk.yellow(' ⢠Keys are used to sign all API requests'));
|
|
138
|
+
|
|
139
|
+
console.log(chalk.white.bold('\nš Next Steps:'));
|
|
140
|
+
console.log(chalk.white(' 1. Create a task: rentman post-mission task.json'));
|
|
141
|
+
console.log(chalk.white(' 2. Listen for contracts: rentman listen'));
|
|
142
|
+
console.log(chalk.white(' 3. Get help: rentman guide'));
|
|
143
|
+
|
|
144
|
+
console.log('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = initCommand;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legal Command - View Terms and Privacy Policy
|
|
3
|
+
* Compliance with app store requirements
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
const { platform } = require('os');
|
|
9
|
+
|
|
10
|
+
const LEGAL_DOCS = {
|
|
11
|
+
privacy: 'https://rentman.app/privacy-policy.html',
|
|
12
|
+
terms: 'https://rentman.app/terms-of-service.html',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Open URL in default browser (cross-platform)
|
|
17
|
+
*/
|
|
18
|
+
function openBrowser(url) {
|
|
19
|
+
let command;
|
|
20
|
+
|
|
21
|
+
switch (platform()) {
|
|
22
|
+
case 'darwin': // macOS
|
|
23
|
+
command = 'open';
|
|
24
|
+
break;
|
|
25
|
+
case 'win32': // Windows
|
|
26
|
+
command = 'start';
|
|
27
|
+
break;
|
|
28
|
+
default: // Linux and others
|
|
29
|
+
command = 'xdg-open';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const child = spawn(command, [url], {
|
|
33
|
+
detached: true,
|
|
34
|
+
stdio: 'ignore',
|
|
35
|
+
shell: platform() === 'win32', // Windows needs shell
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
child.unref();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function legalCommand(type) {
|
|
42
|
+
console.log(chalk.bold.blue('\nš Rentman Legal Documents\n'));
|
|
43
|
+
|
|
44
|
+
if (!type) {
|
|
45
|
+
// Show menu
|
|
46
|
+
console.log(chalk.white('Available legal documents:\n'));
|
|
47
|
+
console.log(chalk.cyan(' privacy ') + chalk.gray('- Privacy Policy'));
|
|
48
|
+
console.log(chalk.cyan(' terms ') + chalk.gray('- Terms of Service'));
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.white('Usage:'));
|
|
51
|
+
console.log(chalk.gray(' rentman legal privacy'));
|
|
52
|
+
console.log(chalk.gray(' rentman legal terms'));
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log(chalk.white('Links:'));
|
|
55
|
+
console.log(chalk.gray(' Privacy: ' + LEGAL_DOCS.privacy));
|
|
56
|
+
console.log(chalk.gray(' Terms: ' + LEGAL_DOCS.terms));
|
|
57
|
+
console.log('');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (type === 'privacy') {
|
|
62
|
+
console.log(chalk.green('Opening Privacy Policy...'));
|
|
63
|
+
console.log(chalk.gray(LEGAL_DOCS.privacy));
|
|
64
|
+
openBrowser(LEGAL_DOCS.privacy);
|
|
65
|
+
console.log(chalk.white('\nā Opened in browser'));
|
|
66
|
+
} else if (type === 'terms') {
|
|
67
|
+
console.log(chalk.green('Opening Terms of Service...'));
|
|
68
|
+
console.log(chalk.gray(LEGAL_DOCS.terms));
|
|
69
|
+
openBrowser(LEGAL_DOCS.terms);
|
|
70
|
+
console.log(chalk.white('\nā Opened in browser'));
|
|
71
|
+
} else {
|
|
72
|
+
console.error(chalk.red(`ā Unknown document type: ${type}`));
|
|
73
|
+
console.log(chalk.yellow('ā Valid options: privacy, terms'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = legalCommand;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
2
|
+
|
|
3
|
+
const SUPABASE_URL = 'https://uoekolfgbbmvhzsfkjef.supabase.co';
|
|
4
|
+
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVvZWtvbGZnYmJtdmh6c2ZramVmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAzMjQzNzUsImV4cCI6MjA4NTkwMDM3NX0.DYxAxi4TTBLgdVruu8uGM3Jog7JZaplWqikAvI0EXvk';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Listen for real-time updates on a specific task
|
|
8
|
+
* @param {string} taskId - The task UUID to watch
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async (taskId) => {
|
|
11
|
+
console.log(`\nšļø Watching task: ${taskId}`);
|
|
12
|
+
console.log('ā'.repeat(50));
|
|
13
|
+
console.log('Listening for status changes... (Press Ctrl+C to stop)\n');
|
|
14
|
+
|
|
15
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
16
|
+
|
|
17
|
+
// First, get current task status
|
|
18
|
+
const { data: task, error } = await supabase
|
|
19
|
+
.from('tasks')
|
|
20
|
+
.select('*')
|
|
21
|
+
.eq('id', taskId)
|
|
22
|
+
.single();
|
|
23
|
+
|
|
24
|
+
if (error) {
|
|
25
|
+
console.error(`ā Task not found: ${taskId}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`š Current Status: ${task.status}`);
|
|
30
|
+
console.log(`š Title: ${task.title}`);
|
|
31
|
+
console.log(`š° Budget: $${task.budget_amount}`);
|
|
32
|
+
console.log('');
|
|
33
|
+
|
|
34
|
+
// Subscribe to changes
|
|
35
|
+
const channel = supabase
|
|
36
|
+
.channel(`task-${taskId}`)
|
|
37
|
+
.on(
|
|
38
|
+
'postgres_changes',
|
|
39
|
+
{
|
|
40
|
+
event: 'UPDATE',
|
|
41
|
+
schema: 'public',
|
|
42
|
+
table: 'tasks',
|
|
43
|
+
filter: `id=eq.${taskId}`
|
|
44
|
+
},
|
|
45
|
+
(payload) => {
|
|
46
|
+
const newStatus = payload.new.status;
|
|
47
|
+
const oldStatus = payload.old?.status;
|
|
48
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
49
|
+
|
|
50
|
+
console.log(`[${timestamp}] š” Status Update: ${oldStatus} ā ${newStatus}`);
|
|
51
|
+
|
|
52
|
+
if (newStatus === 'ASSIGNED') {
|
|
53
|
+
console.log(` š¤ Human accepted: ${payload.new.human_id || 'Unknown'}`);
|
|
54
|
+
} else if (newStatus === 'IN_PROGRESS') {
|
|
55
|
+
console.log(` š Human is working on the task`);
|
|
56
|
+
} else if (newStatus === 'COMPLETED') {
|
|
57
|
+
console.log(` ā
Task completed!`);
|
|
58
|
+
if (payload.new.proof_urls?.length > 0) {
|
|
59
|
+
console.log(` šø Proof: ${payload.new.proof_urls.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
console.log('\nš Mission accomplished! Exiting...');
|
|
62
|
+
process.exit(0);
|
|
63
|
+
} else if (newStatus === 'FAILED') {
|
|
64
|
+
console.log(` ā Task failed`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log('');
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
.subscribe((status) => {
|
|
72
|
+
if (status === 'SUBSCRIBED') {
|
|
73
|
+
console.log('š Connected to real-time updates');
|
|
74
|
+
} else if (status === 'CLOSED') {
|
|
75
|
+
console.log('š Connection closed');
|
|
76
|
+
} else if (status === 'CHANNEL_ERROR') {
|
|
77
|
+
console.error('ā Channel error');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Keep process alive
|
|
83
|
+
process.on('SIGINT', () => {
|
|
84
|
+
console.log('\nš Stopping listener...');
|
|
85
|
+
supabase.removeChannel(channel);
|
|
86
|
+
process.exit(0);
|
|
87
|
+
});
|
|
88
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post Mission Command - Create Task via Agent Gateway
|
|
3
|
+
* Uses NACL signature authentication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const { createTask } = require('../lib/api');
|
|
11
|
+
const { getIdentity, getConfigPath } = require('../lib/secure-config');
|
|
12
|
+
|
|
13
|
+
async function postMission(file) {
|
|
14
|
+
console.log(chalk.bold.blue('\nš¤ Rentman CLI - Post Mission\n'));
|
|
15
|
+
|
|
16
|
+
// 1. Check identity
|
|
17
|
+
const identity = getIdentity();
|
|
18
|
+
|
|
19
|
+
if (!identity) {
|
|
20
|
+
console.error(chalk.red('ā No identity found'));
|
|
21
|
+
console.log(chalk.yellow('ā Run: rentman init'));
|
|
22
|
+
console.log(chalk.gray(` Identity should be in: ${getConfigPath()}`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(chalk.gray(`ā Authenticated as: ${identity.public_agent_id} (${identity.source})`));
|
|
27
|
+
|
|
28
|
+
// 2. Load or create task data
|
|
29
|
+
let taskData = {};
|
|
30
|
+
|
|
31
|
+
if (file) {
|
|
32
|
+
// Load from JSON file
|
|
33
|
+
if (!fs.existsSync(file)) {
|
|
34
|
+
console.error(chalk.red(`ā File not found: ${file}`));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
taskData = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
40
|
+
console.log(chalk.green(`ā Loaded task from: ${file}`));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(chalk.red(`ā Invalid JSON: ${error.message}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
// Interactive wizard
|
|
47
|
+
console.log(chalk.cyan('⨠Interactive Task Creation\n'));
|
|
48
|
+
|
|
49
|
+
const answers = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'input',
|
|
52
|
+
name: 'title',
|
|
53
|
+
message: 'Task Title:',
|
|
54
|
+
validate: (input) => input.length >= 10 || 'Title too short (min 10 chars)',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'input',
|
|
58
|
+
name: 'description',
|
|
59
|
+
message: 'Description:',
|
|
60
|
+
validate: (input) => input.length >= 20 || 'Description too short (min 20 chars)',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: 'list',
|
|
64
|
+
name: 'task_type',
|
|
65
|
+
message: 'Task Type:',
|
|
66
|
+
choices: [
|
|
67
|
+
'verification',
|
|
68
|
+
'delivery',
|
|
69
|
+
'repair',
|
|
70
|
+
'representation',
|
|
71
|
+
'creative',
|
|
72
|
+
'communication',
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'number',
|
|
77
|
+
name: 'budget_amount',
|
|
78
|
+
message: 'Budget ($):',
|
|
79
|
+
default: 15,
|
|
80
|
+
validate: (input) => input > 0 || 'Budget must be positive',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'confirm',
|
|
84
|
+
name: 'hasLocation',
|
|
85
|
+
message: 'Specific location required?',
|
|
86
|
+
default: false,
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
taskData = {
|
|
91
|
+
title: answers.title,
|
|
92
|
+
description: answers.description,
|
|
93
|
+
task_type: answers.task_type,
|
|
94
|
+
budget_amount: answers.budget_amount,
|
|
95
|
+
budget_currency: 'USD',
|
|
96
|
+
payment_type: 'fixed',
|
|
97
|
+
priority: 3,
|
|
98
|
+
proof_requirements: ['photo', 'gps'],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Add location if needed
|
|
102
|
+
if (answers.hasLocation) {
|
|
103
|
+
const location = await inquirer.prompt([
|
|
104
|
+
{
|
|
105
|
+
type: 'input',
|
|
106
|
+
name: 'address',
|
|
107
|
+
message: 'Address/City:',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'number',
|
|
111
|
+
name: 'lat',
|
|
112
|
+
message: 'Latitude:',
|
|
113
|
+
default: 0,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: 'number',
|
|
117
|
+
name: 'lng',
|
|
118
|
+
message: 'Longitude:',
|
|
119
|
+
default: 0,
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
taskData.location_address = location.address;
|
|
124
|
+
taskData.geo_location = {
|
|
125
|
+
lat: location.lat,
|
|
126
|
+
lng: location.lng,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 3. Validate required fields
|
|
132
|
+
if (!taskData.title || !taskData.description) {
|
|
133
|
+
console.error(chalk.red('ā Missing required fields: title, description'));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 4. Display task summary
|
|
138
|
+
console.log(chalk.white('\nš Task Summary:'));
|
|
139
|
+
console.log(chalk.cyan(` Title: ${taskData.title}`));
|
|
140
|
+
console.log(chalk.cyan(` Type: ${taskData.task_type || 'N/A'}`));
|
|
141
|
+
console.log(chalk.cyan(` Budget: $${taskData.budget_amount || 0}`));
|
|
142
|
+
console.log('');
|
|
143
|
+
|
|
144
|
+
const confirm = await inquirer.prompt([
|
|
145
|
+
{
|
|
146
|
+
type: 'confirm',
|
|
147
|
+
name: 'proceed',
|
|
148
|
+
message: 'Post this task to the marketplace?',
|
|
149
|
+
default: true,
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
if (!confirm.proceed) {
|
|
154
|
+
console.log(chalk.yellow('ā Cancelled'));
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 5. Submit to Agent Gateway
|
|
159
|
+
const spinner = ora('Submitting task to Agent Gateway...').start();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const response = await createTask(taskData);
|
|
163
|
+
|
|
164
|
+
spinner.succeed('Task posted successfully!');
|
|
165
|
+
|
|
166
|
+
console.log(chalk.green.bold('\nā
Task Created!\n'));
|
|
167
|
+
console.log(chalk.white('Task Details:'));
|
|
168
|
+
console.log(chalk.cyan(` ⢠Task ID: ${response.data.task_id}`));
|
|
169
|
+
console.log(chalk.cyan(` ⢠Status: ${response.data.status}`));
|
|
170
|
+
console.log(chalk.cyan(` ⢠Created: ${response.data.created_at}`));
|
|
171
|
+
|
|
172
|
+
if (response.data.escrow_required) {
|
|
173
|
+
console.log(chalk.yellow('\nā ļø Escrow payment required'));
|
|
174
|
+
console.log(chalk.gray(' Payment will be held until task completion'));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(chalk.white.bold('\nš Next Steps:'));
|
|
178
|
+
console.log(chalk.white(' 1. Listen for contracts: rentman listen'));
|
|
179
|
+
console.log(chalk.white(' 2. View task: rentman task view ' + response.data.task_id));
|
|
180
|
+
|
|
181
|
+
console.log('');
|
|
182
|
+
} catch (error) {
|
|
183
|
+
spinner.fail('Failed to post task');
|
|
184
|
+
|
|
185
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
186
|
+
|
|
187
|
+
if (error.code === 'AUTH_FAILED') {
|
|
188
|
+
console.log(chalk.yellow('ā Re-initialize: rentman init'));
|
|
189
|
+
} else if (error.code === 'VALIDATION_ERROR') {
|
|
190
|
+
console.log(chalk.yellow('ā Check task data format'));
|
|
191
|
+
if (error.details) {
|
|
192
|
+
console.log(chalk.gray(' ' + JSON.stringify(error.details, null, 2)));
|
|
193
|
+
}
|
|
194
|
+
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
|
|
195
|
+
console.log(chalk.yellow('ā Wait before retrying'));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = postMission;
|