clementine-agent 1.4.3 → 1.5.0
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/cli/dashboard.js +954 -164
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -866,7 +866,13 @@ function getStatus() {
|
|
|
866
866
|
try {
|
|
867
867
|
const st = JSON.parse(readFileSync(statusFile, 'utf-8'));
|
|
868
868
|
if (st.status === 'running') {
|
|
869
|
-
|
|
869
|
+
// If the unleashed run has a real jobName, use it; otherwise just say
|
|
870
|
+
// "Deep work in progress" — exposing the auto-generated dir id like
|
|
871
|
+
// "deep-1776118926610..." in the header reads as debug noise.
|
|
872
|
+
const friendly = st.jobName && !/^deep-\d{10,}$/.test(st.jobName)
|
|
873
|
+
? st.jobName
|
|
874
|
+
: null;
|
|
875
|
+
currentActivity = friendly ? ('Deep work: ' + friendly) : 'Deep work in progress';
|
|
870
876
|
break;
|
|
871
877
|
}
|
|
872
878
|
}
|
|
@@ -2240,6 +2246,359 @@ export async function cmdDashboard(opts) {
|
|
|
2240
2246
|
writeFileSync(queueFile, JSON.stringify(queue, null, 2));
|
|
2241
2247
|
res.json({ ok: true, id });
|
|
2242
2248
|
});
|
|
2249
|
+
app.get('/api/home-digest', async (_req, res) => {
|
|
2250
|
+
try {
|
|
2251
|
+
const now = new Date();
|
|
2252
|
+
const dayName = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][now.getDay()];
|
|
2253
|
+
const dayLabel = now.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' });
|
|
2254
|
+
const hour = now.getHours();
|
|
2255
|
+
const greetingPart = hour < 5 ? 'Good evening' : hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening';
|
|
2256
|
+
// ── Resolve user name (USER_MODEL.md → MEMORY.md → env → OS user) ──
|
|
2257
|
+
let userName = null;
|
|
2258
|
+
const tryName = (v) => {
|
|
2259
|
+
if (typeof v === 'string' && v.trim()) {
|
|
2260
|
+
userName = v.trim().split(/\s+/)[0];
|
|
2261
|
+
return true;
|
|
2262
|
+
}
|
|
2263
|
+
return false;
|
|
2264
|
+
};
|
|
2265
|
+
try {
|
|
2266
|
+
const matter = (await import('gray-matter')).default;
|
|
2267
|
+
const userModelPath = path.join(VAULT_DIR, '00-System', 'USER_MODEL.md');
|
|
2268
|
+
if (!userName && existsSync(userModelPath)) {
|
|
2269
|
+
const um = matter(readFileSync(userModelPath, 'utf-8'));
|
|
2270
|
+
const d = um.data;
|
|
2271
|
+
tryName(d.preferred_name) || tryName(d.first_name) || tryName(d.name) || tryName(d.user_name);
|
|
2272
|
+
}
|
|
2273
|
+
const memoryPath = path.join(VAULT_DIR, '00-System', 'MEMORY.md');
|
|
2274
|
+
if (!userName && existsSync(memoryPath)) {
|
|
2275
|
+
const mm = matter(readFileSync(memoryPath, 'utf-8'));
|
|
2276
|
+
const d = mm.data;
|
|
2277
|
+
tryName(d.preferred_name) || tryName(d.first_name) || tryName(d.name) || tryName(d.user_name);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
catch { /* */ }
|
|
2281
|
+
if (!userName)
|
|
2282
|
+
tryName(process.env.CLEMENTINE_USER_NAME || process.env.USER_NAME);
|
|
2283
|
+
if (!userName) {
|
|
2284
|
+
try {
|
|
2285
|
+
const os = await import('os');
|
|
2286
|
+
const u = os.userInfo().username;
|
|
2287
|
+
if (u && u !== 'root')
|
|
2288
|
+
tryName(u.replace(/[._-].*$/, ''));
|
|
2289
|
+
}
|
|
2290
|
+
catch { /* */ }
|
|
2291
|
+
}
|
|
2292
|
+
if (!userName)
|
|
2293
|
+
userName = 'there';
|
|
2294
|
+
// Capitalize first letter so "nathan" → "Nathan"
|
|
2295
|
+
userName = userName.charAt(0).toUpperCase() + userName.slice(1);
|
|
2296
|
+
// ── KPIs ──
|
|
2297
|
+
const kpis = {
|
|
2298
|
+
activeRuns: 0,
|
|
2299
|
+
runsToday: 0,
|
|
2300
|
+
timeSavedMinutes: 0,
|
|
2301
|
+
pendingApprovals: 0,
|
|
2302
|
+
overdueTasks: 0,
|
|
2303
|
+
};
|
|
2304
|
+
// active unleashed runs
|
|
2305
|
+
try {
|
|
2306
|
+
const unleashedDir = path.join(BASE_DIR, 'unleashed');
|
|
2307
|
+
if (existsSync(unleashedDir)) {
|
|
2308
|
+
const dirs = readdirSync(unleashedDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
2309
|
+
for (const d of dirs) {
|
|
2310
|
+
const statusFile = path.join(unleashedDir, d.name, 'status.json');
|
|
2311
|
+
if (existsSync(statusFile)) {
|
|
2312
|
+
try {
|
|
2313
|
+
const s = JSON.parse(readFileSync(statusFile, 'utf-8'));
|
|
2314
|
+
if (s.status === 'running')
|
|
2315
|
+
kpis.activeRuns++;
|
|
2316
|
+
}
|
|
2317
|
+
catch { /* */ }
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
catch { /* */ }
|
|
2323
|
+
// runs today + time saved (rough heuristic: 5 min per cron run)
|
|
2324
|
+
try {
|
|
2325
|
+
const cronRunsDir = path.join(BASE_DIR, 'cron', 'runs');
|
|
2326
|
+
if (existsSync(cronRunsDir)) {
|
|
2327
|
+
const today = now.toISOString().slice(0, 10);
|
|
2328
|
+
const files = readdirSync(cronRunsDir).filter(f => f.endsWith('.jsonl'));
|
|
2329
|
+
for (const f of files) {
|
|
2330
|
+
try {
|
|
2331
|
+
const lines = readFileSync(path.join(cronRunsDir, f), 'utf-8').trim().split('\n').filter(Boolean);
|
|
2332
|
+
for (const line of lines.slice(-30)) {
|
|
2333
|
+
try {
|
|
2334
|
+
const e = JSON.parse(line);
|
|
2335
|
+
const ts = e.completedAt || e.startedAt || e.timestamp;
|
|
2336
|
+
if (!ts)
|
|
2337
|
+
continue;
|
|
2338
|
+
if (String(ts).slice(0, 10) === today)
|
|
2339
|
+
kpis.runsToday++;
|
|
2340
|
+
}
|
|
2341
|
+
catch { /* */ }
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
catch { /* */ }
|
|
2345
|
+
}
|
|
2346
|
+
// Time saved this week — count last 7 days × 5 min
|
|
2347
|
+
const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
2348
|
+
let weeklyRuns = 0;
|
|
2349
|
+
for (const f of files) {
|
|
2350
|
+
try {
|
|
2351
|
+
const lines = readFileSync(path.join(cronRunsDir, f), 'utf-8').trim().split('\n').filter(Boolean);
|
|
2352
|
+
for (const line of lines) {
|
|
2353
|
+
try {
|
|
2354
|
+
const e = JSON.parse(line);
|
|
2355
|
+
const ts = new Date(e.completedAt || e.startedAt || e.timestamp || 0).getTime();
|
|
2356
|
+
if (ts >= weekAgo)
|
|
2357
|
+
weeklyRuns++;
|
|
2358
|
+
}
|
|
2359
|
+
catch { /* */ }
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
catch { /* */ }
|
|
2363
|
+
}
|
|
2364
|
+
kpis.timeSavedMinutes = weeklyRuns * 5;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
catch { /* */ }
|
|
2368
|
+
// pending approvals (self-improve)
|
|
2369
|
+
try {
|
|
2370
|
+
const siDir = path.join(BASE_DIR, 'self-improve');
|
|
2371
|
+
const proposalsFile = path.join(siDir, 'proposals.json');
|
|
2372
|
+
if (existsSync(proposalsFile)) {
|
|
2373
|
+
const data = JSON.parse(readFileSync(proposalsFile, 'utf-8'));
|
|
2374
|
+
const items = Array.isArray(data) ? data : (data.proposals || []);
|
|
2375
|
+
kpis.pendingApprovals = items.filter((p) => p.status === 'pending').length;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
catch { /* */ }
|
|
2379
|
+
// overdue tasks
|
|
2380
|
+
try {
|
|
2381
|
+
const tasksFile = path.join(VAULT_DIR, '05-Tasks', 'TASKS.md');
|
|
2382
|
+
if (existsSync(tasksFile)) {
|
|
2383
|
+
const content = readFileSync(tasksFile, 'utf-8');
|
|
2384
|
+
const todayStr = now.toISOString().slice(0, 10);
|
|
2385
|
+
// Match "- [ ] task @YYYY-MM-DD" pattern; overdue if date < today
|
|
2386
|
+
const lines = content.split('\n').filter(l => /^[-*]\s*\[ \]/.test(l));
|
|
2387
|
+
for (const line of lines) {
|
|
2388
|
+
const m = line.match(/@(\d{4}-\d{2}-\d{2})/);
|
|
2389
|
+
if (m && m[1] < todayStr)
|
|
2390
|
+
kpis.overdueTasks++;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
catch { /* */ }
|
|
2395
|
+
// ── Today's runs ──
|
|
2396
|
+
const todayRuns = [];
|
|
2397
|
+
try {
|
|
2398
|
+
const { parseCronJobs, parseAgentCronJobs } = await import('../gateway/cron-scheduler.js');
|
|
2399
|
+
const all = [...parseCronJobs(), ...parseAgentCronJobs(path.join(VAULT_DIR, '00-System', 'agents'))];
|
|
2400
|
+
const cronParser = await import('cron-parser');
|
|
2401
|
+
const todayStart = new Date(now.toDateString()).getTime();
|
|
2402
|
+
const todayEnd = todayStart + 24 * 60 * 60 * 1000;
|
|
2403
|
+
for (const job of all) {
|
|
2404
|
+
if (!job.enabled)
|
|
2405
|
+
continue;
|
|
2406
|
+
let next = null;
|
|
2407
|
+
let firedToday = false;
|
|
2408
|
+
try {
|
|
2409
|
+
const interval = cronParser.CronExpressionParser?.parse?.(job.schedule, { currentDate: now }) ??
|
|
2410
|
+
cronParser.default?.parseExpression?.(job.schedule, { currentDate: now }) ??
|
|
2411
|
+
cronParser.parseExpression?.(job.schedule, { currentDate: now });
|
|
2412
|
+
const nextDate = typeof interval.next === 'function' ? interval.next().toDate() : interval.next();
|
|
2413
|
+
next = nextDate.toISOString();
|
|
2414
|
+
}
|
|
2415
|
+
catch { /* */ }
|
|
2416
|
+
// Did it fire today already? Look at cron runs.
|
|
2417
|
+
try {
|
|
2418
|
+
const runsFile = path.join(BASE_DIR, 'cron', 'runs', job.name + '.jsonl');
|
|
2419
|
+
if (existsSync(runsFile)) {
|
|
2420
|
+
const tail = readFileSync(runsFile, 'utf-8').trim().split('\n').filter(Boolean).slice(-12);
|
|
2421
|
+
for (const line of tail) {
|
|
2422
|
+
try {
|
|
2423
|
+
const e = JSON.parse(line);
|
|
2424
|
+
const ts = new Date(e.completedAt || e.startedAt || 0).getTime();
|
|
2425
|
+
if (ts >= todayStart && ts < todayEnd) {
|
|
2426
|
+
firedToday = true;
|
|
2427
|
+
break;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
catch { /* */ }
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
catch { /* */ }
|
|
2435
|
+
todayRuns.push({
|
|
2436
|
+
name: job.name,
|
|
2437
|
+
nextRun: next,
|
|
2438
|
+
lastRun: null,
|
|
2439
|
+
firedToday,
|
|
2440
|
+
agentSlug: job.agentSlug ?? null,
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
catch { /* */ }
|
|
2445
|
+
// Sort: fired today first (so user sees what already happened), then upcoming by nextRun
|
|
2446
|
+
todayRuns.sort((a, b) => {
|
|
2447
|
+
if (a.firedToday !== b.firedToday)
|
|
2448
|
+
return a.firedToday ? -1 : 1;
|
|
2449
|
+
if (!a.nextRun)
|
|
2450
|
+
return 1;
|
|
2451
|
+
if (!b.nextRun)
|
|
2452
|
+
return -1;
|
|
2453
|
+
return new Date(a.nextRun).getTime() - new Date(b.nextRun).getTime();
|
|
2454
|
+
});
|
|
2455
|
+
// ── Briefing: parse today's daily note ──
|
|
2456
|
+
const briefing = {
|
|
2457
|
+
highlights: [],
|
|
2458
|
+
needsReview: [],
|
|
2459
|
+
};
|
|
2460
|
+
const todayStr = now.toISOString().slice(0, 10);
|
|
2461
|
+
const dailyNotePath = path.join(VAULT_DIR, '01-Daily-Notes', todayStr + '.md');
|
|
2462
|
+
try {
|
|
2463
|
+
if (existsSync(dailyNotePath)) {
|
|
2464
|
+
const note = readFileSync(dailyNotePath, 'utf-8');
|
|
2465
|
+
// Parse Sends (cron job result lines)
|
|
2466
|
+
const sendsMatch = note.match(/##\s+([\w-]+)\s+\d{4}-\d{2}-\d{2}\s*\n\s*\n###\s+Sends\s*\n([\s\S]*?)(?:\n###|\n##|$)/g);
|
|
2467
|
+
if (sendsMatch) {
|
|
2468
|
+
for (const block of sendsMatch.slice(0, 3)) {
|
|
2469
|
+
const titleM = block.match(/##\s+([\w-]+)\s/);
|
|
2470
|
+
const title = titleM ? titleM[1] : 'cron';
|
|
2471
|
+
const sendLines = block.split('\n').filter(l => l.trim().startsWith('- ')).slice(0, 5);
|
|
2472
|
+
if (sendLines.length > 0) {
|
|
2473
|
+
briefing.highlights.push({
|
|
2474
|
+
text: title + ' sent ' + sendLines.length + (sendLines.length === 1 ? ' message' : ' messages'),
|
|
2475
|
+
source: 'daily-note',
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
// Parse one-line summary lines like "<job-name> 2026-04-28: T2: 0 sent | T3: 0 ... | Replies: 0 | Skipped: ..."
|
|
2481
|
+
const summaryLines = note.split('\n').filter(l => /\d{4}-\d{2}-\d{2}.*Replies/i.test(l)).slice(0, 3);
|
|
2482
|
+
for (const s of summaryLines) {
|
|
2483
|
+
briefing.highlights.push({ text: s.trim().replace(/^.*?\d{4}-\d{2}-\d{2}:?\s*/, ''), source: 'daily-note' });
|
|
2484
|
+
}
|
|
2485
|
+
// Parse "## Interactions" entries
|
|
2486
|
+
const interactionsMatch = note.match(/## Interactions\s*\n([\s\S]*?)(?:\n##|$)/);
|
|
2487
|
+
if (interactionsMatch) {
|
|
2488
|
+
const lines = interactionsMatch[1].split('\n').filter(l => l.trim().startsWith('- **'));
|
|
2489
|
+
// Take the most informative lines (longest contentful body)
|
|
2490
|
+
const interactiveOnes = lines
|
|
2491
|
+
.map(l => {
|
|
2492
|
+
const stripped = l.replace(/^- \*\*[^*]+\*\*[^:]*:\s*/, '');
|
|
2493
|
+
return { line: l, body: stripped, len: stripped.length };
|
|
2494
|
+
})
|
|
2495
|
+
.filter(x => x.len > 40)
|
|
2496
|
+
.sort((a, b) => b.len - a.len)
|
|
2497
|
+
.slice(0, 4);
|
|
2498
|
+
for (const i of interactiveOnes) {
|
|
2499
|
+
const titleM = i.line.match(/\*\*([^*]+)\*\*/);
|
|
2500
|
+
const title = titleM ? titleM[1] : 'agent';
|
|
2501
|
+
briefing.highlights.push({
|
|
2502
|
+
text: '[' + title + '] ' + i.body.slice(0, 200),
|
|
2503
|
+
source: 'interactions',
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
catch { /* */ }
|
|
2510
|
+
// Fallback: if briefing.highlights is sparse, pull from /api/activity-style events
|
|
2511
|
+
if (briefing.highlights.length < 3) {
|
|
2512
|
+
try {
|
|
2513
|
+
const cronRunsDir = path.join(BASE_DIR, 'cron', 'runs');
|
|
2514
|
+
if (existsSync(cronRunsDir)) {
|
|
2515
|
+
const todayMs = new Date(todayStr + 'T00:00:00Z').getTime();
|
|
2516
|
+
const recent = [];
|
|
2517
|
+
for (const f of readdirSync(cronRunsDir).filter(x => x.endsWith('.jsonl')).slice(0, 30)) {
|
|
2518
|
+
try {
|
|
2519
|
+
const lines = readFileSync(path.join(cronRunsDir, f), 'utf-8').trim().split('\n').filter(Boolean).slice(-5);
|
|
2520
|
+
for (const line of lines) {
|
|
2521
|
+
try {
|
|
2522
|
+
const e = JSON.parse(line);
|
|
2523
|
+
const ts = new Date(e.completedAt || e.startedAt || 0).getTime();
|
|
2524
|
+
if (ts < todayMs)
|
|
2525
|
+
continue;
|
|
2526
|
+
const body = (e.responsePreview || e.summary || e.output || '').slice(0, 180);
|
|
2527
|
+
if (!body)
|
|
2528
|
+
continue;
|
|
2529
|
+
recent.push({ ts, title: f.replace('.jsonl', ''), body });
|
|
2530
|
+
}
|
|
2531
|
+
catch { /* */ }
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
catch { /* */ }
|
|
2535
|
+
}
|
|
2536
|
+
recent.sort((a, b) => b.ts - a.ts);
|
|
2537
|
+
for (const r of recent.slice(0, 4 - briefing.highlights.length)) {
|
|
2538
|
+
briefing.highlights.push({ text: '[' + r.title + '] ' + r.body, source: 'activity' });
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
catch { /* */ }
|
|
2543
|
+
}
|
|
2544
|
+
// Needs review: pending approvals + overdue + broken jobs + active unleashed
|
|
2545
|
+
if (kpis.pendingApprovals > 0) {
|
|
2546
|
+
briefing.needsReview.push({
|
|
2547
|
+
text: kpis.pendingApprovals + ' self-improve proposal' + (kpis.pendingApprovals === 1 ? '' : 's') + ' awaiting review',
|
|
2548
|
+
href: '#brain/learning',
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
if (kpis.overdueTasks > 0) {
|
|
2552
|
+
briefing.needsReview.push({
|
|
2553
|
+
text: kpis.overdueTasks + ' overdue task' + (kpis.overdueTasks === 1 ? '' : 's'),
|
|
2554
|
+
href: '#team/goals',
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
if (kpis.activeRuns > 0) {
|
|
2558
|
+
briefing.needsReview.push({
|
|
2559
|
+
text: kpis.activeRuns + ' unleashed task' + (kpis.activeRuns === 1 ? '' : 's') + ' running',
|
|
2560
|
+
href: '#build/workflows',
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
// Broken jobs (consecutive failures)
|
|
2564
|
+
try {
|
|
2565
|
+
const cronRunsDir = path.join(BASE_DIR, 'cron', 'runs');
|
|
2566
|
+
if (existsSync(cronRunsDir)) {
|
|
2567
|
+
for (const f of readdirSync(cronRunsDir).filter(x => x.endsWith('.jsonl'))) {
|
|
2568
|
+
try {
|
|
2569
|
+
const tail = readFileSync(path.join(cronRunsDir, f), 'utf-8').trim().split('\n').filter(Boolean).slice(-3);
|
|
2570
|
+
const failures = tail.filter(l => {
|
|
2571
|
+
try {
|
|
2572
|
+
const e = JSON.parse(l);
|
|
2573
|
+
return e.status === 'error' || e.error;
|
|
2574
|
+
}
|
|
2575
|
+
catch {
|
|
2576
|
+
return false;
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2579
|
+
if (failures.length >= 2) {
|
|
2580
|
+
briefing.needsReview.push({
|
|
2581
|
+
text: f.replace('.jsonl', '') + ' failing — last ' + failures.length + ' runs errored',
|
|
2582
|
+
href: '#build/crons',
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
catch { /* */ }
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
catch { /* */ }
|
|
2591
|
+
res.json({
|
|
2592
|
+
greeting: { phrase: greetingPart, name: userName, dayLabel, dayName },
|
|
2593
|
+
kpis,
|
|
2594
|
+
briefing,
|
|
2595
|
+
todayRuns: todayRuns.slice(0, 6),
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
catch (err) {
|
|
2599
|
+
res.status(500).json({ error: String(err) });
|
|
2600
|
+
}
|
|
2601
|
+
});
|
|
2243
2602
|
app.get('/api/vault-files', async (req, res) => {
|
|
2244
2603
|
try {
|
|
2245
2604
|
const limit = Math.min(parseInt(String(req.query.limit ?? '120'), 10) || 120, 500);
|
|
@@ -8346,6 +8705,22 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8346
8705
|
--motion-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
8347
8706
|
/* ── Focus ring ── */
|
|
8348
8707
|
--ring: 0 0 0 2px var(--clementine), 0 0 0 4px rgba(255, 140, 33, 0.18);
|
|
8708
|
+
/* ── 0-10 numerical bg/text scales (additive — existing semantic tokens still work) ── */
|
|
8709
|
+
--bg-0: #ffffff;
|
|
8710
|
+
--bg-1: #fcfdfe;
|
|
8711
|
+
--bg-2: #f8fafc;
|
|
8712
|
+
--bg-3: #f1f5f9;
|
|
8713
|
+
--bg-4: #e2e8f0;
|
|
8714
|
+
--bg-5: #cbd5e1;
|
|
8715
|
+
--bg-6: #94a3b8;
|
|
8716
|
+
--bg-7: #64748b;
|
|
8717
|
+
--bg-8: #475569;
|
|
8718
|
+
--bg-9: #1e293b;
|
|
8719
|
+
--bg-10: #0f172a;
|
|
8720
|
+
--text-1: #0f172a;
|
|
8721
|
+
--text-2: #334155;
|
|
8722
|
+
--text-3: #64748b;
|
|
8723
|
+
--text-4: #94a3b8;
|
|
8349
8724
|
}
|
|
8350
8725
|
[data-theme="dark"] {
|
|
8351
8726
|
--bg-primary: #0b0f17;
|
|
@@ -8377,6 +8752,22 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8377
8752
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
8378
8753
|
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.6), 0 4px 8px rgba(0, 0, 0, 0.4);
|
|
8379
8754
|
--ring: 0 0 0 2px var(--clementine), 0 0 0 4px rgba(255, 165, 79, 0.22);
|
|
8755
|
+
/* Dark variants of the 0-10 scale */
|
|
8756
|
+
--bg-0: #0b0f17;
|
|
8757
|
+
--bg-1: #0f1421;
|
|
8758
|
+
--bg-2: #131923;
|
|
8759
|
+
--bg-3: #1a212d;
|
|
8760
|
+
--bg-4: #1f2733;
|
|
8761
|
+
--bg-5: #2d3748;
|
|
8762
|
+
--bg-6: #475569;
|
|
8763
|
+
--bg-7: #64748b;
|
|
8764
|
+
--bg-8: #94a3b8;
|
|
8765
|
+
--bg-9: #cbd5e1;
|
|
8766
|
+
--bg-10: #f1f5f9;
|
|
8767
|
+
--text-1: #e2e8f0;
|
|
8768
|
+
--text-2: #cbd5e1;
|
|
8769
|
+
--text-3: #94a3b8;
|
|
8770
|
+
--text-4: #64748b;
|
|
8380
8771
|
}
|
|
8381
8772
|
/* OS-preference dark mode unless user has explicitly chosen a theme */
|
|
8382
8773
|
@media (prefers-color-scheme: dark) {
|
|
@@ -8416,6 +8807,17 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8416
8807
|
box-shadow: var(--ring);
|
|
8417
8808
|
border-radius: var(--radius-xs);
|
|
8418
8809
|
}
|
|
8810
|
+
/* Honor user's reduced-motion preference: kill every animation/transition. */
|
|
8811
|
+
@media (prefers-reduced-motion: reduce) {
|
|
8812
|
+
*,
|
|
8813
|
+
*::before,
|
|
8814
|
+
*::after {
|
|
8815
|
+
animation-duration: 0.001ms !important;
|
|
8816
|
+
animation-iteration-count: 1 !important;
|
|
8817
|
+
transition-duration: 0.001ms !important;
|
|
8818
|
+
scroll-behavior: auto !important;
|
|
8819
|
+
}
|
|
8820
|
+
}
|
|
8419
8821
|
body {
|
|
8420
8822
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
8421
8823
|
font-feature-settings: 'cv02', 'cv11', 'ss01', 'ss03';
|
|
@@ -8787,66 +9189,279 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8787
9189
|
.icn-lg { width: 22px; height: 22px; stroke-width: 1.75; }
|
|
8788
9190
|
.icn-xl { width: 28px; height: 28px; stroke-width: 1.5; }
|
|
8789
9191
|
|
|
8790
|
-
/* ── Home
|
|
8791
|
-
.home-
|
|
9192
|
+
/* ── Home (v1.5 daily-driver dashboard) ─────── */
|
|
9193
|
+
.home-shell {
|
|
9194
|
+
max-width: 1180px;
|
|
9195
|
+
margin: 0 auto;
|
|
9196
|
+
padding: 24px 28px 120px;
|
|
9197
|
+
display: flex;
|
|
9198
|
+
flex-direction: column;
|
|
9199
|
+
gap: 22px;
|
|
9200
|
+
}
|
|
9201
|
+
.home-greeting {
|
|
9202
|
+
display: flex;
|
|
9203
|
+
align-items: flex-end;
|
|
9204
|
+
justify-content: space-between;
|
|
9205
|
+
gap: 16px;
|
|
9206
|
+
margin-top: 4px;
|
|
9207
|
+
}
|
|
9208
|
+
.home-greeting-text h1 {
|
|
9209
|
+
font-size: 26px;
|
|
9210
|
+
font-weight: 600;
|
|
9211
|
+
letter-spacing: -0.025em;
|
|
9212
|
+
color: var(--text-primary);
|
|
9213
|
+
margin: 0;
|
|
9214
|
+
line-height: 1.15;
|
|
9215
|
+
}
|
|
9216
|
+
.home-greeting-day {
|
|
9217
|
+
font-size: var(--text-base);
|
|
9218
|
+
color: var(--text-muted);
|
|
9219
|
+
margin: 4px 0 0;
|
|
9220
|
+
}
|
|
9221
|
+
.home-greeting-actions { display: flex; gap: 8px; }
|
|
9222
|
+
|
|
9223
|
+
/* KPI strip */
|
|
9224
|
+
.kpi-strip {
|
|
8792
9225
|
display: grid;
|
|
8793
|
-
grid-template-columns: 1fr
|
|
8794
|
-
gap:
|
|
8795
|
-
height: calc(100vh - var(--header-h));
|
|
8796
|
-
padding: 18px;
|
|
8797
|
-
box-sizing: border-box;
|
|
9226
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
9227
|
+
gap: 10px;
|
|
8798
9228
|
}
|
|
8799
|
-
.
|
|
9229
|
+
.kpi-tile {
|
|
9230
|
+
background: var(--bg-card);
|
|
9231
|
+
border: 1px solid var(--border);
|
|
9232
|
+
border-radius: var(--radius-md);
|
|
9233
|
+
padding: 12px 14px;
|
|
8800
9234
|
display: flex;
|
|
8801
9235
|
flex-direction: column;
|
|
9236
|
+
gap: 4px;
|
|
9237
|
+
transition: background var(--motion), border-color var(--motion), transform var(--motion-fast);
|
|
9238
|
+
position: relative;
|
|
9239
|
+
min-width: 0;
|
|
9240
|
+
}
|
|
9241
|
+
.kpi-tile[onclick] { cursor: pointer; }
|
|
9242
|
+
.kpi-tile[onclick]:hover { background: var(--bg-hover); border-color: var(--clementine); transform: translateY(-1px); }
|
|
9243
|
+
.kpi-tile .kpi-icon {
|
|
9244
|
+
width: 22px;
|
|
9245
|
+
height: 22px;
|
|
9246
|
+
display: inline-flex;
|
|
9247
|
+
align-items: center;
|
|
9248
|
+
justify-content: center;
|
|
9249
|
+
color: var(--text-muted);
|
|
9250
|
+
margin-bottom: 2px;
|
|
9251
|
+
}
|
|
9252
|
+
.kpi-tile .kpi-icon .icn { width: 16px; height: 16px; }
|
|
9253
|
+
.kpi-tile .kpi-value {
|
|
9254
|
+
font-size: 22px;
|
|
9255
|
+
font-weight: 600;
|
|
9256
|
+
color: var(--text-primary);
|
|
9257
|
+
line-height: 1;
|
|
9258
|
+
letter-spacing: -0.01em;
|
|
9259
|
+
}
|
|
9260
|
+
.kpi-tile .kpi-value.muted { color: var(--text-muted); font-weight: 500; }
|
|
9261
|
+
.kpi-tile .kpi-label {
|
|
9262
|
+
font-size: var(--text-xs);
|
|
9263
|
+
color: var(--text-muted);
|
|
9264
|
+
text-transform: uppercase;
|
|
9265
|
+
letter-spacing: 0.05em;
|
|
9266
|
+
font-weight: 500;
|
|
9267
|
+
}
|
|
9268
|
+
.kpi-tile.alert .kpi-value { color: var(--clementine); }
|
|
9269
|
+
.kpi-tile.alert::before {
|
|
9270
|
+
content: '';
|
|
9271
|
+
position: absolute;
|
|
9272
|
+
top: 0; right: 0;
|
|
9273
|
+
width: 6px; height: 6px;
|
|
9274
|
+
border-radius: 50%;
|
|
9275
|
+
background: var(--clementine);
|
|
9276
|
+
transform: translate(50%, -50%);
|
|
9277
|
+
box-shadow: 0 0 0 3px var(--bg-primary);
|
|
9278
|
+
}
|
|
9279
|
+
|
|
9280
|
+
/* Two-column home grid (briefing + today's runs) */
|
|
9281
|
+
.home-grid-2 {
|
|
9282
|
+
display: grid;
|
|
9283
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
|
8802
9284
|
gap: 14px;
|
|
8803
|
-
|
|
8804
|
-
overflow-y: auto;
|
|
9285
|
+
align-items: start;
|
|
8805
9286
|
}
|
|
8806
|
-
.
|
|
9287
|
+
.briefing-card .card-body { padding: 14px 18px; line-height: 1.55; }
|
|
9288
|
+
.briefing-section-title {
|
|
9289
|
+
font-size: var(--text-xs);
|
|
9290
|
+
text-transform: uppercase;
|
|
9291
|
+
letter-spacing: 0.06em;
|
|
9292
|
+
color: var(--text-muted);
|
|
9293
|
+
font-weight: 600;
|
|
9294
|
+
margin: 4px 0 8px;
|
|
9295
|
+
}
|
|
9296
|
+
.briefing-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
9297
|
+
.briefing-item {
|
|
8807
9298
|
display: flex;
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
9299
|
+
align-items: flex-start;
|
|
9300
|
+
gap: 10px;
|
|
9301
|
+
font-size: var(--text-base);
|
|
9302
|
+
color: var(--text-primary);
|
|
9303
|
+
line-height: 1.45;
|
|
9304
|
+
}
|
|
9305
|
+
.briefing-item::before {
|
|
9306
|
+
content: '';
|
|
9307
|
+
width: 4px;
|
|
9308
|
+
height: 4px;
|
|
9309
|
+
border-radius: 50%;
|
|
9310
|
+
background: var(--clementine);
|
|
9311
|
+
margin-top: 8px;
|
|
9312
|
+
flex-shrink: 0;
|
|
9313
|
+
}
|
|
9314
|
+
.briefing-item.review {
|
|
9315
|
+
background: var(--clementine-bg);
|
|
9316
|
+
padding: 8px 10px;
|
|
9317
|
+
border-radius: var(--radius-sm);
|
|
9318
|
+
cursor: pointer;
|
|
9319
|
+
transition: background var(--motion);
|
|
9320
|
+
}
|
|
9321
|
+
.briefing-item.review::before { background: var(--clementine); }
|
|
9322
|
+
.briefing-item.review:hover { background: rgba(255,140,33,0.14); }
|
|
9323
|
+
.briefing-item.review .arrow { margin-left: auto; color: var(--clementine); flex-shrink: 0; }
|
|
9324
|
+
|
|
9325
|
+
.runs-card .card-body { padding: 8px 0; }
|
|
9326
|
+
.run-row {
|
|
9327
|
+
display: flex;
|
|
9328
|
+
align-items: center;
|
|
9329
|
+
gap: 10px;
|
|
9330
|
+
padding: 8px 16px;
|
|
9331
|
+
font-size: var(--text-base);
|
|
9332
|
+
border-left: 2px solid transparent;
|
|
9333
|
+
transition: background var(--motion), border-color var(--motion);
|
|
9334
|
+
}
|
|
9335
|
+
.run-row.fired {
|
|
9336
|
+
border-left-color: var(--green);
|
|
9337
|
+
color: var(--text-muted);
|
|
9338
|
+
}
|
|
9339
|
+
.run-row.upcoming { border-left-color: var(--clementine); }
|
|
9340
|
+
.run-row .run-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9341
|
+
.run-row .run-time { font-size: var(--text-xs); color: var(--text-muted); flex-shrink: 0; }
|
|
9342
|
+
.run-row .run-icon { width: 14px; height: 14px; flex-shrink: 0; }
|
|
9343
|
+
.run-row.fired .run-icon { color: var(--green); }
|
|
9344
|
+
.run-row.upcoming .run-icon { color: var(--clementine); }
|
|
9345
|
+
|
|
9346
|
+
.home-activity { margin: 0; }
|
|
9347
|
+
|
|
9348
|
+
@media (max-width: 900px) {
|
|
9349
|
+
.kpi-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
9350
|
+
.home-grid-2 { grid-template-columns: 1fr; }
|
|
9351
|
+
.home-greeting-text h1 { font-size: 22px; }
|
|
9352
|
+
}
|
|
9353
|
+
|
|
9354
|
+
/* ── Floating chat FAB + panel ─────────────── */
|
|
9355
|
+
.home-chat-fab {
|
|
9356
|
+
position: fixed;
|
|
9357
|
+
right: 22px;
|
|
9358
|
+
bottom: 22px;
|
|
9359
|
+
z-index: 200;
|
|
9360
|
+
background: var(--clementine);
|
|
9361
|
+
color: #fff;
|
|
9362
|
+
border: none;
|
|
9363
|
+
border-radius: 999px;
|
|
9364
|
+
padding: 12px 18px;
|
|
9365
|
+
display: inline-flex;
|
|
9366
|
+
align-items: center;
|
|
9367
|
+
gap: 8px;
|
|
9368
|
+
font-size: var(--text-base);
|
|
9369
|
+
font-weight: 500;
|
|
9370
|
+
box-shadow: var(--shadow-md);
|
|
9371
|
+
cursor: pointer;
|
|
9372
|
+
transition: transform var(--motion-fast), box-shadow var(--motion);
|
|
9373
|
+
}
|
|
9374
|
+
.home-chat-fab:hover { transform: translateY(-1px); box-shadow: var(--shadow-lg); }
|
|
9375
|
+
.home-chat-fab .icn { width: 16px; height: 16px; }
|
|
9376
|
+
.home-chat-fab-label { white-space: nowrap; }
|
|
9377
|
+
.home-chat-fab.compact .home-chat-fab-label { display: none; }
|
|
9378
|
+
.home-chat-fab.hidden { display: none; }
|
|
9379
|
+
|
|
9380
|
+
.home-chat-panel {
|
|
9381
|
+
position: fixed;
|
|
9382
|
+
right: 22px;
|
|
9383
|
+
bottom: 22px;
|
|
9384
|
+
z-index: 199;
|
|
9385
|
+
width: 420px;
|
|
9386
|
+
max-width: calc(100vw - 44px);
|
|
9387
|
+
height: 560px;
|
|
9388
|
+
max-height: calc(100vh - var(--header-h) - 44px);
|
|
9389
|
+
background: var(--bg-secondary);
|
|
8812
9390
|
border: 1px solid var(--border);
|
|
8813
|
-
border-radius:
|
|
9391
|
+
border-radius: var(--radius-lg);
|
|
9392
|
+
box-shadow: var(--shadow-lg);
|
|
9393
|
+
display: flex;
|
|
9394
|
+
flex-direction: column;
|
|
8814
9395
|
overflow: hidden;
|
|
8815
|
-
|
|
9396
|
+
transform: translateY(20px) scale(0.96);
|
|
9397
|
+
opacity: 0;
|
|
9398
|
+
pointer-events: none;
|
|
9399
|
+
transition: transform var(--motion), opacity var(--motion);
|
|
9400
|
+
}
|
|
9401
|
+
.home-chat-panel.open {
|
|
9402
|
+
transform: translateY(0) scale(1);
|
|
9403
|
+
opacity: 1;
|
|
9404
|
+
pointer-events: auto;
|
|
9405
|
+
}
|
|
9406
|
+
.home-chat-panel-header {
|
|
9407
|
+
display: flex;
|
|
9408
|
+
align-items: center;
|
|
9409
|
+
gap: 8px;
|
|
9410
|
+
padding: 12px 14px;
|
|
9411
|
+
border-bottom: 1px solid var(--border);
|
|
9412
|
+
font-size: var(--text-base);
|
|
9413
|
+
font-weight: 600;
|
|
9414
|
+
background: var(--bg-secondary);
|
|
8816
9415
|
}
|
|
8817
|
-
.home-chat-
|
|
9416
|
+
.home-chat-panel-header .icn { width: 16px; height: 16px; color: var(--clementine); }
|
|
9417
|
+
.home-chat-panel-header select {
|
|
9418
|
+
padding: 4px 8px;
|
|
9419
|
+
font-size: var(--text-xs);
|
|
9420
|
+
border: 1px solid var(--border);
|
|
9421
|
+
border-radius: var(--radius-xs);
|
|
9422
|
+
background: var(--bg-input);
|
|
9423
|
+
color: var(--text-primary);
|
|
9424
|
+
}
|
|
9425
|
+
.home-chat-panel-messages {
|
|
8818
9426
|
flex: 1;
|
|
8819
9427
|
overflow-y: auto;
|
|
8820
|
-
padding:
|
|
8821
|
-
|
|
9428
|
+
padding: 14px 16px;
|
|
9429
|
+
background: var(--bg-primary);
|
|
8822
9430
|
}
|
|
8823
|
-
.home-chat-input-row {
|
|
9431
|
+
.home-chat-panel-input-row {
|
|
8824
9432
|
display: flex;
|
|
8825
9433
|
gap: 8px;
|
|
8826
|
-
padding: 12px
|
|
9434
|
+
padding: 10px 12px;
|
|
8827
9435
|
border-top: 1px solid var(--border);
|
|
8828
9436
|
background: var(--bg-secondary);
|
|
8829
9437
|
align-items: center;
|
|
8830
9438
|
}
|
|
8831
|
-
.home-chat-input-row input[type="text"] {
|
|
9439
|
+
.home-chat-panel-input-row input[type="text"] {
|
|
8832
9440
|
flex: 1;
|
|
8833
|
-
padding:
|
|
9441
|
+
padding: 8px 12px;
|
|
8834
9442
|
border: 1px solid var(--border);
|
|
8835
|
-
border-radius:
|
|
9443
|
+
border-radius: var(--radius-sm);
|
|
8836
9444
|
background: var(--bg-input);
|
|
8837
9445
|
color: var(--text-primary);
|
|
8838
|
-
font-size:
|
|
9446
|
+
font-size: var(--text-base);
|
|
9447
|
+
font-family: inherit;
|
|
8839
9448
|
}
|
|
8840
|
-
.home-chat-input-row
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
9449
|
+
.home-chat-panel-input-row .btn-icon { padding: 6px; }
|
|
9450
|
+
.home-chat-panel-input-row .icn { width: 14px; height: 14px; }
|
|
9451
|
+
|
|
9452
|
+
@media (max-width: 600px) {
|
|
9453
|
+
.home-chat-panel {
|
|
9454
|
+
right: 8px;
|
|
9455
|
+
bottom: 8px;
|
|
9456
|
+
width: calc(100vw - 16px);
|
|
9457
|
+
height: calc(100vh - var(--header-h) - 16px);
|
|
9458
|
+
}
|
|
9459
|
+
.home-chat-fab {
|
|
9460
|
+
right: 12px;
|
|
9461
|
+
bottom: 12px;
|
|
9462
|
+
padding: 10px 14px;
|
|
9463
|
+
}
|
|
8847
9464
|
}
|
|
8848
|
-
.home-chat-input-row button { padding: 9px 18px; border-radius: 8px; }
|
|
8849
|
-
.home-activity { margin: 0; }
|
|
8850
9465
|
|
|
8851
9466
|
/* Right rail */
|
|
8852
9467
|
.home-rail {
|
|
@@ -11526,11 +12141,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11526
12141
|
function run() {
|
|
11527
12142
|
document.querySelectorAll('[data-icon]').forEach(function(el) {
|
|
11528
12143
|
if (el.dataset._iconHydrated) return;
|
|
12144
|
+
if (!window.LUCIDE || !window.lucide) return;
|
|
11529
12145
|
el.dataset._iconHydrated = '1';
|
|
11530
12146
|
var name = el.getAttribute('data-icon');
|
|
11531
|
-
if (!window.LUCIDE || !window.lucide) return;
|
|
11532
12147
|
var slot = el.querySelector('.nav-icon, .hire-plus, .icon-slot');
|
|
11533
|
-
|
|
12148
|
+
var target = slot || el;
|
|
12149
|
+
target.innerHTML = window.lucide(name, 'icn-md');
|
|
11534
12150
|
});
|
|
11535
12151
|
}
|
|
11536
12152
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run);
|
|
@@ -11546,143 +12162,140 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11546
12162
|
<!-- Content -->
|
|
11547
12163
|
<div class="content">
|
|
11548
12164
|
|
|
11549
|
-
<!-- ═══ Home —
|
|
12165
|
+
<!-- ═══ Home — daily-driver dashboard ═══ -->
|
|
11550
12166
|
<div class="page active" id="page-home">
|
|
11551
|
-
<div class="home-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
<div
|
|
11555
|
-
<div class="gs-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
12167
|
+
<div class="home-shell">
|
|
12168
|
+
<!-- Getting Started (shown for new users only) -->
|
|
12169
|
+
<div id="getting-started" class="getting-started" style="display:none">
|
|
12170
|
+
<div class="gs-header">
|
|
12171
|
+
<div class="gs-title">Get Started</div>
|
|
12172
|
+
<div class="gs-subtitle">Set up your AI assistant in 5 steps</div>
|
|
12173
|
+
<button class="btn-ghost btn-sm gs-dismiss" onclick="dismissGettingStarted()" title="Dismiss">×</button>
|
|
12174
|
+
</div>
|
|
12175
|
+
<div class="gs-grid">
|
|
12176
|
+
<div class="gs-card" id="gs-step-auth">
|
|
12177
|
+
<div class="gs-step-num">1</div>
|
|
12178
|
+
<div class="gs-card-icon">🔒</div>
|
|
12179
|
+
<div class="gs-card-title">Login with Anthropic</div>
|
|
12180
|
+
<div class="gs-card-desc" id="gs-auth-desc">Connect your Anthropic account to power your agents.</div>
|
|
12181
|
+
<button class="btn btn-sm btn-primary" id="gs-auth-btn" onclick="startAnthropicOAuth()">Login with Claude</button>
|
|
11559
12182
|
</div>
|
|
11560
|
-
<div class="gs-
|
|
11561
|
-
<div class="gs-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
<button class="btn btn-sm btn-primary" id="gs-auth-btn" onclick="startAnthropicOAuth()">Login with Claude</button>
|
|
11567
|
-
</div>
|
|
11568
|
-
<div class="gs-card" id="gs-step-agent">
|
|
11569
|
-
<div class="gs-step-num">2</div>
|
|
11570
|
-
<div class="gs-card-icon">👥</div>
|
|
11571
|
-
<div class="gs-card-title">Create an Agent</div>
|
|
11572
|
-
<div class="gs-card-desc">Hire your first AI team member with a role, tools, and a channel.</div>
|
|
11573
|
-
<button class="btn btn-sm btn-primary" onclick="navigateTo('team')">Go to The Office</button>
|
|
11574
|
-
</div>
|
|
11575
|
-
<div class="gs-card" id="gs-step-channel">
|
|
11576
|
-
<div class="gs-step-num">3</div>
|
|
11577
|
-
<div class="gs-card-icon">💬</div>
|
|
11578
|
-
<div class="gs-card-title">Connect a Channel</div>
|
|
11579
|
-
<div class="gs-card-desc">Link Discord or Slack so your agents can communicate.</div>
|
|
11580
|
-
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'channels' })">Open Settings</button>
|
|
11581
|
-
</div>
|
|
11582
|
-
<div class="gs-card" id="gs-step-task">
|
|
11583
|
-
<div class="gs-step-num">4</div>
|
|
11584
|
-
<div class="gs-card-icon">⏰</div>
|
|
11585
|
-
<div class="gs-card-title">Schedule a Task</div>
|
|
11586
|
-
<div class="gs-card-desc">Set up cron jobs so agents work on autopilot.</div>
|
|
11587
|
-
<button class="btn btn-sm" onclick="navigateTo('build', { tab: 'crons' })">Add a Task</button>
|
|
11588
|
-
</div>
|
|
11589
|
-
<div class="gs-card" id="gs-step-project">
|
|
11590
|
-
<div class="gs-step-num">5</div>
|
|
11591
|
-
<div class="gs-card-icon">📂</div>
|
|
11592
|
-
<div class="gs-card-title">Link a Project</div>
|
|
11593
|
-
<div class="gs-card-desc">Give agents context about your codebases and tools.</div>
|
|
11594
|
-
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'projects' })">Browse Projects</button>
|
|
11595
|
-
</div>
|
|
12183
|
+
<div class="gs-card" id="gs-step-agent">
|
|
12184
|
+
<div class="gs-step-num">2</div>
|
|
12185
|
+
<div class="gs-card-icon">👥</div>
|
|
12186
|
+
<div class="gs-card-title">Create an Agent</div>
|
|
12187
|
+
<div class="gs-card-desc">Hire your first AI team member with a role, tools, and a channel.</div>
|
|
12188
|
+
<button class="btn btn-sm btn-primary" onclick="navigateTo('team')">Go to The Office</button>
|
|
11596
12189
|
</div>
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
<
|
|
11603
|
-
<p style="margin-bottom:14px;color:var(--text-muted)">What can I help with?</p>
|
|
11604
|
-
<div style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center">
|
|
11605
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat("What's on my schedule?")">What's on my schedule?</button>
|
|
11606
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('Check my email')">Check my email</button>
|
|
11607
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('Run morning briefing')">Run morning briefing</button>
|
|
11608
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('What did you do today?')">What did you do today?</button>
|
|
11609
|
-
</div>
|
|
11610
|
-
</div>
|
|
12190
|
+
<div class="gs-card" id="gs-step-channel">
|
|
12191
|
+
<div class="gs-step-num">3</div>
|
|
12192
|
+
<div class="gs-card-icon">💬</div>
|
|
12193
|
+
<div class="gs-card-title">Connect a Channel</div>
|
|
12194
|
+
<div class="gs-card-desc">Link Discord or Slack so your agents can communicate.</div>
|
|
12195
|
+
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'channels' })">Open Settings</button>
|
|
11611
12196
|
</div>
|
|
11612
|
-
<div class="
|
|
11613
|
-
<
|
|
11614
|
-
<
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
<button class="btn
|
|
12197
|
+
<div class="gs-card" id="gs-step-task">
|
|
12198
|
+
<div class="gs-step-num">4</div>
|
|
12199
|
+
<div class="gs-card-icon">⏰</div>
|
|
12200
|
+
<div class="gs-card-title">Schedule a Task</div>
|
|
12201
|
+
<div class="gs-card-desc">Set up cron jobs so agents work on autopilot.</div>
|
|
12202
|
+
<button class="btn btn-sm" onclick="navigateTo('build', { tab: 'crons' })">Add a Task</button>
|
|
12203
|
+
</div>
|
|
12204
|
+
<div class="gs-card" id="gs-step-project">
|
|
12205
|
+
<div class="gs-step-num">5</div>
|
|
12206
|
+
<div class="gs-card-icon">📂</div>
|
|
12207
|
+
<div class="gs-card-title">Link a Project</div>
|
|
12208
|
+
<div class="gs-card-desc">Give agents context about your codebases and tools.</div>
|
|
12209
|
+
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'projects' })">Browse Projects</button>
|
|
11618
12210
|
</div>
|
|
11619
12211
|
</div>
|
|
12212
|
+
</div>
|
|
11620
12213
|
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
12214
|
+
<!-- Greeting -->
|
|
12215
|
+
<header class="home-greeting">
|
|
12216
|
+
<div class="home-greeting-text">
|
|
12217
|
+
<h1 id="home-greet-line"><span id="home-greet-phrase">Hello</span><span id="home-greet-comma">,</span> <span id="home-greet-name">there</span></h1>
|
|
12218
|
+
<p id="home-greet-day" class="home-greeting-day">…</p>
|
|
12219
|
+
</div>
|
|
12220
|
+
<div class="home-greeting-actions">
|
|
12221
|
+
<button class="btn-sm btn-ghost" onclick="refreshHomeDigest()" title="Refresh dashboard">
|
|
12222
|
+
<span class="icon-slot" data-icon="refresh"></span>
|
|
12223
|
+
</button>
|
|
12224
|
+
</div>
|
|
12225
|
+
</header>
|
|
12226
|
+
|
|
12227
|
+
<!-- KPI strip -->
|
|
12228
|
+
<section class="kpi-strip" id="home-kpis">
|
|
12229
|
+
<div class="kpi-tile" data-kpi="activeRuns" onclick="navigateTo('build',{tab:'workflows'})">
|
|
12230
|
+
<div class="kpi-icon" data-icon="zap"></div>
|
|
12231
|
+
<div class="kpi-value" id="kpi-active-runs">--</div>
|
|
12232
|
+
<div class="kpi-label">Active runs</div>
|
|
12233
|
+
</div>
|
|
12234
|
+
<div class="kpi-tile" data-kpi="runsToday">
|
|
12235
|
+
<div class="kpi-icon" data-icon="clock"></div>
|
|
12236
|
+
<div class="kpi-value" id="kpi-runs-today">--</div>
|
|
12237
|
+
<div class="kpi-label">Runs today</div>
|
|
12238
|
+
</div>
|
|
12239
|
+
<div class="kpi-tile" data-kpi="timeSaved">
|
|
12240
|
+
<div class="kpi-icon" data-icon="sparkles"></div>
|
|
12241
|
+
<div class="kpi-value" id="kpi-time-saved">--</div>
|
|
12242
|
+
<div class="kpi-label">Saved this week</div>
|
|
12243
|
+
</div>
|
|
12244
|
+
<div class="kpi-tile" data-kpi="approvals" onclick="navigateTo('brain',{tab:'learning'})">
|
|
12245
|
+
<div class="kpi-icon" data-icon="check"></div>
|
|
12246
|
+
<div class="kpi-value" id="kpi-approvals">--</div>
|
|
12247
|
+
<div class="kpi-label">Need approval</div>
|
|
12248
|
+
</div>
|
|
12249
|
+
<div class="kpi-tile" data-kpi="overdue" onclick="navigateTo('team',{tab:'goals'})">
|
|
12250
|
+
<div class="kpi-icon" data-icon="target"></div>
|
|
12251
|
+
<div class="kpi-value" id="kpi-overdue">--</div>
|
|
12252
|
+
<div class="kpi-label">Overdue</div>
|
|
12253
|
+
</div>
|
|
12254
|
+
</section>
|
|
12255
|
+
|
|
12256
|
+
<!-- Today's briefing + Today's runs side-by-side -->
|
|
12257
|
+
<section class="home-grid-2">
|
|
12258
|
+
<div class="card briefing-card">
|
|
12259
|
+
<div class="card-header">
|
|
12260
|
+
<span><span class="icon-slot" data-icon="fileText"></span> Today's briefing</span>
|
|
12261
|
+
<span style="font-size:var(--text-xs);color:var(--text-muted)" id="briefing-source-hint"></span>
|
|
12262
|
+
</div>
|
|
12263
|
+
<div class="card-body" id="briefing-body">
|
|
12264
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
11638
12265
|
</div>
|
|
11639
|
-
<div class="card-body" id="panel-activity"><div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div></div>
|
|
11640
12266
|
</div>
|
|
11641
|
-
</main>
|
|
11642
12267
|
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
<section class="rail-card">
|
|
11648
|
-
<div class="rail-header">
|
|
11649
|
-
<span><span class="status-pip green"></span>Daemon</span>
|
|
11650
|
-
<span id="rail-daemon-uptime" style="font-size:11px;color:var(--text-muted)">--</span>
|
|
12268
|
+
<div class="card runs-card">
|
|
12269
|
+
<div class="card-header">
|
|
12270
|
+
<span><span class="icon-slot" data-icon="clock"></span> Today's runs</span>
|
|
12271
|
+
<button class="btn-sm btn-ghost" onclick="navigateTo('build',{tab:'crons'})" style="font-size:var(--text-xs);padding:2px 8px">View all</button>
|
|
11651
12272
|
</div>
|
|
11652
|
-
<div class="
|
|
11653
|
-
<div class="
|
|
12273
|
+
<div class="card-body" id="today-runs-body">
|
|
12274
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
11654
12275
|
</div>
|
|
11655
|
-
</
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
12276
|
+
</div>
|
|
12277
|
+
</section>
|
|
12278
|
+
|
|
12279
|
+
<!-- Recent activity -->
|
|
12280
|
+
<section class="card home-activity">
|
|
12281
|
+
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center">
|
|
12282
|
+
<span><span class="icon-slot" data-icon="list"></span> Recent activity</span>
|
|
12283
|
+
<div style="display:flex;gap:6px;align-items:center">
|
|
12284
|
+
<select id="activity-source-filter" onchange="refreshActivity()" style="font-size:var(--text-xs);padding:3px 8px;border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--bg-secondary);color:var(--text-primary)">
|
|
12285
|
+
<option value="">All sources</option>
|
|
12286
|
+
<option value="cron">Cron</option>
|
|
12287
|
+
<option value="activity">Activities</option>
|
|
12288
|
+
<option value="send">Emails</option>
|
|
12289
|
+
<option value="approval">Approvals</option>
|
|
12290
|
+
<option value="memory">Memory</option>
|
|
12291
|
+
</select>
|
|
12292
|
+
<select id="activity-agent-filter" onchange="refreshActivity()" style="font-size:var(--text-xs);padding:3px 8px;border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--bg-secondary);color:var(--text-primary)">
|
|
12293
|
+
<option value="">All agents</option>
|
|
12294
|
+
</select>
|
|
11661
12295
|
</div>
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
<section class="rail-card">
|
|
11666
|
-
<div class="rail-header"><span>Upcoming runs</span><span id="rail-upcoming-count" class="rail-badge">0</span></div>
|
|
11667
|
-
<div class="rail-body" id="rail-upcoming"><div class="skel-row short"></div></div>
|
|
11668
|
-
</section>
|
|
11669
|
-
|
|
11670
|
-
<section class="rail-card">
|
|
11671
|
-
<div class="rail-header"><span>Active runs</span><span id="rail-active-count" class="rail-badge" style="display:none">0</span></div>
|
|
11672
|
-
<div class="rail-body" id="rail-active"><div style="font-size:12px;color:var(--text-muted)">Nothing running.</div></div>
|
|
11673
|
-
</section>
|
|
11674
|
-
|
|
11675
|
-
<section class="rail-card">
|
|
11676
|
-
<div class="rail-header"><span>Time saved this week</span></div>
|
|
11677
|
-
<div class="rail-body" id="rail-time-saved"><div class="skel-row short"></div></div>
|
|
11678
|
-
</section>
|
|
11679
|
-
|
|
11680
|
-
<section class="rail-card">
|
|
11681
|
-
<div class="rail-header"><span>Approvals</span><span id="rail-approvals-count" class="rail-badge" style="display:none">0</span></div>
|
|
11682
|
-
<div class="rail-body" id="rail-approvals"><div style="font-size:12px;color:var(--text-muted)">Nothing pending.</div></div>
|
|
11683
|
-
</section>
|
|
11684
|
-
</aside>
|
|
11685
|
-
<button class="home-rail-toggle" onclick="toggleHomeRail()" title="Open status rail">☰</button>
|
|
12296
|
+
</div>
|
|
12297
|
+
<div class="card-body" id="panel-activity"><div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div></div>
|
|
12298
|
+
</section>
|
|
11686
12299
|
</div>
|
|
11687
12300
|
</div>
|
|
11688
12301
|
|
|
@@ -13164,6 +13777,41 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13164
13777
|
</script>
|
|
13165
13778
|
</div>
|
|
13166
13779
|
|
|
13780
|
+
<!-- Floating chat FAB + panel — anchored bottom-right.
|
|
13781
|
+
Pass 3 styles this; structure is here so existing chat handlers
|
|
13782
|
+
(sendChat, switchProfile, quickChat) keep finding the IDs they target. -->
|
|
13783
|
+
<button id="home-chat-fab" class="home-chat-fab" onclick="toggleHomeChat()" title="Ask Clementine (?)">
|
|
13784
|
+
<span class="icon-slot" data-icon="messageSquare"></span>
|
|
13785
|
+
<span class="home-chat-fab-label">Ask Clementine</span>
|
|
13786
|
+
</button>
|
|
13787
|
+
<div id="home-chat-panel" class="home-chat-panel" aria-hidden="true">
|
|
13788
|
+
<div class="home-chat-panel-header">
|
|
13789
|
+
<span><span class="icon-slot" data-icon="messageSquare"></span> Chat</span>
|
|
13790
|
+
<span style="flex:1"></span>
|
|
13791
|
+
<select id="chat-profile-select" onchange="switchProfile(this.value)" title="Active profile">
|
|
13792
|
+
<option value="">Default</option>
|
|
13793
|
+
</select>
|
|
13794
|
+
<button class="btn-icon btn-sm" onclick="toggleHomeChat()" title="Close chat">×</button>
|
|
13795
|
+
</div>
|
|
13796
|
+
<div id="chat-messages" class="home-chat-panel-messages">
|
|
13797
|
+
<div class="empty-state" style="margin-top:24px">
|
|
13798
|
+
<p style="margin-bottom:12px;color:var(--text-muted);font-size:var(--text-base)">What can I help with?</p>
|
|
13799
|
+
<div style="display:flex;flex-wrap:wrap;gap:6px;justify-content:center">
|
|
13800
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat("What's on my schedule?")">Schedule?</button>
|
|
13801
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat('Check my email')">Email</button>
|
|
13802
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat('Run morning briefing')">Briefing</button>
|
|
13803
|
+
<button class="btn btn-sm quick-pill" onclick="quickChat('What did you do today?')">Today's work</button>
|
|
13804
|
+
</div>
|
|
13805
|
+
</div>
|
|
13806
|
+
</div>
|
|
13807
|
+
<div class="home-chat-panel-input-row">
|
|
13808
|
+
<input type="text" id="chat-input" placeholder="Ask anything..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}">
|
|
13809
|
+
<button class="btn-primary btn-sm" id="chat-send-btn" onclick="sendChat()" title="Send">
|
|
13810
|
+
<span class="icon-slot" data-icon="send"></span>
|
|
13811
|
+
</button>
|
|
13812
|
+
</div>
|
|
13813
|
+
</div>
|
|
13814
|
+
|
|
13167
13815
|
<!-- Sessions, Trust & Claims, Logs, Chat, Metrics, Daily Plan pages
|
|
13168
13816
|
removed in Session 2 — content lives on Home or migrates to other
|
|
13169
13817
|
destinations in Sessions 3-4. Page references for these IDs are
|
|
@@ -14357,6 +15005,7 @@ function navigateTo(page, opts) {
|
|
|
14357
15005
|
switch (page) {
|
|
14358
15006
|
case 'home':
|
|
14359
15007
|
refreshAll();
|
|
15008
|
+
if (typeof refreshHomeDigest === 'function') refreshHomeDigest();
|
|
14360
15009
|
// tab is a soft hint on Home (one cohesive layout): focus the relevant area.
|
|
14361
15010
|
var t = opts.tab || 'chat';
|
|
14362
15011
|
setTimeout(function() {
|
|
@@ -20889,6 +21538,147 @@ async function saveDiscordSetup() {
|
|
|
20889
21538
|
} catch(e) { toast(String(e), 'error'); }
|
|
20890
21539
|
}
|
|
20891
21540
|
|
|
21541
|
+
// ── Home digest (v1.5 daily-driver) ────────────────────────────
|
|
21542
|
+
async function refreshHomeDigest() {
|
|
21543
|
+
try {
|
|
21544
|
+
var r = await apiFetch('/api/home-digest');
|
|
21545
|
+
var d = await r.json();
|
|
21546
|
+
if (!d || d.error) return;
|
|
21547
|
+
|
|
21548
|
+
// Greeting
|
|
21549
|
+
var greetPhraseEl = document.getElementById('home-greet-phrase');
|
|
21550
|
+
var greetNameEl = document.getElementById('home-greet-name');
|
|
21551
|
+
var greetDayEl = document.getElementById('home-greet-day');
|
|
21552
|
+
if (d.greeting) {
|
|
21553
|
+
if (greetPhraseEl) greetPhraseEl.textContent = d.greeting.phrase || 'Hello';
|
|
21554
|
+
if (greetNameEl) greetNameEl.textContent = d.greeting.name || 'there';
|
|
21555
|
+
if (greetDayEl) greetDayEl.textContent = d.greeting.dayLabel || '';
|
|
21556
|
+
}
|
|
21557
|
+
|
|
21558
|
+
// KPIs
|
|
21559
|
+
var k = d.kpis || {};
|
|
21560
|
+
var setKpi = function(id, value, alertWhen) {
|
|
21561
|
+
var el = document.getElementById(id);
|
|
21562
|
+
if (!el) return;
|
|
21563
|
+
el.textContent = (value == null) ? '0' : String(value);
|
|
21564
|
+
var tile = el.closest('.kpi-tile');
|
|
21565
|
+
if (tile) {
|
|
21566
|
+
tile.classList.toggle('alert', alertWhen);
|
|
21567
|
+
tile.classList.toggle('muted', !value);
|
|
21568
|
+
}
|
|
21569
|
+
el.classList.toggle('muted', !value);
|
|
21570
|
+
};
|
|
21571
|
+
setKpi('kpi-active-runs', k.activeRuns || 0, (k.activeRuns || 0) > 0);
|
|
21572
|
+
setKpi('kpi-runs-today', k.runsToday || 0, false);
|
|
21573
|
+
var minutes = k.timeSavedMinutes || 0;
|
|
21574
|
+
var savedEl = document.getElementById('kpi-time-saved');
|
|
21575
|
+
if (savedEl) {
|
|
21576
|
+
savedEl.textContent = minutes >= 60 ? (minutes / 60).toFixed(1) + 'h' : (minutes + 'm');
|
|
21577
|
+
savedEl.classList.toggle('muted', minutes === 0);
|
|
21578
|
+
}
|
|
21579
|
+
setKpi('kpi-approvals', k.pendingApprovals || 0, (k.pendingApprovals || 0) > 0);
|
|
21580
|
+
setKpi('kpi-overdue', k.overdueTasks || 0, (k.overdueTasks || 0) > 0);
|
|
21581
|
+
|
|
21582
|
+
// Briefing
|
|
21583
|
+
var briefBody = document.getElementById('briefing-body');
|
|
21584
|
+
var briefHint = document.getElementById('briefing-source-hint');
|
|
21585
|
+
if (briefBody) {
|
|
21586
|
+
var b = d.briefing || { highlights: [], needsReview: [] };
|
|
21587
|
+
var html = '';
|
|
21588
|
+
if (b.highlights.length) {
|
|
21589
|
+
html += '<div class="briefing-section-title">What\\x27s happened today</div>';
|
|
21590
|
+
html += '<ul class="briefing-list">';
|
|
21591
|
+
for (var i = 0; i < b.highlights.length; i++) {
|
|
21592
|
+
html += '<li class="briefing-item">' + esc(b.highlights[i].text) + '</li>';
|
|
21593
|
+
}
|
|
21594
|
+
html += '</ul>';
|
|
21595
|
+
} else {
|
|
21596
|
+
html += '<div style="color:var(--text-muted);font-size:var(--text-base);padding:6px 0">No notable activity in today\\x27s daily note yet. Your team\\x27s morning briefing fires at 8:00am.</div>';
|
|
21597
|
+
}
|
|
21598
|
+
if (b.needsReview.length) {
|
|
21599
|
+
html += '<div class="briefing-section-title" style="margin-top:18px">Needs your review</div>';
|
|
21600
|
+
html += '<ul class="briefing-list">';
|
|
21601
|
+
for (var j = 0; j < b.needsReview.length; j++) {
|
|
21602
|
+
var item = b.needsReview[j];
|
|
21603
|
+
var hrefAttr = item.href ? 'onclick="briefingNeedsReviewClick(\\x27' + esc(item.href) + '\\x27)"' : '';
|
|
21604
|
+
html += '<li class="briefing-item review" ' + hrefAttr + '>' + esc(item.text) + (item.href ? '<span class="arrow">→</span>' : '') + '</li>';
|
|
21605
|
+
}
|
|
21606
|
+
html += '</ul>';
|
|
21607
|
+
}
|
|
21608
|
+
briefBody.innerHTML = html;
|
|
21609
|
+
if (briefHint) {
|
|
21610
|
+
var sourceCount = b.highlights.filter(function(h) { return h.source === 'daily-note'; }).length;
|
|
21611
|
+
briefHint.textContent = sourceCount > 0 ? ('from today\\x27s daily note') : (b.highlights.length > 0 ? 'from recent activity' : '');
|
|
21612
|
+
}
|
|
21613
|
+
}
|
|
21614
|
+
|
|
21615
|
+
// Today's runs
|
|
21616
|
+
var runsBody = document.getElementById('today-runs-body');
|
|
21617
|
+
if (runsBody) {
|
|
21618
|
+
var runs = d.todayRuns || [];
|
|
21619
|
+
if (!runs.length) {
|
|
21620
|
+
runsBody.innerHTML = '<div style="padding:14px 18px;color:var(--text-muted);font-size:var(--text-sm)">No scheduled runs configured. <a href="#" onclick="navigateTo(\\x27build\\x27,{tab:\\x27crons\\x27});return false" style="color:var(--clementine)">Add one in Build → Crons</a>.</div>';
|
|
21621
|
+
} else {
|
|
21622
|
+
var html2 = '';
|
|
21623
|
+
for (var n = 0; n < runs.length; n++) {
|
|
21624
|
+
var run = runs[n];
|
|
21625
|
+
var status = run.firedToday ? 'fired' : 'upcoming';
|
|
21626
|
+
var label = run.firedToday ? 'fired today' : (run.nextRun ? timeUntil(run.nextRun) : 'manual');
|
|
21627
|
+
html2 += '<div class="run-row ' + status + '" onclick="navigateTo(\\x27build\\x27,{tab:\\x27crons\\x27})">'
|
|
21628
|
+
+ '<span class="run-icon">' + (status === 'fired' ? lucide('check', 'icn-sm') : lucide('clock', 'icn-sm')) + '</span>'
|
|
21629
|
+
+ '<span class="run-name">' + esc(run.name) + '</span>'
|
|
21630
|
+
+ '<span class="run-time">' + esc(label) + '</span>'
|
|
21631
|
+
+ '</div>';
|
|
21632
|
+
}
|
|
21633
|
+
runsBody.innerHTML = html2;
|
|
21634
|
+
}
|
|
21635
|
+
}
|
|
21636
|
+
} catch (err) {
|
|
21637
|
+
console.warn('refreshHomeDigest failed:', err);
|
|
21638
|
+
}
|
|
21639
|
+
}
|
|
21640
|
+
|
|
21641
|
+
// ── Home chat FAB + panel ────────────────────────────────────────
|
|
21642
|
+
function toggleHomeChat(forceOpen) {
|
|
21643
|
+
var fab = document.getElementById('home-chat-fab');
|
|
21644
|
+
var panel = document.getElementById('home-chat-panel');
|
|
21645
|
+
if (!panel || !fab) return;
|
|
21646
|
+
var willOpen = forceOpen != null ? !!forceOpen : !panel.classList.contains('open');
|
|
21647
|
+
panel.classList.toggle('open', willOpen);
|
|
21648
|
+
panel.setAttribute('aria-hidden', willOpen ? 'false' : 'true');
|
|
21649
|
+
fab.classList.toggle('hidden', willOpen);
|
|
21650
|
+
if (willOpen) {
|
|
21651
|
+
setTimeout(function() {
|
|
21652
|
+
var input = document.getElementById('chat-input');
|
|
21653
|
+
if (input) input.focus();
|
|
21654
|
+
}, 80);
|
|
21655
|
+
if (typeof loadProfiles === 'function') loadProfiles();
|
|
21656
|
+
}
|
|
21657
|
+
}
|
|
21658
|
+
|
|
21659
|
+
// Keyboard shortcut: ? toggles chat (when not typing in another input)
|
|
21660
|
+
document.addEventListener('keydown', function(e) {
|
|
21661
|
+
if (e.key === '?' && !e.metaKey && !e.ctrlKey) {
|
|
21662
|
+
var t = e.target;
|
|
21663
|
+
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
|
|
21664
|
+
e.preventDefault();
|
|
21665
|
+
toggleHomeChat();
|
|
21666
|
+
}
|
|
21667
|
+
// Esc closes chat panel
|
|
21668
|
+
if (e.key === 'Escape') {
|
|
21669
|
+
var panel = document.getElementById('home-chat-panel');
|
|
21670
|
+
if (panel && panel.classList.contains('open')) toggleHomeChat(false);
|
|
21671
|
+
}
|
|
21672
|
+
});
|
|
21673
|
+
|
|
21674
|
+
function briefingNeedsReviewClick(href) {
|
|
21675
|
+
if (!href) return;
|
|
21676
|
+
if (href.startsWith('#')) {
|
|
21677
|
+
var parts = href.replace(/^#/, '').split('/');
|
|
21678
|
+
if (parts[0]) navigateTo(parts[0], parts[1] ? { tab: parts[1] } : {});
|
|
21679
|
+
}
|
|
21680
|
+
}
|
|
21681
|
+
|
|
20892
21682
|
function toggleHomeRail() {
|
|
20893
21683
|
var rail = document.getElementById('home-rail');
|
|
20894
21684
|
if (!rail) return;
|
|
@@ -24343,8 +25133,8 @@ async function refreshSalesforce() {
|
|
|
24343
25133
|
if (d.status) { try { refreshStatus(d.status); } catch(e) { console.warn('init: status', e); } }
|
|
24344
25134
|
if (d.activity) { try { refreshActivity(false, d.activity); } catch(e) { console.warn('init: activity', e); } }
|
|
24345
25135
|
else { try { refreshActivity(); } catch(e) { console.warn('init: activity fallback', e); } }
|
|
24346
|
-
// Populate the home
|
|
24347
|
-
if (typeof
|
|
25136
|
+
// Populate the home digest (greeting, KPIs, briefing, today's runs)
|
|
25137
|
+
if (typeof refreshHomeDigest === 'function') { try { refreshHomeDigest(); } catch(e) { console.warn('init: digest', e); } }
|
|
24348
25138
|
if (d.office) { try { refreshTeamNav(d.office); refreshTeamPulse(d.office); } catch(e) { console.warn('init: office', e); } }
|
|
24349
25139
|
if (d.plan) { try { refreshHomePlan(d.plan); } catch(e) { console.warn('init: plan', e); } }
|
|
24350
25140
|
if (d.version) { try { _loadedHash = d.version.started; } catch(e) { /* ignore */ } }
|