claude-memory-layer 1.0.9 → 1.0.11
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/index.js +1373 -184
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +445 -7
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +705 -43
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/session-end.js +593 -52
- package/dist/hooks/session-end.js.map +3 -3
- package/dist/hooks/session-start.js +581 -25
- package/dist/hooks/session-start.js.map +3 -3
- package/dist/hooks/stop.js +693 -73
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +674 -94
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1045 -42
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1054 -51
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +599 -25
- package/dist/services/memory-service.js.map +3 -3
- package/dist/ui/app.js +1380 -55
- package/dist/ui/index.html +311 -148
- package/dist/ui/style.css +892 -0
- package/docs/OPERATIONS.md +18 -0
- package/package.json +8 -2
- package/scripts/fix-sync-gap.js +32 -0
- package/scripts/heartbeat-memory-orchestrator.sh +28 -0
- package/scripts/report-sync-gap.js +26 -0
- package/scripts/review-queue-auto-resolve.js +21 -0
- package/scripts/sync-gap-auto-heal.sh +17 -0
- package/specs/20260207-dashboard-upgrade/context.md +38 -0
- package/specs/20260207-dashboard-upgrade/spec.md +96 -0
- package/src/cli/index.ts +110 -58
- package/src/core/sqlite-event-store.ts +542 -6
- package/src/core/sqlite-wrapper.ts +8 -0
- package/src/core/turn-state.ts +159 -0
- package/src/core/types.ts +23 -8
- package/src/core/vector-store.ts +21 -3
- package/src/hooks/post-tool-use.ts +68 -23
- package/src/hooks/session-end.ts +8 -3
- package/src/hooks/stop.ts +96 -25
- package/src/hooks/user-prompt-submit.ts +78 -65
- package/src/server/api/chat.ts +244 -0
- package/src/server/api/citations.ts +3 -3
- package/src/server/api/events.ts +30 -5
- package/src/server/api/index.ts +7 -1
- package/src/server/api/projects.ts +74 -0
- package/src/server/api/search.ts +3 -3
- package/src/server/api/sessions.ts +3 -3
- package/src/server/api/stats.ts +43 -7
- package/src/server/api/turns.ts +143 -0
- package/src/server/api/utils.ts +46 -0
- package/src/services/memory-service.ts +208 -9
- package/src/services/session-history-importer.ts +215 -51
- package/src/ui/app.js +1380 -55
- package/src/ui/index.html +311 -148
- package/src/ui/style.css +892 -0
- package/.claude/settings.local.json +0 -27
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260201112328.json +0 -45
- package/.history/package_20260201113602.json +0 -45
- package/.history/package_20260201113713.json +0 -45
- package/.history/package_20260201114110.json +0 -45
- package/.history/package_20260201114632.json +0 -46
- package/.history/package_20260201133143.json +0 -45
- package/.history/package_20260201134319.json +0 -45
- package/.history/package_20260201134326.json +0 -45
- package/.history/package_20260201134334.json +0 -45
- package/.history/package_20260201134912.json +0 -45
- package/.history/package_20260201142928.json +0 -46
- package/.history/package_20260201192048.json +0 -47
- package/.history/package_20260202114053.json +0 -49
- package/test_access.js +0 -49
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Operations Runbook
|
|
2
|
+
|
|
3
|
+
## Health scripts
|
|
4
|
+
|
|
5
|
+
- `npm run ops:sync-gap:report`
|
|
6
|
+
- `npm run ops:sync-gap:fix`
|
|
7
|
+
- `npm run ops:sync-gap:heal`
|
|
8
|
+
- `npm run ops:review:resolve`
|
|
9
|
+
- `npm run ops:heartbeat`
|
|
10
|
+
|
|
11
|
+
## Status policy
|
|
12
|
+
|
|
13
|
+
- `ok`: no failed outbox items and no un-leveled events after heal
|
|
14
|
+
- `needs-attention`: failed outbox remains or level sync still broken
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
|
|
18
|
+
These scripts are best-effort operational automation for Claude memory DB (`~/.claude-code/memory/events.sqlite`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-memory-layer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Claude Code plugin that learns from conversations to provide personalized assistance",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
"test": "vitest",
|
|
14
14
|
"test:coverage": "vitest --coverage",
|
|
15
15
|
"lint": "eslint src/**/*.ts",
|
|
16
|
-
"typecheck": "tsc --noEmit"
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"ops:sync-gap:report": "node scripts/report-sync-gap.js",
|
|
18
|
+
"ops:sync-gap:fix": "node scripts/fix-sync-gap.js",
|
|
19
|
+
"ops:sync-gap:heal": "bash scripts/sync-gap-auto-heal.sh",
|
|
20
|
+
"ops:review:resolve": "node scripts/review-queue-auto-resolve.js",
|
|
21
|
+
"ops:heartbeat": "bash scripts/heartbeat-memory-orchestrator.sh",
|
|
22
|
+
"ops:health": "npm run ops:sync-gap:report && npm run ops:sync-gap:heal && npm run ops:review:resolve"
|
|
17
23
|
},
|
|
18
24
|
"keywords": [
|
|
19
25
|
"claude-code",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
|
|
7
|
+
const dbPath = process.env.CML_DB_PATH || path.join(os.homedir(), '.claude-code', 'memory', 'events.sqlite');
|
|
8
|
+
if (!fs.existsSync(dbPath)) {
|
|
9
|
+
console.log(JSON.stringify({ status: 'skip', reason: 'db_not_found', dbPath }, null, 2));
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const db = new Database(dbPath);
|
|
14
|
+
const hasTable = (name) => !!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
|
|
15
|
+
|
|
16
|
+
let inserted = { changes: 0 };
|
|
17
|
+
if (hasTable('events') && hasTable('memory_levels')) {
|
|
18
|
+
inserted = db.prepare(`
|
|
19
|
+
INSERT OR IGNORE INTO memory_levels (event_id, level, promoted_at)
|
|
20
|
+
SELECT e.id, 'L0', datetime('now')
|
|
21
|
+
FROM events e
|
|
22
|
+
LEFT JOIN memory_levels ml ON e.id = ml.event_id
|
|
23
|
+
WHERE ml.event_id IS NULL
|
|
24
|
+
`).run();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let recovered = { changes: 0 };
|
|
28
|
+
if (hasTable('vector_outbox')) {
|
|
29
|
+
recovered = db.prepare(`UPDATE vector_outbox SET status='pending', updated_at=datetime('now') WHERE status='processing'`).run();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(JSON.stringify({ status: 'ok', dbPath, leveledInserted: Number(inserted.changes||0), recoveredProcessingOutbox: Number(recovered.changes||0) }, null, 2));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
TMP_REPORT=$(mktemp)
|
|
5
|
+
TMP_HEAL=$(mktemp)
|
|
6
|
+
TMP_REVIEW=$(mktemp)
|
|
7
|
+
trap 'rm -f "$TMP_REPORT" "$TMP_HEAL" "$TMP_REVIEW"' EXIT
|
|
8
|
+
|
|
9
|
+
node scripts/report-sync-gap.js >"$TMP_REPORT" || echo '{"status":"error","reason":"report_failed"}' >"$TMP_REPORT"
|
|
10
|
+
bash scripts/sync-gap-auto-heal.sh >"$TMP_HEAL" || echo '{"status":"error","reason":"heal_failed"}' >"$TMP_HEAL"
|
|
11
|
+
node scripts/review-queue-auto-resolve.js >"$TMP_REVIEW" || echo '{"status":"error","reason":"review_failed"}' >"$TMP_REVIEW"
|
|
12
|
+
|
|
13
|
+
node - <<'EOF' "$TMP_REPORT" "$TMP_HEAL" "$TMP_REVIEW"
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const safe = (p) => { try { return JSON.parse(fs.readFileSync(p,'utf8')); } catch { return { status:'error', reason:'parse_failed' }; } };
|
|
16
|
+
const [r,h,v] = process.argv.slice(2);
|
|
17
|
+
const report = safe(r);
|
|
18
|
+
const heal = safe(h);
|
|
19
|
+
const review = safe(v);
|
|
20
|
+
const after = heal.after || {};
|
|
21
|
+
const needsAttention = (report.outboxFailedCount||0) > 0 || (after.inEventsNotLeveledCount||0) > 0 || report.status === 'error' || heal.status === 'error';
|
|
22
|
+
console.log(JSON.stringify({
|
|
23
|
+
status: needsAttention ? 'needs-attention' : 'ok',
|
|
24
|
+
report,
|
|
25
|
+
heal,
|
|
26
|
+
review
|
|
27
|
+
}, null, 2));
|
|
28
|
+
EOF
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
|
|
7
|
+
const dbPath = process.env.CML_DB_PATH || path.join(os.homedir(), '.claude-code', 'memory', 'events.sqlite');
|
|
8
|
+
if (!fs.existsSync(dbPath)) {
|
|
9
|
+
console.log(JSON.stringify({ status: 'skip', reason: 'db_not_found', dbPath }, null, 2));
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const db = new Database(dbPath, { readonly: true });
|
|
14
|
+
const total = db.prepare('SELECT COUNT(*) as c FROM events').get();
|
|
15
|
+
const noLevel = db.prepare(`SELECT COUNT(*) as c FROM events e LEFT JOIN memory_levels ml ON e.id=ml.event_id WHERE ml.event_id IS NULL`).get();
|
|
16
|
+
const outboxPending = db.prepare(`SELECT COUNT(*) as c FROM vector_outbox WHERE status='pending'`).get();
|
|
17
|
+
const outboxFailed = db.prepare(`SELECT COUNT(*) as c FROM vector_outbox WHERE status='failed'`).get();
|
|
18
|
+
|
|
19
|
+
console.log(JSON.stringify({
|
|
20
|
+
status: 'ok',
|
|
21
|
+
dbPath,
|
|
22
|
+
totalEvents: total.c,
|
|
23
|
+
inEventsNotLeveledCount: noLevel.c,
|
|
24
|
+
outboxPendingCount: outboxPending.c,
|
|
25
|
+
outboxFailedCount: outboxFailed.c,
|
|
26
|
+
}, null, 2));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
|
|
7
|
+
const dbPath = process.env.CML_DB_PATH || path.join(os.homedir(), '.claude-code', 'memory', 'events.sqlite');
|
|
8
|
+
if (!fs.existsSync(dbPath)) {
|
|
9
|
+
console.log(JSON.stringify({ status: 'skip', reason: 'db_not_found', dbPath }, null, 2));
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const db = new Database(dbPath);
|
|
14
|
+
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='candidate_memories'").get();
|
|
15
|
+
if (!table) {
|
|
16
|
+
console.log(JSON.stringify({ status: 'skip', reason: 'candidate_memories_table_missing', dbPath }, null, 2));
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const res = db.prepare(`UPDATE candidate_memories SET status='resolved', updated_at=datetime('now') WHERE status='pending' AND confidence >= 0.9`).run();
|
|
21
|
+
console.log(JSON.stringify({ status: 'ok', resolved: Number(res.changes || 0) }, null, 2));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
REPORT=$(node scripts/report-sync-gap.js)
|
|
5
|
+
PENDING=$(echo "$REPORT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(String(o.outboxPendingCount||0));')
|
|
6
|
+
FAILED=$(echo "$REPORT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(String(o.outboxFailedCount||0));')
|
|
7
|
+
NOLEVEL=$(echo "$REPORT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(String(o.inEventsNotLeveledCount||0));')
|
|
8
|
+
|
|
9
|
+
AUTO=0
|
|
10
|
+
if [[ "$FAILED" -gt 0 || "$NOLEVEL" -gt 0 ]]; then
|
|
11
|
+
AUTO=1
|
|
12
|
+
node scripts/fix-sync-gap.js >/dev/null
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
AFTER=$(node scripts/report-sync-gap.js)
|
|
16
|
+
|
|
17
|
+
node -e 'const before=JSON.parse(process.argv[1]); const after=JSON.parse(process.argv[2]); const auto=Number(process.argv[3]); console.log(JSON.stringify({status:"ok",autoFixApplied:auto,before,after},null,2));' "$REPORT" "$AFTER" "$AUTO"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Dashboard Upgrade - Context
|
|
2
|
+
|
|
3
|
+
## Implementation Status: COMPLETE
|
|
4
|
+
|
|
5
|
+
## Changes Made
|
|
6
|
+
|
|
7
|
+
### `src/ui/index.html`
|
|
8
|
+
- Added `data-nav` attributes to sidebar nav items (overview, knowledge-graph, memory-banks, configuration)
|
|
9
|
+
- Added `data-stat` attributes to stat cards (events, sessions, shared, vectors)
|
|
10
|
+
- Wrapped existing dashboard content in `#view-overview` page view container
|
|
11
|
+
- Added new page view containers: `#view-knowledge-graph`, `#view-memory-banks`, `#view-configuration`
|
|
12
|
+
- Added Detail Modal (`#detail-modal`) for event details
|
|
13
|
+
- Added List Modal (`#list-modal`) for stat card drill-downs
|
|
14
|
+
|
|
15
|
+
### `src/ui/style.css`
|
|
16
|
+
- Page view system (`.page-view`, show/hide with animation)
|
|
17
|
+
- Modal overlay, container, header, body, close button styles
|
|
18
|
+
- Modal content styles (meta items, content block, context list, list items)
|
|
19
|
+
- Knowledge Graph view styles (grid, memory cards, topic tags)
|
|
20
|
+
- Memory Banks view styles (level tabs, event cards)
|
|
21
|
+
- Configuration view styles (grid sections, rows)
|
|
22
|
+
- Responsive modal styles for mobile
|
|
23
|
+
|
|
24
|
+
### `src/ui/app.js`
|
|
25
|
+
- **Modal system**: `openModal()`, `closeModal()`, `closeAllModals()`, ESC key handler
|
|
26
|
+
- **Event detail modal**: `openDetailModal(eventId)` - fetches full content + context from `/api/events/:id`
|
|
27
|
+
- **Stat card handlers**:
|
|
28
|
+
- Total Events → `showEventsListModal()` via `/api/events?limit=50`
|
|
29
|
+
- Active Sessions → `showSessionsModal()` via `/api/sessions` + `showSessionDetailInModal()`
|
|
30
|
+
- Shared Items → `showSharedModal()` using cached state
|
|
31
|
+
- Vector Nodes → `showVectorsModal()` using cached state
|
|
32
|
+
- **Navigation**: `switchView()` toggles page views and loads content
|
|
33
|
+
- **Knowledge Graph view**: `loadKnowledgeGraphView()` - top topics, most accessed, most helpful
|
|
34
|
+
- **Memory Banks view**: `loadMemoryBanksView()` + `loadMemoryBankLevel()` - L0-L4 tabs with events
|
|
35
|
+
- **Configuration view**: `loadConfigurationView()` - storage, endless mode, graduation criteria
|
|
36
|
+
|
|
37
|
+
## API Endpoints Used
|
|
38
|
+
All existing endpoints - no new APIs needed.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Dashboard Upgrade Spec
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
대시보드에 인터랙티브 기능 추가. 메모리 이벤트 클릭 시 상세 모달, stat 카드 클릭 시 관련 데이터 목록, 사이드바 네비게이션으로 페이지 전환.
|
|
5
|
+
|
|
6
|
+
## User Stories
|
|
7
|
+
|
|
8
|
+
### US-1: 메모리 이벤트 상세 모달
|
|
9
|
+
- **As a** 사용자
|
|
10
|
+
- **I want** 메모리 이벤트를 클릭하면 전체 내용을 모달로 볼 수 있다
|
|
11
|
+
- **So that** 잘린 preview가 아닌 전체 content와 context를 확인할 수 있다
|
|
12
|
+
|
|
13
|
+
### US-2: Stat 카드 클릭 → 관련 데이터 표시
|
|
14
|
+
- **As a** 사용자
|
|
15
|
+
- **I want** Total Events, Active Sessions, Shared Items, Vector Nodes 카드를 클릭하면 해당 데이터 목록을 볼 수 있다
|
|
16
|
+
- **So that** 대시보드 숫자의 실제 데이터를 탐색할 수 있다
|
|
17
|
+
|
|
18
|
+
### US-3: 사이드바 네비게이션 페이지 전환
|
|
19
|
+
- **As a** 사용자
|
|
20
|
+
- **I want** Knowledge Graph, Memory Banks, Configuration을 클릭하면 메인 콘텐츠 영역이 해당 뷰로 전환된다
|
|
21
|
+
- **So that** 대시보드 외의 기능에도 접근할 수 있다
|
|
22
|
+
|
|
23
|
+
## Acceptance Criteria
|
|
24
|
+
|
|
25
|
+
### AC-1: 메모리 이벤트 모달
|
|
26
|
+
- [ ] 이벤트 아이템 클릭 시 모달 오픈
|
|
27
|
+
- [ ] 모달에 전체 content 표시 (잘림 없이)
|
|
28
|
+
- [ ] eventType, timestamp, sessionId 메타데이터 표시
|
|
29
|
+
- [ ] context (앞뒤 이벤트) 표시
|
|
30
|
+
- [ ] ESC 키 또는 오버레이 클릭으로 모달 닫기
|
|
31
|
+
- [ ] 로딩 상태 표시
|
|
32
|
+
|
|
33
|
+
### AC-2: Stat 카드 클릭
|
|
34
|
+
- [ ] **Total Events** 클릭 → 전체 이벤트 목록 모달 (타입별 필터, 페이지네이션)
|
|
35
|
+
- [ ] **Active Sessions** 클릭 → 세션 목록 모달 (세션 ID, 이벤트 수, 시작/종료 시간)
|
|
36
|
+
- [ ] **Shared Items** 클릭 → 공유 아이템 상세 모달 (Troubleshooting, Best Practices, Common Errors 카운트)
|
|
37
|
+
- [ ] **Vector Nodes** 클릭 → 벡터 노드 정보 모달 (벡터 수, 메모리 사용량)
|
|
38
|
+
|
|
39
|
+
### AC-3: 사이드바 네비게이션
|
|
40
|
+
- [ ] **Overview** → 현재 대시보드 (기존 동작 유지)
|
|
41
|
+
- [ ] **Knowledge Graph** → 가장 많이 접근된 메모리, 토픽별 분포 표시
|
|
42
|
+
- [ ] **Memory Banks** → 레벨별 메모리 목록 (L0~L4), 졸업 기준 표시
|
|
43
|
+
- [ ] **Configuration** → 졸업 기준 설정, 시스템 정보 표시
|
|
44
|
+
- [ ] active nav-item 하이라이트 전환
|
|
45
|
+
- [ ] 페이지 전환 시 부드러운 트랜지션
|
|
46
|
+
|
|
47
|
+
## Technical Constraints
|
|
48
|
+
- 순수 HTML/CSS/JS (프레임워크 없음)
|
|
49
|
+
- 기존 `app.js`, `style.css`, `index.html` 수정
|
|
50
|
+
- 기존 API 엔드포인트 최대한 활용
|
|
51
|
+
- ApexCharts는 Overview 페이지에서만 사용
|
|
52
|
+
|
|
53
|
+
## API Endpoints (기존)
|
|
54
|
+
|
|
55
|
+
| Endpoint | Method | 용도 |
|
|
56
|
+
|----------|--------|------|
|
|
57
|
+
| `/api/events/:id` | GET | 이벤트 상세 (content + context) |
|
|
58
|
+
| `/api/events` | GET | 이벤트 목록 (level, sort, limit, offset) |
|
|
59
|
+
| `/api/sessions` | GET | 세션 목록 (page, pageSize) |
|
|
60
|
+
| `/api/sessions/:id` | GET | 세션 상세 |
|
|
61
|
+
| `/api/stats` | GET | 전체 통계 |
|
|
62
|
+
| `/api/stats/shared` | GET | 공유 스토어 통계 |
|
|
63
|
+
| `/api/stats/most-accessed` | GET | 가장 많이 접근된 메모리 |
|
|
64
|
+
| `/api/stats/levels/:level` | GET | 레벨별 이벤트 |
|
|
65
|
+
| `/api/stats/graduation` | GET | 졸업 기준 정보 |
|
|
66
|
+
| `/api/stats/helpfulness` | GET | 도움됨 통계 |
|
|
67
|
+
| `/api/stats/timeline` | GET | 활동 타임라인 |
|
|
68
|
+
| `/api/search?q=` | GET | 메모리 검색 |
|
|
69
|
+
|
|
70
|
+
## UI Components (신규)
|
|
71
|
+
|
|
72
|
+
### 1. Detail Modal (`#detail-modal`)
|
|
73
|
+
- overlay + centered content box
|
|
74
|
+
- header: 타입 배지 + 타임스탬프 + 닫기 버튼
|
|
75
|
+
- body: 전체 content (코드 블록 포맷팅)
|
|
76
|
+
- footer: context 이벤트 목록
|
|
77
|
+
- 애니메이션: fadeIn/fadeOut
|
|
78
|
+
|
|
79
|
+
### 2. List Modal (`#list-modal`)
|
|
80
|
+
- stat 카드 클릭 시 사용하는 범용 목록 모달
|
|
81
|
+
- header: 제목 + 닫기 버튼
|
|
82
|
+
- body: 스크롤 가능한 아이템 목록
|
|
83
|
+
- 각 아이템 클릭 시 detail modal 열기 가능
|
|
84
|
+
|
|
85
|
+
### 3. Page Views
|
|
86
|
+
- `#view-overview` - 기존 대시보드 (기본)
|
|
87
|
+
- `#view-knowledge-graph` - Knowledge Graph 뷰
|
|
88
|
+
- `#view-memory-banks` - Memory Banks 뷰
|
|
89
|
+
- `#view-configuration` - Configuration 뷰
|
|
90
|
+
- 한 번에 하나만 visible (display: none/block 전환)
|
|
91
|
+
|
|
92
|
+
## Design Guidelines
|
|
93
|
+
- 기존 Deep Space 테마 유지
|
|
94
|
+
- 모달: `var(--bg-panel)` 배경, `var(--glass-border)` 테두리
|
|
95
|
+
- 애니메이션: 200~300ms ease 트랜지션
|
|
96
|
+
- 모바일 대응: 모달 full-width on mobile
|
package/src/cli/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getDefaultMemoryService,
|
|
14
14
|
getMemoryServiceForProject
|
|
15
15
|
} from '../services/memory-service.js';
|
|
16
|
-
import { createSessionHistoryImporter } from '../services/session-history-importer.js';
|
|
16
|
+
import { createSessionHistoryImporter, type ProgressEvent } from '../services/session-history-importer.js';
|
|
17
17
|
import { startServer, stopServer, isServerRunning } from '../server/index.js';
|
|
18
18
|
|
|
19
19
|
// ============================================================
|
|
@@ -427,6 +427,81 @@ program
|
|
|
427
427
|
}
|
|
428
428
|
});
|
|
429
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Render import progress to terminal
|
|
432
|
+
*/
|
|
433
|
+
function renderProgress(event: ProgressEvent): void {
|
|
434
|
+
switch (event.phase) {
|
|
435
|
+
case 'scan':
|
|
436
|
+
console.log(` 🔍 ${event.message}`);
|
|
437
|
+
break;
|
|
438
|
+
case 'session-start': {
|
|
439
|
+
const pct = Math.round(((event.sessionIndex) / event.totalSessions) * 100);
|
|
440
|
+
const sessionName = path.basename(event.filePath, '.jsonl').slice(0, 8);
|
|
441
|
+
process.stdout.write(
|
|
442
|
+
`\r 📄 [${event.sessionIndex + 1}/${event.totalSessions}] ${pct}% | Session ${sessionName}... `
|
|
443
|
+
);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case 'session-progress': {
|
|
447
|
+
process.stdout.write(
|
|
448
|
+
`\r 📄 [${event.sessionIndex + 1}/...] ${event.messagesProcessed} msgs | +${event.imported} imported, ~${event.skipped} skipped `
|
|
449
|
+
);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case 'session-done': {
|
|
453
|
+
const imported = event.importedPrompts + event.importedResponses;
|
|
454
|
+
if (imported > 0) {
|
|
455
|
+
process.stdout.write(
|
|
456
|
+
`\r ✅ [${event.sessionIndex + 1}] +${event.importedPrompts} prompts, +${event.importedResponses} responses${event.skipped > 0 ? `, ~${event.skipped} skipped` : ''} \n`
|
|
457
|
+
);
|
|
458
|
+
} else if (event.skipped > 0) {
|
|
459
|
+
process.stdout.write(
|
|
460
|
+
`\r ⏭️ [${event.sessionIndex + 1}] All ${event.skipped} already imported \n`
|
|
461
|
+
);
|
|
462
|
+
} else {
|
|
463
|
+
process.stdout.write(
|
|
464
|
+
`\r ⏭️ [${event.sessionIndex + 1}] Empty session \n`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case 'embedding':
|
|
470
|
+
process.stdout.write(
|
|
471
|
+
`\r 🧠 Embeddings: ${event.processed}/${event.total} processed `
|
|
472
|
+
);
|
|
473
|
+
if (event.processed >= event.total) {
|
|
474
|
+
process.stdout.write('\n');
|
|
475
|
+
}
|
|
476
|
+
break;
|
|
477
|
+
case 'done':
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function printImportSummary(result: import('../services/session-history-importer.js').ImportResult, embedCount: number): void {
|
|
483
|
+
console.log('\n┌─────────────────────────────────┐');
|
|
484
|
+
console.log('│ ✅ Import Complete │');
|
|
485
|
+
console.log('├─────────────────────────────────┤');
|
|
486
|
+
console.log(`│ Sessions processed: ${String(result.totalSessions).padStart(8)} │`);
|
|
487
|
+
console.log(`│ Total messages: ${String(result.totalMessages).padStart(8)} │`);
|
|
488
|
+
console.log(`│ Imported prompts: ${String(result.importedPrompts).padStart(8)} │`);
|
|
489
|
+
console.log(`│ Imported responses: ${String(result.importedResponses).padStart(8)} │`);
|
|
490
|
+
console.log(`│ Skipped duplicates: ${String(result.skippedDuplicates).padStart(8)} │`);
|
|
491
|
+
console.log(`│ Embeddings queued: ${String(embedCount).padStart(8)} │`);
|
|
492
|
+
console.log('└─────────────────────────────────┘');
|
|
493
|
+
|
|
494
|
+
if (result.errors.length > 0) {
|
|
495
|
+
console.log(`\n⚠️ Errors (${result.errors.length}):`);
|
|
496
|
+
for (const error of result.errors.slice(0, 5)) {
|
|
497
|
+
console.log(` - ${error}`);
|
|
498
|
+
}
|
|
499
|
+
if (result.errors.length > 5) {
|
|
500
|
+
console.log(` ... and ${result.errors.length - 5} more`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
430
505
|
/**
|
|
431
506
|
* Import command - import existing Claude Code sessions
|
|
432
507
|
*/
|
|
@@ -437,8 +512,11 @@ program
|
|
|
437
512
|
.option('-s, --session <file>', 'Import specific session file (JSONL)')
|
|
438
513
|
.option('-a, --all', 'Import all sessions from all projects')
|
|
439
514
|
.option('-l, --limit <number>', 'Limit messages per session')
|
|
515
|
+
.option('-f, --force', 'Force reimport: delete existing events and reimport with turn_id grouping')
|
|
440
516
|
.option('-v, --verbose', 'Show detailed progress')
|
|
441
517
|
.action(async (options) => {
|
|
518
|
+
const startTime = Date.now();
|
|
519
|
+
|
|
442
520
|
// Determine target project path for storage
|
|
443
521
|
const targetProjectPath = options.project || process.cwd();
|
|
444
522
|
|
|
@@ -446,102 +524,76 @@ program
|
|
|
446
524
|
const service = getMemoryServiceForProject(targetProjectPath);
|
|
447
525
|
const importer = createSessionHistoryImporter(service);
|
|
448
526
|
|
|
527
|
+
const importOpts = {
|
|
528
|
+
limit: options.limit ? parseInt(options.limit) : undefined,
|
|
529
|
+
force: options.force,
|
|
530
|
+
verbose: options.verbose,
|
|
531
|
+
onProgress: renderProgress
|
|
532
|
+
};
|
|
533
|
+
|
|
449
534
|
try {
|
|
535
|
+
console.log('\n⏳ Initializing memory service...');
|
|
450
536
|
await service.initialize();
|
|
537
|
+
console.log(' ✅ Ready\n');
|
|
538
|
+
|
|
539
|
+
if (options.force) {
|
|
540
|
+
console.log('🔄 Force mode: existing events will be deleted and reimported with turn_id grouping\n');
|
|
541
|
+
}
|
|
451
542
|
|
|
452
543
|
let result;
|
|
453
544
|
|
|
454
545
|
if (options.session) {
|
|
455
546
|
// Import specific session file
|
|
456
|
-
console.log(
|
|
457
|
-
console.log(` Target
|
|
547
|
+
console.log(`📥 Importing session: ${options.session}`);
|
|
548
|
+
console.log(` Target: ${targetProjectPath}\n`);
|
|
458
549
|
result = await importer.importSessionFile(options.session, {
|
|
550
|
+
...importOpts,
|
|
459
551
|
projectPath: targetProjectPath,
|
|
460
|
-
limit: options.limit ? parseInt(options.limit) : undefined,
|
|
461
|
-
verbose: options.verbose
|
|
462
552
|
});
|
|
463
553
|
} else if (options.project) {
|
|
464
554
|
// Import all sessions from a project
|
|
465
|
-
console.log(
|
|
466
|
-
result = await importer.importProject(options.project,
|
|
467
|
-
limit: options.limit ? parseInt(options.limit) : undefined,
|
|
468
|
-
verbose: options.verbose
|
|
469
|
-
});
|
|
555
|
+
console.log(`📥 Importing project: ${options.project}\n`);
|
|
556
|
+
result = await importer.importProject(options.project, importOpts);
|
|
470
557
|
} else if (options.all) {
|
|
471
558
|
// Import all sessions from all projects
|
|
472
|
-
|
|
473
|
-
console.log('\n📥 Importing all sessions from all projects');
|
|
559
|
+
console.log('📥 Importing all sessions from all projects');
|
|
474
560
|
console.log(' ⚠️ Using global storage (use -p for project-specific)\n');
|
|
475
561
|
const globalService = getDefaultMemoryService();
|
|
476
562
|
const globalImporter = createSessionHistoryImporter(globalService);
|
|
477
563
|
await globalService.initialize();
|
|
478
|
-
result = await globalImporter.importAll(
|
|
479
|
-
limit: options.limit ? parseInt(options.limit) : undefined,
|
|
480
|
-
verbose: options.verbose
|
|
481
|
-
});
|
|
564
|
+
result = await globalImporter.importAll(importOpts);
|
|
482
565
|
|
|
483
566
|
// Process embeddings
|
|
484
|
-
console.log('\n
|
|
567
|
+
console.log('\n🧠 Processing embeddings...');
|
|
485
568
|
const embedCount = await globalService.processPendingEmbeddings();
|
|
486
569
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
console.log(
|
|
490
|
-
console.log(`Total messages: ${result.totalMessages}`);
|
|
491
|
-
console.log(`Imported prompts: ${result.importedPrompts}`);
|
|
492
|
-
console.log(`Imported responses: ${result.importedResponses}`);
|
|
493
|
-
console.log(`Skipped duplicates: ${result.skippedDuplicates}`);
|
|
494
|
-
console.log(`Embeddings processed: ${embedCount}`);
|
|
495
|
-
|
|
496
|
-
if (result.errors.length > 0) {
|
|
497
|
-
console.log(`\n⚠️ Errors (${result.errors.length}):`);
|
|
498
|
-
for (const error of result.errors.slice(0, 5)) {
|
|
499
|
-
console.log(` - ${error}`);
|
|
500
|
-
}
|
|
501
|
-
if (result.errors.length > 5) {
|
|
502
|
-
console.log(` ... and ${result.errors.length - 5} more`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
570
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
571
|
+
printImportSummary(result, embedCount);
|
|
572
|
+
console.log(`\n⏱️ Completed in ${elapsed}s`);
|
|
505
573
|
|
|
506
574
|
await globalService.shutdown();
|
|
507
575
|
return;
|
|
508
576
|
} else {
|
|
509
577
|
// Default: import current project
|
|
510
578
|
const cwd = process.cwd();
|
|
511
|
-
console.log(
|
|
579
|
+
console.log(`📥 Importing sessions for: ${cwd}\n`);
|
|
512
580
|
result = await importer.importProject(cwd, {
|
|
581
|
+
...importOpts,
|
|
513
582
|
projectPath: cwd,
|
|
514
|
-
limit: options.limit ? parseInt(options.limit) : undefined,
|
|
515
|
-
verbose: options.verbose
|
|
516
583
|
});
|
|
517
584
|
}
|
|
518
585
|
|
|
519
586
|
// Process embeddings
|
|
520
|
-
console.log('\n
|
|
587
|
+
console.log('\n🧠 Processing embeddings...');
|
|
521
588
|
const embedCount = await service.processPendingEmbeddings();
|
|
522
589
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
console.log(
|
|
526
|
-
console.log(`Total messages: ${result.totalMessages}`);
|
|
527
|
-
console.log(`Imported prompts: ${result.importedPrompts}`);
|
|
528
|
-
console.log(`Imported responses: ${result.importedResponses}`);
|
|
529
|
-
console.log(`Skipped duplicates: ${result.skippedDuplicates}`);
|
|
530
|
-
console.log(`Embeddings processed: ${embedCount}`);
|
|
531
|
-
|
|
532
|
-
if (result.errors.length > 0) {
|
|
533
|
-
console.log(`\n⚠️ Errors (${result.errors.length}):`);
|
|
534
|
-
for (const error of result.errors.slice(0, 5)) {
|
|
535
|
-
console.log(` - ${error}`);
|
|
536
|
-
}
|
|
537
|
-
if (result.errors.length > 5) {
|
|
538
|
-
console.log(` ... and ${result.errors.length - 5} more`);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
590
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
591
|
+
printImportSummary(result, embedCount);
|
|
592
|
+
console.log(`\n⏱️ Completed in ${elapsed}s`);
|
|
541
593
|
|
|
542
594
|
await service.shutdown();
|
|
543
595
|
} catch (error) {
|
|
544
|
-
console.error('Import failed:', error);
|
|
596
|
+
console.error('\n❌ Import failed:', error);
|
|
545
597
|
process.exit(1);
|
|
546
598
|
}
|
|
547
599
|
});
|