switchman-dev 0.1.10 → 0.1.11
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 +93 -139
- 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
|
@@ -224,153 +224,107 @@ async function refreshToken(refreshToken) {
|
|
|
224
224
|
*/
|
|
225
225
|
export async function loginWithGitHub() {
|
|
226
226
|
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
|
-
|
|
273
|
-
// If Supabase sent the token directly as query params
|
|
274
|
-
if (accessToken) {
|
|
275
|
-
saveSession({
|
|
276
|
-
access_token: accessToken,
|
|
277
|
-
refresh_token: refreshToken ?? null,
|
|
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
227
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
}
|
|
228
|
+
// ── Step 1: Generate a short human-readable code ──────────────────────────
|
|
229
|
+
const adjectives = ['SWIFT', 'CLEAR', 'SAFE', 'CLEAN', 'FAST', 'BOLD', 'CALM', 'KEEN'];
|
|
230
|
+
const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
231
|
+
const num = Math.floor(1000 + Math.random() * 9000);
|
|
232
|
+
const code = `${adj}-${num}`;
|
|
345
233
|
|
|
346
|
-
|
|
347
|
-
|
|
234
|
+
// ── Step 2: Store the pending code in Supabase ────────────────────────────
|
|
235
|
+
try {
|
|
236
|
+
const insertRes = await fetch(`${SUPABASE_URL}/rest/v1/cli_auth_codes`, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: {
|
|
239
|
+
'Content-Type': 'application/json',
|
|
240
|
+
'apikey': SUPABASE_ANON,
|
|
241
|
+
'Prefer': 'return=minimal',
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
code,
|
|
245
|
+
status: 'pending',
|
|
246
|
+
}),
|
|
348
247
|
});
|
|
349
248
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
249
|
+
if (!insertRes.ok) {
|
|
250
|
+
return { success: false, error: 'Could not create auth session. Please try again.' };
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
return { success: false, error: 'Network error. Please check your connection.' };
|
|
254
|
+
}
|
|
356
255
|
|
|
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('');
|
|
256
|
+
// ── Step 3: Open the activate page in the browser ─────────────────────────
|
|
257
|
+
const activateUrl = `https://switchman.dev/activate?code=${code}`;
|
|
362
258
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log(' Visit this URL to sign in:');
|
|
261
|
+
console.log(` ${activateUrl}`);
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log(` Your code: ${code}`);
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(' Waiting for authorization...');
|
|
368
266
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
resolve({ success: false, error: err.message });
|
|
372
|
-
});
|
|
267
|
+
open(activateUrl).catch(() => {
|
|
268
|
+
// Browser didn't open — user can copy the URL manually
|
|
373
269
|
});
|
|
270
|
+
|
|
271
|
+
// ── Step 4: Poll Supabase every 2 seconds for up to 10 minutes ────────────
|
|
272
|
+
const POLL_INTERVAL_MS = 2000;
|
|
273
|
+
const MAX_WAIT_MS = 10 * 60 * 1000;
|
|
274
|
+
const started = Date.now();
|
|
275
|
+
|
|
276
|
+
while (Date.now() - started < MAX_WAIT_MS) {
|
|
277
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const pollRes = await fetch(
|
|
281
|
+
`${SUPABASE_URL}/rest/v1/cli_auth_codes?code=eq.${code}&select=status,access_token,refresh_token,user_email,user_id`,
|
|
282
|
+
{
|
|
283
|
+
headers: {
|
|
284
|
+
'apikey': SUPABASE_ANON,
|
|
285
|
+
'Accept': 'application/json',
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (!pollRes.ok) continue;
|
|
291
|
+
|
|
292
|
+
const rows = await pollRes.json();
|
|
293
|
+
const row = rows?.[0];
|
|
294
|
+
|
|
295
|
+
if (!row) continue;
|
|
296
|
+
|
|
297
|
+
if (row.status === 'expired') {
|
|
298
|
+
return { success: false, error: 'Code expired. Please run switchman login again.' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (row.status === 'authorized' && row.access_token) {
|
|
302
|
+
// Save the credentials
|
|
303
|
+
writeCredentials({
|
|
304
|
+
access_token: row.access_token,
|
|
305
|
+
refresh_token: row.refresh_token ?? null,
|
|
306
|
+
expires_at: Date.now() + 3600 * 1000,
|
|
307
|
+
email: row.user_email ?? null,
|
|
308
|
+
user_id: row.user_id ?? null,
|
|
309
|
+
});
|
|
310
|
+
clearLicenceCache();
|
|
311
|
+
|
|
312
|
+
// Clean up the code row
|
|
313
|
+
fetch(`${SUPABASE_URL}/rest/v1/cli_auth_codes?code=eq.${code}`, {
|
|
314
|
+
method: 'DELETE',
|
|
315
|
+
headers: { 'apikey': SUPABASE_ANON },
|
|
316
|
+
}).catch(() => {});
|
|
317
|
+
|
|
318
|
+
return { success: true, email: row.user_email };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// status === 'pending' — keep polling
|
|
322
|
+
} catch {
|
|
323
|
+
// Network blip — keep polling
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return { success: false, error: 'timeout' };
|
|
374
328
|
}
|
|
375
329
|
|
|
376
330
|
function saveSession(session) {
|
package/src.zip
DELETED
|
Binary file
|