clementine-agent 1.4.2 → 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 +1258 -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,493 @@ 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
|
+
});
|
|
2602
|
+
app.get('/api/vault-files', async (req, res) => {
|
|
2603
|
+
try {
|
|
2604
|
+
const limit = Math.min(parseInt(String(req.query.limit ?? '120'), 10) || 120, 500);
|
|
2605
|
+
const sinceDays = Math.max(parseInt(String(req.query.sinceDays ?? '30'), 10) || 30, 1);
|
|
2606
|
+
const agentFilter = typeof req.query.agent === 'string' ? req.query.agent : '';
|
|
2607
|
+
const folderFilter = typeof req.query.folder === 'string' ? req.query.folder : '';
|
|
2608
|
+
const search = typeof req.query.q === 'string' ? req.query.q.toLowerCase() : '';
|
|
2609
|
+
const includeAuto = req.query.includeAuto === '1';
|
|
2610
|
+
const cutoffMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000;
|
|
2611
|
+
const vaultRoot = path.join(BASE_DIR, 'vault');
|
|
2612
|
+
const matter = (await import('gray-matter')).default;
|
|
2613
|
+
const files = [];
|
|
2614
|
+
function walk(dir) {
|
|
2615
|
+
let entries = [];
|
|
2616
|
+
try {
|
|
2617
|
+
entries = readdirSync(dir);
|
|
2618
|
+
}
|
|
2619
|
+
catch {
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
for (const e of entries) {
|
|
2623
|
+
if (e.startsWith('.'))
|
|
2624
|
+
continue;
|
|
2625
|
+
const full = path.join(dir, e);
|
|
2626
|
+
let stat;
|
|
2627
|
+
try {
|
|
2628
|
+
stat = statSync(full);
|
|
2629
|
+
}
|
|
2630
|
+
catch {
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
if (stat.isDirectory()) {
|
|
2634
|
+
walk(full);
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
if (!e.endsWith('.md'))
|
|
2638
|
+
continue;
|
|
2639
|
+
if (e.endsWith('.md.bak'))
|
|
2640
|
+
continue;
|
|
2641
|
+
if (stat.mtimeMs < cutoffMs)
|
|
2642
|
+
continue;
|
|
2643
|
+
const rel = path.relative(vaultRoot, full);
|
|
2644
|
+
// Skip auto-generated MCP/skill wrappers unless explicitly requested.
|
|
2645
|
+
// Path patterns: 00-System/skills/auto/* (generated tool wrappers).
|
|
2646
|
+
if (!includeAuto && rel.startsWith('00-System/skills/auto/'))
|
|
2647
|
+
continue;
|
|
2648
|
+
if (!includeAuto && rel.startsWith('00-System/agents/') && /\/(MEMORY|HEARTBEAT|TASKS|CRON)\.md$/.test(rel))
|
|
2649
|
+
continue;
|
|
2650
|
+
const folder = path.dirname(rel).split(path.sep)[0] || '';
|
|
2651
|
+
let agentSlug = null;
|
|
2652
|
+
if (rel.startsWith('00-System/agents/')) {
|
|
2653
|
+
const m = rel.match(/^00-System\/agents\/([^/]+)\//);
|
|
2654
|
+
if (m)
|
|
2655
|
+
agentSlug = m[1];
|
|
2656
|
+
}
|
|
2657
|
+
// Skip system housekeeping files (their author will surface via mtime in agent's own dir)
|
|
2658
|
+
let title = path.basename(rel, '.md');
|
|
2659
|
+
let typeTag = null;
|
|
2660
|
+
try {
|
|
2661
|
+
const head = readFileSync(full, 'utf-8').slice(0, 4000);
|
|
2662
|
+
const parsed = matter(head);
|
|
2663
|
+
const data = parsed.data;
|
|
2664
|
+
if (typeof data.title === 'string')
|
|
2665
|
+
title = data.title;
|
|
2666
|
+
else if (typeof data.name === 'string')
|
|
2667
|
+
title = data.name;
|
|
2668
|
+
else {
|
|
2669
|
+
const h1 = (parsed.content || '').match(/^#\s+(.+)$/m);
|
|
2670
|
+
if (h1)
|
|
2671
|
+
title = h1[1].trim();
|
|
2672
|
+
}
|
|
2673
|
+
if (typeof data.type === 'string')
|
|
2674
|
+
typeTag = data.type;
|
|
2675
|
+
}
|
|
2676
|
+
catch { /* */ }
|
|
2677
|
+
files.push({
|
|
2678
|
+
path: full,
|
|
2679
|
+
relPath: rel,
|
|
2680
|
+
title,
|
|
2681
|
+
folder,
|
|
2682
|
+
agentSlug,
|
|
2683
|
+
mtime: new Date(stat.mtimeMs).toISOString(),
|
|
2684
|
+
sizeBytes: stat.size,
|
|
2685
|
+
type: typeTag,
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
walk(vaultRoot);
|
|
2690
|
+
let filtered = files
|
|
2691
|
+
.sort((a, b) => b.mtime.localeCompare(a.mtime))
|
|
2692
|
+
.filter(f => {
|
|
2693
|
+
if (agentFilter === '__shared__' && f.agentSlug != null)
|
|
2694
|
+
return false;
|
|
2695
|
+
if (agentFilter && agentFilter !== '__shared__' && f.agentSlug !== agentFilter)
|
|
2696
|
+
return false;
|
|
2697
|
+
if (folderFilter && f.folder !== folderFilter)
|
|
2698
|
+
return false;
|
|
2699
|
+
if (search) {
|
|
2700
|
+
const hay = (f.title + ' ' + f.relPath).toLowerCase();
|
|
2701
|
+
if (!hay.includes(search))
|
|
2702
|
+
return false;
|
|
2703
|
+
}
|
|
2704
|
+
return true;
|
|
2705
|
+
})
|
|
2706
|
+
.slice(0, limit);
|
|
2707
|
+
// Compute folder counts for filter chips
|
|
2708
|
+
const folderCounts = {};
|
|
2709
|
+
for (const f of files)
|
|
2710
|
+
folderCounts[f.folder] = (folderCounts[f.folder] || 0) + 1;
|
|
2711
|
+
res.json({ files: filtered, total: files.length, folderCounts });
|
|
2712
|
+
}
|
|
2713
|
+
catch (err) {
|
|
2714
|
+
res.status(500).json({ error: String(err) });
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
app.get('/api/vault-file', async (req, res) => {
|
|
2718
|
+
try {
|
|
2719
|
+
const relPath = typeof req.query.path === 'string' ? req.query.path : '';
|
|
2720
|
+
if (!relPath || relPath.includes('..')) {
|
|
2721
|
+
res.status(400).json({ error: 'Bad path' });
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
const full = path.join(BASE_DIR, 'vault', relPath);
|
|
2725
|
+
if (!existsSync(full)) {
|
|
2726
|
+
res.status(404).json({ error: 'Not found' });
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
const content = readFileSync(full, 'utf-8');
|
|
2730
|
+
res.json({ path: relPath, content });
|
|
2731
|
+
}
|
|
2732
|
+
catch (err) {
|
|
2733
|
+
res.status(500).json({ error: String(err) });
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2243
2736
|
app.get('/api/memory', async (_req, res) => {
|
|
2244
2737
|
res.json(await getMemory());
|
|
2245
2738
|
});
|
|
@@ -8212,6 +8705,22 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8212
8705
|
--motion-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
8213
8706
|
/* ── Focus ring ── */
|
|
8214
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;
|
|
8215
8724
|
}
|
|
8216
8725
|
[data-theme="dark"] {
|
|
8217
8726
|
--bg-primary: #0b0f17;
|
|
@@ -8243,6 +8752,22 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8243
8752
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
8244
8753
|
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.6), 0 4px 8px rgba(0, 0, 0, 0.4);
|
|
8245
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;
|
|
8246
8771
|
}
|
|
8247
8772
|
/* OS-preference dark mode unless user has explicitly chosen a theme */
|
|
8248
8773
|
@media (prefers-color-scheme: dark) {
|
|
@@ -8282,6 +8807,17 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8282
8807
|
box-shadow: var(--ring);
|
|
8283
8808
|
border-radius: var(--radius-xs);
|
|
8284
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
|
+
}
|
|
8285
8821
|
body {
|
|
8286
8822
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
8287
8823
|
font-feature-settings: 'cv02', 'cv11', 'ss01', 'ss03';
|
|
@@ -8653,66 +9189,279 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
8653
9189
|
.icn-lg { width: 22px; height: 22px; stroke-width: 1.75; }
|
|
8654
9190
|
.icn-xl { width: 28px; height: 28px; stroke-width: 1.5; }
|
|
8655
9191
|
|
|
8656
|
-
/* ── Home
|
|
8657
|
-
.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 {
|
|
8658
9225
|
display: grid;
|
|
8659
|
-
grid-template-columns: 1fr
|
|
8660
|
-
gap:
|
|
8661
|
-
height: calc(100vh - var(--header-h));
|
|
8662
|
-
padding: 18px;
|
|
8663
|
-
box-sizing: border-box;
|
|
9226
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
9227
|
+
gap: 10px;
|
|
8664
9228
|
}
|
|
8665
|
-
.
|
|
9229
|
+
.kpi-tile {
|
|
9230
|
+
background: var(--bg-card);
|
|
9231
|
+
border: 1px solid var(--border);
|
|
9232
|
+
border-radius: var(--radius-md);
|
|
9233
|
+
padding: 12px 14px;
|
|
8666
9234
|
display: flex;
|
|
8667
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);
|
|
8668
9284
|
gap: 14px;
|
|
8669
|
-
|
|
8670
|
-
|
|
9285
|
+
align-items: start;
|
|
9286
|
+
}
|
|
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;
|
|
8671
9295
|
}
|
|
8672
|
-
.
|
|
9296
|
+
.briefing-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
9297
|
+
.briefing-item {
|
|
8673
9298
|
display: flex;
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
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);
|
|
8678
9390
|
border: 1px solid var(--border);
|
|
8679
|
-
border-radius:
|
|
9391
|
+
border-radius: var(--radius-lg);
|
|
9392
|
+
box-shadow: var(--shadow-lg);
|
|
9393
|
+
display: flex;
|
|
9394
|
+
flex-direction: column;
|
|
8680
9395
|
overflow: hidden;
|
|
8681
|
-
|
|
9396
|
+
transform: translateY(20px) scale(0.96);
|
|
9397
|
+
opacity: 0;
|
|
9398
|
+
pointer-events: none;
|
|
9399
|
+
transition: transform var(--motion), opacity var(--motion);
|
|
8682
9400
|
}
|
|
8683
|
-
.home-chat-
|
|
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);
|
|
9415
|
+
}
|
|
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 {
|
|
8684
9426
|
flex: 1;
|
|
8685
9427
|
overflow-y: auto;
|
|
8686
|
-
padding:
|
|
8687
|
-
|
|
9428
|
+
padding: 14px 16px;
|
|
9429
|
+
background: var(--bg-primary);
|
|
8688
9430
|
}
|
|
8689
|
-
.home-chat-input-row {
|
|
9431
|
+
.home-chat-panel-input-row {
|
|
8690
9432
|
display: flex;
|
|
8691
9433
|
gap: 8px;
|
|
8692
|
-
padding: 12px
|
|
9434
|
+
padding: 10px 12px;
|
|
8693
9435
|
border-top: 1px solid var(--border);
|
|
8694
9436
|
background: var(--bg-secondary);
|
|
8695
9437
|
align-items: center;
|
|
8696
9438
|
}
|
|
8697
|
-
.home-chat-input-row input[type="text"] {
|
|
9439
|
+
.home-chat-panel-input-row input[type="text"] {
|
|
8698
9440
|
flex: 1;
|
|
8699
|
-
padding:
|
|
9441
|
+
padding: 8px 12px;
|
|
8700
9442
|
border: 1px solid var(--border);
|
|
8701
|
-
border-radius:
|
|
9443
|
+
border-radius: var(--radius-sm);
|
|
8702
9444
|
background: var(--bg-input);
|
|
8703
9445
|
color: var(--text-primary);
|
|
8704
|
-
font-size:
|
|
9446
|
+
font-size: var(--text-base);
|
|
9447
|
+
font-family: inherit;
|
|
8705
9448
|
}
|
|
8706
|
-
.home-chat-input-row
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
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
|
+
}
|
|
8713
9464
|
}
|
|
8714
|
-
.home-chat-input-row button { padding: 9px 18px; border-radius: 8px; }
|
|
8715
|
-
.home-activity { margin: 0; }
|
|
8716
9465
|
|
|
8717
9466
|
/* Right rail */
|
|
8718
9467
|
.home-rail {
|
|
@@ -10610,6 +11359,26 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
10610
11359
|
/* Hide chat profile selector when default — the row gets cleaner */
|
|
10611
11360
|
.home-chat-input-row .chat-profile-spacer { display: none; }
|
|
10612
11361
|
|
|
11362
|
+
/* Vault file folder chips */
|
|
11363
|
+
.vault-folder-chip {
|
|
11364
|
+
padding: 4px 12px;
|
|
11365
|
+
border: 1px solid var(--border);
|
|
11366
|
+
border-radius: 16px;
|
|
11367
|
+
font-size: var(--text-sm);
|
|
11368
|
+
color: var(--text-secondary);
|
|
11369
|
+
cursor: pointer;
|
|
11370
|
+
transition: all var(--motion);
|
|
11371
|
+
background: var(--bg-secondary);
|
|
11372
|
+
user-select: none;
|
|
11373
|
+
}
|
|
11374
|
+
.vault-folder-chip:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
11375
|
+
.vault-folder-chip.active {
|
|
11376
|
+
background: var(--clementine-bg);
|
|
11377
|
+
color: var(--clementine);
|
|
11378
|
+
border-color: var(--clementine);
|
|
11379
|
+
font-weight: 500;
|
|
11380
|
+
}
|
|
11381
|
+
|
|
10613
11382
|
/* ── Task Cards ─────────────────────────── */
|
|
10614
11383
|
.task-grid {
|
|
10615
11384
|
display: grid;
|
|
@@ -11372,11 +12141,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11372
12141
|
function run() {
|
|
11373
12142
|
document.querySelectorAll('[data-icon]').forEach(function(el) {
|
|
11374
12143
|
if (el.dataset._iconHydrated) return;
|
|
12144
|
+
if (!window.LUCIDE || !window.lucide) return;
|
|
11375
12145
|
el.dataset._iconHydrated = '1';
|
|
11376
12146
|
var name = el.getAttribute('data-icon');
|
|
11377
|
-
if (!window.LUCIDE || !window.lucide) return;
|
|
11378
12147
|
var slot = el.querySelector('.nav-icon, .hire-plus, .icon-slot');
|
|
11379
|
-
|
|
12148
|
+
var target = slot || el;
|
|
12149
|
+
target.innerHTML = window.lucide(name, 'icn-md');
|
|
11380
12150
|
});
|
|
11381
12151
|
}
|
|
11382
12152
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run);
|
|
@@ -11392,143 +12162,140 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11392
12162
|
<!-- Content -->
|
|
11393
12163
|
<div class="content">
|
|
11394
12164
|
|
|
11395
|
-
<!-- ═══ Home —
|
|
12165
|
+
<!-- ═══ Home — daily-driver dashboard ═══ -->
|
|
11396
12166
|
<div class="page active" id="page-home">
|
|
11397
|
-
<div class="home-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
<div
|
|
11401
|
-
<div class="gs-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
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>
|
|
11405
12182
|
</div>
|
|
11406
|
-
<div class="gs-
|
|
11407
|
-
<div class="gs-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
<button class="btn btn-sm btn-primary" id="gs-auth-btn" onclick="startAnthropicOAuth()">Login with Claude</button>
|
|
11413
|
-
</div>
|
|
11414
|
-
<div class="gs-card" id="gs-step-agent">
|
|
11415
|
-
<div class="gs-step-num">2</div>
|
|
11416
|
-
<div class="gs-card-icon">👥</div>
|
|
11417
|
-
<div class="gs-card-title">Create an Agent</div>
|
|
11418
|
-
<div class="gs-card-desc">Hire your first AI team member with a role, tools, and a channel.</div>
|
|
11419
|
-
<button class="btn btn-sm btn-primary" onclick="navigateTo('team')">Go to The Office</button>
|
|
11420
|
-
</div>
|
|
11421
|
-
<div class="gs-card" id="gs-step-channel">
|
|
11422
|
-
<div class="gs-step-num">3</div>
|
|
11423
|
-
<div class="gs-card-icon">💬</div>
|
|
11424
|
-
<div class="gs-card-title">Connect a Channel</div>
|
|
11425
|
-
<div class="gs-card-desc">Link Discord or Slack so your agents can communicate.</div>
|
|
11426
|
-
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'channels' })">Open Settings</button>
|
|
11427
|
-
</div>
|
|
11428
|
-
<div class="gs-card" id="gs-step-task">
|
|
11429
|
-
<div class="gs-step-num">4</div>
|
|
11430
|
-
<div class="gs-card-icon">⏰</div>
|
|
11431
|
-
<div class="gs-card-title">Schedule a Task</div>
|
|
11432
|
-
<div class="gs-card-desc">Set up cron jobs so agents work on autopilot.</div>
|
|
11433
|
-
<button class="btn btn-sm" onclick="navigateTo('build', { tab: 'crons' })">Add a Task</button>
|
|
11434
|
-
</div>
|
|
11435
|
-
<div class="gs-card" id="gs-step-project">
|
|
11436
|
-
<div class="gs-step-num">5</div>
|
|
11437
|
-
<div class="gs-card-icon">📂</div>
|
|
11438
|
-
<div class="gs-card-title">Link a Project</div>
|
|
11439
|
-
<div class="gs-card-desc">Give agents context about your codebases and tools.</div>
|
|
11440
|
-
<button class="btn btn-sm" onclick="navigateTo('settings', { tab: 'projects' })">Browse Projects</button>
|
|
11441
|
-
</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>
|
|
11442
12189
|
</div>
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
<
|
|
11449
|
-
<p style="margin-bottom:14px;color:var(--text-muted)">What can I help with?</p>
|
|
11450
|
-
<div style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center">
|
|
11451
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat("What's on my schedule?")">What's on my schedule?</button>
|
|
11452
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('Check my email')">Check my email</button>
|
|
11453
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('Run morning briefing')">Run morning briefing</button>
|
|
11454
|
-
<button class="btn btn-sm quick-pill" onclick="quickChat('What did you do today?')">What did you do today?</button>
|
|
11455
|
-
</div>
|
|
11456
|
-
</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>
|
|
11457
12196
|
</div>
|
|
11458
|
-
<div class="
|
|
11459
|
-
<
|
|
11460
|
-
<
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
<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>
|
|
11464
12210
|
</div>
|
|
11465
12211
|
</div>
|
|
12212
|
+
</div>
|
|
11466
12213
|
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
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>
|
|
11484
12265
|
</div>
|
|
11485
|
-
<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>
|
|
11486
12266
|
</div>
|
|
11487
|
-
</main>
|
|
11488
|
-
|
|
11489
|
-
<!-- Right rail: Today / Upcoming / Active / Time-saved / Approvals -->
|
|
11490
|
-
<aside class="home-rail" id="home-rail">
|
|
11491
|
-
<button class="rail-collapse-btn" onclick="toggleHomeRail()" title="Hide rail">×</button>
|
|
11492
12267
|
|
|
11493
|
-
<
|
|
11494
|
-
<div class="
|
|
11495
|
-
<span><span class="
|
|
11496
|
-
<
|
|
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>
|
|
11497
12272
|
</div>
|
|
11498
|
-
<div class="
|
|
11499
|
-
<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>
|
|
11500
12275
|
</div>
|
|
11501
|
-
</
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
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>
|
|
11507
12295
|
</div>
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
<section class="rail-card">
|
|
11512
|
-
<div class="rail-header"><span>Upcoming runs</span><span id="rail-upcoming-count" class="rail-badge">0</span></div>
|
|
11513
|
-
<div class="rail-body" id="rail-upcoming"><div class="skel-row short"></div></div>
|
|
11514
|
-
</section>
|
|
11515
|
-
|
|
11516
|
-
<section class="rail-card">
|
|
11517
|
-
<div class="rail-header"><span>Active runs</span><span id="rail-active-count" class="rail-badge" style="display:none">0</span></div>
|
|
11518
|
-
<div class="rail-body" id="rail-active"><div style="font-size:12px;color:var(--text-muted)">Nothing running.</div></div>
|
|
11519
|
-
</section>
|
|
11520
|
-
|
|
11521
|
-
<section class="rail-card">
|
|
11522
|
-
<div class="rail-header"><span>Time saved this week</span></div>
|
|
11523
|
-
<div class="rail-body" id="rail-time-saved"><div class="skel-row short"></div></div>
|
|
11524
|
-
</section>
|
|
11525
|
-
|
|
11526
|
-
<section class="rail-card">
|
|
11527
|
-
<div class="rail-header"><span>Approvals</span><span id="rail-approvals-count" class="rail-badge" style="display:none">0</span></div>
|
|
11528
|
-
<div class="rail-body" id="rail-approvals"><div style="font-size:12px;color:var(--text-muted)">Nothing pending.</div></div>
|
|
11529
|
-
</section>
|
|
11530
|
-
</aside>
|
|
11531
|
-
<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>
|
|
11532
12299
|
</div>
|
|
11533
12300
|
</div>
|
|
11534
12301
|
|
|
@@ -11765,6 +12532,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11765
12532
|
<div class="tab-bar" id="intelligence-tabs" style="margin:0 0 0 18px">
|
|
11766
12533
|
<button class="active" data-icon="database" onclick="switchTab('intelligence','search')"><span class="icon-slot"></span> Memory</button>
|
|
11767
12534
|
<button data-icon="sparkles" onclick="switchTab('intelligence','graph')"><span class="icon-slot"></span> Knowledge</button>
|
|
12535
|
+
<button data-icon="fileText" onclick="switchTab('intelligence','files')"><span class="icon-slot"></span> Files</button>
|
|
11768
12536
|
<button data-icon="folder" onclick="switchTab('intelligence','sources')"><span class="icon-slot"></span> Ingestion</button>
|
|
11769
12537
|
<button data-icon="zap" onclick="switchTab('intelligence','health')"><span class="icon-slot"></span> Health <span class="tab-badge" id="brain-health-badge" style="display:none;background:#ef4444;color:#fff">0</span></button>
|
|
11770
12538
|
<button data-icon="users" onclick="switchTab('intelligence','user-model')"><span class="icon-slot"></span> User Model</button>
|
|
@@ -11973,6 +12741,26 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11973
12741
|
<div class="tab-pane" id="tab-intelligence-runs">
|
|
11974
12742
|
<div id="brain-runs-list"></div>
|
|
11975
12743
|
</div>
|
|
12744
|
+
<div class="tab-pane" id="tab-intelligence-files">
|
|
12745
|
+
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap">
|
|
12746
|
+
<input type="text" id="vault-files-search" placeholder="Search title or path..." style="flex:1;min-width:200px;padding:7px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-input);color:var(--text-primary);font-size:13px" oninput="refreshVaultFiles()">
|
|
12747
|
+
<select id="vault-files-agent-filter" onchange="refreshVaultFiles()" style="padding:7px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-primary);font-size:12px">
|
|
12748
|
+
<option value="">All authors</option>
|
|
12749
|
+
<option value="__shared__">Shared (vault root)</option>
|
|
12750
|
+
</select>
|
|
12751
|
+
<select id="vault-files-since" onchange="refreshVaultFiles()" style="padding:7px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-primary);font-size:12px">
|
|
12752
|
+
<option value="7">Past 7 days</option>
|
|
12753
|
+
<option value="30" selected>Past 30 days</option>
|
|
12754
|
+
<option value="90">Past 90 days</option>
|
|
12755
|
+
<option value="365">Past year</option>
|
|
12756
|
+
</select>
|
|
12757
|
+
<button class="btn-sm" onclick="refreshVaultFiles()">Refresh</button>
|
|
12758
|
+
</div>
|
|
12759
|
+
<div id="vault-files-folder-chips" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px"></div>
|
|
12760
|
+
<div id="vault-files-list">
|
|
12761
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>
|
|
12762
|
+
</div>
|
|
12763
|
+
</div>
|
|
11976
12764
|
<div class="tab-pane" id="tab-intelligence-health">
|
|
11977
12765
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap">
|
|
11978
12766
|
<button class="btn-sm" onclick="memoryHealthAction('janitor')" title="Run the janitor cleanup pass now">Run cleanup</button>
|
|
@@ -12989,6 +13777,41 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
12989
13777
|
</script>
|
|
12990
13778
|
</div>
|
|
12991
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
|
+
|
|
12992
13815
|
<!-- Sessions, Trust & Claims, Logs, Chat, Metrics, Daily Plan pages
|
|
12993
13816
|
removed in Session 2 — content lives on Home or migrates to other
|
|
12994
13817
|
destinations in Sessions 3-4. Page references for these IDs are
|
|
@@ -14182,6 +15005,7 @@ function navigateTo(page, opts) {
|
|
|
14182
15005
|
switch (page) {
|
|
14183
15006
|
case 'home':
|
|
14184
15007
|
refreshAll();
|
|
15008
|
+
if (typeof refreshHomeDigest === 'function') refreshHomeDigest();
|
|
14185
15009
|
// tab is a soft hint on Home (one cohesive layout): focus the relevant area.
|
|
14186
15010
|
var t = opts.tab || 'chat';
|
|
14187
15011
|
setTimeout(function() {
|
|
@@ -14565,6 +15389,7 @@ function switchTab(group, tab) {
|
|
|
14565
15389
|
if (group === 'intelligence') {
|
|
14566
15390
|
if (tab === 'graph') refreshGraph();
|
|
14567
15391
|
if (tab === 'memory') refreshMemory();
|
|
15392
|
+
if (tab === 'files' && typeof refreshVaultFiles === 'function') refreshVaultFiles();
|
|
14568
15393
|
if (tab === 'health') {
|
|
14569
15394
|
if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
|
|
14570
15395
|
if (typeof refreshClaims === 'function') refreshClaims();
|
|
@@ -19816,6 +20641,134 @@ async function memoryHealthAction(action) {
|
|
|
19816
20641
|
}
|
|
19817
20642
|
}
|
|
19818
20643
|
|
|
20644
|
+
// ── Vault Files (Brain → Files tab) ──────────────────────────────
|
|
20645
|
+
var _vaultFilesCache = null;
|
|
20646
|
+
var _vaultFilesFolder = ''; // current folder filter
|
|
20647
|
+
|
|
20648
|
+
async function refreshVaultFiles() {
|
|
20649
|
+
var listEl = document.getElementById('vault-files-list');
|
|
20650
|
+
if (!listEl) return;
|
|
20651
|
+
var q = document.getElementById('vault-files-search')?.value || '';
|
|
20652
|
+
var agent = document.getElementById('vault-files-agent-filter')?.value || '';
|
|
20653
|
+
var since = document.getElementById('vault-files-since')?.value || '30';
|
|
20654
|
+
// Show skeleton while loading
|
|
20655
|
+
listEl.innerHTML = '<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div><div class="skel-row short"></div></div>';
|
|
20656
|
+
try {
|
|
20657
|
+
var url = '/api/vault-files?sinceDays=' + encodeURIComponent(since)
|
|
20658
|
+
+ (q ? '&q=' + encodeURIComponent(q) : '')
|
|
20659
|
+
+ (agent ? '&agent=' + encodeURIComponent(agent) : '')
|
|
20660
|
+
+ (_vaultFilesFolder ? '&folder=' + encodeURIComponent(_vaultFilesFolder) : '');
|
|
20661
|
+
var r = await apiFetch(url);
|
|
20662
|
+
var d = await r.json();
|
|
20663
|
+
var files = d.files || [];
|
|
20664
|
+
_vaultFilesCache = files;
|
|
20665
|
+
// Populate agent filter from ALL files response (use server's full set, not filtered)
|
|
20666
|
+
var agentSel = document.getElementById('vault-files-agent-filter');
|
|
20667
|
+
if (agentSel && agentSel.options.length <= 2) {
|
|
20668
|
+
var slugs = [...new Set(files.map(function(f) { return f.agentSlug; }).filter(Boolean))].sort();
|
|
20669
|
+
slugs.forEach(function(slug) {
|
|
20670
|
+
var opt = document.createElement('option');
|
|
20671
|
+
opt.value = slug;
|
|
20672
|
+
opt.textContent = slug;
|
|
20673
|
+
agentSel.appendChild(opt);
|
|
20674
|
+
});
|
|
20675
|
+
}
|
|
20676
|
+
// Render folder filter chips (using folderCounts from server)
|
|
20677
|
+
var chipsEl = document.getElementById('vault-files-folder-chips');
|
|
20678
|
+
if (chipsEl && d.folderCounts) {
|
|
20679
|
+
var folders = Object.entries(d.folderCounts).sort(function(a, b) { return b[1] - a[1]; });
|
|
20680
|
+
var totalCount = folders.reduce(function(s, p) { return s + p[1]; }, 0);
|
|
20681
|
+
var chipHtml = '<div class="vault-folder-chip' + (_vaultFilesFolder === '' ? ' active' : '') + '" data-folder="" onclick="setVaultFolderFilter(\\x27\\x27)">All <span style="opacity:0.6">' + totalCount + '</span></div>';
|
|
20682
|
+
folders.forEach(function(p) {
|
|
20683
|
+
var folder = p[0]; var count = p[1];
|
|
20684
|
+
if (!folder) return;
|
|
20685
|
+
chipHtml += '<div class="vault-folder-chip' + (_vaultFilesFolder === folder ? ' active' : '') + '" data-folder="' + esc(folder) + '" onclick="setVaultFolderFilter(\\x27' + esc(folder) + '\\x27)">' + esc(folder) + ' <span style="opacity:0.6">' + count + '</span></div>';
|
|
20686
|
+
});
|
|
20687
|
+
chipsEl.innerHTML = chipHtml;
|
|
20688
|
+
}
|
|
20689
|
+
if (files.length === 0) {
|
|
20690
|
+
listEl.innerHTML = '<div class="empty-cta"><div class="label">No recent files</div><div class="hint">Try a wider time window or different filter.</div></div>';
|
|
20691
|
+
return;
|
|
20692
|
+
}
|
|
20693
|
+
var html = '<div style="font-size:11px;color:var(--text-muted);margin-bottom:10px">Showing ' + files.length + ' of ' + d.total + ' files modified in the last ' + since + ' days.</div>';
|
|
20694
|
+
html += '<div style="display:flex;flex-direction:column;gap:1px;border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;background:var(--bg-card)">';
|
|
20695
|
+
for (var i = 0; i < files.length; i++) {
|
|
20696
|
+
var f = files[i];
|
|
20697
|
+
var agentBadge = f.agentSlug
|
|
20698
|
+
? '<span style="font-size:10px;background:var(--clementine-bg);color:var(--clementine);padding:2px 7px;border-radius:var(--radius-xs);font-weight:500">' + esc(f.agentSlug) + '</span>'
|
|
20699
|
+
: '<span style="font-size:10px;background:var(--bg-tertiary);color:var(--text-muted);padding:2px 7px;border-radius:var(--radius-xs)">shared</span>';
|
|
20700
|
+
var typeBadge = f.type ? '<span style="font-size:10px;color:var(--text-muted);margin-right:6px">' + esc(f.type) + '</span>' : '';
|
|
20701
|
+
html += '<div class="vault-file-row clickable-row" data-path="' + esc(f.relPath) + '" style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--bg-secondary);border-bottom:1px solid var(--border-light);font-size:13px">'
|
|
20702
|
+
+ '<div style="flex:1;min-width:0">'
|
|
20703
|
+
+ '<div style="font-weight:500;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(f.title) + '</div>'
|
|
20704
|
+
+ '<div style="font-size:11px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px">' + esc(f.relPath) + '</div>'
|
|
20705
|
+
+ '</div>'
|
|
20706
|
+
+ '<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">'
|
|
20707
|
+
+ typeBadge + agentBadge
|
|
20708
|
+
+ '<span style="font-size:11px;color:var(--text-muted);min-width:60px;text-align:right">' + esc(timeAgo(f.mtime)) + '</span>'
|
|
20709
|
+
+ '</div>'
|
|
20710
|
+
+ '</div>';
|
|
20711
|
+
}
|
|
20712
|
+
html += '</div>';
|
|
20713
|
+
listEl.innerHTML = html;
|
|
20714
|
+
// Wire row clicks
|
|
20715
|
+
listEl.querySelectorAll('.vault-file-row').forEach(function(row) {
|
|
20716
|
+
row.onclick = function() { openVaultFile(row.getAttribute('data-path')); };
|
|
20717
|
+
});
|
|
20718
|
+
} catch (err) {
|
|
20719
|
+
listEl.innerHTML = '<div style="padding:24px;color:var(--red);font-size:13px">Failed to load: ' + esc(String(err)) + '</div>';
|
|
20720
|
+
}
|
|
20721
|
+
}
|
|
20722
|
+
|
|
20723
|
+
async function openVaultFile(relPath) {
|
|
20724
|
+
if (!relPath) return;
|
|
20725
|
+
// Build/reuse a slide-out drawer for content preview
|
|
20726
|
+
var drawer = document.getElementById('vault-file-drawer');
|
|
20727
|
+
if (!drawer) {
|
|
20728
|
+
drawer = document.createElement('div');
|
|
20729
|
+
drawer.id = 'vault-file-drawer';
|
|
20730
|
+
drawer.style.cssText = 'position:fixed;right:0;top:0;bottom:0;width:560px;max-width:92vw;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-8px 0 32px rgba(0,0,0,0.18);z-index:200;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 200ms ease';
|
|
20731
|
+
drawer.innerHTML =
|
|
20732
|
+
'<div style="display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0">'
|
|
20733
|
+
+ '<div style="flex:1;min-width:0">'
|
|
20734
|
+
+ '<div id="vault-file-drawer-title" style="font-weight:600;font-size:15px;letter-spacing:-0.01em"></div>'
|
|
20735
|
+
+ '<div id="vault-file-drawer-path" style="font-size:11px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></div>'
|
|
20736
|
+
+ '</div>'
|
|
20737
|
+
+ '<button class="btn-icon btn-sm" onclick="closeVaultFileDrawer()" title="Close">' + lucide('x', 'icn-sm') + '</button>'
|
|
20738
|
+
+ '</div>'
|
|
20739
|
+
+ '<div id="vault-file-drawer-body" style="flex:1;overflow-y:auto;padding:18px 22px;font-size:13px;line-height:1.55"></div>';
|
|
20740
|
+
document.body.appendChild(drawer);
|
|
20741
|
+
}
|
|
20742
|
+
var titleEl = document.getElementById('vault-file-drawer-title');
|
|
20743
|
+
var pathEl = document.getElementById('vault-file-drawer-path');
|
|
20744
|
+
var body = document.getElementById('vault-file-drawer-body');
|
|
20745
|
+
if (titleEl) titleEl.textContent = relPath.split('/').pop().replace(/\\.md$/, '');
|
|
20746
|
+
if (pathEl) pathEl.textContent = relPath;
|
|
20747
|
+
if (body) body.innerHTML = '<div class="skel-block"><div class="skel-row"></div><div class="skel-row med"></div><div class="skel-row short"></div></div>';
|
|
20748
|
+
drawer.style.transform = 'translateX(0)';
|
|
20749
|
+
try {
|
|
20750
|
+
var r = await apiFetch('/api/vault-file?path=' + encodeURIComponent(relPath));
|
|
20751
|
+
var d = await r.json();
|
|
20752
|
+
if (d.error) {
|
|
20753
|
+
body.innerHTML = '<div style="color:var(--red)">' + esc(d.error) + '</div>';
|
|
20754
|
+
return;
|
|
20755
|
+
}
|
|
20756
|
+
body.innerHTML = renderMd(d.content);
|
|
20757
|
+
} catch (err) {
|
|
20758
|
+
body.innerHTML = '<div style="color:var(--red)">Failed: ' + esc(String(err)) + '</div>';
|
|
20759
|
+
}
|
|
20760
|
+
}
|
|
20761
|
+
|
|
20762
|
+
function closeVaultFileDrawer() {
|
|
20763
|
+
var drawer = document.getElementById('vault-file-drawer');
|
|
20764
|
+
if (drawer) drawer.style.transform = 'translateX(100%)';
|
|
20765
|
+
}
|
|
20766
|
+
|
|
20767
|
+
function setVaultFolderFilter(folder) {
|
|
20768
|
+
_vaultFilesFolder = folder || '';
|
|
20769
|
+
refreshVaultFiles();
|
|
20770
|
+
}
|
|
20771
|
+
|
|
19819
20772
|
// ── Goals: inline create form ────────────────────────────────────
|
|
19820
20773
|
function openNewGoalForm() {
|
|
19821
20774
|
var el = document.getElementById('new-goal-form');
|
|
@@ -20585,6 +21538,147 @@ async function saveDiscordSetup() {
|
|
|
20585
21538
|
} catch(e) { toast(String(e), 'error'); }
|
|
20586
21539
|
}
|
|
20587
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
|
+
|
|
20588
21682
|
function toggleHomeRail() {
|
|
20589
21683
|
var rail = document.getElementById('home-rail');
|
|
20590
21684
|
if (!rail) return;
|
|
@@ -24039,8 +25133,8 @@ async function refreshSalesforce() {
|
|
|
24039
25133
|
if (d.status) { try { refreshStatus(d.status); } catch(e) { console.warn('init: status', e); } }
|
|
24040
25134
|
if (d.activity) { try { refreshActivity(false, d.activity); } catch(e) { console.warn('init: activity', e); } }
|
|
24041
25135
|
else { try { refreshActivity(); } catch(e) { console.warn('init: activity fallback', e); } }
|
|
24042
|
-
// Populate the home
|
|
24043
|
-
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); } }
|
|
24044
25138
|
if (d.office) { try { refreshTeamNav(d.office); refreshTeamPulse(d.office); } catch(e) { console.warn('init: office', e); } }
|
|
24045
25139
|
if (d.plan) { try { refreshHomePlan(d.plan); } catch(e) { console.warn('init: plan', e); } }
|
|
24046
25140
|
if (d.version) { try { _loadedHash = d.version.started; } catch(e) { /* ignore */ } }
|