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