@devness/useai 0.4.5 → 0.4.6
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/dist/index.js +272 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -112,7 +112,7 @@ var VERSION;
|
|
|
112
112
|
var init_version = __esm({
|
|
113
113
|
"../shared/dist/constants/version.js"() {
|
|
114
114
|
"use strict";
|
|
115
|
-
VERSION = "0.4.
|
|
115
|
+
VERSION = "0.4.6";
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -2302,6 +2302,12 @@ function getDashboardHtml() {
|
|
|
2302
2302
|
color: var(--muted);
|
|
2303
2303
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
2304
2304
|
}
|
|
2305
|
+
.milestone-duration {
|
|
2306
|
+
font-size: 0.7rem;
|
|
2307
|
+
color: var(--muted);
|
|
2308
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
2309
|
+
white-space: nowrap;
|
|
2310
|
+
}
|
|
2305
2311
|
.milestone-date {
|
|
2306
2312
|
font-size: 0.72rem;
|
|
2307
2313
|
color: var(--muted);
|
|
@@ -2348,6 +2354,77 @@ function getDashboardHtml() {
|
|
|
2348
2354
|
text-decoration: underline;
|
|
2349
2355
|
}
|
|
2350
2356
|
|
|
2357
|
+
/* Login form */
|
|
2358
|
+
.login-form {
|
|
2359
|
+
max-width: 320px;
|
|
2360
|
+
margin: 0 auto;
|
|
2361
|
+
text-align: center;
|
|
2362
|
+
}
|
|
2363
|
+
.login-form h3 {
|
|
2364
|
+
font-size: 0.95rem;
|
|
2365
|
+
color: var(--text);
|
|
2366
|
+
margin-bottom: 4px;
|
|
2367
|
+
font-weight: 600;
|
|
2368
|
+
}
|
|
2369
|
+
.login-form .login-sub {
|
|
2370
|
+
font-size: 0.8rem;
|
|
2371
|
+
color: var(--muted);
|
|
2372
|
+
margin-bottom: 16px;
|
|
2373
|
+
}
|
|
2374
|
+
.login-input {
|
|
2375
|
+
width: 100%;
|
|
2376
|
+
padding: 10px 12px;
|
|
2377
|
+
background: var(--bg);
|
|
2378
|
+
border: 1px solid var(--border);
|
|
2379
|
+
border-radius: var(--radius);
|
|
2380
|
+
color: var(--text);
|
|
2381
|
+
font-size: 0.88rem;
|
|
2382
|
+
font-family: system-ui, sans-serif;
|
|
2383
|
+
outline: none;
|
|
2384
|
+
margin-bottom: 10px;
|
|
2385
|
+
}
|
|
2386
|
+
.login-input:focus { border-color: var(--amber); }
|
|
2387
|
+
.login-input::placeholder { color: #5a5248; }
|
|
2388
|
+
.login-input.otp-input {
|
|
2389
|
+
text-align: center;
|
|
2390
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
2391
|
+
font-size: 1.2rem;
|
|
2392
|
+
letter-spacing: 6px;
|
|
2393
|
+
}
|
|
2394
|
+
.login-btn {
|
|
2395
|
+
width: 100%;
|
|
2396
|
+
padding: 10px;
|
|
2397
|
+
background: var(--amber);
|
|
2398
|
+
color: var(--bg);
|
|
2399
|
+
border: none;
|
|
2400
|
+
border-radius: var(--radius);
|
|
2401
|
+
font-weight: 600;
|
|
2402
|
+
font-size: 0.88rem;
|
|
2403
|
+
cursor: pointer;
|
|
2404
|
+
font-family: system-ui, sans-serif;
|
|
2405
|
+
transition: opacity 0.15s;
|
|
2406
|
+
}
|
|
2407
|
+
.login-btn:hover { opacity: 0.85; }
|
|
2408
|
+
.login-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
2409
|
+
.login-msg {
|
|
2410
|
+
margin-top: 10px;
|
|
2411
|
+
font-size: 0.8rem;
|
|
2412
|
+
}
|
|
2413
|
+
.login-msg.error { color: var(--red); }
|
|
2414
|
+
.login-msg.success { color: var(--green); }
|
|
2415
|
+
.login-msg.dim { color: var(--muted); }
|
|
2416
|
+
.login-link {
|
|
2417
|
+
background: none;
|
|
2418
|
+
border: none;
|
|
2419
|
+
color: var(--amber);
|
|
2420
|
+
cursor: pointer;
|
|
2421
|
+
font-size: 0.8rem;
|
|
2422
|
+
font-family: system-ui, sans-serif;
|
|
2423
|
+
text-decoration: underline;
|
|
2424
|
+
padding: 0;
|
|
2425
|
+
margin-top: 6px;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2351
2428
|
.empty {
|
|
2352
2429
|
text-align: center;
|
|
2353
2430
|
color: var(--muted);
|
|
@@ -2450,6 +2527,14 @@ function getDashboardHtml() {
|
|
|
2450
2527
|
return d.innerHTML;
|
|
2451
2528
|
}
|
|
2452
2529
|
|
|
2530
|
+
function fmtDuration(mins) {
|
|
2531
|
+
if (!mins || mins <= 0) return '';
|
|
2532
|
+
if (mins < 60) return mins + 'm';
|
|
2533
|
+
var h = Math.floor(mins / 60);
|
|
2534
|
+
var m = mins % 60;
|
|
2535
|
+
return m > 0 ? h + 'h ' + m + 'm' : h + 'h';
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2453
2538
|
function renderMilestones(milestones) {
|
|
2454
2539
|
var section = document.getElementById('milestones-section');
|
|
2455
2540
|
var list = document.getElementById('milestones-list');
|
|
@@ -2462,17 +2547,21 @@ function getDashboardHtml() {
|
|
|
2462
2547
|
var recent = milestones.slice(-20).reverse();
|
|
2463
2548
|
list.innerHTML = recent.map(function(m) {
|
|
2464
2549
|
var date = m.created_at ? m.created_at.slice(0, 10) : '';
|
|
2550
|
+
var dur = fmtDuration(m.duration_minutes);
|
|
2465
2551
|
return '<div class="milestone-item">' +
|
|
2466
2552
|
'<div class="milestone-title">' + escapeHtml(m.title) + '</div>' +
|
|
2467
2553
|
'<div class="milestone-meta">' +
|
|
2468
2554
|
'<span class="' + badgeClass(m.category) + '">' + escapeHtml(m.category) + '</span>' +
|
|
2469
2555
|
(m.complexity ? '<span class="complexity">' + escapeHtml(m.complexity) + '</span>' : '') +
|
|
2556
|
+
(dur ? '<span class="milestone-duration">' + dur + '</span>' : '') +
|
|
2470
2557
|
'<span class="milestone-date">' + escapeHtml(date) + '</span>' +
|
|
2471
2558
|
'</div>' +
|
|
2472
2559
|
'</div>';
|
|
2473
2560
|
}).join('');
|
|
2474
2561
|
}
|
|
2475
2562
|
|
|
2563
|
+
var loginEmail = '';
|
|
2564
|
+
|
|
2476
2565
|
function renderSync(config) {
|
|
2477
2566
|
var section = document.getElementById('sync-section');
|
|
2478
2567
|
if (!config) { section.style.display = 'none'; return; }
|
|
@@ -2482,20 +2571,134 @@ function getDashboardHtml() {
|
|
|
2482
2571
|
var lastSync = config.last_sync_at ? 'Last sync: ' + config.last_sync_at : 'Never synced';
|
|
2483
2572
|
section.innerHTML = '<div class="sync-section">' +
|
|
2484
2573
|
'<h2 style="font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:12px;font-weight:600;">Sync</h2>' +
|
|
2574
|
+
'<div class="sync-status" id="sync-status" style="margin-bottom:12px;">Logged in as <strong>' + escapeHtml(config.email || '') + '</strong></div>' +
|
|
2485
2575
|
'<button class="sync-btn" id="sync-btn">Sync to useai.dev</button>' +
|
|
2486
|
-
'<div class="sync-status" id="sync-
|
|
2576
|
+
'<div class="sync-status" id="sync-time">' + escapeHtml(lastSync) + '</div>' +
|
|
2487
2577
|
'<div class="sync-result" id="sync-result"></div>' +
|
|
2488
2578
|
'</div>';
|
|
2489
2579
|
document.getElementById('sync-btn').addEventListener('click', doSync);
|
|
2490
2580
|
} else {
|
|
2491
|
-
|
|
2581
|
+
renderLoginForm();
|
|
2492
2582
|
}
|
|
2493
2583
|
}
|
|
2494
2584
|
|
|
2585
|
+
function renderLoginForm() {
|
|
2586
|
+
var section = document.getElementById('sync-section');
|
|
2587
|
+
section.innerHTML = '<div class="login-form" id="login-form">' +
|
|
2588
|
+
'<h3>Sign in to sync</h3>' +
|
|
2589
|
+
'<div class="login-sub">Sync sessions & milestones to useai.dev</div>' +
|
|
2590
|
+
'<input class="login-input" id="login-email" type="email" placeholder="you@email.com" autocomplete="email">' +
|
|
2591
|
+
'<button class="login-btn" id="login-send-btn">Send verification code</button>' +
|
|
2592
|
+
'<div class="login-msg" id="login-msg"></div>' +
|
|
2593
|
+
'</div>';
|
|
2594
|
+
document.getElementById('login-send-btn').addEventListener('click', sendOtp);
|
|
2595
|
+
document.getElementById('login-email').addEventListener('keydown', function(e) {
|
|
2596
|
+
if (e.key === 'Enter') sendOtp();
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
function renderOtpForm() {
|
|
2601
|
+
var section = document.getElementById('sync-section');
|
|
2602
|
+
section.innerHTML = '<div class="login-form" id="otp-form">' +
|
|
2603
|
+
'<h3>Check your email</h3>' +
|
|
2604
|
+
'<div class="login-sub">Enter the 6-digit code sent to ' + escapeHtml(loginEmail) + '</div>' +
|
|
2605
|
+
'<input class="login-input otp-input" id="login-otp" type="text" maxlength="6" placeholder="000000" inputmode="numeric" autocomplete="one-time-code">' +
|
|
2606
|
+
'<button class="login-btn" id="login-verify-btn">Verify</button>' +
|
|
2607
|
+
'<div class="login-msg" id="login-msg"></div>' +
|
|
2608
|
+
'<button class="login-link" id="login-resend">Resend code</button>' +
|
|
2609
|
+
'</div>';
|
|
2610
|
+
document.getElementById('login-verify-btn').addEventListener('click', verifyOtp);
|
|
2611
|
+
document.getElementById('login-resend').addEventListener('click', function() { sendOtp(); });
|
|
2612
|
+
document.getElementById('login-otp').addEventListener('keydown', function(e) {
|
|
2613
|
+
if (e.key === 'Enter') verifyOtp();
|
|
2614
|
+
});
|
|
2615
|
+
document.getElementById('login-otp').focus();
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
function sendOtp() {
|
|
2619
|
+
var emailEl = document.getElementById('login-email');
|
|
2620
|
+
var msg = document.getElementById('login-msg');
|
|
2621
|
+
var btn = document.getElementById('login-send-btn') || document.getElementById('login-resend');
|
|
2622
|
+
var email = emailEl ? emailEl.value.trim() : loginEmail;
|
|
2623
|
+
|
|
2624
|
+
if (!email || email.indexOf('@') === -1) {
|
|
2625
|
+
msg.textContent = 'Please enter a valid email';
|
|
2626
|
+
msg.className = 'login-msg error';
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
loginEmail = email;
|
|
2631
|
+
if (btn) { btn.disabled = true; btn.textContent = btn.id === 'login-resend' ? 'Sending...' : 'Sending code...'; }
|
|
2632
|
+
|
|
2633
|
+
fetch(API + '/api/local/auth/send-otp', {
|
|
2634
|
+
method: 'POST',
|
|
2635
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2636
|
+
body: JSON.stringify({ email: email }),
|
|
2637
|
+
})
|
|
2638
|
+
.then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); })
|
|
2639
|
+
.then(function(res) {
|
|
2640
|
+
if (!res.ok) {
|
|
2641
|
+
msg.textContent = res.data.message || 'Failed to send code';
|
|
2642
|
+
msg.className = 'login-msg error';
|
|
2643
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Send verification code'; }
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
renderOtpForm();
|
|
2647
|
+
})
|
|
2648
|
+
.catch(function(err) {
|
|
2649
|
+
msg.textContent = 'Error: ' + err.message;
|
|
2650
|
+
msg.className = 'login-msg error';
|
|
2651
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Send verification code'; }
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function verifyOtp() {
|
|
2656
|
+
var otpEl = document.getElementById('login-otp');
|
|
2657
|
+
var msg = document.getElementById('login-msg');
|
|
2658
|
+
var btn = document.getElementById('login-verify-btn');
|
|
2659
|
+
var code = otpEl ? otpEl.value.trim() : '';
|
|
2660
|
+
|
|
2661
|
+
if (!/^d{6}$/.test(code)) {
|
|
2662
|
+
msg.textContent = 'Please enter the 6-digit code';
|
|
2663
|
+
msg.className = 'login-msg error';
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
btn.disabled = true;
|
|
2668
|
+
btn.textContent = 'Verifying...';
|
|
2669
|
+
msg.textContent = '';
|
|
2670
|
+
|
|
2671
|
+
fetch(API + '/api/local/auth/verify-otp', {
|
|
2672
|
+
method: 'POST',
|
|
2673
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2674
|
+
body: JSON.stringify({ email: loginEmail, code: code }),
|
|
2675
|
+
})
|
|
2676
|
+
.then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); })
|
|
2677
|
+
.then(function(res) {
|
|
2678
|
+
if (!res.ok) {
|
|
2679
|
+
msg.textContent = res.data.message || 'Invalid code';
|
|
2680
|
+
msg.className = 'login-msg error';
|
|
2681
|
+
btn.disabled = false;
|
|
2682
|
+
btn.textContent = 'Verify';
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
// Success \u2014 reload config and re-render sync section
|
|
2686
|
+
msg.textContent = 'Logged in!';
|
|
2687
|
+
msg.className = 'login-msg success';
|
|
2688
|
+
setTimeout(function() { loadAll(); }, 500);
|
|
2689
|
+
})
|
|
2690
|
+
.catch(function(err) {
|
|
2691
|
+
msg.textContent = 'Error: ' + err.message;
|
|
2692
|
+
msg.className = 'login-msg error';
|
|
2693
|
+
btn.disabled = false;
|
|
2694
|
+
btn.textContent = 'Verify';
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2495
2698
|
function doSync() {
|
|
2496
2699
|
var btn = document.getElementById('sync-btn');
|
|
2497
2700
|
var result = document.getElementById('sync-result');
|
|
2498
|
-
var
|
|
2701
|
+
var timeEl = document.getElementById('sync-time');
|
|
2499
2702
|
btn.disabled = true;
|
|
2500
2703
|
btn.textContent = 'Syncing...';
|
|
2501
2704
|
result.textContent = '';
|
|
@@ -2509,7 +2712,7 @@ function getDashboardHtml() {
|
|
|
2509
2712
|
if (data.success) {
|
|
2510
2713
|
result.textContent = 'Synced successfully';
|
|
2511
2714
|
result.className = 'sync-result success';
|
|
2512
|
-
if (data.last_sync_at)
|
|
2715
|
+
if (data.last_sync_at && timeEl) timeEl.textContent = 'Last sync: ' + data.last_sync_at;
|
|
2513
2716
|
} else {
|
|
2514
2717
|
result.textContent = 'Sync failed: ' + (data.error || 'Unknown error');
|
|
2515
2718
|
result.className = 'sync-result error';
|
|
@@ -2725,10 +2928,66 @@ async function handleLocalSync(req, res) {
|
|
|
2725
2928
|
json(res, 500, { success: false, error: err2.message });
|
|
2726
2929
|
}
|
|
2727
2930
|
}
|
|
2931
|
+
async function handleLocalSendOtp(req, res) {
|
|
2932
|
+
try {
|
|
2933
|
+
const raw = await readBody(req);
|
|
2934
|
+
const body = raw ? JSON.parse(raw) : {};
|
|
2935
|
+
const apiRes = await fetch(`${USEAI_API}/api/auth/send-otp`, {
|
|
2936
|
+
method: "POST",
|
|
2937
|
+
headers: { "Content-Type": "application/json" },
|
|
2938
|
+
body: JSON.stringify({ email: body.email })
|
|
2939
|
+
});
|
|
2940
|
+
const data = await apiRes.json();
|
|
2941
|
+
if (!apiRes.ok) {
|
|
2942
|
+
json(res, apiRes.status, data);
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
json(res, 200, data);
|
|
2946
|
+
} catch (err2) {
|
|
2947
|
+
json(res, 500, { error: err2.message });
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
async function handleLocalVerifyOtp(req, res) {
|
|
2951
|
+
try {
|
|
2952
|
+
const raw = await readBody(req);
|
|
2953
|
+
const body = raw ? JSON.parse(raw) : {};
|
|
2954
|
+
const apiRes = await fetch(`${USEAI_API}/api/auth/verify-otp`, {
|
|
2955
|
+
method: "POST",
|
|
2956
|
+
headers: { "Content-Type": "application/json" },
|
|
2957
|
+
body: JSON.stringify({ email: body.email, code: body.code })
|
|
2958
|
+
});
|
|
2959
|
+
const data = await apiRes.json();
|
|
2960
|
+
if (!apiRes.ok) {
|
|
2961
|
+
json(res, apiRes.status, data);
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
if (data.token && data.user) {
|
|
2965
|
+
const config = readJson(CONFIG_FILE, {
|
|
2966
|
+
milestone_tracking: true,
|
|
2967
|
+
auto_sync: true,
|
|
2968
|
+
sync_interval_hours: 24
|
|
2969
|
+
});
|
|
2970
|
+
config.auth = {
|
|
2971
|
+
token: data.token,
|
|
2972
|
+
user: {
|
|
2973
|
+
id: data.user.id,
|
|
2974
|
+
email: data.user.email,
|
|
2975
|
+
username: data.user.username
|
|
2976
|
+
}
|
|
2977
|
+
};
|
|
2978
|
+
writeJson(CONFIG_FILE, config);
|
|
2979
|
+
}
|
|
2980
|
+
json(res, 200, { success: true, email: data.user?.email, username: data.user?.username });
|
|
2981
|
+
} catch (err2) {
|
|
2982
|
+
json(res, 500, { error: err2.message });
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
var USEAI_API;
|
|
2728
2986
|
var init_local_api = __esm({
|
|
2729
2987
|
"src/dashboard/local-api.ts"() {
|
|
2730
2988
|
"use strict";
|
|
2731
2989
|
init_dist();
|
|
2990
|
+
USEAI_API = "https://api.useai.dev";
|
|
2732
2991
|
}
|
|
2733
2992
|
});
|
|
2734
2993
|
|
|
@@ -3017,6 +3276,14 @@ async function startDaemon(port) {
|
|
|
3017
3276
|
await handleLocalSync(req, res);
|
|
3018
3277
|
return;
|
|
3019
3278
|
}
|
|
3279
|
+
if (url.pathname === "/api/local/auth/send-otp" && req.method === "POST") {
|
|
3280
|
+
await handleLocalSendOtp(req, res);
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
if (url.pathname === "/api/local/auth/verify-otp" && req.method === "POST") {
|
|
3284
|
+
await handleLocalVerifyOtp(req, res);
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3020
3287
|
if (url.pathname === "/api/seal-active" && req.method === "POST") {
|
|
3021
3288
|
const sids = [...sessions.keys()];
|
|
3022
3289
|
for (const sid of sids) {
|