claudeck 1.0.7 → 1.1.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.
- package/README.md +16 -3
- package/db.js +199 -0
- package/package.json +1 -1
- package/plugins/claude-editor/client.js +5 -8
- package/public/css/panels/memory.css +550 -0
- package/public/css/ui/messages.css +69 -0
- package/public/css/ui/status-bar.css +42 -12
- package/public/index.html +25 -0
- package/public/js/core/dom.js +10 -0
- package/public/js/features/chat.js +57 -0
- package/public/js/main.js +1 -0
- package/public/js/panels/memory.js +439 -0
- package/public/js/ui/tab-sdk.js +41 -2
- package/public/style.css +1 -0
- package/server/agent-loop.js +31 -3
- package/server/memory-extractor.js +139 -0
- package/server/memory-injector.js +221 -0
- package/server/memory-optimizer.js +279 -0
- package/server/orchestrator.js +13 -1
- package/server/routes/memory.js +146 -0
- package/server/ws-handler.js +81 -1
- package/server.js +2 -0
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ User data lives in `~/.claudeck/` (config, database, plugins) — safe for NPX u
|
|
|
44
44
|
|
|
45
45
|
- **Zero-framework** — Vanilla JS, 6 npm dependencies, no build step
|
|
46
46
|
- **Full agent orchestration** — Chains, DAGs, orchestrator, and monitoring dashboard
|
|
47
|
+
- **Persistent memory** — Cross-session project knowledge with FTS5 search and AI optimization
|
|
47
48
|
- **Cost visibility** — Per-session tracking, daily charts, token breakdowns
|
|
48
49
|
- **Works everywhere** — PWA, mobile responsive, Telegram AFK approval
|
|
49
50
|
- **Extensible** — Full-stack plugin system with auto-discovery
|
|
@@ -87,6 +88,15 @@ User data lives in `~/.claudeck/` (config, database, plugins) — safe for NPX u
|
|
|
87
88
|
- Input/output token breakdown, streaming token counter
|
|
88
89
|
- Error pattern analysis (9 categories), tool usage stats
|
|
89
90
|
|
|
91
|
+
### Persistent Memory
|
|
92
|
+
|
|
93
|
+
- Cross-session project knowledge that survives restarts
|
|
94
|
+
- Auto-capture from assistant responses using pattern-based heuristic extraction
|
|
95
|
+
- `/remember` command for manual memory creation
|
|
96
|
+
- FTS5 full-text search with relevance scoring and time-decay
|
|
97
|
+
- AI-powered optimization (consolidation via Claude Haiku)
|
|
98
|
+
- Memory panel in right sidebar with search, filtering, and inline editing
|
|
99
|
+
|
|
90
100
|
### Integrations
|
|
91
101
|
|
|
92
102
|
- **MCP Manager** — Add/edit/remove MCP servers (global + per-project)
|
|
@@ -127,8 +137,9 @@ browser ──── WebSocket ──── server.js ──── Claude Code S
|
|
|
127
137
|
server/routes/ ~/.claudeck/
|
|
128
138
|
server/agent-loop.js ├── config/ (JSON configs)
|
|
129
139
|
server/orchestrator.js ├── plugins/ (user plugins)
|
|
130
|
-
server/dag-executor.js ├── data.db (SQLite)
|
|
131
|
-
|
|
140
|
+
server/dag-executor.js ├── data.db (SQLite + memories)
|
|
141
|
+
server/memory-optimizer.js └── .env (VAPID keys)
|
|
142
|
+
plugins/
|
|
132
143
|
```
|
|
133
144
|
|
|
134
145
|
| Layer | Technology |
|
|
@@ -148,6 +159,7 @@ browser ──── WebSocket ──── server.js ──── Claude Code S
|
|
|
148
159
|
/clear /new /parallel /export /theme /shortcuts App
|
|
149
160
|
/costs /analytics Dashboards
|
|
150
161
|
/files /git /repos /events /mcp /tips Panels
|
|
162
|
+
/remember Memory
|
|
151
163
|
/review-pr /onboard-repo /migration-plan /code-health Workflows
|
|
152
164
|
/agent-pr-reviewer /agent-bug-hunter /agent-test-writer Agents
|
|
153
165
|
/orchestrate /monitor /chain-* /dag-* Multi-Agent
|
|
@@ -199,7 +211,7 @@ See [CONFIGURATION.md](docs/CONFIGURATION.md) for the full guide.
|
|
|
199
211
|
|
|
200
212
|
## Plugins
|
|
201
213
|
|
|
202
|
-
Claudeck includes
|
|
214
|
+
Claudeck includes 7 built-in plugins and supports user plugins via `~/.claudeck/plugins/`:
|
|
203
215
|
|
|
204
216
|
| Plugin | Description |
|
|
205
217
|
|--------|-------------|
|
|
@@ -228,6 +240,7 @@ npx skills add https://github.com/hamedafarag/claudeck-skills
|
|
|
228
240
|
|----------|-------------|
|
|
229
241
|
| [DOCUMENTATION.md](docs/DOCUMENTATION.md) | Full feature docs, API reference, database schema |
|
|
230
242
|
| [CONFIGURATION.md](docs/CONFIGURATION.md) | User data directory, config files, plugin system |
|
|
243
|
+
| [AGENT-ARCHITECTURE.md](docs/AGENT-ARCHITECTURE.md) | How agents, chains, DAGs, and orchestrator work |
|
|
231
244
|
| [CROSS-PLATFORM-AUDIT.md](docs/CROSS-PLATFORM-AUDIT.md) | Windows/Linux compatibility |
|
|
232
245
|
| [COMPETITIVE-ANALYSIS.md](docs/COMPETITIVE-ANALYSIS.md) | Feature comparison with similar tools |
|
|
233
246
|
|
package/db.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
+
import { createHash } from "crypto";
|
|
2
3
|
import { dbPath } from "./server/paths.js";
|
|
3
4
|
|
|
4
5
|
const db = new Database(dbPath);
|
|
@@ -119,6 +120,75 @@ db.exec(`
|
|
|
119
120
|
CREATE INDEX IF NOT EXISTS idx_agent_runs_run_id ON agent_runs(run_id);
|
|
120
121
|
`);
|
|
121
122
|
|
|
123
|
+
// Persistent memories table (cross-session context)
|
|
124
|
+
db.exec(`
|
|
125
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
126
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
127
|
+
project_path TEXT NOT NULL,
|
|
128
|
+
category TEXT NOT NULL DEFAULT 'discovery',
|
|
129
|
+
content TEXT NOT NULL,
|
|
130
|
+
content_hash TEXT,
|
|
131
|
+
source_session_id TEXT,
|
|
132
|
+
source_agent_id TEXT,
|
|
133
|
+
relevance_score REAL DEFAULT 1.0,
|
|
134
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
135
|
+
accessed_at INTEGER DEFAULT (unixepoch()),
|
|
136
|
+
expires_at INTEGER
|
|
137
|
+
);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_path);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_memories_relevance ON memories(relevance_score DESC);
|
|
141
|
+
`);
|
|
142
|
+
|
|
143
|
+
// Migration: add content_hash column if missing (existing DBs)
|
|
144
|
+
try { db.exec(`ALTER TABLE memories ADD COLUMN content_hash TEXT`); } catch { /* already exists */ }
|
|
145
|
+
try { db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_hash ON memories(project_path, content_hash)`); } catch { /* already exists */ }
|
|
146
|
+
|
|
147
|
+
// FTS5 full-text search for memories
|
|
148
|
+
db.exec(`
|
|
149
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
150
|
+
content,
|
|
151
|
+
content='memories',
|
|
152
|
+
content_rowid='id'
|
|
153
|
+
);
|
|
154
|
+
`);
|
|
155
|
+
|
|
156
|
+
// Triggers to keep FTS in sync
|
|
157
|
+
db.exec(`
|
|
158
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
159
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
160
|
+
END;
|
|
161
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
162
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.id, old.content);
|
|
163
|
+
END;
|
|
164
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE OF content ON memories BEGIN
|
|
165
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.id, old.content);
|
|
166
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
167
|
+
END;
|
|
168
|
+
`);
|
|
169
|
+
|
|
170
|
+
// Backfill content_hash for existing rows
|
|
171
|
+
const unhashed = db.prepare(`SELECT id, project_path, content FROM memories WHERE content_hash IS NULL`).all();
|
|
172
|
+
if (unhashed.length > 0) {
|
|
173
|
+
const backfill = db.prepare(`UPDATE memories SET content_hash = ? WHERE id = ?`);
|
|
174
|
+
const backfillTx = db.transaction((rows) => {
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
const hash = createHash("sha256").update(`${row.project_path}:${row.content}`).digest("hex");
|
|
177
|
+
backfill.run(hash, row.id);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
backfillTx(unhashed);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Backfill FTS index for existing memories not yet indexed
|
|
184
|
+
try {
|
|
185
|
+
const ftsCount = db.prepare(`SELECT COUNT(*) as c FROM memories_fts`).get();
|
|
186
|
+
const memCount = db.prepare(`SELECT COUNT(*) as c FROM memories`).get();
|
|
187
|
+
if (ftsCount.c < memCount.c) {
|
|
188
|
+
db.exec(`INSERT INTO memories_fts(memories_fts) VALUES ('rebuild')`);
|
|
189
|
+
}
|
|
190
|
+
} catch { /* ignore */ }
|
|
191
|
+
|
|
122
192
|
// Brags table
|
|
123
193
|
db.exec(`
|
|
124
194
|
CREATE TABLE IF NOT EXISTS brags (
|
|
@@ -1193,6 +1263,135 @@ export function getAgentRunsDaily() {
|
|
|
1193
1263
|
return runStmts.dailyRuns.all();
|
|
1194
1264
|
}
|
|
1195
1265
|
|
|
1266
|
+
// ── Memories (persistent cross-session context) ──────────
|
|
1267
|
+
function hashContent(projectPath, content) {
|
|
1268
|
+
return createHash("sha256").update(`${projectPath}:${content}`).digest("hex");
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const memStmts = {
|
|
1272
|
+
insert: db.prepare(
|
|
1273
|
+
`INSERT OR IGNORE INTO memories (project_path, category, content, content_hash, source_session_id, source_agent_id)
|
|
1274
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1275
|
+
),
|
|
1276
|
+
findByHash: db.prepare(
|
|
1277
|
+
`SELECT id FROM memories WHERE project_path = ? AND content_hash = ?`
|
|
1278
|
+
),
|
|
1279
|
+
list: db.prepare(
|
|
1280
|
+
`SELECT * FROM memories WHERE project_path = ?
|
|
1281
|
+
ORDER BY relevance_score DESC, accessed_at DESC`
|
|
1282
|
+
),
|
|
1283
|
+
listByCategory: db.prepare(
|
|
1284
|
+
`SELECT * FROM memories WHERE project_path = ? AND category = ?
|
|
1285
|
+
ORDER BY relevance_score DESC, accessed_at DESC`
|
|
1286
|
+
),
|
|
1287
|
+
searchFts: db.prepare(
|
|
1288
|
+
`SELECT m.* FROM memories m
|
|
1289
|
+
JOIN memories_fts fts ON fts.rowid = m.id
|
|
1290
|
+
WHERE m.project_path = ? AND memories_fts MATCH ?
|
|
1291
|
+
ORDER BY rank, m.relevance_score DESC LIMIT ?`
|
|
1292
|
+
),
|
|
1293
|
+
searchLike: db.prepare(
|
|
1294
|
+
`SELECT * FROM memories WHERE project_path = ? AND content LIKE ?
|
|
1295
|
+
ORDER BY relevance_score DESC LIMIT ?`
|
|
1296
|
+
),
|
|
1297
|
+
topRelevant: db.prepare(
|
|
1298
|
+
`SELECT * FROM memories WHERE project_path = ?
|
|
1299
|
+
ORDER BY relevance_score DESC, accessed_at DESC LIMIT ?`
|
|
1300
|
+
),
|
|
1301
|
+
update: db.prepare(
|
|
1302
|
+
`UPDATE memories SET content = ?, category = ? WHERE id = ?`
|
|
1303
|
+
),
|
|
1304
|
+
touch: db.prepare(
|
|
1305
|
+
`UPDATE memories SET accessed_at = unixepoch(),
|
|
1306
|
+
relevance_score = MIN(relevance_score + 0.1, 2.0) WHERE id = ?`
|
|
1307
|
+
),
|
|
1308
|
+
decay: db.prepare(
|
|
1309
|
+
`UPDATE memories SET relevance_score = MAX(relevance_score * 0.95, 0.1)
|
|
1310
|
+
WHERE project_path = ? AND accessed_at < unixepoch() - ?`
|
|
1311
|
+
),
|
|
1312
|
+
delete: db.prepare(`DELETE FROM memories WHERE id = ?`),
|
|
1313
|
+
deleteExpired: db.prepare(
|
|
1314
|
+
`DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < unixepoch()`
|
|
1315
|
+
),
|
|
1316
|
+
count: db.prepare(
|
|
1317
|
+
`SELECT category, COUNT(*) as count FROM memories
|
|
1318
|
+
WHERE project_path = ? GROUP BY category`
|
|
1319
|
+
),
|
|
1320
|
+
stats: db.prepare(
|
|
1321
|
+
`SELECT COUNT(*) as total,
|
|
1322
|
+
SUM(CASE WHEN accessed_at > unixepoch() - 86400 THEN 1 ELSE 0 END) as accessed_today,
|
|
1323
|
+
AVG(relevance_score) as avg_relevance
|
|
1324
|
+
FROM memories WHERE project_path = ?`
|
|
1325
|
+
),
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
export function createMemory(projectPath, category, content, sourceSessionId = null, sourceAgentId = null) {
|
|
1329
|
+
const hash = hashContent(projectPath, content);
|
|
1330
|
+
// Dedup: if identical content already exists, just touch it
|
|
1331
|
+
const existing = memStmts.findByHash.get(projectPath, hash);
|
|
1332
|
+
if (existing) {
|
|
1333
|
+
memStmts.touch.run(existing.id);
|
|
1334
|
+
return { lastInsertRowid: existing.id, changes: 0, isDuplicate: true };
|
|
1335
|
+
}
|
|
1336
|
+
return memStmts.insert.run(projectPath, category, content, hash, sourceSessionId, sourceAgentId);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
export function listMemories(projectPath, category = null) {
|
|
1340
|
+
if (category) return memStmts.listByCategory.all(projectPath, category);
|
|
1341
|
+
return memStmts.list.all(projectPath);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
export function searchMemories(projectPath, queryText, limit = 20) {
|
|
1345
|
+
// Try FTS5 first, fall back to LIKE for non-FTS-compatible queries
|
|
1346
|
+
try {
|
|
1347
|
+
const ftsQuery = queryText.split(/\s+/).filter(Boolean).map(w => `"${w}"`).join(" OR ");
|
|
1348
|
+
if (ftsQuery) {
|
|
1349
|
+
return memStmts.searchFts.all(projectPath, ftsQuery, limit);
|
|
1350
|
+
}
|
|
1351
|
+
} catch {
|
|
1352
|
+
// FTS parse error — fall back
|
|
1353
|
+
}
|
|
1354
|
+
return memStmts.searchLike.all(projectPath, `%${queryText}%`, limit);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
export function getTopMemories(projectPath, limit = 10) {
|
|
1358
|
+
return memStmts.topRelevant.all(projectPath, limit);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
export function updateMemory(id, content, category) {
|
|
1362
|
+
return memStmts.update.run(content, category, id);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
export function touchMemory(id) {
|
|
1366
|
+
return memStmts.touch.run(id);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
export function decayMemories(projectPath, olderThanSecs = 604800) {
|
|
1370
|
+
return memStmts.decay.run(projectPath, olderThanSecs);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
export function deleteMemory(id) {
|
|
1374
|
+
return memStmts.delete.run(id);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
export function deleteExpiredMemories() {
|
|
1378
|
+
return memStmts.deleteExpired.run();
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
export function getMemoryCounts(projectPath) {
|
|
1382
|
+
return memStmts.count.all(projectPath);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
export function getMemoryStats(projectPath) {
|
|
1386
|
+
return memStmts.stats.get(projectPath);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// Run decay + cleanup for a project (call on session start)
|
|
1390
|
+
export function maintainMemories(projectPath) {
|
|
1391
|
+
decayMemories(projectPath, 604800); // 7 days
|
|
1392
|
+
deleteExpiredMemories();
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1196
1395
|
export function getDb() {
|
|
1197
1396
|
return db;
|
|
1198
1397
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A browser-based UI for Claude Code — chat, run workflows, manage MCP servers, track costs, and orchestrate autonomous agents from a local web interface. Installable as a PWA.",
|
|
6
6
|
"main": "server.js",
|
|
@@ -158,14 +158,11 @@ registerTab({
|
|
|
158
158
|
}
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
loadFile();
|
|
167
|
-
});
|
|
168
|
-
}
|
|
161
|
+
// Reload when project changes
|
|
162
|
+
ctx.on('projectChanged', () => {
|
|
163
|
+
if (isDirty && !confirm('You have unsaved CLAUDE.md changes. Discard them?')) return;
|
|
164
|
+
loadFile();
|
|
165
|
+
});
|
|
169
166
|
|
|
170
167
|
// Also reload when projects data arrives (covers initial page load)
|
|
171
168
|
ctx.onState('projectsData', () => {
|