switchman-dev 0.1.10 → 0.1.12
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/package.json +1 -1
- package/src/cli/index.js +8 -5
- package/src/core/licence.js +100 -145
- package/src.zip +0 -0
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -6352,19 +6352,22 @@ Examples:
|
|
|
6352
6352
|
const licence = await checkLicence();
|
|
6353
6353
|
|
|
6354
6354
|
console.log(` ${chalk.green('✓')} Signed in as ${chalk.cyan(result.email ?? 'unknown')}`);
|
|
6355
|
-
|
|
6355
|
+
|
|
6356
6356
|
if (licence.valid) {
|
|
6357
|
+
console.log(` ${chalk.green('✓')} Pro licence verified — all features unlocked`);
|
|
6357
6358
|
console.log(` ${chalk.green('✓')} Switchman Pro active`);
|
|
6358
6359
|
console.log(` ${chalk.dim('Plan:')} ${licence.plan ?? 'Pro'}`);
|
|
6359
6360
|
console.log('');
|
|
6360
6361
|
console.log(` ${chalk.dim('Credentials saved · valid 24h · 7-day offline grace')}`);
|
|
6361
6362
|
console.log('');
|
|
6362
|
-
console.log(` Run ${chalk.cyan('switchman setup --agents 10')} to
|
|
6363
|
+
console.log(` Run ${chalk.cyan('switchman setup --agents 10')} to start with unlimited agents.`);
|
|
6363
6364
|
} else {
|
|
6364
|
-
console.log(` ${chalk.yellow('⚠')}
|
|
6365
|
+
console.log(` ${chalk.yellow('⚠')} Signed in — no Pro licence found yet`);
|
|
6366
|
+
console.log('');
|
|
6367
|
+
console.log(` ${chalk.dim('If you just subscribed, Polar may take 30–60 seconds to activate.')}`);
|
|
6368
|
+
console.log(` ${chalk.dim('Check your status with:')} ${chalk.cyan('switchman login --status')}`);
|
|
6365
6369
|
console.log('');
|
|
6366
|
-
console.log(`
|
|
6367
|
-
console.log(` ${chalk.dim('Upgrade at:')} ${chalk.cyan(PRO_PAGE_URL)}`);
|
|
6370
|
+
console.log(` ${chalk.dim('Not subscribed yet?')} ${chalk.cyan(PRO_PAGE_URL)}`);
|
|
6368
6371
|
}
|
|
6369
6372
|
|
|
6370
6373
|
console.log('');
|
package/src/core/licence.js
CHANGED
|
@@ -134,12 +134,13 @@ export async function checkLicence() {
|
|
|
134
134
|
|
|
135
135
|
// Try live validation
|
|
136
136
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
const res = await fetch(VALIDATE_URL, {
|
|
138
|
+
headers: {
|
|
139
|
+
'Authorization': `Bearer ${SUPABASE_ANON}`,
|
|
140
|
+
'apikey': SUPABASE_ANON,
|
|
141
|
+
'x-user-token': creds.access_token,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
143
144
|
|
|
144
145
|
if (!res.ok) {
|
|
145
146
|
// If token is expired, try to refresh
|
|
@@ -224,153 +225,107 @@ async function refreshToken(refreshToken) {
|
|
|
224
225
|
*/
|
|
225
226
|
export async function loginWithGitHub() {
|
|
226
227
|
const { default: open } = await import('open');
|
|
227
|
-
const { createServer } = await import('http');
|
|
228
|
-
|
|
229
|
-
return new Promise((resolve) => {
|
|
230
|
-
let server;
|
|
231
|
-
const timeout = setTimeout(() => {
|
|
232
|
-
server?.close();
|
|
233
|
-
resolve({ success: false, error: 'timeout' });
|
|
234
|
-
}, 5 * 60 * 1000);
|
|
235
|
-
|
|
236
|
-
server = createServer(async (req, res) => {
|
|
237
|
-
const url = new URL(req.url, 'http://localhost:7429');
|
|
238
|
-
|
|
239
|
-
if (url.pathname === '/callback') {
|
|
240
|
-
const code = url.searchParams.get('code');
|
|
241
|
-
const accessToken = url.searchParams.get('access_token');
|
|
242
|
-
const refreshToken = url.searchParams.get('refresh_token');
|
|
243
|
-
const error = url.searchParams.get('error');
|
|
244
|
-
|
|
245
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
246
|
-
res.end(`
|
|
247
|
-
<!DOCTYPE html><html>
|
|
248
|
-
<head><style>
|
|
249
|
-
body { background:#0b1020; color:#e6eef8; font-family:monospace;
|
|
250
|
-
display:flex; align-items:center; justify-content:center;
|
|
251
|
-
min-height:100vh; margin:0; }
|
|
252
|
-
.box { text-align:center; }
|
|
253
|
-
.ok { color:#4ade80; font-size:48px; }
|
|
254
|
-
.err { color:#f87171; font-size:48px; }
|
|
255
|
-
h2 { font-size:24px; margin:16px 0 8px; }
|
|
256
|
-
p { color:#5f7189; }
|
|
257
|
-
</style></head>
|
|
258
|
-
<body><div class="box">
|
|
259
|
-
<div class="${error ? 'err' : 'ok'}">${error ? '✕' : '✓'}</div>
|
|
260
|
-
<h2>${error ? 'Sign in failed' : 'Signed in successfully'}</h2>
|
|
261
|
-
<p>${error ? 'You can close this tab.' : 'You can close this tab and return to your terminal.'}</p>
|
|
262
|
-
</div></body></html>
|
|
263
|
-
`);
|
|
264
|
-
|
|
265
|
-
clearTimeout(timeout);
|
|
266
|
-
server.close();
|
|
267
|
-
|
|
268
|
-
if (error) {
|
|
269
|
-
resolve({ success: false, error });
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
228
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
expires_in: 3600,
|
|
279
|
-
user: null, // will be fetched on next checkLicence
|
|
280
|
-
});
|
|
281
|
-
// Fetch the user email from Supabase
|
|
282
|
-
try {
|
|
283
|
-
const userRes = await fetch(`${AUTH_URL}/user`, {
|
|
284
|
-
headers: {
|
|
285
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
286
|
-
'apikey': SUPABASE_ANON,
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
if (userRes.ok) {
|
|
290
|
-
const user = await userRes.json();
|
|
291
|
-
const creds = readCredentials() || {};
|
|
292
|
-
writeCredentials({ ...creds, email: user.email, user_id: user.id });
|
|
293
|
-
resolve({ success: true, email: user.email });
|
|
294
|
-
} else {
|
|
295
|
-
resolve({ success: true, email: null });
|
|
296
|
-
}
|
|
297
|
-
} catch {
|
|
298
|
-
resolve({ success: true, email: null });
|
|
299
|
-
}
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Exchange the code for a session
|
|
304
|
-
if (code) {
|
|
305
|
-
try {
|
|
306
|
-
const tokenRes = await fetch(`${AUTH_URL}/token?grant_type=pkce`, {
|
|
307
|
-
method: 'POST',
|
|
308
|
-
headers: {
|
|
309
|
-
'Content-Type': 'application/json',
|
|
310
|
-
'apikey': SUPABASE_ANON,
|
|
311
|
-
},
|
|
312
|
-
body: JSON.stringify({ auth_code: code }),
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
if (tokenRes.ok) {
|
|
316
|
-
const session = await tokenRes.json();
|
|
317
|
-
saveSession(session);
|
|
318
|
-
resolve({ success: true, email: session.user?.email ?? null });
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Fallback exchange
|
|
323
|
-
const exchangeRes = await fetch(`${AUTH_URL}/token?grant_type=authorization_code`, {
|
|
324
|
-
method: 'POST',
|
|
325
|
-
headers: {
|
|
326
|
-
'Content-Type': 'application/json',
|
|
327
|
-
'apikey': SUPABASE_ANON,
|
|
328
|
-
},
|
|
329
|
-
body: JSON.stringify({ code }),
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
if (exchangeRes.ok) {
|
|
333
|
-
const session = await exchangeRes.json();
|
|
334
|
-
saveSession(session);
|
|
335
|
-
resolve({ success: true, email: session.user?.email ?? null });
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
resolve({ success: false, error: 'token_exchange_failed' });
|
|
340
|
-
} catch (err) {
|
|
341
|
-
resolve({ success: false, error: err.message });
|
|
342
|
-
}
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
229
|
+
// ── Step 1: Generate a short human-readable code ──────────────────────────
|
|
230
|
+
const adjectives = ['SWIFT', 'CLEAR', 'SAFE', 'CLEAN', 'FAST', 'BOLD', 'CALM', 'KEEN'];
|
|
231
|
+
const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
232
|
+
const num = Math.floor(1000 + Math.random() * 9000);
|
|
233
|
+
const code = `${adj}-${num}`;
|
|
345
234
|
|
|
346
|
-
|
|
347
|
-
|
|
235
|
+
// ── Step 2: Store the pending code in Supabase ────────────────────────────
|
|
236
|
+
try {
|
|
237
|
+
const insertRes = await fetch(`${SUPABASE_URL}/rest/v1/cli_auth_codes`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-Type': 'application/json',
|
|
241
|
+
'apikey': SUPABASE_ANON,
|
|
242
|
+
'Prefer': 'return=minimal',
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
code,
|
|
246
|
+
status: 'pending',
|
|
247
|
+
}),
|
|
348
248
|
});
|
|
349
249
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
250
|
+
if (!insertRes.ok) {
|
|
251
|
+
return { success: false, error: 'Could not create auth session. Please try again.' };
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
return { success: false, error: 'Network error. Please check your connection.' };
|
|
255
|
+
}
|
|
356
256
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log(' Opening GitHub sign-in in your browser...');
|
|
360
|
-
console.log(` If it doesn\'t open, visit: ${loginUrl}`);
|
|
361
|
-
console.log('');
|
|
257
|
+
// ── Step 3: Open the activate page in the browser ─────────────────────────
|
|
258
|
+
const activateUrl = `https://switchman.dev/activate?code=${code}`;
|
|
362
259
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(' Visit this URL to sign in:');
|
|
262
|
+
console.log(` ${activateUrl}`);
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(` Your code: ${code}`);
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log(' Waiting for authorization...');
|
|
368
267
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
resolve({ success: false, error: err.message });
|
|
372
|
-
});
|
|
268
|
+
open(activateUrl).catch(() => {
|
|
269
|
+
// Browser didn't open — user can copy the URL manually
|
|
373
270
|
});
|
|
271
|
+
|
|
272
|
+
// ── Step 4: Poll Supabase every 2 seconds for up to 10 minutes ────────────
|
|
273
|
+
const POLL_INTERVAL_MS = 2000;
|
|
274
|
+
const MAX_WAIT_MS = 10 * 60 * 1000;
|
|
275
|
+
const started = Date.now();
|
|
276
|
+
|
|
277
|
+
while (Date.now() - started < MAX_WAIT_MS) {
|
|
278
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const pollRes = await fetch(
|
|
282
|
+
`${SUPABASE_URL}/rest/v1/cli_auth_codes?code=eq.${code}&select=status,access_token,refresh_token,user_email,user_id`,
|
|
283
|
+
{
|
|
284
|
+
headers: {
|
|
285
|
+
'apikey': SUPABASE_ANON,
|
|
286
|
+
'Accept': 'application/json',
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (!pollRes.ok) continue;
|
|
292
|
+
|
|
293
|
+
const rows = await pollRes.json();
|
|
294
|
+
const row = rows?.[0];
|
|
295
|
+
|
|
296
|
+
if (!row) continue;
|
|
297
|
+
|
|
298
|
+
if (row.status === 'expired') {
|
|
299
|
+
return { success: false, error: 'Code expired. Please run switchman login again.' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (row.status === 'authorized' && row.access_token) {
|
|
303
|
+
// Save the credentials
|
|
304
|
+
writeCredentials({
|
|
305
|
+
access_token: row.access_token,
|
|
306
|
+
refresh_token: row.refresh_token ?? null,
|
|
307
|
+
expires_at: Date.now() + 3600 * 1000,
|
|
308
|
+
email: row.user_email ?? null,
|
|
309
|
+
user_id: row.user_id ?? null,
|
|
310
|
+
});
|
|
311
|
+
clearLicenceCache();
|
|
312
|
+
|
|
313
|
+
// Clean up the code row
|
|
314
|
+
fetch(`${SUPABASE_URL}/rest/v1/cli_auth_codes?code=eq.${code}`, {
|
|
315
|
+
method: 'DELETE',
|
|
316
|
+
headers: { 'apikey': SUPABASE_ANON },
|
|
317
|
+
}).catch(() => {});
|
|
318
|
+
|
|
319
|
+
return { success: true, email: row.user_email };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// status === 'pending' — keep polling
|
|
323
|
+
} catch {
|
|
324
|
+
// Network blip — keep polling
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return { success: false, error: 'timeout' };
|
|
374
329
|
}
|
|
375
330
|
|
|
376
331
|
function saveSession(session) {
|
package/src.zip
DELETED
|
Binary file
|