create-walle 0.9.8 → 0.9.9
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 +1 -1
- package/bin/create-walle.js +32 -1
- package/bin/mcp-inject.js +60 -0
- package/package.json +11 -3
- package/template/bin/setup.js +2 -1
- package/template/claude-code-skill.md +35 -34
- package/template/claude-task-manager/api-prompts.js +113 -70
- package/template/claude-task-manager/approval-agent.js +127 -22
- package/template/claude-task-manager/atomic-write.js +22 -0
- package/template/claude-task-manager/db.js +515 -28
- package/template/claude-task-manager/git-utils.js +112 -1
- package/template/claude-task-manager/public/css/setup.css +191 -0
- package/template/claude-task-manager/public/css/walle-session.css +3 -0
- package/template/claude-task-manager/public/css/walle.css +305 -0
- package/template/claude-task-manager/public/index.html +3098 -293
- package/template/claude-task-manager/public/js/setup.js +698 -0
- package/template/claude-task-manager/public/js/walle-session.js +19 -1
- package/template/claude-task-manager/public/js/walle.js +1420 -42
- package/template/claude-task-manager/public/setup.html +693 -104
- package/template/claude-task-manager/server-state.js +8 -1
- package/template/claude-task-manager/server.js +2143 -229
- package/template/claude-task-manager/session-utils.js +50 -3
- package/template/claude-task-manager/workers/harvest-worker.js +36 -0
- package/template/claude-task-manager/workers/scrollback-worker.js +60 -0
- package/template/claude-task-manager/workers/vterm-worker.js +179 -0
- package/template/package.json +2 -2
- package/template/wall-e/agent.js +285 -150
- package/template/wall-e/api-walle.js +584 -4
- package/template/wall-e/brain.js +565 -10
- package/template/wall-e/chat.js +289 -50
- package/template/wall-e/coding-orchestrator.js +31 -0
- package/template/wall-e/context/compactor.js +21 -0
- package/template/wall-e/context/context-builder.js +216 -64
- package/template/wall-e/context/topic-matcher.js +40 -11
- package/template/wall-e/docs/prompt-architecture.md +121 -0
- package/template/wall-e/embeddings.js +810 -0
- package/template/wall-e/eval/aggregator.js +115 -0
- package/template/wall-e/eval/benchmarks/chat-eval.json +1041 -0
- package/template/wall-e/eval/benchmarks/chat.json +82 -0
- package/template/wall-e/eval/benchmarks/coding.json +122 -0
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +82 -0
- package/template/wall-e/eval/benchmarks/reasoning.json +82 -0
- package/template/wall-e/eval/benchmarks.js +464 -0
- package/template/wall-e/eval/chat-eval.js +509 -0
- package/template/wall-e/eval/evaluate.js +202 -0
- package/template/wall-e/eval/evaluator.js +373 -0
- package/template/wall-e/eval/exporter.js +212 -0
- package/template/wall-e/eval/harvester.js +472 -0
- package/template/wall-e/eval/head-to-head.js +337 -0
- package/template/wall-e/eval/promoter.js +146 -0
- package/template/wall-e/eval/replay.js +381 -0
- package/template/wall-e/eval/shadow.js +144 -0
- package/template/wall-e/eval/train.py +320 -0
- package/template/wall-e/eval/trainer.js +232 -0
- package/template/wall-e/evaluation/complexity.js +3 -0
- package/template/wall-e/evaluation/index.js +1 -1
- package/template/wall-e/evaluation/learner.js +14 -2
- package/template/wall-e/evaluation/quorum-evaluator.js +544 -0
- package/template/wall-e/evaluation/router.js +237 -29
- package/template/wall-e/evaluation/scorecard.js +74 -2
- package/template/wall-e/evaluation/self-critique.js +188 -0
- package/template/wall-e/evaluation/tier-selector.js +166 -0
- package/template/wall-e/evaluation/user-signals.js +109 -0
- package/template/wall-e/extraction/knowledge-extractor.js +60 -17
- package/template/wall-e/lib/scheduler.js +389 -0
- package/template/wall-e/llm/client.js +74 -6
- package/template/wall-e/llm/index.js +1 -0
- package/template/wall-e/llm/mlx.js +220 -0
- package/template/wall-e/llm/ollama-setup.js +228 -0
- package/template/wall-e/llm/provider-availability.js +213 -0
- package/template/wall-e/llm/provider-detector.js +273 -0
- package/template/wall-e/loops/backfill.js +338 -0
- package/template/wall-e/loops/initiative.js +13 -0
- package/template/wall-e/loops/reflect.js +13 -0
- package/template/wall-e/loops/tasks.js +56 -11
- package/template/wall-e/loops/think.js +55 -4
- package/template/wall-e/mcp-server.js +235 -0
- package/template/wall-e/package.json +1 -0
- package/template/wall-e/scripts/eval-embeddings.js +311 -0
- package/template/wall-e/scripts/slack-channel-history.js +1 -1
- package/template/wall-e/server.js +10 -1
- package/template/wall-e/skills/_bundled/coding-agent/run.js +9 -1
- package/template/wall-e/skills/_bundled/email-sync/run.js +9 -1
- package/template/wall-e/skills/_bundled/file-ingest/run.js +6 -1
- package/template/wall-e/skills/_bundled/glean-team-sync/run.js +9 -4
- package/template/wall-e/skills/_bundled/google-calendar/run.js +40 -6
- package/template/wall-e/skills/_bundled/mcp-scan/run.js +9 -4
- package/template/wall-e/skills/_bundled/morning-briefing/run.js +82 -6
- package/template/wall-e/skills/_bundled/proactive-alerts/run.js +9 -4
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +24 -17
- package/template/wall-e/skills/_bundled/training-harvester/SKILL.md +43 -0
- package/template/wall-e/skills/_bundled/training-harvester/run.js +46 -0
- package/template/wall-e/skills/skill-planner.js +92 -64
- package/template/wall-e/telemetry.js +44 -1
- package/template/wall-e/test/eval/aggregator.test.js +180 -0
- package/template/wall-e/test/eval/benchmarks.test.js +373 -0
- package/template/wall-e/test/eval/brain-shadow.test.js +359 -0
- package/template/wall-e/test/eval/evaluate.test.js +221 -0
- package/template/wall-e/test/eval/evaluator.test.js +340 -0
- package/template/wall-e/test/eval/exporter.test.js +353 -0
- package/template/wall-e/test/eval/harvester.test.js +718 -0
- package/template/wall-e/test/eval/head-to-head.test.js +485 -0
- package/template/wall-e/test/eval/promoter.test.js +175 -0
- package/template/wall-e/test/eval/shadow.test.js +156 -0
- package/template/wall-e/test/eval/trainer.test.js +314 -0
- package/template/wall-e/test/evaluation/scorecard.test.js +33 -0
- package/template/wall-e/test/llm/client.test.js +74 -6
- package/template/wall-e/test/training/aggregator.test.js +180 -0
- package/template/wall-e/test/training/brain-shadow.test.js +359 -0
- package/template/wall-e/test/training/evaluator.test.js +340 -0
- package/template/wall-e/test/training/exporter.test.js +141 -1
- package/template/wall-e/test/training/harvester.test.js +718 -0
- package/template/wall-e/test/training/promoter.test.js +175 -0
- package/template/wall-e/test/training/shadow.test.js +156 -0
- package/template/wall-e/test/training/trainer.test.js +73 -0
- package/template/wall-e/tests/backfill.test.js +126 -0
- package/template/wall-e/tests/brain.test.js +4 -4
- package/template/wall-e/tests/coding-orchestrator.test.js +212 -217
- package/template/wall-e/tests/context-builder.test.js +42 -35
- package/template/wall-e/tests/embeddings.test.js +378 -0
- package/template/wall-e/tests/mcp-inject.test.js +68 -0
- package/template/wall-e/tests/mcp-server.test.js +219 -0
- package/template/wall-e/tests/ollama-setup.test.js +81 -0
- package/template/wall-e/tests/provider-availability.test.js +231 -0
- package/template/wall-e/tests/provider-detector.test.js +127 -0
- package/template/wall-e/tests/quorum-evaluator.test.js +277 -0
- package/template/wall-e/tests/scheduler.test.js +546 -0
- package/template/wall-e/tests/scorecard-evolution.test.js +96 -0
- package/template/wall-e/tests/self-critique.test.js +162 -0
- package/template/wall-e/tests/think.test.js +34 -15
- package/template/wall-e/tests/tier-selector.test.js +109 -0
- package/template/wall-e/tests/user-signals.test.js +73 -0
- package/template/wall-e/tools/local-tools.js +141 -14
- package/template/wall-e/tools/slack-mcp.js +20 -16
- package/template/wall-e/training/aggregator.js +115 -0
- package/template/wall-e/training/evaluator.js +373 -0
- package/template/wall-e/training/exporter.js +86 -1
- package/template/wall-e/training/harvester.js +476 -0
- package/template/wall-e/training/promoter.js +146 -0
- package/template/wall-e/training/replay.js +381 -0
- package/template/wall-e/training/shadow.js +144 -0
- package/template/wall-e/training/train.py +78 -32
- package/template/wall-e/training/trainer.js +30 -1
- package/template/wall-e/evaluation/quorum.js +0 -256
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ An always-on AI agent that learns from your Slack, email, calendar, and coding s
|
|
|
23
23
|
- **Second Brain** — Automatically ingests your digital life into a searchable memory store with full-text search, knowledge extraction, and pattern detection
|
|
24
24
|
- **Proactive Intelligence** — Surfaces time-sensitive items, suggests actions, and delivers morning briefings and weekly reflections without being asked
|
|
25
25
|
- **Chat with Tools** — Talk to Wall-E in the browser — it can search your memories, look up people, run skills, and call external tools via MCP (Slack, Glean, etc.)
|
|
26
|
-
- **
|
|
26
|
+
- **17 Bundled Skills** — Morning briefing, weekly reflection, proactive alerts, Slack monitoring, email sync, calendar integration, coding agent, model evaluation, and more
|
|
27
27
|
- **Multi-Model** — Works with Claude, GPT, Gemini, and local models via Ollama with smart routing
|
|
28
28
|
- **Skill Management GUI** — Search, filter, create, edit, and monitor skills from the browser with rich cards, config forms, execution history, export/import, and pre-flight validation
|
|
29
29
|
- **Multi-Device** — Share your brain across machines via Dropbox or iCloud
|
package/bin/create-walle.js
CHANGED
|
@@ -13,6 +13,7 @@ const RED = '\x1b[31m';
|
|
|
13
13
|
const CYAN = '\x1b[36m';
|
|
14
14
|
const RESET = '\x1b[0m';
|
|
15
15
|
|
|
16
|
+
const { injectMcpConfigs } = require('./mcp-inject');
|
|
16
17
|
const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
|
|
17
18
|
const LABEL = 'com.walle.server';
|
|
18
19
|
const INSTALL_PATH_FILE = path.join(process.env.HOME, '.walle', 'install-path');
|
|
@@ -70,6 +71,29 @@ ${BOLD}${CYAN} Wall-E${RESET} — Your personal digital twin
|
|
|
70
71
|
`);
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
function printMcpResults(wallePort) {
|
|
75
|
+
try {
|
|
76
|
+
const results = injectMcpConfigs(wallePort);
|
|
77
|
+
const hasAny = results.some(r => r.action !== 'not_installed');
|
|
78
|
+
if (!hasAny) return;
|
|
79
|
+
|
|
80
|
+
console.log(` ${BOLD}MCP Integrations:${RESET}`);
|
|
81
|
+
for (const r of results) {
|
|
82
|
+
if (r.action === 'not_installed') {
|
|
83
|
+
console.log(` ${DIM}- ${r.tool} -- not installed${RESET}`);
|
|
84
|
+
} else if (r.action === 'already_configured') {
|
|
85
|
+
console.log(` ${DIM}= ${r.tool} -- already configured${RESET}`);
|
|
86
|
+
} else if (r.action === 'updated') {
|
|
87
|
+
console.log(` ${GREEN}~ ${r.tool}${RESET} -- updated port in ${DIM}${r.configPath}${RESET}`);
|
|
88
|
+
} else {
|
|
89
|
+
console.log(` ${GREEN}+ ${r.tool}${RESET} -- added Wall-E to ${DIM}${r.configPath}${RESET}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
console.log(`\n ${DIM}Your AI coding tools can now access Wall-E's memory and knowledge.`);
|
|
93
|
+
console.log(` To undo: remove the "wall-e" entry from the config files above.${RESET}\n`);
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
function install(targetDir) {
|
|
74
98
|
// If no target dir given, check for existing install
|
|
75
99
|
if (!targetDir) {
|
|
@@ -128,6 +152,10 @@ function install(targetDir) {
|
|
|
128
152
|
`WALLE_OWNER_NAME=${ownerName}`,
|
|
129
153
|
'# ANTHROPIC_API_KEY=',
|
|
130
154
|
'',
|
|
155
|
+
'# AI Provider: anthropic, openai, google, ollama',
|
|
156
|
+
'# WALLE_PROVIDER=anthropic',
|
|
157
|
+
'# WALLE_MODEL=',
|
|
158
|
+
'',
|
|
131
159
|
`CTM_PORT=${port}`,
|
|
132
160
|
`WALL_E_PORT=${wallePort}`,
|
|
133
161
|
'',
|
|
@@ -172,6 +200,7 @@ function install(targetDir) {
|
|
|
172
200
|
npx create-walle status ${DIM}Check status${RESET}
|
|
173
201
|
npx create-walle logs ${DIM}View logs${RESET}
|
|
174
202
|
`);
|
|
203
|
+
printMcpResults(parseInt(wallePort));
|
|
175
204
|
}
|
|
176
205
|
|
|
177
206
|
function update() {
|
|
@@ -218,7 +247,7 @@ function update() {
|
|
|
218
247
|
console.log(` Installing dependencies...\n`);
|
|
219
248
|
npmInstall(dir);
|
|
220
249
|
|
|
221
|
-
//
|
|
250
|
+
// 7. Start again
|
|
222
251
|
console.log(`\n Starting Wall-E...`);
|
|
223
252
|
startForegroundOrService(dir, port);
|
|
224
253
|
|
|
@@ -227,6 +256,7 @@ function update() {
|
|
|
227
256
|
|
|
228
257
|
${DIM}Your .env and config were preserved.${RESET}
|
|
229
258
|
`);
|
|
259
|
+
printMcpResults(parseInt(readWallePort(dir)));
|
|
230
260
|
}
|
|
231
261
|
|
|
232
262
|
function start() {
|
|
@@ -235,6 +265,7 @@ function start() {
|
|
|
235
265
|
console.log(`\n Starting Wall-E from ${DIM}${dir}${RESET} on port ${port}...`);
|
|
236
266
|
installService(dir, port);
|
|
237
267
|
console.log(` ${GREEN}Running!${RESET} http://localhost:${port}\n`);
|
|
268
|
+
printMcpResults(parseInt(readWallePort(dir)));
|
|
238
269
|
}
|
|
239
270
|
|
|
240
271
|
function stop() {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const MCP_TARGETS = [
|
|
6
|
+
{ tool: 'Claude Code', configPath: '.claude/mcp.json', detectDir: '.claude' },
|
|
7
|
+
{ tool: 'Cursor', configPath: '.cursor/mcp.json', detectDir: '.cursor' },
|
|
8
|
+
{ tool: 'Windsurf', configPath: '.codeium/windsurf/mcp_config.json', detectDir: '.codeium/windsurf' },
|
|
9
|
+
{ tool: 'Claude Desktop', configPath: 'Library/Application Support/Claude/claude_desktop_config.json', detectDir: 'Library/Application Support/Claude' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inject Wall-E MCP server config into detected AI tool config files.
|
|
14
|
+
* @param {number} wallePort - The Wall-E HTTP port
|
|
15
|
+
* @param {string} [homeDir] - Home directory override for testing
|
|
16
|
+
* @returns {Array<{tool: string, action: string, configPath?: string}>}
|
|
17
|
+
*/
|
|
18
|
+
function injectMcpConfigs(wallePort, homeDir) {
|
|
19
|
+
const home = homeDir || process.env.HOME;
|
|
20
|
+
const walleUrl = `http://localhost:${wallePort}/mcp`;
|
|
21
|
+
const results = [];
|
|
22
|
+
|
|
23
|
+
for (const target of MCP_TARGETS) {
|
|
24
|
+
const detectPath = path.join(home, target.detectDir);
|
|
25
|
+
const configPath = path.join(home, target.configPath);
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(detectPath)) {
|
|
28
|
+
results.push({ tool: target.tool, action: 'not_installed' });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let config = {};
|
|
33
|
+
if (fs.existsSync(configPath)) {
|
|
34
|
+
try {
|
|
35
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
36
|
+
} catch {
|
|
37
|
+
config = {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
42
|
+
|
|
43
|
+
const existing = config.mcpServers['wall-e'];
|
|
44
|
+
if (existing && existing.url === walleUrl) {
|
|
45
|
+
results.push({ tool: target.tool, action: 'already_configured', configPath });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const action = existing ? 'updated' : 'added';
|
|
50
|
+
config.mcpServers['wall-e'] = { type: 'http', url: walleUrl };
|
|
51
|
+
|
|
52
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
53
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
54
|
+
results.push({ tool: target.tool, action, configPath });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { injectMcpConfigs, MCP_TARGETS };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-walle",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "CTM + Wall-E
|
|
3
|
+
"version": "0.9.9",
|
|
4
|
+
"description": "CTM + Wall-E \u2014 AI coding dashboard and personal digital twin agent. Multi-agent terminal for Claude Code, Codex, Gemini & Aider, plus prompt editor, task queue, and an agent that learns from Slack, email & calendar.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-walle": "bin/create-walle.js"
|
|
7
7
|
},
|
|
@@ -15,5 +15,13 @@
|
|
|
15
15
|
"prepublishOnly": "bash build.sh"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ctm",
|
|
20
|
+
"task-manager",
|
|
21
|
+
"wall-e",
|
|
22
|
+
"ai",
|
|
23
|
+
"digital-twin",
|
|
24
|
+
"claude",
|
|
25
|
+
"personal-assistant"
|
|
26
|
+
]
|
|
19
27
|
}
|
package/template/bin/setup.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const { execFileSync } = require('child_process');
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const { atomicWriteFileSync } = require('../claude-task-manager/atomic-write');
|
|
10
11
|
|
|
11
12
|
const ROOT = path.resolve(__dirname, '..');
|
|
12
13
|
const ENV_PATH = path.join(ROOT, '.env');
|
|
@@ -41,7 +42,7 @@ function runIfNeeded() {
|
|
|
41
42
|
`WALLE_OWNER_NAME=${ownerName}`,
|
|
42
43
|
'# ANTHROPIC_API_KEY=sk-ant-...',
|
|
43
44
|
];
|
|
44
|
-
|
|
45
|
+
atomicWriteFileSync(ENV_PATH, envLines.join('\n') + '\n', { mode: 0o600 });
|
|
45
46
|
|
|
46
47
|
// Set in current process
|
|
47
48
|
process.env.WALLE_OWNER_NAME = ownerName;
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
# Wall-E
|
|
1
|
+
# Wall-E -- Claude Code MCP Server
|
|
2
2
|
|
|
3
|
-
Wall-E
|
|
3
|
+
Wall-E automatically registers as an MCP server in Claude Code, Cursor, Windsurf, and Claude Desktop during installation.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Automatic Setup
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
When you install or start Wall-E (`npx create-walle install ./my-agent`), it detects installed AI coding tools and adds itself to their MCP config. You'll see output like:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MCP Integrations:
|
|
11
|
+
+ Claude Code -- added Wall-E to ~/.claude/mcp.json
|
|
12
|
+
+ Cursor -- added Wall-E to ~/.cursor/mcp.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
No manual configuration needed.
|
|
16
|
+
|
|
17
|
+
## Manual Setup
|
|
18
|
+
|
|
19
|
+
If auto-detection missed your tool, add this to your MCP config:
|
|
8
20
|
|
|
9
21
|
```json
|
|
10
22
|
{
|
|
@@ -17,44 +29,33 @@ Add to your Claude Code MCP config (`~/.claude/mcp.json`):
|
|
|
17
29
|
}
|
|
18
30
|
```
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```
|
|
32
|
+
Config file locations:
|
|
33
|
+
- Claude Code: `~/.claude/mcp.json`
|
|
34
|
+
- Cursor: `~/.cursor/mcp.json`
|
|
35
|
+
- Windsurf: `~/.codeium/windsurf/mcp_config.json`
|
|
36
|
+
- Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
26
37
|
|
|
27
38
|
## Available Tools
|
|
28
39
|
|
|
29
|
-
Once connected,
|
|
40
|
+
Once connected, your AI coding tool gains access to:
|
|
30
41
|
|
|
31
42
|
| Tool | Description |
|
|
32
43
|
|---|---|
|
|
33
44
|
| `search_memories` | Full-text search across all ingested memories (Slack, email, calendar) |
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
|
|
39
|
-
## Use Cases in Claude Code
|
|
45
|
+
| `remember` | Store new knowledge in the brain |
|
|
46
|
+
| `list_knowledge` | Browse stored knowledge |
|
|
47
|
+
| `list_people` | List known people with relationships |
|
|
48
|
+
| `get_brief` | Get today's daily briefing |
|
|
49
|
+
| `ask_walle` | Ask Wall-E a question (uses full chat pipeline with memory context) |
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
## Use Cases
|
|
42
52
|
|
|
43
|
-
- "What meetings do I have tomorrow?"
|
|
44
|
-
- "What did I discuss with Alice last week?"
|
|
45
|
-
- "Remember that I prefer Tailwind over Bootstrap"
|
|
46
|
-
- "
|
|
47
|
-
- "
|
|
53
|
+
- "What meetings do I have tomorrow?" -- searches calendar memories
|
|
54
|
+
- "What did I discuss with Alice last week?" -- searches Slack memories
|
|
55
|
+
- "Remember that I prefer Tailwind over Bootstrap" -- stores knowledge
|
|
56
|
+
- "Who do I know at Acme Corp?" -- searches people
|
|
57
|
+
- "Give me my daily brief" -- generates a summary
|
|
48
58
|
|
|
49
|
-
##
|
|
59
|
+
## To Undo
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```markdown
|
|
54
|
-
## Wall-E (Personal Digital Twin)
|
|
55
|
-
|
|
56
|
-
This project has Wall-E connected as an MCP server. Use it to:
|
|
57
|
-
- Search the owner's memories and knowledge base
|
|
58
|
-
- Check calendar events and meeting schedules
|
|
59
|
-
- Look up past Slack conversations for context
|
|
60
|
-
```
|
|
61
|
+
Remove the `"wall-e"` entry from the config files listed above.
|
|
@@ -638,75 +638,114 @@ function handleListConversations(req, res, url) {
|
|
|
638
638
|
jsonResponse(res, 200, db.listSessionConversations(opts));
|
|
639
639
|
}
|
|
640
640
|
|
|
641
|
+
// Core import logic shared by API handler and auto-import
|
|
642
|
+
const { getAllSessionFiles, getAllSessionFilesAsync, parseSessionFile } = require('./session-utils');
|
|
643
|
+
const fsp = require('fs').promises;
|
|
644
|
+
|
|
645
|
+
function importSessionFile(filePath, projectPath, projectEntry) {
|
|
646
|
+
const parsed = parseSessionFile(filePath, projectPath, projectEntry);
|
|
647
|
+
if (parsed.isEmpty) return false;
|
|
648
|
+
|
|
649
|
+
const existing = db.getSessionConversation(parsed.sessionId);
|
|
650
|
+
// Skip re-import if file size unchanged AND model already populated
|
|
651
|
+
if (existing && existing.file_size === parsed.fileSize && existing.model_provider) return false;
|
|
652
|
+
|
|
653
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
654
|
+
const lines = content.split('\n').filter(Boolean);
|
|
655
|
+
const messages = [];
|
|
656
|
+
let assistantCount = 0;
|
|
657
|
+
|
|
658
|
+
for (const line of lines) {
|
|
659
|
+
try {
|
|
660
|
+
const entry = JSON.parse(line);
|
|
661
|
+
if (entry.type === 'user' && entry.message?.role === 'user') {
|
|
662
|
+
const c = entry.message.content;
|
|
663
|
+
const text = typeof c === 'string' ? c
|
|
664
|
+
: Array.isArray(c) ? c.filter(b => b.type === 'text').map(b => b.text).join('\n') : '';
|
|
665
|
+
if (text) messages.push({ role: 'user', text: text.slice(0, 5000), timestamp: entry.timestamp });
|
|
666
|
+
} else if (entry.type === 'assistant' && entry.message?.role === 'assistant') {
|
|
667
|
+
const c = entry.message.content;
|
|
668
|
+
if (!Array.isArray(c)) continue;
|
|
669
|
+
const parts = [];
|
|
670
|
+
for (const block of c) {
|
|
671
|
+
if (block.type === 'text' && block.text) parts.push(block.text);
|
|
672
|
+
else if (block.type === 'tool_use') parts.push(`[Tool: ${block.name}]`);
|
|
673
|
+
}
|
|
674
|
+
if (parts.length > 0) {
|
|
675
|
+
const lastMsg = messages[messages.length - 1];
|
|
676
|
+
if (lastMsg && lastMsg.role === 'assistant' && lastMsg._parent === entry.parentUuid) {
|
|
677
|
+
lastMsg.text = parts.join('\n').slice(0, 5000);
|
|
678
|
+
} else {
|
|
679
|
+
messages.push({ role: 'assistant', text: parts.join('\n').slice(0, 5000), timestamp: entry.timestamp, _parent: entry.parentUuid });
|
|
680
|
+
assistantCount++;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
} catch {}
|
|
685
|
+
}
|
|
686
|
+
messages.forEach(m => delete m._parent);
|
|
687
|
+
|
|
688
|
+
db.importSessionConversation({
|
|
689
|
+
session_id: parsed.sessionId,
|
|
690
|
+
project_path: parsed.project,
|
|
691
|
+
messages,
|
|
692
|
+
user_msg_count: parsed.userMsgCount,
|
|
693
|
+
assistant_msg_count: assistantCount,
|
|
694
|
+
title: parsed.title,
|
|
695
|
+
first_message: parsed.firstMessage,
|
|
696
|
+
git_branch: parsed.gitBranch,
|
|
697
|
+
file_size: parsed.fileSize,
|
|
698
|
+
session_created_at: parsed.timestamp,
|
|
699
|
+
hostname: parsed.hostname,
|
|
700
|
+
model_provider: parsed.modelProvider,
|
|
701
|
+
model_id: parsed.modelId,
|
|
702
|
+
});
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
|
|
641
706
|
async function handleImportConversations(req, res) {
|
|
642
|
-
// Import all sessions from ~/.claude/projects/ into the DB
|
|
643
707
|
try {
|
|
644
|
-
const { getAllSessionFiles, parseSessionFile } = require('./session-utils');
|
|
645
708
|
let imported = 0;
|
|
646
|
-
const
|
|
709
|
+
for (const { filePath, projectPath, projectEntry } of getAllSessionFiles()) {
|
|
710
|
+
try { if (importSessionFile(filePath, projectPath, projectEntry)) imported++; } catch {}
|
|
711
|
+
}
|
|
712
|
+
jsonResponse(res, 200, { ok: true, imported });
|
|
713
|
+
} catch (e) { jsonResponse(res, 500, { error: e.message }); }
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Incremental auto-import: only processes files modified since last scan
|
|
717
|
+
// Uses async filesystem operations to avoid blocking the event loop
|
|
718
|
+
// (critical when ~/.claude/projects is on Dropbox/network-synced FS)
|
|
719
|
+
async function runIncrementalConversationImport() {
|
|
720
|
+
try {
|
|
721
|
+
// One-time backfill: force full rescan to populate model_provider for existing sessions
|
|
722
|
+
if (!db.getSetting('conversation_model_backfill_done', false)) {
|
|
723
|
+
db.setSetting('conversation_import_last_scan_at', 0);
|
|
724
|
+
db.setSetting('conversation_model_backfill_done', true);
|
|
725
|
+
console.log('[auto-import] One-time model backfill: forcing full rescan');
|
|
726
|
+
}
|
|
727
|
+
const lastScanAt = db.getSetting('conversation_import_last_scan_at', 0);
|
|
728
|
+
const scanStart = Date.now();
|
|
729
|
+
const allFiles = await getAllSessionFilesAsync();
|
|
730
|
+
let imported = 0;
|
|
731
|
+
let scanned = 0;
|
|
647
732
|
|
|
648
733
|
for (const { filePath, projectPath, projectEntry } of allFiles) {
|
|
649
734
|
try {
|
|
650
|
-
const
|
|
651
|
-
if (
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (existing && existing.file_size === parsed.fileSize) continue;
|
|
655
|
-
|
|
656
|
-
// Read full messages
|
|
657
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
658
|
-
const lines = content.split('\n').filter(Boolean);
|
|
659
|
-
const messages = [];
|
|
660
|
-
let assistantCount = 0;
|
|
661
|
-
|
|
662
|
-
for (const line of lines) {
|
|
663
|
-
try {
|
|
664
|
-
const entry = JSON.parse(line);
|
|
665
|
-
if (entry.type === 'user' && entry.message?.role === 'user') {
|
|
666
|
-
const c = entry.message.content;
|
|
667
|
-
const text = typeof c === 'string' ? c
|
|
668
|
-
: Array.isArray(c) ? c.filter(b => b.type === 'text').map(b => b.text).join('\n') : '';
|
|
669
|
-
if (text) messages.push({ role: 'user', text: text.slice(0, 5000), timestamp: entry.timestamp });
|
|
670
|
-
} else if (entry.type === 'assistant' && entry.message?.role === 'assistant') {
|
|
671
|
-
const c = entry.message.content;
|
|
672
|
-
if (!Array.isArray(c)) continue;
|
|
673
|
-
const parts = [];
|
|
674
|
-
for (const block of c) {
|
|
675
|
-
if (block.type === 'text' && block.text) parts.push(block.text);
|
|
676
|
-
else if (block.type === 'tool_use') parts.push(`[Tool: ${block.name}]`);
|
|
677
|
-
}
|
|
678
|
-
if (parts.length > 0) {
|
|
679
|
-
const lastMsg = messages[messages.length - 1];
|
|
680
|
-
if (lastMsg && lastMsg.role === 'assistant' && lastMsg._parent === entry.parentUuid) {
|
|
681
|
-
lastMsg.text = parts.join('\n').slice(0, 5000);
|
|
682
|
-
} else {
|
|
683
|
-
messages.push({ role: 'assistant', text: parts.join('\n').slice(0, 5000), timestamp: entry.timestamp, _parent: entry.parentUuid });
|
|
684
|
-
assistantCount++;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
} catch {}
|
|
689
|
-
}
|
|
690
|
-
messages.forEach(m => delete m._parent);
|
|
691
|
-
|
|
692
|
-
db.importSessionConversation({
|
|
693
|
-
session_id: parsed.sessionId,
|
|
694
|
-
project_path: parsed.project,
|
|
695
|
-
messages,
|
|
696
|
-
user_msg_count: parsed.userMsgCount,
|
|
697
|
-
assistant_msg_count: assistantCount,
|
|
698
|
-
title: parsed.title,
|
|
699
|
-
first_message: parsed.firstMessage,
|
|
700
|
-
git_branch: parsed.gitBranch,
|
|
701
|
-
file_size: parsed.fileSize,
|
|
702
|
-
session_created_at: parsed.timestamp,
|
|
703
|
-
});
|
|
704
|
-
imported++;
|
|
735
|
+
const stat = await fsp.stat(filePath);
|
|
736
|
+
if (stat.mtimeMs <= lastScanAt) continue;
|
|
737
|
+
scanned++;
|
|
738
|
+
if (importSessionFile(filePath, projectPath, projectEntry)) imported++;
|
|
705
739
|
} catch {}
|
|
706
740
|
}
|
|
707
741
|
|
|
708
|
-
|
|
709
|
-
|
|
742
|
+
db.setSetting('conversation_import_last_scan_at', scanStart);
|
|
743
|
+
if (imported > 0) console.log(`[auto-import] Imported ${imported} session conversations (scanned ${scanned}/${allFiles.length})`);
|
|
744
|
+
return { imported, scanned, total: allFiles.length };
|
|
745
|
+
} catch (e) {
|
|
746
|
+
console.error('[auto-import] Error:', e.message);
|
|
747
|
+
return { imported: 0, scanned: 0, total: 0 };
|
|
748
|
+
}
|
|
710
749
|
}
|
|
711
750
|
|
|
712
751
|
function handleGetConversation(req, res, url) {
|
|
@@ -1057,7 +1096,7 @@ async function handlePermAISuggest(req, res) {
|
|
|
1057
1096
|
}
|
|
1058
1097
|
|
|
1059
1098
|
// Use LLM to interpret the natural language permission request
|
|
1060
|
-
const result = await classifyPermissionIntent(
|
|
1099
|
+
const result = await classifyPermissionIntent(query);
|
|
1061
1100
|
jsonResponse(res, 200, result);
|
|
1062
1101
|
} catch (e) {
|
|
1063
1102
|
jsonResponse(res, 500, { error: e.message });
|
|
@@ -1093,10 +1132,11 @@ For allow rules, match the scope the user requests.
|
|
|
1093
1132
|
|
|
1094
1133
|
IMPORTANT: Output ONLY the JSON object, no markdown fencing.
|
|
1095
1134
|
|
|
1096
|
-
User request:
|
|
1135
|
+
User request: (see below)`;
|
|
1097
1136
|
|
|
1098
1137
|
try {
|
|
1099
|
-
|
|
1138
|
+
// Pass query as a separate message to avoid prompt injection via string interpolation
|
|
1139
|
+
const result = await callClaude([{ role: 'user', content: prompt + '\n\n' + query }], 256);
|
|
1100
1140
|
const text = result.content?.[0]?.text || '';
|
|
1101
1141
|
const parsed = JSON.parse(text);
|
|
1102
1142
|
return {
|
|
@@ -1155,12 +1195,17 @@ function handlePermAIException(res, query, parentRule, body) {
|
|
|
1155
1195
|
},
|
|
1156
1196
|
};
|
|
1157
1197
|
|
|
1158
|
-
// Try to match known dangerous patterns
|
|
1198
|
+
// Try to match known dangerous patterns (longest match first to avoid duplicates)
|
|
1159
1199
|
const binPatterns = dangerousPatterns[parentBin] || {};
|
|
1160
|
-
|
|
1200
|
+
const sortedKeys = Object.keys(binPatterns).sort((a, b) => b.length - a.length);
|
|
1201
|
+
let matched = false;
|
|
1202
|
+
for (const key of sortedKeys) {
|
|
1161
1203
|
if (query.includes(key)) {
|
|
1162
|
-
|
|
1163
|
-
|
|
1204
|
+
for (const r of binPatterns[key]) {
|
|
1205
|
+
if (!rules.includes(r)) rules.push(r);
|
|
1206
|
+
}
|
|
1207
|
+
if (!matched) explanation = `Deny ${key} operations for ${parentBin}.`;
|
|
1208
|
+
matched = true;
|
|
1164
1209
|
}
|
|
1165
1210
|
}
|
|
1166
1211
|
|
|
@@ -1234,7 +1279,6 @@ function handleGetScanCache(req, res) {
|
|
|
1234
1279
|
|
|
1235
1280
|
function handleScanToolUsage(req, res) {
|
|
1236
1281
|
// Scan session JSONL files and extract tool usage, then generalize into rules
|
|
1237
|
-
const { getAllSessionFiles } = require('./session-utils');
|
|
1238
1282
|
const allFiles = getAllSessionFiles();
|
|
1239
1283
|
const toolUsage = {}; // tool_name -> { count, commands: Map<binary, Set<subcmds>>, projects: Set }
|
|
1240
1284
|
|
|
@@ -1384,7 +1428,6 @@ function handleDeleteAutoApproval(req, res, id) {
|
|
|
1384
1428
|
async function handleScanAutoApprovals(req, res) {
|
|
1385
1429
|
// Scan session JSONL files and find approval patterns:
|
|
1386
1430
|
// assistant asks a question -> user responds with short affirmative
|
|
1387
|
-
const { getAllSessionFiles } = require('./session-utils');
|
|
1388
1431
|
const allFiles = getAllSessionFiles();
|
|
1389
1432
|
const recentFiles = allFiles
|
|
1390
1433
|
.map(f => { try { return { ...f, mtime: fs.statSync(f.filePath).mtime }; } catch { return null; } })
|
|
@@ -1849,4 +1892,4 @@ function handleLifecycleRefresh(req, res) {
|
|
|
1849
1892
|
} catch (e) { jsonResponse(res, 500, { error: e.message }); }
|
|
1850
1893
|
}
|
|
1851
1894
|
|
|
1852
|
-
module.exports = { handlePromptApi, queueEngine, importPermissionsToDb };
|
|
1895
|
+
module.exports = { handlePromptApi, queueEngine, importPermissionsToDb, runIncrementalConversationImport };
|