securegate-cli-tool 2.1.3 → 2.1.5
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/index.js +351 -351
- package/package.json +35 -35
- package/scripts/postinstall.js +37 -37
- package/src/api.js +213 -213
- package/src/commands/connect.js +102 -102
- package/src/commands/keys.js +281 -280
- package/src/commands/login.js +189 -190
- package/src/commands/providers.js +51 -51
- package/src/commands/status.js +78 -78
- package/src/config.js +85 -85
- package/src/index.js +165 -165
- package/templates/SKILL.md +59 -59
package/index.js
CHANGED
|
@@ -1,351 +1,351 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SecureGate CLI - Setup and manage security keys
|
|
5
|
-
* Run: npx securegate setup
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const https = require('https');
|
|
9
|
-
const http = require('http');
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
const os = require('os');
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const readline = require('readline');
|
|
15
|
-
|
|
16
|
-
// Configuration
|
|
17
|
-
const SUPABASE_URL = 'https://pbrmsfoowrjqsikgkijb.supabase.co';
|
|
18
|
-
const CONFIG_DIR = path.join(os.homedir(), '.securegate');
|
|
19
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
20
|
-
|
|
21
|
-
// ANSI colors
|
|
22
|
-
const colors = {
|
|
23
|
-
reset: '\x1b[0m',
|
|
24
|
-
green: '\x1b[32m',
|
|
25
|
-
yellow: '\x1b[33m',
|
|
26
|
-
cyan: '\x1b[36m',
|
|
27
|
-
red: '\x1b[31m',
|
|
28
|
-
bold: '\x1b[1m',
|
|
29
|
-
dim: '\x1b[2m',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function log(msg, color = '') {
|
|
33
|
-
console.log(`${color}${msg}${colors.reset}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function banner() {
|
|
37
|
-
console.log(`
|
|
38
|
-
${colors.cyan}${colors.bold}🔐 SecureGate Setup${colors.reset}
|
|
39
|
-
${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
|
|
40
|
-
`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Get device fingerprint (machine-based)
|
|
44
|
-
function getDeviceFingerprint() {
|
|
45
|
-
const cpus = os.cpus();
|
|
46
|
-
const networkInterfaces = os.networkInterfaces();
|
|
47
|
-
|
|
48
|
-
// Get first MAC address
|
|
49
|
-
let mac = '';
|
|
50
|
-
for (const [name, interfaces] of Object.entries(networkInterfaces)) {
|
|
51
|
-
for (const iface of interfaces) {
|
|
52
|
-
if (iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
|
53
|
-
mac = iface.mac;
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (mac) break;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const fingerprint = [
|
|
61
|
-
os.hostname(),
|
|
62
|
-
os.platform(),
|
|
63
|
-
os.arch(),
|
|
64
|
-
cpus[0]?.model || 'unknown',
|
|
65
|
-
mac,
|
|
66
|
-
os.totalmem().toString(),
|
|
67
|
-
].join('|');
|
|
68
|
-
|
|
69
|
-
return crypto.createHash('sha256').update(fingerprint).digest('hex').substring(0, 32);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Get public IP
|
|
73
|
-
async function getPublicIP() {
|
|
74
|
-
return new Promise((resolve) => {
|
|
75
|
-
https.get('https://api.ipify.org?format=json', (res) => {
|
|
76
|
-
let data = '';
|
|
77
|
-
res.on('data', chunk => data += chunk);
|
|
78
|
-
res.on('end', () => {
|
|
79
|
-
try {
|
|
80
|
-
resolve(JSON.parse(data).ip);
|
|
81
|
-
} catch {
|
|
82
|
-
resolve('unknown');
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}).on('error', () => resolve('unknown'));
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Get geolocation
|
|
90
|
-
async function getGeoLocation(ip) {
|
|
91
|
-
return new Promise((resolve) => {
|
|
92
|
-
http.get(`http://ip-api.com/json/${ip}?fields=status,country,countryCode,city`, (res) => {
|
|
93
|
-
let data = '';
|
|
94
|
-
res.on('data', chunk => data += chunk);
|
|
95
|
-
res.on('end', () => {
|
|
96
|
-
try {
|
|
97
|
-
const result = JSON.parse(data);
|
|
98
|
-
if (result.status === 'success') {
|
|
99
|
-
resolve({ country: result.country, code: result.countryCode, city: result.city });
|
|
100
|
-
} else {
|
|
101
|
-
resolve(null);
|
|
102
|
-
}
|
|
103
|
-
} catch {
|
|
104
|
-
resolve(null);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}).on('error', () => resolve(null));
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Make HTTP request
|
|
112
|
-
function httpRequest(url, options = {}) {
|
|
113
|
-
return new Promise((resolve, reject) => {
|
|
114
|
-
const urlObj = new URL(url);
|
|
115
|
-
const isHttps = urlObj.protocol === 'https:';
|
|
116
|
-
const lib = isHttps ? https : http;
|
|
117
|
-
|
|
118
|
-
const req = lib.request(url, {
|
|
119
|
-
method: options.method || 'GET',
|
|
120
|
-
headers: options.headers || {},
|
|
121
|
-
}, (res) => {
|
|
122
|
-
let data = '';
|
|
123
|
-
res.on('data', chunk => data += chunk);
|
|
124
|
-
res.on('end', () => {
|
|
125
|
-
try {
|
|
126
|
-
resolve({ status: res.statusCode, data: JSON.parse(data) });
|
|
127
|
-
} catch {
|
|
128
|
-
resolve({ status: res.statusCode, data });
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
req.on('error', reject);
|
|
134
|
-
|
|
135
|
-
if (options.body) {
|
|
136
|
-
req.write(options.body);
|
|
137
|
-
}
|
|
138
|
-
req.end();
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Create readline interface
|
|
143
|
-
function createPrompt() {
|
|
144
|
-
return readline.createInterface({
|
|
145
|
-
input: process.stdin,
|
|
146
|
-
output: process.stdout,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Ask question
|
|
151
|
-
function ask(rl, question) {
|
|
152
|
-
return new Promise((resolve) => {
|
|
153
|
-
rl.question(question, (answer) => {
|
|
154
|
-
resolve(answer.trim());
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Load config
|
|
160
|
-
function loadConfig() {
|
|
161
|
-
try {
|
|
162
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
163
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
164
|
-
}
|
|
165
|
-
} catch { }
|
|
166
|
-
return {};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Save config
|
|
170
|
-
function saveConfig(config) {
|
|
171
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
172
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
173
|
-
}
|
|
174
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Main setup flow
|
|
178
|
-
async function setup() {
|
|
179
|
-
banner();
|
|
180
|
-
|
|
181
|
-
const rl = createPrompt();
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
// Step 1: Get JWT token
|
|
185
|
-
log('1. Authentication', colors.bold);
|
|
186
|
-
log(' Enter your Supabase JWT token (from browser after login):', colors.dim);
|
|
187
|
-
const token = await ask(rl, ' Token: ');
|
|
188
|
-
|
|
189
|
-
if (!token) {
|
|
190
|
-
log(' ❌ Token required', colors.red);
|
|
191
|
-
process.exit(1);
|
|
192
|
-
}
|
|
193
|
-
log(' ✓ Token received', colors.green);
|
|
194
|
-
|
|
195
|
-
// Step 2: Get connection ID
|
|
196
|
-
log('\n2. Connection', colors.bold);
|
|
197
|
-
log(' Enter your connection_id (e.g., open_abc123):', colors.dim);
|
|
198
|
-
const connectionId = await ask(rl, ' Connection ID: ');
|
|
199
|
-
|
|
200
|
-
if (!connectionId) {
|
|
201
|
-
log(' ❌ Connection ID required', colors.red);
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
log(` ✓ Connection: ${connectionId}`, colors.green);
|
|
205
|
-
|
|
206
|
-
// Step 3: Detect environment
|
|
207
|
-
log('\n3. Detecting environment...', colors.bold);
|
|
208
|
-
|
|
209
|
-
const ip = await getPublicIP();
|
|
210
|
-
log(` ✓ IP: ${ip}`, colors.green);
|
|
211
|
-
|
|
212
|
-
const geo = await getGeoLocation(ip);
|
|
213
|
-
if (geo) {
|
|
214
|
-
log(` ✓ Location: ${geo.city}, ${geo.country} (${geo.code})`, colors.green);
|
|
215
|
-
} else {
|
|
216
|
-
log(' ⚠ Could not detect location', colors.yellow);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const fingerprint = getDeviceFingerprint();
|
|
220
|
-
log(` ✓ Device: ${os.hostname()} (${os.platform()})`, colors.green);
|
|
221
|
-
log(` ✓ Fingerprint: ${fingerprint.substring(0, 8)}...`, colors.dim);
|
|
222
|
-
|
|
223
|
-
// Step 4: Activate key
|
|
224
|
-
log('\n4. Generating security key...', colors.bold);
|
|
225
|
-
|
|
226
|
-
const response = await httpRequest(`${SUPABASE_URL}/functions/v1/activate-key`, {
|
|
227
|
-
method: 'POST',
|
|
228
|
-
headers: {
|
|
229
|
-
'Authorization': `Bearer ${token}`,
|
|
230
|
-
'Content-Type': 'application/json',
|
|
231
|
-
},
|
|
232
|
-
body: JSON.stringify({
|
|
233
|
-
connection_id: connectionId,
|
|
234
|
-
device_fingerprint: fingerprint,
|
|
235
|
-
label: `${os.hostname()} (${os.platform()})`,
|
|
236
|
-
}),
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
if (response.status !== 201) {
|
|
240
|
-
log(` ❌ Failed: ${response.data.error || 'Unknown error'}`, colors.red);
|
|
241
|
-
process.exit(1);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const { security_key, bound_to, provider, usage } = response.data;
|
|
245
|
-
|
|
246
|
-
log(' ✓ Key activated!', colors.green);
|
|
247
|
-
|
|
248
|
-
// Save to config
|
|
249
|
-
const config = loadConfig();
|
|
250
|
-
config[connectionId] = {
|
|
251
|
-
security_key,
|
|
252
|
-
provider,
|
|
253
|
-
bound_to,
|
|
254
|
-
created_at: new Date().toISOString(),
|
|
255
|
-
};
|
|
256
|
-
saveConfig(config);
|
|
257
|
-
|
|
258
|
-
// Display result
|
|
259
|
-
console.log(`
|
|
260
|
-
${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
|
|
261
|
-
|
|
262
|
-
${colors.bold}${colors.green}✓ Security Key Generated!${colors.reset}
|
|
263
|
-
|
|
264
|
-
${colors.yellow}⚠ SAVE THIS KEY - IT WILL NOT BE SHOWN AGAIN!${colors.reset}
|
|
265
|
-
|
|
266
|
-
${colors.cyan}Security Key:${colors.reset}
|
|
267
|
-
${colors.bold}${security_key}${colors.reset}
|
|
268
|
-
|
|
269
|
-
${colors.cyan}Bound to:${colors.reset}
|
|
270
|
-
• IP: ${bound_to.ip}
|
|
271
|
-
• Country: ${bound_to.country}
|
|
272
|
-
• Device: ${bound_to.device}
|
|
273
|
-
|
|
274
|
-
${colors.cyan}Usage:${colors.reset}
|
|
275
|
-
${colors.dim}const openai = new OpenAI({
|
|
276
|
-
apiKey: '${security_key}',
|
|
277
|
-
baseURL: '${usage.baseURL}'
|
|
278
|
-
});${colors.reset}
|
|
279
|
-
|
|
280
|
-
${colors.dim}Config saved to: ${CONFIG_FILE}${colors.reset}
|
|
281
|
-
|
|
282
|
-
${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
|
|
283
|
-
`);
|
|
284
|
-
|
|
285
|
-
} finally {
|
|
286
|
-
rl.close();
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// List configured keys
|
|
291
|
-
async function list() {
|
|
292
|
-
banner();
|
|
293
|
-
|
|
294
|
-
const config = loadConfig();
|
|
295
|
-
const keys = Object.entries(config);
|
|
296
|
-
|
|
297
|
-
if (keys.length === 0) {
|
|
298
|
-
log('No security keys configured yet.', colors.dim);
|
|
299
|
-
log('Run: npx securegate setup', colors.cyan);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
log('Configured Security Keys:', colors.bold);
|
|
304
|
-
console.log();
|
|
305
|
-
|
|
306
|
-
for (const [connectionId, data] of keys) {
|
|
307
|
-
const keyPreview = data.security_key.substring(0, 12) + '...';
|
|
308
|
-
log(` ${connectionId}`, colors.cyan);
|
|
309
|
-
log(` Key: ${keyPreview}`, colors.dim);
|
|
310
|
-
log(` Provider: ${data.provider}`, colors.dim);
|
|
311
|
-
log(` IP: ${data.bound_to?.ip || 'unknown'}`, colors.dim);
|
|
312
|
-
log(` Created: ${data.created_at}`, colors.dim);
|
|
313
|
-
console.log();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Show usage instructions
|
|
318
|
-
function usage() {
|
|
319
|
-
console.log(`
|
|
320
|
-
${colors.cyan}${colors.bold}SecureGate CLI${colors.reset}
|
|
321
|
-
|
|
322
|
-
${colors.bold}Usage:${colors.reset}
|
|
323
|
-
npx securegate setup Generate a new security key
|
|
324
|
-
npx securegate list List configured keys
|
|
325
|
-
npx securegate help Show this help
|
|
326
|
-
|
|
327
|
-
${colors.bold}Example:${colors.reset}
|
|
328
|
-
1. First, create a connection in the SecureGate dashboard
|
|
329
|
-
2. Run: npx securegate setup
|
|
330
|
-
3. Enter your JWT token and connection ID
|
|
331
|
-
4. Use the generated key with OpenAI SDK
|
|
332
|
-
`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Main
|
|
336
|
-
const command = process.argv[2] || 'help';
|
|
337
|
-
|
|
338
|
-
switch (command) {
|
|
339
|
-
case 'setup':
|
|
340
|
-
setup().catch(err => {
|
|
341
|
-
log(`Error: ${err.message}`, colors.red);
|
|
342
|
-
process.exit(1);
|
|
343
|
-
});
|
|
344
|
-
break;
|
|
345
|
-
case 'list':
|
|
346
|
-
list();
|
|
347
|
-
break;
|
|
348
|
-
case 'help':
|
|
349
|
-
default:
|
|
350
|
-
usage();
|
|
351
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SecureGate CLI - Setup and manage security keys
|
|
5
|
+
* Run: npx securegate setup
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const readline = require('readline');
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
const SUPABASE_URL = 'https://pbrmsfoowrjqsikgkijb.supabase.co';
|
|
18
|
+
const CONFIG_DIR = path.join(os.homedir(), '.securegate');
|
|
19
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
20
|
+
|
|
21
|
+
// ANSI colors
|
|
22
|
+
const colors = {
|
|
23
|
+
reset: '\x1b[0m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
cyan: '\x1b[36m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
bold: '\x1b[1m',
|
|
29
|
+
dim: '\x1b[2m',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function log(msg, color = '') {
|
|
33
|
+
console.log(`${color}${msg}${colors.reset}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function banner() {
|
|
37
|
+
console.log(`
|
|
38
|
+
${colors.cyan}${colors.bold}🔐 SecureGate Setup${colors.reset}
|
|
39
|
+
${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Get device fingerprint (machine-based)
|
|
44
|
+
function getDeviceFingerprint() {
|
|
45
|
+
const cpus = os.cpus();
|
|
46
|
+
const networkInterfaces = os.networkInterfaces();
|
|
47
|
+
|
|
48
|
+
// Get first MAC address
|
|
49
|
+
let mac = '';
|
|
50
|
+
for (const [name, interfaces] of Object.entries(networkInterfaces)) {
|
|
51
|
+
for (const iface of interfaces) {
|
|
52
|
+
if (iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
|
53
|
+
mac = iface.mac;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (mac) break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fingerprint = [
|
|
61
|
+
os.hostname(),
|
|
62
|
+
os.platform(),
|
|
63
|
+
os.arch(),
|
|
64
|
+
cpus[0]?.model || 'unknown',
|
|
65
|
+
mac,
|
|
66
|
+
os.totalmem().toString(),
|
|
67
|
+
].join('|');
|
|
68
|
+
|
|
69
|
+
return crypto.createHash('sha256').update(fingerprint).digest('hex').substring(0, 32);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get public IP
|
|
73
|
+
async function getPublicIP() {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
https.get('https://api.ipify.org?format=json', (res) => {
|
|
76
|
+
let data = '';
|
|
77
|
+
res.on('data', chunk => data += chunk);
|
|
78
|
+
res.on('end', () => {
|
|
79
|
+
try {
|
|
80
|
+
resolve(JSON.parse(data).ip);
|
|
81
|
+
} catch {
|
|
82
|
+
resolve('unknown');
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}).on('error', () => resolve('unknown'));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get geolocation
|
|
90
|
+
async function getGeoLocation(ip) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
http.get(`http://ip-api.com/json/${ip}?fields=status,country,countryCode,city`, (res) => {
|
|
93
|
+
let data = '';
|
|
94
|
+
res.on('data', chunk => data += chunk);
|
|
95
|
+
res.on('end', () => {
|
|
96
|
+
try {
|
|
97
|
+
const result = JSON.parse(data);
|
|
98
|
+
if (result.status === 'success') {
|
|
99
|
+
resolve({ country: result.country, code: result.countryCode, city: result.city });
|
|
100
|
+
} else {
|
|
101
|
+
resolve(null);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
resolve(null);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}).on('error', () => resolve(null));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Make HTTP request
|
|
112
|
+
function httpRequest(url, options = {}) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const urlObj = new URL(url);
|
|
115
|
+
const isHttps = urlObj.protocol === 'https:';
|
|
116
|
+
const lib = isHttps ? https : http;
|
|
117
|
+
|
|
118
|
+
const req = lib.request(url, {
|
|
119
|
+
method: options.method || 'GET',
|
|
120
|
+
headers: options.headers || {},
|
|
121
|
+
}, (res) => {
|
|
122
|
+
let data = '';
|
|
123
|
+
res.on('data', chunk => data += chunk);
|
|
124
|
+
res.on('end', () => {
|
|
125
|
+
try {
|
|
126
|
+
resolve({ status: res.statusCode, data: JSON.parse(data) });
|
|
127
|
+
} catch {
|
|
128
|
+
resolve({ status: res.statusCode, data });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
req.on('error', reject);
|
|
134
|
+
|
|
135
|
+
if (options.body) {
|
|
136
|
+
req.write(options.body);
|
|
137
|
+
}
|
|
138
|
+
req.end();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Create readline interface
|
|
143
|
+
function createPrompt() {
|
|
144
|
+
return readline.createInterface({
|
|
145
|
+
input: process.stdin,
|
|
146
|
+
output: process.stdout,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Ask question
|
|
151
|
+
function ask(rl, question) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
rl.question(question, (answer) => {
|
|
154
|
+
resolve(answer.trim());
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Load config
|
|
160
|
+
function loadConfig() {
|
|
161
|
+
try {
|
|
162
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
163
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
164
|
+
}
|
|
165
|
+
} catch { }
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Save config
|
|
170
|
+
function saveConfig(config) {
|
|
171
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
172
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Main setup flow
|
|
178
|
+
async function setup() {
|
|
179
|
+
banner();
|
|
180
|
+
|
|
181
|
+
const rl = createPrompt();
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Step 1: Get JWT token
|
|
185
|
+
log('1. Authentication', colors.bold);
|
|
186
|
+
log(' Enter your Supabase JWT token (from browser after login):', colors.dim);
|
|
187
|
+
const token = await ask(rl, ' Token: ');
|
|
188
|
+
|
|
189
|
+
if (!token) {
|
|
190
|
+
log(' ❌ Token required', colors.red);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
log(' ✓ Token received', colors.green);
|
|
194
|
+
|
|
195
|
+
// Step 2: Get connection ID
|
|
196
|
+
log('\n2. Connection', colors.bold);
|
|
197
|
+
log(' Enter your connection_id (e.g., open_abc123):', colors.dim);
|
|
198
|
+
const connectionId = await ask(rl, ' Connection ID: ');
|
|
199
|
+
|
|
200
|
+
if (!connectionId) {
|
|
201
|
+
log(' ❌ Connection ID required', colors.red);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
log(` ✓ Connection: ${connectionId}`, colors.green);
|
|
205
|
+
|
|
206
|
+
// Step 3: Detect environment
|
|
207
|
+
log('\n3. Detecting environment...', colors.bold);
|
|
208
|
+
|
|
209
|
+
const ip = await getPublicIP();
|
|
210
|
+
log(` ✓ IP: ${ip}`, colors.green);
|
|
211
|
+
|
|
212
|
+
const geo = await getGeoLocation(ip);
|
|
213
|
+
if (geo) {
|
|
214
|
+
log(` ✓ Location: ${geo.city}, ${geo.country} (${geo.code})`, colors.green);
|
|
215
|
+
} else {
|
|
216
|
+
log(' ⚠ Could not detect location', colors.yellow);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const fingerprint = getDeviceFingerprint();
|
|
220
|
+
log(` ✓ Device: ${os.hostname()} (${os.platform()})`, colors.green);
|
|
221
|
+
log(` ✓ Fingerprint: ${fingerprint.substring(0, 8)}...`, colors.dim);
|
|
222
|
+
|
|
223
|
+
// Step 4: Activate key
|
|
224
|
+
log('\n4. Generating security key...', colors.bold);
|
|
225
|
+
|
|
226
|
+
const response = await httpRequest(`${SUPABASE_URL}/functions/v1/activate-key`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: {
|
|
229
|
+
'Authorization': `Bearer ${token}`,
|
|
230
|
+
'Content-Type': 'application/json',
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
connection_id: connectionId,
|
|
234
|
+
device_fingerprint: fingerprint,
|
|
235
|
+
label: `${os.hostname()} (${os.platform()})`,
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (response.status !== 201) {
|
|
240
|
+
log(` ❌ Failed: ${response.data.error || 'Unknown error'}`, colors.red);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const { security_key, bound_to, provider, usage } = response.data;
|
|
245
|
+
|
|
246
|
+
log(' ✓ Key activated!', colors.green);
|
|
247
|
+
|
|
248
|
+
// Save to config
|
|
249
|
+
const config = loadConfig();
|
|
250
|
+
config[connectionId] = {
|
|
251
|
+
security_key,
|
|
252
|
+
provider,
|
|
253
|
+
bound_to,
|
|
254
|
+
created_at: new Date().toISOString(),
|
|
255
|
+
};
|
|
256
|
+
saveConfig(config);
|
|
257
|
+
|
|
258
|
+
// Display result
|
|
259
|
+
console.log(`
|
|
260
|
+
${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
|
|
261
|
+
|
|
262
|
+
${colors.bold}${colors.green}✓ Security Key Generated!${colors.reset}
|
|
263
|
+
|
|
264
|
+
${colors.yellow}⚠ SAVE THIS KEY - IT WILL NOT BE SHOWN AGAIN!${colors.reset}
|
|
265
|
+
|
|
266
|
+
${colors.cyan}Security Key:${colors.reset}
|
|
267
|
+
${colors.bold}${security_key}${colors.reset}
|
|
268
|
+
|
|
269
|
+
${colors.cyan}Bound to:${colors.reset}
|
|
270
|
+
• IP: ${bound_to.ip}
|
|
271
|
+
• Country: ${bound_to.country}
|
|
272
|
+
• Device: ${bound_to.device}
|
|
273
|
+
|
|
274
|
+
${colors.cyan}Usage:${colors.reset}
|
|
275
|
+
${colors.dim}const openai = new OpenAI({
|
|
276
|
+
apiKey: '${security_key}',
|
|
277
|
+
baseURL: '${usage.baseURL}'
|
|
278
|
+
});${colors.reset}
|
|
279
|
+
|
|
280
|
+
${colors.dim}Config saved to: ${CONFIG_FILE}${colors.reset}
|
|
281
|
+
|
|
282
|
+
${colors.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
|
|
283
|
+
`);
|
|
284
|
+
|
|
285
|
+
} finally {
|
|
286
|
+
rl.close();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// List configured keys
|
|
291
|
+
async function list() {
|
|
292
|
+
banner();
|
|
293
|
+
|
|
294
|
+
const config = loadConfig();
|
|
295
|
+
const keys = Object.entries(config);
|
|
296
|
+
|
|
297
|
+
if (keys.length === 0) {
|
|
298
|
+
log('No security keys configured yet.', colors.dim);
|
|
299
|
+
log('Run: npx securegate setup', colors.cyan);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
log('Configured Security Keys:', colors.bold);
|
|
304
|
+
console.log();
|
|
305
|
+
|
|
306
|
+
for (const [connectionId, data] of keys) {
|
|
307
|
+
const keyPreview = data.security_key.substring(0, 12) + '...';
|
|
308
|
+
log(` ${connectionId}`, colors.cyan);
|
|
309
|
+
log(` Key: ${keyPreview}`, colors.dim);
|
|
310
|
+
log(` Provider: ${data.provider}`, colors.dim);
|
|
311
|
+
log(` IP: ${data.bound_to?.ip || 'unknown'}`, colors.dim);
|
|
312
|
+
log(` Created: ${data.created_at}`, colors.dim);
|
|
313
|
+
console.log();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Show usage instructions
|
|
318
|
+
function usage() {
|
|
319
|
+
console.log(`
|
|
320
|
+
${colors.cyan}${colors.bold}SecureGate CLI${colors.reset}
|
|
321
|
+
|
|
322
|
+
${colors.bold}Usage:${colors.reset}
|
|
323
|
+
npx securegate setup Generate a new security key
|
|
324
|
+
npx securegate list List configured keys
|
|
325
|
+
npx securegate help Show this help
|
|
326
|
+
|
|
327
|
+
${colors.bold}Example:${colors.reset}
|
|
328
|
+
1. First, create a connection in the SecureGate dashboard
|
|
329
|
+
2. Run: npx securegate setup
|
|
330
|
+
3. Enter your JWT token and connection ID
|
|
331
|
+
4. Use the generated key with OpenAI SDK
|
|
332
|
+
`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Main
|
|
336
|
+
const command = process.argv[2] || 'help';
|
|
337
|
+
|
|
338
|
+
switch (command) {
|
|
339
|
+
case 'setup':
|
|
340
|
+
setup().catch(err => {
|
|
341
|
+
log(`Error: ${err.message}`, colors.red);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
});
|
|
344
|
+
break;
|
|
345
|
+
case 'list':
|
|
346
|
+
list();
|
|
347
|
+
break;
|
|
348
|
+
case 'help':
|
|
349
|
+
default:
|
|
350
|
+
usage();
|
|
351
|
+
}
|