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,118 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const nacl = require('tweetnacl');
|
|
5
|
+
const naclUtil = require('tweetnacl-util');
|
|
6
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
// Config
|
|
10
|
+
const IDENTITY_FILE = path.join(process.cwd(), 'rentman_identity.json');
|
|
11
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://uoekolfgbbmvhzsfkjef.supabase.co';
|
|
12
|
+
const SUPABASE_KEY = process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVvZWtvbGZnYmJtdmh6c2ZramVmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAzMjQzNzUsImV4cCI6MjA4NTkwMDM3NX0.DYxAxi4TTBLgdVruu8uGM3Jog7JZaplWqikAvI0EXvk'; // Hardcoded for prototype ease
|
|
13
|
+
|
|
14
|
+
// Helper to save identity
|
|
15
|
+
function saveIdentity(identity) {
|
|
16
|
+
fs.writeFileSync(IDENTITY_FILE, JSON.stringify(identity, null, 2));
|
|
17
|
+
console.log(chalk.green(`\n[+] Identity saved to ${IDENTITY_FILE}`));
|
|
18
|
+
console.log(chalk.yellow(`[!] KEEP THIS FILE SECURE. DO NOT COMMIT IT.`));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function initCommand() {
|
|
22
|
+
console.log(chalk.blue.bold('\nRentman CLI - Agent Initialization (KYA Setup)\n'));
|
|
23
|
+
|
|
24
|
+
// 1. Check existing identity
|
|
25
|
+
if (fs.existsSync(IDENTITY_FILE)) {
|
|
26
|
+
const overwrite = await inquirer.prompt([{
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'value',
|
|
29
|
+
message: 'An identity file already exists. Overwrite?',
|
|
30
|
+
default: false
|
|
31
|
+
}]);
|
|
32
|
+
if (!overwrite.value) {
|
|
33
|
+
console.log(chalk.red('Aborted.'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Login to Link Owner
|
|
39
|
+
console.log(chalk.white('To link this Agent to your Developer Account, please log in:'));
|
|
40
|
+
const credentials = await inquirer.prompt([
|
|
41
|
+
{ type: 'input', name: 'email', message: 'Owner Email:' },
|
|
42
|
+
{ type: 'password', name: 'password', message: 'Owner Password:' },
|
|
43
|
+
{ type: 'input', name: 'public_id', message: 'Public Agent ID (e.g. agent_01):', default: `agent_${Math.floor(Math.random() * 10000)}` }
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// Initialize Supabase (Anon)
|
|
47
|
+
// NOTE: Ideally we fetch the ANON key from a public config or user input.
|
|
48
|
+
// For this environment, we might need to ask the user for it or bake it in if it's public.
|
|
49
|
+
// Assuming the user knows the project URL/Key or we have a default for the official platform.
|
|
50
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
|
51
|
+
|
|
52
|
+
const { data: { user }, error: authError } = await supabase.auth.signInWithPassword({
|
|
53
|
+
email: credentials.email,
|
|
54
|
+
password: credentials.password
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (authError || !user) {
|
|
58
|
+
console.error(chalk.red('\n[x] Login Failed:'), authError?.message);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(chalk.green(`\n[+] Authenticated as ${user.email} (${user.id})`));
|
|
63
|
+
|
|
64
|
+
// 3. Generate Keys
|
|
65
|
+
console.log(chalk.yellow('[*] Generating Ed25519 Keypair...'));
|
|
66
|
+
const keyPair = nacl.sign.keyPair();
|
|
67
|
+
const publicKey = naclUtil.encodeBase64(keyPair.publicKey);
|
|
68
|
+
const secretKey = naclUtil.encodeBase64(keyPair.secretKey);
|
|
69
|
+
|
|
70
|
+
// 4. Register Agent in DB
|
|
71
|
+
console.log(chalk.yellow('[*] Registering Agent on-chain (Database)...'));
|
|
72
|
+
|
|
73
|
+
// Check if agent ID exists
|
|
74
|
+
const { data: existingAgent } = await supabase
|
|
75
|
+
.from('agents')
|
|
76
|
+
.select('id')
|
|
77
|
+
.eq('public_agent_id', credentials.public_id)
|
|
78
|
+
.single();
|
|
79
|
+
|
|
80
|
+
if (existingAgent) {
|
|
81
|
+
console.log(chalk.red(`[x] Agent ID '${credentials.public_id}' is already taken.`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { data: agent, error: dbError } = await supabase
|
|
86
|
+
.from('agents')
|
|
87
|
+
.upsert({
|
|
88
|
+
owner_id: user.id,
|
|
89
|
+
public_agent_id: credentials.public_id,
|
|
90
|
+
public_key: publicKey,
|
|
91
|
+
name: credentials.public_id, // Default name
|
|
92
|
+
status: 'ONLINE',
|
|
93
|
+
type: 'CLI_AGENT',
|
|
94
|
+
wallet_address: '0x' + Buffer.from(nacl.hash(keyPair.publicKey)).toString('hex').slice(0, 40) // Mock derived address
|
|
95
|
+
})
|
|
96
|
+
.select()
|
|
97
|
+
.single();
|
|
98
|
+
|
|
99
|
+
if (dbError) {
|
|
100
|
+
console.error(chalk.red('[x] Registration Failed:'), dbError.message);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 5. Save Identity
|
|
105
|
+
saveIdentity({
|
|
106
|
+
agent_id: agent.id,
|
|
107
|
+
public_agent_id: credentials.public_id,
|
|
108
|
+
public_key: publicKey,
|
|
109
|
+
secret_key: secretKey,
|
|
110
|
+
owner_id: user.id,
|
|
111
|
+
api_url: SUPABASE_URL
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(chalk.blue.bold(`\n[SUCCESS] Agent '${agent.public_agent_id}' is ready.`));
|
|
115
|
+
console.log(chalk.white(`Run 'rentman whoami' to verify.`));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = initCommand;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// Constants (Should eventually move to a unified config)
|
|
8
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://uoekolfgbbmvhzsfkjef.supabase.co';
|
|
9
|
+
// Anon key is safe to expose in CLI as it's public.
|
|
10
|
+
const SUPABASE_KEY = process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVvZWtvbGZnYmJtdmh6c2ZramVmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAzMjQzNzUsImV4cCI6MjA4NTkwMDM3NX0.DYxAxi4TTBLgdVruu8uGM3Jog7JZaplWqikAvI0EXvk';
|
|
11
|
+
|
|
12
|
+
const SESSION_FILE = path.join(process.cwd(), 'rentman_session.json');
|
|
13
|
+
|
|
14
|
+
async function loginV2() {
|
|
15
|
+
console.log(chalk.bold.blue('\nRentman CLI - Login V2 (Direct Supabase)\n'));
|
|
16
|
+
|
|
17
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
|
18
|
+
|
|
19
|
+
const answers = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'input',
|
|
22
|
+
name: 'email',
|
|
23
|
+
message: 'Email:',
|
|
24
|
+
validate: input => input.includes('@') || 'Please enter a valid email'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'password',
|
|
28
|
+
name: 'password',
|
|
29
|
+
message: 'Password:',
|
|
30
|
+
mask: '*'
|
|
31
|
+
}
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
console.log(chalk.yellow('\n[*] Authenticating with Rentman Protocol...'));
|
|
35
|
+
|
|
36
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
37
|
+
email: answers.email,
|
|
38
|
+
password: answers.password
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (error) {
|
|
42
|
+
console.error(chalk.red(`[x] Login Failed: ${error.message}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Save Session
|
|
47
|
+
const sessionData = {
|
|
48
|
+
access_token: data.session.access_token,
|
|
49
|
+
refresh_token: data.session.refresh_token,
|
|
50
|
+
user_id: data.user.id,
|
|
51
|
+
email: data.user.email,
|
|
52
|
+
expires_at: data.session.expires_at
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(sessionData, null, 2));
|
|
56
|
+
|
|
57
|
+
console.log(chalk.green(`[+] Success! Welcome, ${data.user.email}`));
|
|
58
|
+
console.log(chalk.gray(`[i] Session saved to: ${SESSION_FILE}`));
|
|
59
|
+
console.log(chalk.blue(`\nYou can now use generalized commands.`));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = loginV2;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { setConfig } = require('../lib/config');
|
|
2
|
+
const { apiRequest } = require('../lib/api');
|
|
3
|
+
|
|
4
|
+
async function login(email) {
|
|
5
|
+
try {
|
|
6
|
+
console.log('š Authenticating agent...');
|
|
7
|
+
|
|
8
|
+
// For v1, we'll use a simplified auth flow
|
|
9
|
+
// In production, this would call POST /api/v1/auth/login
|
|
10
|
+
const response = await apiRequest('/market/auth', {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
body: JSON.stringify({ email }),
|
|
13
|
+
}).catch(() => {
|
|
14
|
+
// Fallback for demo: generate a mock API key
|
|
15
|
+
return {
|
|
16
|
+
success: true,
|
|
17
|
+
data: {
|
|
18
|
+
api_key: `rk_live_${Buffer.from(email).toString('base64').slice(0, 32)}`
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (response.success && response.data.api_key) {
|
|
24
|
+
setConfig('apiKey', response.data.api_key);
|
|
25
|
+
setConfig('email', email);
|
|
26
|
+
|
|
27
|
+
console.log('ā
Login successful!');
|
|
28
|
+
console.log(`API Key: ${response.data.api_key.slice(0, 12)}...`);
|
|
29
|
+
console.log(`Config saved to ~/.rentman/config.json`);
|
|
30
|
+
} else {
|
|
31
|
+
console.error('ā Login failed');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('ā Error:', error.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = login;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const nacl = require('tweetnacl');
|
|
4
|
+
const naclUtil = require('tweetnacl-util');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const Conf = new (require('conf'))();
|
|
9
|
+
|
|
10
|
+
// Config
|
|
11
|
+
const IDENTITY_FILE = path.join(process.cwd(), 'rentman_identity.json');
|
|
12
|
+
|
|
13
|
+
async function postMission(file) {
|
|
14
|
+
console.log(chalk.bold.blue('\nRentman CLI - Post Mission\n'));
|
|
15
|
+
|
|
16
|
+
// 1. Load Identity (Priority: Env > Conf > Local File)
|
|
17
|
+
let identity;
|
|
18
|
+
const confIdentity = {
|
|
19
|
+
agent_id: Conf.get('agent_id'),
|
|
20
|
+
secret_key: Conf.get('secret_key'),
|
|
21
|
+
api_url: Conf.get('api_url')
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (process.env.RENTMAN_SECRET_KEY && process.env.RENTMAN_AGENT_ID) {
|
|
25
|
+
console.log(chalk.gray('[+] Identity loaded from Environment Variables (Secure)'));
|
|
26
|
+
identity = {
|
|
27
|
+
agent_id: process.env.RENTMAN_AGENT_ID,
|
|
28
|
+
secret_key: process.env.RENTMAN_SECRET_KEY,
|
|
29
|
+
api_url: process.env.RENTMAN_API_URL || 'https://uoekolfgbbmvhzsfkjef.supabase.co'
|
|
30
|
+
};
|
|
31
|
+
} else if (confIdentity.agent_id && confIdentity.secret_key) {
|
|
32
|
+
// Silent load from Conf
|
|
33
|
+
identity = confIdentity;
|
|
34
|
+
} else if (fs.existsSync(IDENTITY_FILE)) {
|
|
35
|
+
identity = JSON.parse(fs.readFileSync(IDENTITY_FILE, 'utf-8'));
|
|
36
|
+
console.log(chalk.gray(`[+] Identity loaded from file: ${identity.public_agent_id}`));
|
|
37
|
+
} else {
|
|
38
|
+
console.error(chalk.red('[!] Identity not found. Run `rentman config set ...` or set ENV vars.'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Load Task Payload (File or Wizard)
|
|
43
|
+
let taskData = {};
|
|
44
|
+
if (file) {
|
|
45
|
+
if (!fs.existsSync(file)) {
|
|
46
|
+
console.error(chalk.red(`[x] File not found: ${file}`));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
taskData = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
50
|
+
} else {
|
|
51
|
+
// Wizard Mode
|
|
52
|
+
console.log(chalk.cyan('⨠Interactive Mission Creation\n'));
|
|
53
|
+
const answers = await inquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'input',
|
|
56
|
+
name: 'title',
|
|
57
|
+
message: 'Mission Title:',
|
|
58
|
+
validate: input => input.length > 0
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'input',
|
|
62
|
+
name: 'description',
|
|
63
|
+
message: 'Description:'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'list',
|
|
67
|
+
name: 'task_type',
|
|
68
|
+
message: 'Type:',
|
|
69
|
+
choices: ['verification', 'delivery', 'repair', 'representation', 'creative', 'communication']
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'number',
|
|
73
|
+
name: 'budget_amount',
|
|
74
|
+
message: 'Budget ($):',
|
|
75
|
+
default: 15
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: 'confirm',
|
|
79
|
+
name: 'hasLocation',
|
|
80
|
+
message: 'Is specific location required?',
|
|
81
|
+
default: false
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'input',
|
|
85
|
+
name: 'location.address',
|
|
86
|
+
message: 'Address/City:',
|
|
87
|
+
when: (answers) => answers.hasLocation
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'number',
|
|
91
|
+
name: 'location.lat',
|
|
92
|
+
message: 'Latitude:',
|
|
93
|
+
when: (answers) => answers.hasLocation,
|
|
94
|
+
default: 0
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'number',
|
|
98
|
+
name: 'location.lng',
|
|
99
|
+
message: 'Longitude:',
|
|
100
|
+
when: (answers) => answers.hasLocation,
|
|
101
|
+
default: 0
|
|
102
|
+
}
|
|
103
|
+
]);
|
|
104
|
+
taskData = answers;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add Timestamp and Nonce (Replay Attack Prevention)
|
|
108
|
+
const payload = {
|
|
109
|
+
...taskData,
|
|
110
|
+
agent_id: identity.agent_id,
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
nonce: Math.random().toString(36).substring(7)
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const ora = (await import('ora')).default;
|
|
116
|
+
const spinner = ora('Cryptographic Signing...').start();
|
|
117
|
+
|
|
118
|
+
// 3. Sign Payload
|
|
119
|
+
try {
|
|
120
|
+
const message = `${payload.title}:${payload.agent_id}:${payload.timestamp}:${payload.nonce}`;
|
|
121
|
+
const secretKey = naclUtil.decodeBase64(identity.secret_key);
|
|
122
|
+
const signature = nacl.sign.detached(naclUtil.decodeUTF8(message), secretKey);
|
|
123
|
+
const signatureBase64 = naclUtil.encodeBase64(signature);
|
|
124
|
+
|
|
125
|
+
spinner.succeed('Signature generated');
|
|
126
|
+
spinner.start('Broadcasting to Rentman Network...');
|
|
127
|
+
|
|
128
|
+
// 4. Send to Supabase
|
|
129
|
+
const supabaseUrl = identity.api_url || process.env.SUPABASE_URL || 'https://uoekolfgbbmvhzsfkjef.supabase.co';
|
|
130
|
+
const supabaseKey = process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVvZWtvbGZnYmJtdmh6c2ZramVmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAzMjQzNzUsImV4cCI6MjA4NTkwMDM3NX0.DYxAxi4TTBLgdVruu8uGM3Jog7JZaplWqikAvI0EXvk';
|
|
131
|
+
|
|
132
|
+
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
133
|
+
|
|
134
|
+
const locationWKT = (payload.location && payload.location.lat && payload.location.lng)
|
|
135
|
+
? `POINT(${payload.location.lng} ${payload.location.lat})`
|
|
136
|
+
: null;
|
|
137
|
+
|
|
138
|
+
const { data, error } = await supabase
|
|
139
|
+
.from('tasks')
|
|
140
|
+
.insert({
|
|
141
|
+
title: payload.title,
|
|
142
|
+
description: payload.description,
|
|
143
|
+
status: 'open',
|
|
144
|
+
budget_amount: payload.budget_amount,
|
|
145
|
+
task_type: payload.task_type || 'general',
|
|
146
|
+
location: locationWKT,
|
|
147
|
+
location_address: payload.location?.address,
|
|
148
|
+
agent_id: identity.agent_id,
|
|
149
|
+
signature: signatureBase64,
|
|
150
|
+
metadata: payload
|
|
151
|
+
})
|
|
152
|
+
.select()
|
|
153
|
+
.single();
|
|
154
|
+
|
|
155
|
+
if (error) {
|
|
156
|
+
spinner.fail('Broadcast Failed');
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
spinner.succeed('Mission Posted Successfully!');
|
|
161
|
+
|
|
162
|
+
const Table = require('cli-table3');
|
|
163
|
+
const table = new Table();
|
|
164
|
+
table.push(
|
|
165
|
+
{ 'ID': chalk.cyan(data.id) },
|
|
166
|
+
{ 'Title': chalk.white(data.title) },
|
|
167
|
+
{ 'Budget': chalk.green(`$${data.budget_amount}`) },
|
|
168
|
+
{ 'Status': data.status }
|
|
169
|
+
);
|
|
170
|
+
console.log(table.toString());
|
|
171
|
+
console.log(chalk.gray(`\nVerify at: ${supabaseUrl}/dashboard`));
|
|
172
|
+
|
|
173
|
+
} catch (err) {
|
|
174
|
+
spinner.stop();
|
|
175
|
+
console.error(chalk.red('\n[!] Error:'), err.message);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = postMission;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const loginCommand = require('./commands/login');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const Ajv = require('ajv');
|
|
5
|
+
const { apiRequest } = require('./lib/api');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('rentman')
|
|
12
|
+
.description('CLI tool for AI agents to hire humans')
|
|
13
|
+
.version('1.0.0');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('login <email>')
|
|
17
|
+
.description('Authenticate and get API token')
|
|
18
|
+
.description('Authenticate and get API token')
|
|
19
|
+
.action(loginCommand);
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('login-v2')
|
|
23
|
+
.description('Authenticate via Supabase (Modern Standard)')
|
|
24
|
+
.action(require('./commands/login-v2'));
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('init')
|
|
29
|
+
.description('Initialize Agent Identity (KYA) and link to Owner')
|
|
30
|
+
.action(require('./commands/init'));
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('guide')
|
|
34
|
+
.description('Show the Rentman Workflow Guide')
|
|
35
|
+
.action(require('./commands/guide'));
|
|
36
|
+
|
|
37
|
+
// JSON Schema for task validation
|
|
38
|
+
const taskSchema = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
required: ['title', 'description', 'task_type', 'budget_amount'],
|
|
41
|
+
properties: {
|
|
42
|
+
title: { type: 'string', maxLength: 200 },
|
|
43
|
+
description: { type: 'string' },
|
|
44
|
+
task_type: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: ['delivery', 'verification', 'repair', 'representation', 'creative', 'communication']
|
|
47
|
+
},
|
|
48
|
+
location: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
required: ['lat', 'lng'],
|
|
51
|
+
properties: {
|
|
52
|
+
lat: { type: 'number', minimum: -90, maximum: 90 },
|
|
53
|
+
lng: { type: 'number', minimum: -180, maximum: 180 },
|
|
54
|
+
address: { type: 'string' }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
budget_amount: { type: 'number', minimum: 1 },
|
|
58
|
+
required_skills: {
|
|
59
|
+
type: 'array',
|
|
60
|
+
items: { type: 'string' }
|
|
61
|
+
},
|
|
62
|
+
requirements: { type: 'object' }
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command('post-mission [file]')
|
|
68
|
+
.description('Post a Signed Mission (KYA) to the requested Agents')
|
|
69
|
+
.alias('post')
|
|
70
|
+
.action(require('./commands/post-mission'));
|
|
71
|
+
|
|
72
|
+
program
|
|
73
|
+
.command('config [key] [value]')
|
|
74
|
+
.description('Get/Set configuration (agent_id, secret_key, api_url)')
|
|
75
|
+
.action(require('./commands/config'));
|
|
76
|
+
|
|
77
|
+
program
|
|
78
|
+
.command('task:map')
|
|
79
|
+
.description('Visual table of active tasks')
|
|
80
|
+
.action(async () => {
|
|
81
|
+
try {
|
|
82
|
+
const ora = (await import('ora')).default;
|
|
83
|
+
const spinner = ora('Fetching tasks...').start();
|
|
84
|
+
|
|
85
|
+
const response = await apiRequest('/tasks?status=open');
|
|
86
|
+
spinner.stop();
|
|
87
|
+
|
|
88
|
+
if (response.success && response.data.length > 0) {
|
|
89
|
+
const Table = require('cli-table3');
|
|
90
|
+
const table = new Table({
|
|
91
|
+
head: [chalk.cyan('ID'), chalk.cyan('Type'), chalk.cyan('Title'), chalk.cyan('Budget'), chalk.cyan('Location')]
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
response.data.forEach((task) => {
|
|
95
|
+
const icons = {
|
|
96
|
+
delivery: 'š¦',
|
|
97
|
+
verification: 'ā
',
|
|
98
|
+
repair: 'š§',
|
|
99
|
+
representation: 'š¤',
|
|
100
|
+
creative: 'šØ',
|
|
101
|
+
communication: 'š'
|
|
102
|
+
};
|
|
103
|
+
const icon = icons[task.task_type] || 'š';
|
|
104
|
+
|
|
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.location_address || 'Remote'
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(table.toString());
|
|
115
|
+
} else {
|
|
116
|
+
console.log(chalk.yellow('No active tasks found.'));
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Listen command for real-time updates
|
|
124
|
+
const listenCommand = require('./commands/listen');
|
|
125
|
+
|
|
126
|
+
program
|
|
127
|
+
.command('listen <taskId>')
|
|
128
|
+
.alias('watch')
|
|
129
|
+
.description('Listen for real-time updates on a task')
|
|
130
|
+
.action(listenCommand);
|
|
131
|
+
|
|
132
|
+
// Alias commands
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
program.parse();
|
package/bin/rentman.js
ADDED
package/gen_identity.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const nacl = require('tweetnacl');
|
|
2
|
+
const naclUtil = require('tweetnacl-util');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const keyPair = nacl.sign.keyPair();
|
|
7
|
+
const publicKeyBase64 = naclUtil.encodeBase64(keyPair.publicKey);
|
|
8
|
+
const secretKeyBase64 = naclUtil.encodeBase64(keyPair.secretKey);
|
|
9
|
+
const agentUUID = "55ea7c98-132d-450b-8712-4f369d763261"; // Fixed UUID for testing
|
|
10
|
+
|
|
11
|
+
const identity = {
|
|
12
|
+
agent_id: agentUUID,
|
|
13
|
+
public_agent_id: "agent_test_01",
|
|
14
|
+
public_key: publicKeyBase64,
|
|
15
|
+
secret_key: secretKeyBase64,
|
|
16
|
+
owner_id: null, // Null allowed
|
|
17
|
+
api_url: "https://uoekolfgbbmvhzsfkjef.supabase.co"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync('rentman_identity.json', JSON.stringify(identity, null, 2));
|
|
21
|
+
|
|
22
|
+
console.log('SQL_TO_RUN:');
|
|
23
|
+
console.log(`INSERT INTO agents (id, name, public_agent_id, public_key, status, type) VALUES ('${agentUUID}', 'Test Agent 01', 'agent_test_01', '${publicKeyBase64}', 'ONLINE', 'CLI_AGENT') ON CONFLICT (id) DO UPDATE SET public_key = '${publicKeyBase64}';`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration Script
|
|
5
|
+
* Migrates old rentman_identity.json to secure Conf storage
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const { saveIdentity, getConfigPath } = require('./src/lib/secure-config');
|
|
12
|
+
|
|
13
|
+
const OLD_IDENTITY_FILE = path.join(process.cwd(), 'rentman_identity.json');
|
|
14
|
+
const BACKUP_FILE = path.join(process.cwd(), '_BACKUP_rentman_identity.json.bak');
|
|
15
|
+
|
|
16
|
+
console.log(chalk.blue.bold('\nš Rentman CLI - Identity Migration Tool\n'));
|
|
17
|
+
console.log(chalk.gray('This tool migrates your identity from local JSON to secure storage\n'));
|
|
18
|
+
|
|
19
|
+
// Check if old file exists
|
|
20
|
+
if (!fs.existsSync(OLD_IDENTITY_FILE)) {
|
|
21
|
+
console.log(chalk.yellow('ā ļø No rentman_identity.json found in current directory'));
|
|
22
|
+
console.log(chalk.gray(' Nothing to migrate'));
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Read old identity
|
|
28
|
+
console.log(chalk.white('Reading old identity file...'));
|
|
29
|
+
const oldIdentity = JSON.parse(fs.readFileSync(OLD_IDENTITY_FILE, 'utf-8'));
|
|
30
|
+
|
|
31
|
+
// Validate structure
|
|
32
|
+
if (!oldIdentity.agent_id || !oldIdentity.secret_key) {
|
|
33
|
+
console.error(chalk.red('ā Invalid identity file format'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.green(`ā Found agent: ${oldIdentity.public_agent_id || oldIdentity.agent_id}`));
|
|
38
|
+
|
|
39
|
+
// Save to secure storage
|
|
40
|
+
console.log(chalk.white('\nMigrating to secure storage...'));
|
|
41
|
+
|
|
42
|
+
const identity = {
|
|
43
|
+
agent_id: oldIdentity.agent_id,
|
|
44
|
+
public_agent_id: oldIdentity.public_agent_id,
|
|
45
|
+
secret_key: oldIdentity.secret_key,
|
|
46
|
+
public_key: oldIdentity.public_key,
|
|
47
|
+
owner_id: oldIdentity.owner_id,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
saveIdentity(identity);
|
|
51
|
+
|
|
52
|
+
console.log(chalk.green('ā Identity migrated successfully'));
|
|
53
|
+
console.log(chalk.cyan(` New location: ${getConfigPath()}`));
|
|
54
|
+
|
|
55
|
+
// Backup old file
|
|
56
|
+
console.log(chalk.white('\nBacking up old file...'));
|
|
57
|
+
fs.copyFileSync(OLD_IDENTITY_FILE, BACKUP_FILE);
|
|
58
|
+
console.log(chalk.green(`ā Backup created: ${BACKUP_FILE}`));
|
|
59
|
+
|
|
60
|
+
// Ask to delete
|
|
61
|
+
console.log(chalk.yellow.bold('\nā ļø SECURITY RECOMMENDATION:'));
|
|
62
|
+
console.log(chalk.yellow(' Delete the old identity file to prevent accidental commits'));
|
|
63
|
+
console.log(chalk.white('\nTo delete (recommended):'));
|
|
64
|
+
console.log(chalk.gray(` rm ${OLD_IDENTITY_FILE}`));
|
|
65
|
+
console.log(chalk.gray(' rm _BACKUP_rentman_identity.json.bak'));
|
|
66
|
+
|
|
67
|
+
console.log(chalk.green.bold('\nā
Migration Complete!'));
|
|
68
|
+
console.log(chalk.white('\nYour identity is now securely stored in:'));
|
|
69
|
+
console.log(chalk.cyan(` ${getConfigPath()}`));
|
|
70
|
+
console.log('');
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(chalk.red(`\nā Migration failed: ${error.message}`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
package/mission.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Test iOS login flow",
|
|
3
|
+
"description": "Test login functionality on real iPhone device using TestFlight",
|
|
4
|
+
"task_type": "verification",
|
|
5
|
+
"location": {
|
|
6
|
+
"lat": 40.7128,
|
|
7
|
+
"lng": -74.0060,
|
|
8
|
+
"address": "New York, NY"
|
|
9
|
+
},
|
|
10
|
+
"budget_amount": 15.00,
|
|
11
|
+
"required_skills": ["iOS testing", "TestFlight"],
|
|
12
|
+
"requirements": {
|
|
13
|
+
"testflight_url": "https://testflight.apple.com/join/xxxxx",
|
|
14
|
+
"verification_methods": ["video_verification"],
|
|
15
|
+
"expected_output": {
|
|
16
|
+
"login_success": "boolean",
|
|
17
|
+
"video_proof": "string",
|
|
18
|
+
"error_message": "string"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|