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.
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ const updateNotifier = require('update-notifier');
3
+ const pkg = require('../package.json');
4
+
5
+ updateNotifier({ pkg }).notify();
6
+
7
+ require('../src/index.js');
@@ -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
+ }