switchman-dev 0.1.9 → 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 -114
- 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
|
@@ -223,129 +223,108 @@ async function refreshToken(refreshToken) {
|
|
|
223
223
|
* Returns { success, email } or { success: false, error }
|
|
224
224
|
*/
|
|
225
225
|
export async function loginWithGitHub() {
|
|
226
|
-
// Use Supabase's PKCE/implicit flow by opening the browser
|
|
227
|
-
// with a special device-style URL that redirects to a local callback
|
|
228
226
|
const { default: open } = await import('open');
|
|
229
227
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const { createServer } = await import('http');
|
|
236
|
-
|
|
237
|
-
return new Promise((resolve) => {
|
|
238
|
-
let server;
|
|
239
|
-
const timeout = setTimeout(() => {
|
|
240
|
-
server?.close();
|
|
241
|
-
resolve({ success: false, error: 'timeout' });
|
|
242
|
-
}, 5 * 60 * 1000); // 5 minute timeout
|
|
243
|
-
|
|
244
|
-
server = createServer(async (req, res) => {
|
|
245
|
-
const url = new URL(req.url, 'http://localhost:7429');
|
|
246
|
-
|
|
247
|
-
if (url.pathname === '/callback') {
|
|
248
|
-
const code = url.searchParams.get('code');
|
|
249
|
-
const error = url.searchParams.get('error');
|
|
250
|
-
|
|
251
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
252
|
-
res.end(`
|
|
253
|
-
<!DOCTYPE html>
|
|
254
|
-
<html>
|
|
255
|
-
<head><style>
|
|
256
|
-
body { background: #0b1020; color: #e6eef8; font-family: monospace;
|
|
257
|
-
display: flex; align-items: center; justify-content: center;
|
|
258
|
-
min-height: 100vh; margin: 0; }
|
|
259
|
-
.box { text-align: center; }
|
|
260
|
-
.ok { color: #4ade80; font-size: 48px; }
|
|
261
|
-
h2 { font-size: 24px; margin: 16px 0 8px; }
|
|
262
|
-
p { color: #5f7189; }
|
|
263
|
-
</style></head>
|
|
264
|
-
<body>
|
|
265
|
-
<div class="box">
|
|
266
|
-
<div class="ok">${error ? '✕' : '✓'}</div>
|
|
267
|
-
<h2>${error ? 'Sign in failed' : 'Signed in successfully'}</h2>
|
|
268
|
-
<p>${error ? 'You can close this tab.' : 'You can close this tab and return to your terminal.'}</p>
|
|
269
|
-
</div>
|
|
270
|
-
</body>
|
|
271
|
-
</html>
|
|
272
|
-
`);
|
|
273
|
-
|
|
274
|
-
clearTimeout(timeout);
|
|
275
|
-
server.close();
|
|
276
|
-
|
|
277
|
-
if (error || !code) {
|
|
278
|
-
resolve({ success: false, error: error || 'no_code' });
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
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}`;
|
|
281
233
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const exchangeRes = await fetch(`${AUTH_URL}/token?grant_type=authorization_code`, {
|
|
296
|
-
method: 'POST',
|
|
297
|
-
headers: {
|
|
298
|
-
'Content-Type': 'application/json',
|
|
299
|
-
'apikey': SUPABASE_ANON,
|
|
300
|
-
},
|
|
301
|
-
body: JSON.stringify({ code }),
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
if (!exchangeRes.ok) {
|
|
305
|
-
resolve({ success: false, error: 'token_exchange_failed' });
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const session = await exchangeRes.json();
|
|
310
|
-
saveSession(session);
|
|
311
|
-
resolve({ success: true, email: session.user?.email });
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const session = await tokenRes.json();
|
|
316
|
-
saveSession(session);
|
|
317
|
-
resolve({ success: true, email: session.user?.email });
|
|
318
|
-
} catch (err) {
|
|
319
|
-
resolve({ success: false, error: err.message });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
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
|
+
}),
|
|
322
247
|
});
|
|
323
248
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
});
|
|
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
|
+
}
|
|
331
255
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
console.log(' Opening GitHub sign-in in your browser...');
|
|
335
|
-
console.log(` If it doesn\'t open, visit: ${loginUrl}`);
|
|
336
|
-
console.log('');
|
|
256
|
+
// ── Step 3: Open the activate page in the browser ─────────────────────────
|
|
257
|
+
const activateUrl = `https://switchman.dev/activate?code=${code}`;
|
|
337
258
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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...');
|
|
343
266
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
resolve({ success: false, error: err.message });
|
|
347
|
-
});
|
|
267
|
+
open(activateUrl).catch(() => {
|
|
268
|
+
// Browser didn't open — user can copy the URL manually
|
|
348
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' };
|
|
349
328
|
}
|
|
350
329
|
|
|
351
330
|
function saveSession(session) {
|
package/src.zip
DELETED
|
Binary file
|