claude-launchpad 0.10.1-dev.7 → 0.11.1

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.
@@ -0,0 +1,1008 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MemoryRepo,
4
+ RelationRepo,
5
+ SearchRepo
6
+ } from "./chunk-JXFTVFPC.js";
7
+ import {
8
+ DEFAULT_DECAY_PARAMS,
9
+ closeDatabase,
10
+ createDatabase,
11
+ loadConfig,
12
+ migrate,
13
+ resolveDataDir
14
+ } from "./chunk-JTKRLIEV.js";
15
+ import "./chunk-Z6FBT44W.js";
16
+ import "./chunk-RJGXPH7P.js";
17
+
18
+ // src/commands/memory/dashboard/tui.tsx
19
+ import { render } from "ink";
20
+
21
+ // src/commands/memory/dashboard/data/data-source.ts
22
+ import { statSync, watchFile, unwatchFile } from "fs";
23
+ import { join } from "path";
24
+ var DashboardDataSource = class {
25
+ #memoryRepo;
26
+ #relationRepo;
27
+ #searchRepo;
28
+ #dbPath;
29
+ #cachedMemories = [];
30
+ constructor(memoryRepo, relationRepo, searchRepo, dataDir) {
31
+ this.#memoryRepo = memoryRepo;
32
+ this.#relationRepo = relationRepo;
33
+ this.#searchRepo = searchRepo;
34
+ this.#dbPath = join(dataDir, "memory.db");
35
+ }
36
+ /** Re-query all memories from DB and cache them. Excludes soft-deleted (importance=0). */
37
+ refresh() {
38
+ this.#cachedMemories = this.#memoryRepo.getAll().filter((m) => m.importance > 0);
39
+ }
40
+ /** Return cached memories, optionally filtered by type, project, or FTS query. */
41
+ getMemories(filter) {
42
+ if (!filter) return this.#cachedMemories;
43
+ let results = this.#cachedMemories;
44
+ if (filter.type) {
45
+ const t = filter.type;
46
+ results = results.filter((m) => m.type === t);
47
+ }
48
+ if (filter.project) {
49
+ const p = filter.project;
50
+ results = results.filter((m) => m.project === p);
51
+ }
52
+ if (filter.query) {
53
+ const matches = this.#searchRepo.searchFts({
54
+ query: filter.query,
55
+ limit: 100
56
+ });
57
+ const matchedIds = new Set(matches.map((m) => m.memoryId));
58
+ results = results.filter((m) => matchedIds.has(m.id));
59
+ }
60
+ return results;
61
+ }
62
+ /** Get all relations for a specific memory. */
63
+ getRelationsForMemory(id) {
64
+ return this.#relationRepo.getByMemory(id);
65
+ }
66
+ /** Compute aggregate stats from cached data + DB queries. */
67
+ getStats() {
68
+ const total = this.#memoryRepo.count();
69
+ const byType = this.#memoryRepo.countByType();
70
+ const relations = this.#relationRepo.count();
71
+ const { oldest, newest } = this.#memoryRepo.dateRange();
72
+ const dbSizeBytes = this.#getDbSize();
73
+ const byProject = this.#computeByProject();
74
+ return { total, byType, byProject, relations, dbSizeBytes, oldest, newest };
75
+ }
76
+ /** Derive unique project names from cached memories. */
77
+ getProjects() {
78
+ const projects = /* @__PURE__ */ new Set();
79
+ for (const m of this.#cachedMemories) {
80
+ if (m.project) {
81
+ projects.add(m.project);
82
+ }
83
+ }
84
+ return [...projects].sort();
85
+ }
86
+ /** Watch the DB file for changes (2s polling interval). Only fires on mtime change. */
87
+ startWatching(onChange) {
88
+ let lastMtime = 0;
89
+ watchFile(this.#dbPath, { interval: 2e3 }, (curr) => {
90
+ const mtime = curr.mtimeMs;
91
+ if (mtime === lastMtime) return;
92
+ lastMtime = mtime;
93
+ this.refresh();
94
+ onChange();
95
+ });
96
+ }
97
+ /** Stop watching the DB file. */
98
+ stopWatching() {
99
+ unwatchFile(this.#dbPath);
100
+ }
101
+ // -- Private helpers --------------------------------------------------------
102
+ #getDbSize() {
103
+ try {
104
+ return statSync(this.#dbPath).size;
105
+ } catch {
106
+ return 0;
107
+ }
108
+ }
109
+ #computeByProject() {
110
+ const counts = {};
111
+ for (const m of this.#cachedMemories) {
112
+ const key = m.project ?? "(none)";
113
+ counts[key] = (counts[key] ?? 0) + 1;
114
+ }
115
+ return counts;
116
+ }
117
+ };
118
+
119
+ // src/commands/memory/dashboard/app.tsx
120
+ import { Box as Box10, useApp } from "ink";
121
+
122
+ // src/commands/memory/dashboard/hooks/use-dashboard-state.ts
123
+ import { useState, useMemo, useEffect, useCallback } from "react";
124
+
125
+ // src/commands/memory/dashboard/data/formatters.ts
126
+ var MINUTE = 6e4;
127
+ var HOUR = 36e5;
128
+ var DAY = 864e5;
129
+ var MONTH = 30 * DAY;
130
+ var YEAR = 365 * DAY;
131
+ function formatRelativeTime(isoString) {
132
+ const diff = Date.now() - new Date(isoString).getTime();
133
+ if (diff < 0) return "just now";
134
+ if (diff < MINUTE) return "just now";
135
+ if (diff < HOUR) return `${Math.floor(diff / MINUTE)}m ago`;
136
+ if (diff < DAY) return `${Math.floor(diff / HOUR)}h ago`;
137
+ if (diff < MONTH) return `${Math.floor(diff / DAY)}d ago`;
138
+ if (diff < YEAR) return `${Math.floor(diff / MONTH)}mo ago`;
139
+ return `${Math.floor(diff / YEAR)}y ago`;
140
+ }
141
+ var FILLED = "\u2588";
142
+ var EMPTY = "\u2591";
143
+ function formatImportanceBar(value, width = 8) {
144
+ const clamped = Math.max(0, Math.min(1, value));
145
+ const filled = Math.round(clamped * width);
146
+ return FILLED.repeat(filled) + EMPTY.repeat(width - filled);
147
+ }
148
+ function truncate(text, maxLen) {
149
+ if (text.length <= maxLen) return text;
150
+ if (maxLen <= 1) return "\u2026";
151
+ return text.slice(0, maxLen - 1) + "\u2026";
152
+ }
153
+ var UNITS = ["B", "KB", "MB", "GB"];
154
+ function formatBytes(bytes) {
155
+ if (bytes < 0) return "0B";
156
+ let value = bytes;
157
+ let unitIndex = 0;
158
+ while (value >= 1024 && unitIndex < UNITS.length - 1) {
159
+ value /= 1024;
160
+ unitIndex++;
161
+ }
162
+ if (unitIndex === 0) return `${value}B`;
163
+ return `${value.toFixed(1)}${UNITS[unitIndex]}`;
164
+ }
165
+ function tauDaysForType(type) {
166
+ return DEFAULT_DECAY_PARAMS.tauByType[type];
167
+ }
168
+ function computeLifespan(memory) {
169
+ const tauDays = tauDaysForType(memory.type);
170
+ if (tauDays === 0) {
171
+ return {
172
+ status: "session",
173
+ tauDays,
174
+ ageDays: 0,
175
+ remaining: 0
176
+ };
177
+ }
178
+ const ageDays = Math.max(
179
+ 0,
180
+ (Date.now() - new Date(memory.updatedAt).getTime()) / DAY
181
+ );
182
+ const remaining = Math.max(0, Math.min(1, 1 - ageDays / (tauDays * 2)));
183
+ const status = remaining > 0.62 ? "healthy" : remaining > 0.32 ? "fading" : "stale";
184
+ return { status, tauDays, ageDays, remaining };
185
+ }
186
+ function formatLifespanLabel(status) {
187
+ switch (status) {
188
+ case "healthy":
189
+ return "HEALTHY";
190
+ case "fading":
191
+ return "FADING ";
192
+ case "stale":
193
+ return "STALE ";
194
+ case "session":
195
+ return "SESSION";
196
+ }
197
+ }
198
+
199
+ // src/commands/memory/dashboard/hooks/use-dashboard-state.ts
200
+ var SORT_MODES = ["importance", "age", "access", "lifespan"];
201
+ var LIFESPAN_FILTERS = [
202
+ void 0,
203
+ "healthy",
204
+ "fading",
205
+ "stale",
206
+ "session"
207
+ ];
208
+ function useDashboardState(dataSource) {
209
+ const [revision, setRevision] = useState(0);
210
+ const [typeFilter, setTypeFilter] = useState();
211
+ const [lifespanFilter, setLifespanFilter] = useState();
212
+ const [searchQuery, setSearchQuery] = useState("");
213
+ const [searchActive, setSearchActive] = useState(false);
214
+ const [currentProject, setCurrentProject] = useState();
215
+ const [sortMode, setSortMode] = useState("importance");
216
+ const [selectedIndex, setSelectedIndex] = useState(0);
217
+ const [focusedPane, setFocusedPane] = useState("list");
218
+ const [showHelp, setShowHelp] = useState(false);
219
+ const [showProjectPicker, setShowProjectPicker] = useState(false);
220
+ useEffect(() => {
221
+ dataSource.refresh();
222
+ setRevision((r) => r + 1);
223
+ dataSource.startWatching(() => {
224
+ dataSource.refresh();
225
+ setRevision((r) => r + 1);
226
+ });
227
+ return () => dataSource.stopWatching();
228
+ }, [dataSource]);
229
+ const projects = useMemo(() => {
230
+ void revision;
231
+ return dataSource.getProjects();
232
+ }, [dataSource, revision]);
233
+ const filteredMemories = useMemo(() => {
234
+ void revision;
235
+ const raw = dataSource.getMemories({ type: typeFilter, project: currentProject });
236
+ const withLife = lifespanFilter ? raw.filter((m) => computeLifespan(m).status === lifespanFilter) : raw;
237
+ const withSearch = searchQuery ? withLife.filter(
238
+ (m) => (m.title?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false) || m.content.toLowerCase().includes(searchQuery.toLowerCase()) || m.tags.some((t) => t.toLowerCase().includes(searchQuery.toLowerCase()))
239
+ ) : withLife;
240
+ return sortMemories(withSearch, sortMode);
241
+ }, [dataSource, revision, typeFilter, lifespanFilter, searchQuery, currentProject, sortMode]);
242
+ const selectedMemory = filteredMemories[selectedIndex];
243
+ const relations = useMemo(
244
+ () => selectedMemory ? dataSource.getRelationsForMemory(selectedMemory.id) : [],
245
+ [dataSource, selectedMemory, revision]
246
+ );
247
+ const stats = useMemo(() => {
248
+ void revision;
249
+ return dataSource.getStats();
250
+ }, [dataSource, revision]);
251
+ const navigateUp = useCallback(() => {
252
+ setSelectedIndex((i) => Math.max(0, i - 1));
253
+ }, []);
254
+ const navigateDown = useCallback(() => {
255
+ setSelectedIndex((i) => Math.min(filteredMemories.length - 1, i + 1));
256
+ }, [filteredMemories.length]);
257
+ const cycleSort = useCallback(() => {
258
+ setSortMode((m) => SORT_MODES[(SORT_MODES.indexOf(m) + 1) % SORT_MODES.length]);
259
+ }, []);
260
+ const cycleLifespan = useCallback(() => {
261
+ setLifespanFilter((f) => {
262
+ const idx = LIFESPAN_FILTERS.findIndex((x) => x === f);
263
+ return LIFESPAN_FILTERS[(idx + 1) % LIFESPAN_FILTERS.length];
264
+ });
265
+ }, []);
266
+ const cycleProjectNext = useCallback(() => {
267
+ setCurrentProject((curr) => {
268
+ const idx = curr ? projects.indexOf(curr) : -1;
269
+ return idx >= projects.length - 1 ? void 0 : projects[idx + 1];
270
+ });
271
+ setSelectedIndex(0);
272
+ }, [projects]);
273
+ const cycleProjectPrev = useCallback(() => {
274
+ setCurrentProject((curr) => {
275
+ const idx = curr ? projects.indexOf(curr) : -1;
276
+ return idx <= 0 ? idx === 0 ? void 0 : projects[projects.length - 1] : projects[idx - 1];
277
+ });
278
+ setSelectedIndex(0);
279
+ }, [projects]);
280
+ const focusNext = useCallback(() => {
281
+ setFocusedPane((p) => p === "list" ? "projects" : p === "projects" ? "detail" : "list");
282
+ }, []);
283
+ const filterByType = useCallback((type) => {
284
+ setTypeFilter(type);
285
+ setSelectedIndex(0);
286
+ }, []);
287
+ const openSearch = useCallback(() => setSearchActive(true), []);
288
+ const closeSearch = useCallback(() => {
289
+ setSearchActive(false);
290
+ setSearchQuery("");
291
+ setSelectedIndex(0);
292
+ }, []);
293
+ const refresh = useCallback(() => {
294
+ dataSource.refresh();
295
+ setRevision((r) => r + 1);
296
+ }, [dataSource]);
297
+ return {
298
+ typeFilter,
299
+ lifespanFilter,
300
+ searchQuery,
301
+ searchActive,
302
+ currentProject,
303
+ sortMode,
304
+ selectedIndex,
305
+ focusedPane,
306
+ showHelp,
307
+ showProjectPicker,
308
+ filteredMemories,
309
+ selectedMemory,
310
+ relations,
311
+ projects,
312
+ stats,
313
+ setSearchQuery,
314
+ setCurrentProject,
315
+ setSelectedIndex,
316
+ setShowHelp,
317
+ setShowProjectPicker,
318
+ navigateUp,
319
+ navigateDown,
320
+ cycleSort,
321
+ cycleLifespan,
322
+ cycleProjectNext,
323
+ cycleProjectPrev,
324
+ focusNext,
325
+ filterByType,
326
+ openSearch,
327
+ closeSearch,
328
+ refresh
329
+ };
330
+ }
331
+ function sortMemories(memories, mode) {
332
+ const sorted = [...memories];
333
+ const sorters = {
334
+ importance: (a, b) => b.importance - a.importance,
335
+ age: (a, b) => b.createdAt.localeCompare(a.createdAt),
336
+ access: (a, b) => b.accessCount - a.accessCount,
337
+ lifespan: (a, b) => computeLifespan(a).remaining - computeLifespan(b).remaining
338
+ };
339
+ sorted.sort(sorters[mode]);
340
+ return sorted;
341
+ }
342
+
343
+ // src/commands/memory/dashboard/hooks/use-keybindings.ts
344
+ import { useInput } from "ink";
345
+ var TYPE_KEYS = {
346
+ "1": "working",
347
+ "2": "episodic",
348
+ "3": "semantic",
349
+ "4": "procedural",
350
+ "5": "pattern"
351
+ };
352
+ function useKeybindings(actions, opts) {
353
+ useInput((input, key) => {
354
+ if (opts.searchActive) {
355
+ if (key.escape) actions.closeSearch();
356
+ return;
357
+ }
358
+ if (opts.pickerOpen) {
359
+ if (key.escape) actions.openProjectPicker();
360
+ return;
361
+ }
362
+ if (input === "j" || key.downArrow) actions.navigateDown();
363
+ if (input === "k" || key.upArrow) actions.navigateUp();
364
+ if (input === "/") actions.openSearch();
365
+ if (key.escape) actions.closeSearch();
366
+ if (input === "0") actions.filterByType(void 0);
367
+ if (input in TYPE_KEYS) actions.filterByType(TYPE_KEYS[input]);
368
+ if (input === "l") actions.cycleLifespan();
369
+ if (input === "s") actions.cycleSort();
370
+ if (input === "p") actions.openProjectPicker();
371
+ if (input === "]" || key.rightArrow) actions.cycleProjectNext();
372
+ if (input === "[" || key.leftArrow) actions.cycleProjectPrev();
373
+ if (key.tab) actions.focusNext();
374
+ if (input === "?") actions.showHelp();
375
+ if (input === "r") actions.refresh();
376
+ if (input === "q") actions.quit();
377
+ }, { isActive: !opts.searchActive });
378
+ }
379
+
380
+ // src/commands/memory/dashboard/hooks/use-terminal-size.ts
381
+ import { useStdout } from "ink";
382
+ import { useState as useState2, useEffect as useEffect2 } from "react";
383
+ function useTerminalSize() {
384
+ const { stdout } = useStdout();
385
+ const [size, setSize] = useState2({
386
+ columns: stdout.columns ?? 80,
387
+ rows: stdout.rows ?? 24
388
+ });
389
+ useEffect2(() => {
390
+ const handler = () => setSize({ columns: stdout.columns, rows: stdout.rows });
391
+ stdout.on("resize", handler);
392
+ return () => {
393
+ stdout.off("resize", handler);
394
+ };
395
+ }, [stdout]);
396
+ const layout = size.columns >= 120 ? "wide" : size.columns >= 80 ? "medium" : "narrow";
397
+ return { ...size, layout };
398
+ }
399
+
400
+ // src/commands/memory/dashboard/components/keybinding-bar.tsx
401
+ import React from "react";
402
+ import { Box, Text } from "ink";
403
+ import { jsx, jsxs } from "react/jsx-runtime";
404
+ var HINTS = [
405
+ ["j/k", "navigate"],
406
+ ["/", "search"],
407
+ ["p", "projects"],
408
+ ["1-5", "type"],
409
+ ["l", "life"],
410
+ ["s", "sort"],
411
+ ["Tab", "focus"],
412
+ ["?", "help"],
413
+ ["q", "quit"]
414
+ ];
415
+ function KeybindingBar() {
416
+ return /* @__PURE__ */ jsx(Box, { children: HINTS.map(([key, label], i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
417
+ i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
418
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: key }),
419
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
420
+ " ",
421
+ label
422
+ ] })
423
+ ] }, key)) });
424
+ }
425
+
426
+ // src/commands/memory/dashboard/components/header.tsx
427
+ import { Box as Box2, Text as Text2 } from "ink";
428
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
429
+ function Header({ project, typeFilter, lifespanFilter, sortMode, searchQuery }) {
430
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
431
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "green", children: " agentic-memory " }),
432
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " | " }),
433
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: project ?? "all projects" }),
434
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " | " }),
435
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: typeFilter ?? "all types" }),
436
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " | " }),
437
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: lifespanFilter ?? "all life" }),
438
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " | " }),
439
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
440
+ "sort:",
441
+ sortMode
442
+ ] }),
443
+ searchQuery && /* @__PURE__ */ jsxs2(Fragment, { children: [
444
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " | " }),
445
+ /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
446
+ "search:",
447
+ searchQuery
448
+ ] })
449
+ ] })
450
+ ] });
451
+ }
452
+
453
+ // src/commands/memory/dashboard/components/search-bar.tsx
454
+ import { Box as Box3, Text as Text3 } from "ink";
455
+ import TextInput from "ink-text-input";
456
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
457
+ function SearchBar({ query, onChange, onClose }) {
458
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
459
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Search: " }),
460
+ /* @__PURE__ */ jsx3(TextInput, { value: query, onChange, onSubmit: onClose }),
461
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " (Esc to clear, Enter to keep)" })
462
+ ] });
463
+ }
464
+
465
+ // src/commands/memory/dashboard/components/memory-list.tsx
466
+ import { useMemo as useMemo2 } from "react";
467
+ import { Box as Box4, Text as Text4 } from "ink";
468
+
469
+ // src/commands/memory/dashboard/colors.ts
470
+ var TYPE_COLORS = {
471
+ working: "red",
472
+ episodic: "yellow",
473
+ semantic: "cyan",
474
+ procedural: "green",
475
+ pattern: "magenta"
476
+ };
477
+ var TYPE_ABBREV = {
478
+ working: "WORK",
479
+ episodic: "EPIS",
480
+ semantic: "SEMA",
481
+ procedural: "PROC",
482
+ pattern: "PTRN"
483
+ };
484
+ var RELATION_COLORS = {
485
+ relates_to: "white",
486
+ depends_on: "blue",
487
+ contradicts: "red",
488
+ extends: "green",
489
+ implements: "cyan",
490
+ derived_from: "yellow"
491
+ };
492
+
493
+ // src/commands/memory/dashboard/components/memory-list.tsx
494
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
495
+ var LIFE_COLORS = {
496
+ healthy: "green",
497
+ fading: "yellow",
498
+ stale: "red",
499
+ session: "magenta"
500
+ };
501
+ function MemoryList({ memories, selectedIndex, isFocused, height }) {
502
+ const viewportHeight = Math.max(1, height - 2);
503
+ const scrollOffset = useMemo2(() => {
504
+ if (selectedIndex < 0) return 0;
505
+ if (memories.length <= viewportHeight) return 0;
506
+ const half = Math.floor(viewportHeight / 2);
507
+ const offset = Math.max(0, selectedIndex - half);
508
+ return Math.min(offset, memories.length - viewportHeight);
509
+ }, [selectedIndex, memories.length, viewportHeight]);
510
+ const visible = memories.slice(scrollOffset, scrollOffset + viewportHeight);
511
+ const label = ` Memories [${memories.length}] `;
512
+ return /* @__PURE__ */ jsxs4(
513
+ Box4,
514
+ {
515
+ flexDirection: "column",
516
+ borderStyle: "round",
517
+ borderColor: isFocused ? "cyan" : "gray",
518
+ children: [
519
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: isFocused ? "cyan" : "gray", children: label }),
520
+ visible.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " No memories found" }),
521
+ visible.map((m, i) => {
522
+ const isSelected = scrollOffset + i === selectedIndex;
523
+ return /* @__PURE__ */ jsx4(MemoryRow, { memory: m, isSelected }, m.id);
524
+ })
525
+ ]
526
+ }
527
+ );
528
+ }
529
+ function MemoryRow({ memory, isSelected }) {
530
+ const life = computeLifespan(memory);
531
+ const title = truncate(memory.title ?? memory.content.replace(/\n/g, " "), 36);
532
+ const project = truncate(memory.project ?? "", 14);
533
+ const type = TYPE_ABBREV[memory.type] ?? memory.type;
534
+ const lifeColor = LIFE_COLORS[life.status] ?? "white";
535
+ const lifeLabel = formatLifespanLabel(life.status).trim();
536
+ const imp = `${Math.round(memory.importance * 100)}%`;
537
+ const updated = formatRelativeTime(memory.updatedAt);
538
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { inverse: isSelected, children: [
539
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: isSelected ? "\u25B8 " : " " }),
540
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: title.padEnd(38) }),
541
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: project.padEnd(16) }),
542
+ /* @__PURE__ */ jsxs4(Text4, { color: "cyan", children: [
543
+ type,
544
+ " "
545
+ ] }),
546
+ /* @__PURE__ */ jsx4(Text4, { color: lifeColor, children: lifeLabel.padEnd(8) }),
547
+ /* @__PURE__ */ jsxs4(Text4, { children: [
548
+ imp.padStart(4),
549
+ " "
550
+ ] }),
551
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: updated.padEnd(8) }),
552
+ /* @__PURE__ */ jsxs4(Text4, { color: "blue", children: [
553
+ "acc:",
554
+ memory.accessCount
555
+ ] })
556
+ ] }) });
557
+ }
558
+
559
+ // src/commands/memory/dashboard/components/memory-detail.tsx
560
+ import { Box as Box5, Text as Text5 } from "ink";
561
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
562
+ function MemoryDetail({ memory, relations, isFocused }) {
563
+ return /* @__PURE__ */ jsxs5(
564
+ Box5,
565
+ {
566
+ flexDirection: "column",
567
+ borderStyle: "round",
568
+ borderColor: isFocused ? "blue" : "gray",
569
+ paddingX: 1,
570
+ children: [
571
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: isFocused ? "blue" : "gray", children: " Detail " }),
572
+ !memory ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Select a memory to view details" }) : /* @__PURE__ */ jsx5(DetailContent, { memory, relations })
573
+ ]
574
+ }
575
+ );
576
+ }
577
+ function DetailContent({ memory, relations }) {
578
+ const life = computeLifespan(memory);
579
+ const typeColor = TYPE_COLORS[memory.type] ?? "white";
580
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
581
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: memory.title ?? "(untitled)" }),
582
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
583
+ /* @__PURE__ */ jsxs5(Text5, { children: [
584
+ "Type: ",
585
+ /* @__PURE__ */ jsx5(Text5, { color: typeColor, children: memory.type })
586
+ ] }),
587
+ /* @__PURE__ */ jsxs5(Text5, { children: [
588
+ "Lifespan: ",
589
+ formatLifespanLabel(life.status),
590
+ " | age ",
591
+ Math.round(life.ageDays),
592
+ "d | tau ",
593
+ life.tauDays,
594
+ "d"
595
+ ] }),
596
+ /* @__PURE__ */ jsxs5(Text5, { children: [
597
+ "Health: ",
598
+ formatImportanceBar(life.remaining),
599
+ " ",
600
+ (life.remaining * 100).toFixed(0),
601
+ "%"
602
+ ] }),
603
+ /* @__PURE__ */ jsxs5(Text5, { children: [
604
+ "Importance: ",
605
+ formatImportanceBar(memory.importance),
606
+ " ",
607
+ memory.importance.toFixed(2)
608
+ ] }),
609
+ /* @__PURE__ */ jsxs5(Text5, { children: [
610
+ "Project: ",
611
+ memory.project ?? "(none)"
612
+ ] }),
613
+ /* @__PURE__ */ jsxs5(Text5, { children: [
614
+ "Tags: ",
615
+ memory.tags.length > 0 ? memory.tags.map((t) => `[${t}]`).join(" ") : "(none)"
616
+ ] }),
617
+ /* @__PURE__ */ jsxs5(Text5, { children: [
618
+ "Source: ",
619
+ memory.source ?? "unknown"
620
+ ] }),
621
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
622
+ /* @__PURE__ */ jsxs5(Text5, { children: [
623
+ "Created: ",
624
+ formatRelativeTime(memory.createdAt)
625
+ ] }),
626
+ /* @__PURE__ */ jsxs5(Text5, { children: [
627
+ "Updated: ",
628
+ formatRelativeTime(memory.updatedAt)
629
+ ] }),
630
+ /* @__PURE__ */ jsxs5(Text5, { children: [
631
+ "Accessed: ",
632
+ memory.accessCount,
633
+ "x",
634
+ memory.lastAccessed ? ` (last: ${formatRelativeTime(memory.lastAccessed)})` : ""
635
+ ] }),
636
+ /* @__PURE__ */ jsxs5(Text5, { children: [
637
+ "Injected: ",
638
+ memory.injectionCount,
639
+ "x"
640
+ ] }),
641
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
642
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Content" }),
643
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500".repeat(40) }),
644
+ /* @__PURE__ */ jsx5(Text5, { children: memory.content }),
645
+ relations.length > 0 && /* @__PURE__ */ jsx5(RelationsList, { relations, memoryId: memory.id })
646
+ ] });
647
+ }
648
+ function RelationsList({ relations, memoryId }) {
649
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
650
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Relations" }),
651
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500".repeat(40) }),
652
+ relations.map((r, i) => {
653
+ const relColor = RELATION_COLORS[r.relationType] ?? "white";
654
+ const direction = r.sourceId === memoryId ? "\u2192" : "\u2190";
655
+ const otherId = r.sourceId === memoryId ? r.targetId : r.sourceId;
656
+ return /* @__PURE__ */ jsxs5(Text5, { children: [
657
+ " ",
658
+ direction,
659
+ " ",
660
+ /* @__PURE__ */ jsx5(Text5, { color: relColor, children: r.relationType }),
661
+ " ",
662
+ otherId
663
+ ] }, i);
664
+ })
665
+ ] });
666
+ }
667
+
668
+ // src/commands/memory/dashboard/components/project-list.tsx
669
+ import { useMemo as useMemo3 } from "react";
670
+ import { Box as Box6, Text as Text6 } from "ink";
671
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
672
+ function ProjectList({ memories, activeProject, isFocused }) {
673
+ const rows = useMemo3(() => buildProjectRows(memories), [memories]);
674
+ return /* @__PURE__ */ jsxs6(
675
+ Box6,
676
+ {
677
+ flexDirection: "column",
678
+ borderStyle: "round",
679
+ borderColor: isFocused ? "magenta" : "gray",
680
+ children: [
681
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: isFocused ? "magenta" : "gray", children: " Projects " }),
682
+ rows.map((row) => {
683
+ const isActive = row.project === activeProject || row.project === void 0 && !activeProject;
684
+ const name = (row.project ?? "All projects").padEnd(20).slice(0, 20);
685
+ const healthColor = row.healthPct > 62 ? "green" : row.healthPct > 32 ? "yellow" : "red";
686
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
687
+ /* @__PURE__ */ jsx6(Text6, { bold: isActive, children: isActive ? "> " : " " }),
688
+ /* @__PURE__ */ jsx6(Text6, { bold: isActive, children: name }),
689
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
690
+ String(row.total).padStart(4),
691
+ " mem"
692
+ ] }),
693
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
694
+ /* @__PURE__ */ jsxs6(Text6, { color: healthColor, children: [
695
+ String(row.healthPct).padStart(3),
696
+ "%"
697
+ ] })
698
+ ] }, row.project ?? "_all");
699
+ })
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ function buildProjectRows(memories) {
705
+ const byProject = /* @__PURE__ */ new Map();
706
+ for (const m of memories) {
707
+ const key = m.project ?? "(none)";
708
+ const list = byProject.get(key) ?? [];
709
+ list.push(m);
710
+ byProject.set(key, list);
711
+ }
712
+ const rows = [];
713
+ for (const [project, mems] of byProject) {
714
+ const avg = mems.reduce((s, m) => s + computeLifespan(m).remaining, 0) / mems.length;
715
+ rows.push({ project, total: mems.length, healthPct: Math.round(avg * 100) });
716
+ }
717
+ rows.sort((a, b) => b.total - a.total);
718
+ const allHealth = memories.length > 0 ? Math.round(memories.reduce((s, m) => s + computeLifespan(m).remaining, 0) / memories.length * 100) : 0;
719
+ return [{ project: void 0, total: memories.length, healthPct: allHealth }, ...rows];
720
+ }
721
+
722
+ // src/commands/memory/dashboard/components/stats-bar.tsx
723
+ import { Box as Box7, Text as Text7 } from "ink";
724
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
725
+ function StatsBar({ stats, visible }) {
726
+ const lifeCounts = { healthy: 0, fading: 0, stale: 0, session: 0 };
727
+ for (const m of visible) {
728
+ lifeCounts[computeLifespan(m).status]++;
729
+ }
730
+ const typeEntries = Object.entries(stats.byType).filter(([, c]) => c > 0);
731
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
732
+ /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
733
+ /* @__PURE__ */ jsxs7(Text7, { children: [
734
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Total:" }),
735
+ " ",
736
+ stats.total
737
+ ] }),
738
+ /* @__PURE__ */ jsxs7(Text7, { children: [
739
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Relations:" }),
740
+ " ",
741
+ stats.relations
742
+ ] }),
743
+ /* @__PURE__ */ jsxs7(Text7, { children: [
744
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Visible:" }),
745
+ " ",
746
+ visible.length
747
+ ] }),
748
+ /* @__PURE__ */ jsxs7(Text7, { children: [
749
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "DB:" }),
750
+ " ",
751
+ formatBytes(stats.dbSizeBytes)
752
+ ] })
753
+ ] }),
754
+ /* @__PURE__ */ jsxs7(Box7, { gap: 2, children: [
755
+ /* @__PURE__ */ jsxs7(Text7, { children: [
756
+ /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
757
+ "H:",
758
+ lifeCounts.healthy
759
+ ] }),
760
+ " ",
761
+ /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
762
+ "F:",
763
+ lifeCounts.fading
764
+ ] }),
765
+ " ",
766
+ /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
767
+ "S:",
768
+ lifeCounts.stale
769
+ ] }),
770
+ " ",
771
+ /* @__PURE__ */ jsxs7(Text7, { color: "magenta", children: [
772
+ "Sess:",
773
+ lifeCounts.session
774
+ ] })
775
+ ] }),
776
+ typeEntries.map(([type, count]) => /* @__PURE__ */ jsxs7(Text7, { color: TYPE_COLORS[type] ?? "white", children: [
777
+ type,
778
+ ":",
779
+ count
780
+ ] }, type))
781
+ ] })
782
+ ] });
783
+ }
784
+
785
+ // src/commands/memory/dashboard/components/help-overlay.tsx
786
+ import { Box as Box8, Text as Text8, useInput as useInput2 } from "ink";
787
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
788
+ var BINDINGS = [
789
+ ["j / k / \u2191\u2193", "Navigate list"],
790
+ ["Enter", "Select memory"],
791
+ ["/", "Search (live filter)"],
792
+ ["Esc", "Clear search"],
793
+ ["1-5", "Filter by type (0 = all)"],
794
+ ["l", "Cycle lifespan filter"],
795
+ ["s", "Cycle sort mode"],
796
+ ["p", "Open project picker"],
797
+ ["[ / ]", "Previous / next project"],
798
+ ["Tab", "Focus next pane"],
799
+ ["r", "Refresh"],
800
+ ["?", "Show this help"],
801
+ ["q", "Quit"]
802
+ ];
803
+ function HelpOverlay({ onClose }) {
804
+ useInput2(() => onClose());
805
+ return /* @__PURE__ */ jsxs8(
806
+ Box8,
807
+ {
808
+ flexDirection: "column",
809
+ borderStyle: "double",
810
+ borderColor: "yellow",
811
+ paddingX: 2,
812
+ paddingY: 1,
813
+ children: [
814
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "yellow", children: "Keybindings" }),
815
+ /* @__PURE__ */ jsx8(Text8, { children: " " }),
816
+ BINDINGS.map(([key, desc]) => /* @__PURE__ */ jsxs8(Box8, { gap: 1, children: [
817
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: key.padEnd(14) }),
818
+ /* @__PURE__ */ jsx8(Text8, { children: desc })
819
+ ] }, key)),
820
+ /* @__PURE__ */ jsx8(Text8, { children: " " }),
821
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press any key to close" })
822
+ ]
823
+ }
824
+ );
825
+ }
826
+
827
+ // src/commands/memory/dashboard/components/project-picker.tsx
828
+ import React4 from "react";
829
+ import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
830
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
831
+ function ProjectPicker({ projects, activeProject, onSelect, onClose }) {
832
+ const [selectedIdx, setSelectedIdx] = React4.useState(() => {
833
+ if (!activeProject) return 0;
834
+ const idx = projects.indexOf(activeProject);
835
+ return idx >= 0 ? idx + 1 : 0;
836
+ });
837
+ const options = [
838
+ { label: "All projects", project: void 0 },
839
+ ...projects.map((p) => ({ label: p, project: p }))
840
+ ];
841
+ useInput3((input, key) => {
842
+ if (key.escape) {
843
+ onClose();
844
+ return;
845
+ }
846
+ if (input === "j" || key.downArrow) setSelectedIdx((i) => Math.min(options.length - 1, i + 1));
847
+ if (input === "k" || key.upArrow) setSelectedIdx((i) => Math.max(0, i - 1));
848
+ if (key.return) {
849
+ onSelect(options[selectedIdx]?.project);
850
+ onClose();
851
+ }
852
+ });
853
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "double", borderColor: "yellow", paddingX: 2, paddingY: 1, children: [
854
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "yellow", children: "Project Picker" }),
855
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter=select Esc=close" }),
856
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
857
+ options.map((opt, i) => {
858
+ const isSelected = i === selectedIdx;
859
+ const isActive = opt.project === activeProject || opt.project === void 0 && !activeProject;
860
+ return /* @__PURE__ */ jsxs9(Text9, { inverse: isSelected, children: [
861
+ isActive ? "> " : " ",
862
+ opt.label
863
+ ] }, opt.label);
864
+ })
865
+ ] });
866
+ }
867
+
868
+ // src/commands/memory/dashboard/app.tsx
869
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
870
+ function App({ dataSource }) {
871
+ const { exit } = useApp();
872
+ const { rows, layout } = useTerminalSize();
873
+ const state = useDashboardState(dataSource);
874
+ useKeybindings({
875
+ navigateUp: state.navigateUp,
876
+ navigateDown: state.navigateDown,
877
+ openSearch: state.openSearch,
878
+ closeSearch: state.closeSearch,
879
+ filterByType: state.filterByType,
880
+ cycleLifespan: state.cycleLifespan,
881
+ cycleProjectNext: state.cycleProjectNext,
882
+ cycleProjectPrev: state.cycleProjectPrev,
883
+ cycleSort: state.cycleSort,
884
+ focusNext: state.focusNext,
885
+ openProjectPicker: () => state.setShowProjectPicker((v) => !v),
886
+ showHelp: () => state.setShowHelp((v) => !v),
887
+ refresh: state.refresh,
888
+ quit: exit
889
+ }, {
890
+ searchActive: state.searchActive,
891
+ pickerOpen: state.showProjectPicker
892
+ });
893
+ if (state.showHelp) {
894
+ return /* @__PURE__ */ jsx10(HelpOverlay, { onClose: () => state.setShowHelp(false) });
895
+ }
896
+ if (state.showProjectPicker) {
897
+ return /* @__PURE__ */ jsx10(
898
+ ProjectPicker,
899
+ {
900
+ projects: [...state.projects],
901
+ activeProject: state.currentProject,
902
+ onSelect: (p) => {
903
+ state.setCurrentProject(p);
904
+ state.setSelectedIndex(0);
905
+ },
906
+ onClose: () => state.setShowProjectPicker(false)
907
+ }
908
+ );
909
+ }
910
+ const contentHeight = Math.max(4, rows - 6);
911
+ const isNarrow = layout === "narrow";
912
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
913
+ /* @__PURE__ */ jsx10(KeybindingBar, {}),
914
+ /* @__PURE__ */ jsx10(
915
+ Header,
916
+ {
917
+ project: state.currentProject,
918
+ typeFilter: state.typeFilter,
919
+ lifespanFilter: state.lifespanFilter,
920
+ sortMode: state.sortMode,
921
+ searchQuery: state.searchQuery
922
+ }
923
+ ),
924
+ state.searchActive && /* @__PURE__ */ jsx10(
925
+ SearchBar,
926
+ {
927
+ query: state.searchQuery,
928
+ onChange: state.setSearchQuery,
929
+ onClose: () => state.setSearchQuery(state.searchQuery)
930
+ }
931
+ ),
932
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: isNarrow ? "column" : "row", children: [
933
+ /* @__PURE__ */ jsx10(Box10, { width: isNarrow ? "100%" : "60%", children: /* @__PURE__ */ jsx10(
934
+ MemoryList,
935
+ {
936
+ memories: state.filteredMemories,
937
+ selectedIndex: state.selectedIndex,
938
+ isFocused: state.focusedPane === "list",
939
+ height: contentHeight
940
+ }
941
+ ) }),
942
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: isNarrow ? "100%" : "40%", children: [
943
+ !isNarrow && /* @__PURE__ */ jsx10(
944
+ ProjectList,
945
+ {
946
+ memories: state.filteredMemories,
947
+ activeProject: state.currentProject,
948
+ isFocused: state.focusedPane === "projects"
949
+ }
950
+ ),
951
+ /* @__PURE__ */ jsx10(
952
+ MemoryDetail,
953
+ {
954
+ memory: state.selectedMemory,
955
+ relations: state.relations,
956
+ isFocused: state.focusedPane === "detail"
957
+ }
958
+ )
959
+ ] })
960
+ ] }),
961
+ /* @__PURE__ */ jsx10(StatsBar, { stats: state.stats, visible: state.filteredMemories })
962
+ ] });
963
+ }
964
+
965
+ // src/commands/memory/dashboard/tui.tsx
966
+ import { jsx as jsx11 } from "react/jsx-runtime";
967
+ async function startTui(options) {
968
+ const config = loadConfig(
969
+ options?.dbPath ? { dataDir: options.dbPath } : void 0
970
+ );
971
+ const dataDir = resolveDataDir(config.dataDir);
972
+ const db = createDatabase({ dataDir });
973
+ migrate(db);
974
+ const memoryRepo = new MemoryRepo(db);
975
+ const relationRepo = new RelationRepo(db);
976
+ const searchRepo = new SearchRepo(db);
977
+ const dataSource = new DashboardDataSource(
978
+ memoryRepo,
979
+ relationRepo,
980
+ searchRepo,
981
+ dataDir
982
+ );
983
+ let shuttingDown = false;
984
+ const { waitUntilExit, unmount } = render(
985
+ /* @__PURE__ */ jsx11(App, { dataSource })
986
+ );
987
+ function shutdown() {
988
+ if (shuttingDown) return;
989
+ shuttingDown = true;
990
+ dataSource.stopWatching();
991
+ unmount();
992
+ closeDatabase(db);
993
+ }
994
+ process.on("SIGINT", shutdown);
995
+ process.on("SIGTERM", shutdown);
996
+ try {
997
+ await waitUntilExit();
998
+ } finally {
999
+ if (!shuttingDown) {
1000
+ dataSource.stopWatching();
1001
+ closeDatabase(db);
1002
+ }
1003
+ }
1004
+ }
1005
+ export {
1006
+ startTui
1007
+ };
1008
+ //# sourceMappingURL=tui-ERRK7V4J.js.map