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
|
+
/**
|
|
2
|
+
* Secure Configuration Module
|
|
3
|
+
* Uses Conf for persistent storage in user's home directory
|
|
4
|
+
* Priority: ENV > Conf > Default
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const Conf = require('conf');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
// Initialize Conf with secure defaults
|
|
12
|
+
const config = new Conf({
|
|
13
|
+
projectName: 'rentman',
|
|
14
|
+
cwd: path.join(os.homedir(), '.config'),
|
|
15
|
+
encryptionKey: process.env.RENTMAN_CONFIG_KEY, // Optional encryption
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get agent identity from secure storage
|
|
20
|
+
* Priority: Environment Variables > Conf Storage
|
|
21
|
+
*/
|
|
22
|
+
function getIdentity() {
|
|
23
|
+
// Check environment variables first (most secure for CI/CD)
|
|
24
|
+
if (process.env.RENTMAN_AGENT_ID && process.env.RENTMAN_SECRET_KEY) {
|
|
25
|
+
return {
|
|
26
|
+
agent_id: process.env.RENTMAN_AGENT_ID,
|
|
27
|
+
public_agent_id: process.env.RENTMAN_PUBLIC_AGENT_ID,
|
|
28
|
+
secret_key: process.env.RENTMAN_SECRET_KEY,
|
|
29
|
+
public_key: process.env.RENTMAN_PUBLIC_KEY,
|
|
30
|
+
source: 'environment'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback to Conf storage
|
|
35
|
+
const agent_id = config.get('agent_id');
|
|
36
|
+
const secret_key = config.get('secret_key');
|
|
37
|
+
|
|
38
|
+
if (!agent_id || !secret_key) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
agent_id,
|
|
44
|
+
public_agent_id: config.get('public_agent_id'),
|
|
45
|
+
secret_key,
|
|
46
|
+
public_key: config.get('public_key'),
|
|
47
|
+
source: 'config'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Save agent identity to secure storage
|
|
53
|
+
*/
|
|
54
|
+
function saveIdentity(identity) {
|
|
55
|
+
config.set('agent_id', identity.agent_id);
|
|
56
|
+
config.set('public_agent_id', identity.public_agent_id);
|
|
57
|
+
config.set('secret_key', identity.secret_key);
|
|
58
|
+
config.set('public_key', identity.public_key);
|
|
59
|
+
|
|
60
|
+
if (identity.owner_id) {
|
|
61
|
+
config.set('owner_id', identity.owner_id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Clear identity (logout)
|
|
67
|
+
*/
|
|
68
|
+
function clearIdentity() {
|
|
69
|
+
config.delete('agent_id');
|
|
70
|
+
config.delete('public_agent_id');
|
|
71
|
+
config.delete('secret_key');
|
|
72
|
+
config.delete('public_key');
|
|
73
|
+
config.delete('owner_id');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get configuration path
|
|
78
|
+
*/
|
|
79
|
+
function getConfigPath() {
|
|
80
|
+
return config.path;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get API configuration
|
|
85
|
+
*/
|
|
86
|
+
function getApiConfig() {
|
|
87
|
+
return {
|
|
88
|
+
gatewayUrl: process.env.AGENT_GATEWAY_URL || 'https://agent-gateway.rentman.app/v1',
|
|
89
|
+
supabaseUrl: process.env.SUPABASE_URL || 'https://uoekolfgbbmvhzsfkjef.supabase.co',
|
|
90
|
+
supabaseKey: process.env.SUPABASE_ANON_KEY,
|
|
91
|
+
logLevel: process.env.LOG_LEVEL || 'info',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get API key (if using API key auth instead of NACL)
|
|
97
|
+
*/
|
|
98
|
+
function getApiKey() {
|
|
99
|
+
return process.env.RENTMAN_API_KEY || config.get('api_key');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Save API key
|
|
104
|
+
*/
|
|
105
|
+
function saveApiKey(apiKey) {
|
|
106
|
+
config.set('api_key', apiKey);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
getIdentity,
|
|
111
|
+
saveIdentity,
|
|
112
|
+
clearIdentity,
|
|
113
|
+
getConfigPath,
|
|
114
|
+
getApiConfig,
|
|
115
|
+
getApiKey,
|
|
116
|
+
saveApiKey,
|
|
117
|
+
config, // Expose for custom settings
|
|
118
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* INTEGRATION TEST: CLI → Supabase
|
|
4
|
+
* Tests task creation without backend/webhook processing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
8
|
+
const nacl = require('tweetnacl');
|
|
9
|
+
const naclUtil = require('tweetnacl-util');
|
|
10
|
+
|
|
11
|
+
const SUPABASE_URL = 'https://uoekolfgbbmvhzsfkjef.supabase.co';
|
|
12
|
+
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVvZWtvbGZnYmJtdmh6c2ZramVmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAzMjQzNzUsImV4cCI6MjA4NTkwMDM3NX0.DYxAxi4TTBLgdVruu8uGM3Jog7JZaplWqikAvI0EXvk';
|
|
13
|
+
|
|
14
|
+
async function runIntegrationTest() {
|
|
15
|
+
console.log('\n🔗 INTEGRATION TEST: CLI → Supabase\n');
|
|
16
|
+
console.log('='.repeat(60));
|
|
17
|
+
|
|
18
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
19
|
+
let testsPassed = 0;
|
|
20
|
+
let testsFailed = 0;
|
|
21
|
+
|
|
22
|
+
// Generate test agent
|
|
23
|
+
const keypair = nacl.sign.keyPair();
|
|
24
|
+
const testAgentId = `test-agent-${Date.now()}`;
|
|
25
|
+
const publicKey = naclUtil.encodeBase64(keypair.publicKey);
|
|
26
|
+
const secretKey = keypair.secretKey;
|
|
27
|
+
|
|
28
|
+
console.log(`\n[1] Registering test agent: ${testAgentId}`);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const { data: agent, error } = await supabase
|
|
32
|
+
.from('agents')
|
|
33
|
+
.insert({
|
|
34
|
+
id: testAgentId,
|
|
35
|
+
public_key: publicKey,
|
|
36
|
+
email: `test-${Date.now()}@rentman.test`
|
|
37
|
+
})
|
|
38
|
+
.select()
|
|
39
|
+
.single();
|
|
40
|
+
|
|
41
|
+
if (error) throw error;
|
|
42
|
+
console.log(' ✅ Agent registered');
|
|
43
|
+
testsPassed++;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.log(' ❌ Failed:', err.message);
|
|
46
|
+
testsFailed++;
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create signed task
|
|
51
|
+
console.log('\n[2] Creating signed task');
|
|
52
|
+
|
|
53
|
+
const taskData = {
|
|
54
|
+
title: `Integration Test ${new Date().toISOString()}`,
|
|
55
|
+
description: 'Automated integration test',
|
|
56
|
+
task_type: 'verification',
|
|
57
|
+
budget_amount: 10,
|
|
58
|
+
agent_id: testAgentId,
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
nonce: Math.random().toString(36).substring(7)
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Sign
|
|
64
|
+
const message = `${taskData.title}:${taskData.agent_id}:${taskData.timestamp}:${taskData.nonce}`;
|
|
65
|
+
const signature = nacl.sign.detached(naclUtil.decodeUTF8(message), secretKey);
|
|
66
|
+
const signatureBase64 = naclUtil.encodeBase64(signature);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const { data: task, error } = await supabase
|
|
70
|
+
.from('tasks')
|
|
71
|
+
.insert({
|
|
72
|
+
title: taskData.title,
|
|
73
|
+
description: taskData.description,
|
|
74
|
+
task_type: taskData.task_type,
|
|
75
|
+
budget_amount: taskData.budget_amount,
|
|
76
|
+
agent_id: taskData.agent_id,
|
|
77
|
+
signature: signatureBase64,
|
|
78
|
+
metadata: taskData,
|
|
79
|
+
status: 'open'
|
|
80
|
+
})
|
|
81
|
+
.select()
|
|
82
|
+
.single();
|
|
83
|
+
|
|
84
|
+
if (error) throw error;
|
|
85
|
+
console.log(' ✅ Task created:', task.id);
|
|
86
|
+
testsPassed++;
|
|
87
|
+
|
|
88
|
+
// Verify task is readable
|
|
89
|
+
console.log('\n[3] Verifying task is readable');
|
|
90
|
+
|
|
91
|
+
const { data: readTask, error: readError } = await supabase
|
|
92
|
+
.from('tasks')
|
|
93
|
+
.select('*')
|
|
94
|
+
.eq('id', task.id)
|
|
95
|
+
.single();
|
|
96
|
+
|
|
97
|
+
if (readError) throw readError;
|
|
98
|
+
|
|
99
|
+
if (readTask.signature !== signatureBase64) {
|
|
100
|
+
throw new Error('Signature mismatch after read');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(' ✅ Task readable and signature intact');
|
|
104
|
+
testsPassed++;
|
|
105
|
+
|
|
106
|
+
// Cleanup
|
|
107
|
+
console.log('\n[4] Cleanup test data');
|
|
108
|
+
|
|
109
|
+
await supabase.from('tasks').delete().eq('id', task.id);
|
|
110
|
+
await supabase.from('agents').delete().eq('id', testAgentId);
|
|
111
|
+
|
|
112
|
+
console.log(' ✅ Cleanup complete');
|
|
113
|
+
testsPassed++;
|
|
114
|
+
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.log(' ❌ Failed:', err.message);
|
|
117
|
+
testsFailed++;
|
|
118
|
+
|
|
119
|
+
// Cleanup on failure
|
|
120
|
+
try {
|
|
121
|
+
await supabase.from('agents').delete().eq('id', testAgentId);
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('\n' + '='.repeat(60));
|
|
126
|
+
console.log(`📊 RESULTS: ${testsPassed} passed, ${testsFailed} failed`);
|
|
127
|
+
console.log('='.repeat(60) + '\n');
|
|
128
|
+
|
|
129
|
+
process.exit(testsFailed > 0 ? 1 : 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
runIntegrationTest().catch(err => {
|
|
133
|
+
console.error('💥 Unhandled error:', err);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Verificar funcionamiento V6 (Verbose Logging)",
|
|
3
|
+
"description": "Prueba de depuracion con logs detallados.",
|
|
4
|
+
"task_type": "verification",
|
|
5
|
+
"budget_amount": 75,
|
|
6
|
+
"location": {
|
|
7
|
+
"address": "Cloud Run Server V6",
|
|
8
|
+
"lat": 40.7128,
|
|
9
|
+
"lng": -74.0060
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Verificar funcionamiento V7 (Stderr Logging)",
|
|
3
|
+
"description": "Prueba de depuracion con logs de stderr.",
|
|
4
|
+
"task_type": "verification",
|
|
5
|
+
"budget_amount": 80,
|
|
6
|
+
"location": {
|
|
7
|
+
"address": "Cloud Run Server V7",
|
|
8
|
+
"lat": 40.7128,
|
|
9
|
+
"lng": -74.0060
|
|
10
|
+
}
|
|
11
|
+
}
|
package/test_task.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Test Delivery Mission",
|
|
3
|
+
"description": "Pick up a package from Office A and deliver it to Building B. Take photo of delivery confirmation.",
|
|
4
|
+
"task_type": "delivery",
|
|
5
|
+
"budget_amount": 25,
|
|
6
|
+
"location_address": "123 Main Street, Downtown",
|
|
7
|
+
"required_skills": [
|
|
8
|
+
"driving",
|
|
9
|
+
"photography"
|
|
10
|
+
]
|
|
11
|
+
}
|