create-walle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/bin/create-walle.js +134 -0
  2. package/package.json +18 -0
  3. package/template/.env.example +40 -0
  4. package/template/CLAUDE.md +12 -0
  5. package/template/LICENSE +21 -0
  6. package/template/README.md +167 -0
  7. package/template/bin/setup.js +100 -0
  8. package/template/claude-code-skill.md +60 -0
  9. package/template/claude-task-manager/api-prompts.js +1841 -0
  10. package/template/claude-task-manager/api-reviews.js +275 -0
  11. package/template/claude-task-manager/approval-agent.js +454 -0
  12. package/template/claude-task-manager/bin/restart-ctm.sh +16 -0
  13. package/template/claude-task-manager/db.js +1721 -0
  14. package/template/claude-task-manager/docs/PROMPT-MANAGEMENT-DESIGN.md +631 -0
  15. package/template/claude-task-manager/git-utils.js +214 -0
  16. package/template/claude-task-manager/package-lock.json +1607 -0
  17. package/template/claude-task-manager/package.json +31 -0
  18. package/template/claude-task-manager/prompt-harvest.js +1148 -0
  19. package/template/claude-task-manager/public/css/prompts.css +880 -0
  20. package/template/claude-task-manager/public/css/reviews.css +430 -0
  21. package/template/claude-task-manager/public/css/walle.css +732 -0
  22. package/template/claude-task-manager/public/favicon.ico +0 -0
  23. package/template/claude-task-manager/public/icon.svg +37 -0
  24. package/template/claude-task-manager/public/index.html +8346 -0
  25. package/template/claude-task-manager/public/js/prompts.js +3159 -0
  26. package/template/claude-task-manager/public/js/reviews.js +1292 -0
  27. package/template/claude-task-manager/public/js/walle.js +3081 -0
  28. package/template/claude-task-manager/public/manifest.json +13 -0
  29. package/template/claude-task-manager/public/prompts.html +4353 -0
  30. package/template/claude-task-manager/public/setup.html +216 -0
  31. package/template/claude-task-manager/queue-engine.js +404 -0
  32. package/template/claude-task-manager/server-state.js +5 -0
  33. package/template/claude-task-manager/server.js +2254 -0
  34. package/template/claude-task-manager/session-utils.js +124 -0
  35. package/template/claude-task-manager/start.sh +17 -0
  36. package/template/claude-task-manager/tests/test-ai-search.js +61 -0
  37. package/template/claude-task-manager/tests/test-editor-ux.js +76 -0
  38. package/template/claude-task-manager/tests/test-editor-ux2.js +51 -0
  39. package/template/claude-task-manager/tests/test-features-v2.js +127 -0
  40. package/template/claude-task-manager/tests/test-insights-cached.js +78 -0
  41. package/template/claude-task-manager/tests/test-insights.js +124 -0
  42. package/template/claude-task-manager/tests/test-permissions-v2.js +127 -0
  43. package/template/claude-task-manager/tests/test-permissions.js +122 -0
  44. package/template/claude-task-manager/tests/test-pin.js +51 -0
  45. package/template/claude-task-manager/tests/test-prompts.js +164 -0
  46. package/template/claude-task-manager/tests/test-recent-sessions.js +96 -0
  47. package/template/claude-task-manager/tests/test-review.js +104 -0
  48. package/template/claude-task-manager/tests/test-send-dropdown.js +76 -0
  49. package/template/claude-task-manager/tests/test-send-final.js +30 -0
  50. package/template/claude-task-manager/tests/test-send-fixes.js +76 -0
  51. package/template/claude-task-manager/tests/test-send-integration.js +107 -0
  52. package/template/claude-task-manager/tests/test-send-visual.js +34 -0
  53. package/template/claude-task-manager/tests/test-session-create.js +147 -0
  54. package/template/claude-task-manager/tests/test-sidebar-ux.js +83 -0
  55. package/template/claude-task-manager/tests/test-url-hash.js +68 -0
  56. package/template/claude-task-manager/tests/test-ux-crop.js +34 -0
  57. package/template/claude-task-manager/tests/test-ux-review.js +130 -0
  58. package/template/claude-task-manager/tests/test-zoom-card.js +76 -0
  59. package/template/claude-task-manager/tests/test-zoom.js +92 -0
  60. package/template/claude-task-manager/tests/test-zoom2.js +67 -0
  61. package/template/docs/site/api/README.md +187 -0
  62. package/template/docs/site/guides/claude-code.md +58 -0
  63. package/template/docs/site/guides/configuration.md +96 -0
  64. package/template/docs/site/guides/quickstart.md +158 -0
  65. package/template/docs/site/index.md +14 -0
  66. package/template/docs/site/skills/README.md +135 -0
  67. package/template/wall-e/.dockerignore +11 -0
  68. package/template/wall-e/Dockerfile +25 -0
  69. package/template/wall-e/adapters/adapter-base.js +37 -0
  70. package/template/wall-e/adapters/ctm.js +193 -0
  71. package/template/wall-e/adapters/slack.js +56 -0
  72. package/template/wall-e/agent.js +319 -0
  73. package/template/wall-e/api-walle.js +1073 -0
  74. package/template/wall-e/brain.js +1235 -0
  75. package/template/wall-e/channels/agent-api.js +172 -0
  76. package/template/wall-e/channels/channel-base.js +14 -0
  77. package/template/wall-e/channels/imessage-channel.js +113 -0
  78. package/template/wall-e/channels/slack-channel.js +118 -0
  79. package/template/wall-e/chat.js +778 -0
  80. package/template/wall-e/decision/confidence.js +93 -0
  81. package/template/wall-e/deploy.sh +35 -0
  82. package/template/wall-e/docs/specs/2026-04-01-publish-plan.md +112 -0
  83. package/template/wall-e/docs/specs/SKILL-FORMAT.md +326 -0
  84. package/template/wall-e/extraction/contradiction.js +168 -0
  85. package/template/wall-e/extraction/knowledge-extractor.js +190 -0
  86. package/template/wall-e/fly.toml +24 -0
  87. package/template/wall-e/loops/ingest.js +34 -0
  88. package/template/wall-e/loops/reflect.js +63 -0
  89. package/template/wall-e/loops/tasks.js +487 -0
  90. package/template/wall-e/loops/think.js +125 -0
  91. package/template/wall-e/package-lock.json +533 -0
  92. package/template/wall-e/package.json +18 -0
  93. package/template/wall-e/scripts/ingest-slack-search.js +85 -0
  94. package/template/wall-e/scripts/pull-slack-via-claude.js +98 -0
  95. package/template/wall-e/scripts/slack-backfill.js +295 -0
  96. package/template/wall-e/scripts/slack-channel-history.js +454 -0
  97. package/template/wall-e/server.js +93 -0
  98. package/template/wall-e/skills/_bundled/email-digest/SKILL.md +95 -0
  99. package/template/wall-e/skills/_bundled/email-sync/SKILL.md +65 -0
  100. package/template/wall-e/skills/_bundled/email-sync/mail-reader.jxa +104 -0
  101. package/template/wall-e/skills/_bundled/email-sync/run.js +213 -0
  102. package/template/wall-e/skills/_bundled/google-calendar/SKILL.md +73 -0
  103. package/template/wall-e/skills/_bundled/google-calendar/cal-reader.swift +81 -0
  104. package/template/wall-e/skills/_bundled/google-calendar/run.js +181 -0
  105. package/template/wall-e/skills/_bundled/memory-search/SKILL.md +92 -0
  106. package/template/wall-e/skills/_bundled/morning-briefing/SKILL.md +131 -0
  107. package/template/wall-e/skills/_bundled/morning-briefing/run.js +264 -0
  108. package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +60 -0
  109. package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +55 -0
  110. package/template/wall-e/skills/claude-code-reader.js +144 -0
  111. package/template/wall-e/skills/mcp-client.js +407 -0
  112. package/template/wall-e/skills/skill-executor.js +163 -0
  113. package/template/wall-e/skills/skill-loader.js +410 -0
  114. package/template/wall-e/skills/skill-planner.js +88 -0
  115. package/template/wall-e/skills/slack-ingest.js +329 -0
  116. package/template/wall-e/skills/slack-pull-live.js +270 -0
  117. package/template/wall-e/skills/tool-executor.js +188 -0
  118. package/template/wall-e/tests/adapter-base.test.js +20 -0
  119. package/template/wall-e/tests/adapter-ctm.test.js +122 -0
  120. package/template/wall-e/tests/adapter-slack.test.js +98 -0
  121. package/template/wall-e/tests/agent-api.test.js +256 -0
  122. package/template/wall-e/tests/api-walle.test.js +222 -0
  123. package/template/wall-e/tests/brain.test.js +602 -0
  124. package/template/wall-e/tests/channels.test.js +104 -0
  125. package/template/wall-e/tests/chat.test.js +103 -0
  126. package/template/wall-e/tests/confidence.test.js +134 -0
  127. package/template/wall-e/tests/contradiction.test.js +217 -0
  128. package/template/wall-e/tests/ingest.test.js +113 -0
  129. package/template/wall-e/tests/mcp-client.test.js +71 -0
  130. package/template/wall-e/tests/reflect.test.js +103 -0
  131. package/template/wall-e/tests/server.test.js +111 -0
  132. package/template/wall-e/tests/skills.test.js +198 -0
  133. package/template/wall-e/tests/slack-ingest.test.js +103 -0
  134. package/template/wall-e/tests/think.test.js +435 -0
  135. package/template/wall-e/tools/local-tools.js +697 -0
  136. package/template/wall-e/tools/slack-mcp.js +290 -0
@@ -0,0 +1,275 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const db = require('./db');
5
+ const gitUtils = require('./git-utils');
6
+
7
+ // --- Helpers ---
8
+ function isValidProjectPath(p) {
9
+ if (!p || typeof p !== 'string') return false;
10
+ const resolved = path.resolve(p);
11
+ const home = process.env.HOME || '/';
12
+ if (!resolved.startsWith(home + path.sep)) return false;
13
+ // Check for .git dir (standalone repo) or use git rev-parse (subdirectory of a repo)
14
+ if (fs.existsSync(path.join(resolved, '.git'))) return true;
15
+ try {
16
+ const { execFileSync } = require('child_process');
17
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd: resolved, stdio: 'pipe' });
18
+ return true;
19
+ } catch { return false; }
20
+ }
21
+ function jsonResponse(res, code, data) {
22
+ const body = JSON.stringify(data);
23
+ res.writeHead(code, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
24
+ res.end(body);
25
+ }
26
+
27
+ function readBody(req, limit = 1024 * 1024) {
28
+ return new Promise((resolve, reject) => {
29
+ let body = '';
30
+ req.on('data', c => {
31
+ body += c;
32
+ if (body.length > limit) { req.destroy(); reject(new Error('Body too large')); }
33
+ });
34
+ req.on('end', () => { try { resolve(JSON.parse(body || '{}')); } catch (e) { reject(e); } });
35
+ req.on('error', reject);
36
+ });
37
+ }
38
+
39
+ // --- Change Detection for Badge Polling ---
40
+ // Track last known diff stats per project to detect changes
41
+ const changeTracker = new Map(); // projectPath -> { files: [...], hash: string }
42
+
43
+ function computeStatsHash(files) {
44
+ return files.map(f => `${f.path}:${f.additions}:${f.deletions}`).sort().join('|');
45
+ }
46
+
47
+ async function checkForChanges(projectPath) {
48
+ try {
49
+ const files = await gitUtils.getDiffStat(projectPath);
50
+ const hash = computeStatsHash(files);
51
+ const prev = changeTracker.get(projectPath);
52
+ changeTracker.set(projectPath, { files, hash });
53
+ const changed = !prev || prev.hash !== hash;
54
+ return { changed, fileCount: files.length, files };
55
+ } catch {
56
+ return { changed: false, fileCount: 0, files: [] };
57
+ }
58
+ }
59
+
60
+ // --- Route Handler ---
61
+ function handleReviewApi(req, res, url) {
62
+ const p = url.pathname;
63
+ const m = req.method;
64
+
65
+ // GET /api/reviews/diff?project=...&base=...
66
+ if (p === '/api/reviews/diff' && m === 'GET') {
67
+ const project = url.searchParams.get('project');
68
+ const base = url.searchParams.get('base') || '';
69
+ if (!project) return jsonResponse(res, 400, { error: 'project required' });
70
+ if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
71
+ const diffFn = base === '--staged'
72
+ ? gitUtils.getStagedDiff(project)
73
+ : gitUtils.getFullDiff(project, base || undefined);
74
+ diffFn.then(files => jsonResponse(res, 200, { files }))
75
+ .catch(e => jsonResponse(res, 500, { error: e.message }));
76
+ return true;
77
+ }
78
+
79
+ // GET /api/reviews/diff-stat?project=...&base=...
80
+ if (p === '/api/reviews/diff-stat' && m === 'GET') {
81
+ const project = url.searchParams.get('project');
82
+ if (!project) return jsonResponse(res, 400, { error: 'project required' });
83
+ if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
84
+ const base = url.searchParams.get('base') || '';
85
+ gitUtils.getDiffStat(project, base || undefined)
86
+ .then(files => jsonResponse(res, 200, { files }))
87
+ .catch(e => jsonResponse(res, 500, { error: e.message }));
88
+ return true;
89
+ }
90
+
91
+ // GET /api/reviews/commits?project=...
92
+ if (p === '/api/reviews/commits' && m === 'GET') {
93
+ const project = url.searchParams.get('project');
94
+ if (!project) return jsonResponse(res, 400, { error: 'project required' });
95
+ if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
96
+ gitUtils.getCommits(project, 30)
97
+ .then(commits => jsonResponse(res, 200, { commits }))
98
+ .catch(e => jsonResponse(res, 500, { error: e.message }));
99
+ return true;
100
+ }
101
+
102
+ // GET /api/reviews/branch?project=...
103
+ if (p === '/api/reviews/branch' && m === 'GET') {
104
+ const project = url.searchParams.get('project');
105
+ if (!project) return jsonResponse(res, 400, { error: 'project required' });
106
+ if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
107
+ gitUtils.getBranch(project)
108
+ .then(branch => jsonResponse(res, 200, { branch }))
109
+ .catch(e => jsonResponse(res, 500, { error: e.message }));
110
+ return true;
111
+ }
112
+
113
+ // GET /api/reviews/tracked-projects - list all tracked project folders with change counts
114
+ if (p === '/api/reviews/tracked-projects' && m === 'GET') {
115
+ const getTrackedProjects = module.exports._getTrackedProjects;
116
+ if (!getTrackedProjects) return jsonResponse(res, 500, { error: 'tracker not initialized' });
117
+ getTrackedProjects()
118
+ .then(projects => jsonResponse(res, 200, { projects }))
119
+ .catch(e => jsonResponse(res, 500, { error: e.message }));
120
+ return true;
121
+ }
122
+
123
+ // GET /api/reviews/changes?project=... - badge polling endpoint
124
+ if (p === '/api/reviews/changes' && m === 'GET') {
125
+ const project = url.searchParams.get('project');
126
+ if (!project) return jsonResponse(res, 400, { error: 'project required' });
127
+ if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
128
+ checkForChanges(project)
129
+ .then(result => jsonResponse(res, 200, result))
130
+ .catch(e => jsonResponse(res, 500, { error: e.message }));
131
+ return true;
132
+ }
133
+
134
+ // GET /api/reviews/file?project=...&path=... - serve image files from project
135
+ if (p === '/api/reviews/file' && m === 'GET') {
136
+ const project = url.searchParams.get('project');
137
+ const filePath = url.searchParams.get('path');
138
+ if (!project || !filePath) return jsonResponse(res, 400, { error: 'project and path required' });
139
+ if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
140
+ // Only allow image extensions
141
+ const ext = path.extname(filePath).toLowerCase();
142
+ const imageExts = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp', '.ico': 'image/x-icon', '.bmp': 'image/bmp' };
143
+ if (!imageExts[ext]) return jsonResponse(res, 400, { error: 'Only image files are supported' });
144
+ // Prevent path traversal
145
+ const resolved = path.resolve(project, filePath);
146
+ if (!resolved.startsWith(path.resolve(project) + path.sep)) return jsonResponse(res, 403, { error: 'Invalid file path' });
147
+ try {
148
+ const data = fs.readFileSync(resolved);
149
+ res.writeHead(200, { 'Content-Type': imageExts[ext], 'Content-Length': data.length, 'Cache-Control': 'no-cache' });
150
+ res.end(data);
151
+ } catch (e) {
152
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
153
+ res.end('File not found');
154
+ }
155
+ return true;
156
+ }
157
+
158
+ // --- Review CRUD ---
159
+
160
+ // POST /api/reviews - create a new review
161
+ if (p === '/api/reviews' && m === 'POST') {
162
+ readBody(req).then(body => {
163
+ const { session_id, project_path, base_ref } = body;
164
+ if (!project_path) return jsonResponse(res, 400, { error: 'project_path required' });
165
+ if (!isValidProjectPath(project_path)) return jsonResponse(res, 403, { error: 'Invalid project path' });
166
+ const id = db.createReview({ session_id, project_path, base_ref });
167
+ jsonResponse(res, 201, { id });
168
+ }).catch(e => jsonResponse(res, 400, { error: e.message }));
169
+ return true;
170
+ }
171
+
172
+ // GET /api/reviews - list reviews
173
+ if (p === '/api/reviews' && m === 'GET') {
174
+ const project_path = url.searchParams.get('project');
175
+ const session_id = url.searchParams.get('session');
176
+ const reviews = db.listReviews({ project_path, session_id });
177
+ return jsonResponse(res, 200, reviews);
178
+ }
179
+
180
+ // GET /api/reviews/:id
181
+ const reviewMatch = p.match(/^\/api\/reviews\/(\d+)$/);
182
+ if (reviewMatch && m === 'GET') {
183
+ const review = db.getReview(parseInt(reviewMatch[1]));
184
+ if (!review) return jsonResponse(res, 404, { error: 'Review not found' });
185
+ return jsonResponse(res, 200, review);
186
+ }
187
+
188
+ // PUT /api/reviews/:id
189
+ if (reviewMatch && m === 'PUT') {
190
+ readBody(req).then(body => {
191
+ db.updateReview(parseInt(reviewMatch[1]), body);
192
+ jsonResponse(res, 200, { ok: true });
193
+ }).catch(e => jsonResponse(res, 400, { error: e.message }));
194
+ return true;
195
+ }
196
+
197
+ // DELETE /api/reviews/:id
198
+ if (reviewMatch && m === 'DELETE') {
199
+ db.deleteReview(parseInt(reviewMatch[1]));
200
+ return jsonResponse(res, 200, { ok: true });
201
+ }
202
+
203
+ // --- Review Comments ---
204
+
205
+ // POST /api/reviews/:id/comments
206
+ const commentsMatch = p.match(/^\/api\/reviews\/(\d+)\/comments$/);
207
+ if (commentsMatch && m === 'POST') {
208
+ readBody(req).then(body => {
209
+ body.review_id = parseInt(commentsMatch[1]);
210
+ const id = db.addReviewComment(body);
211
+ jsonResponse(res, 201, { id });
212
+ }).catch(e => jsonResponse(res, 400, { error: e.message }));
213
+ return true;
214
+ }
215
+
216
+ // PUT /api/review-comments/:id
217
+ const commentMatch = p.match(/^\/api\/review-comments\/(\d+)$/);
218
+ if (commentMatch && m === 'PUT') {
219
+ readBody(req).then(body => {
220
+ db.updateReviewComment(parseInt(commentMatch[1]), body);
221
+ jsonResponse(res, 200, { ok: true });
222
+ }).catch(e => jsonResponse(res, 400, { error: e.message }));
223
+ return true;
224
+ }
225
+
226
+ // DELETE /api/review-comments/:id
227
+ if (commentMatch && m === 'DELETE') {
228
+ db.deleteReviewComment(parseInt(commentMatch[1]));
229
+ return jsonResponse(res, 200, { ok: true });
230
+ }
231
+
232
+ // --- Compose review prompt for sending to Claude ---
233
+ const composeMatch = p.match(/^\/api\/reviews\/(\d+)\/compose$/);
234
+ if (composeMatch && m === 'GET') {
235
+ const review = db.getReview(parseInt(composeMatch[1]));
236
+ if (!review) return jsonResponse(res, 404, { error: 'Review not found' });
237
+ const prompt = composeReviewPrompt(review);
238
+ return jsonResponse(res, 200, { prompt });
239
+ }
240
+
241
+ return false; // Not handled
242
+ }
243
+
244
+ // Compose structured review prompt from comments
245
+ function composeReviewPrompt(review) {
246
+ const comments = review.comments.filter(c => c.status === 'open');
247
+ if (comments.length === 0) return 'No review comments to address.';
248
+
249
+ const byFile = {};
250
+ for (const c of comments) {
251
+ if (!byFile[c.file_path]) byFile[c.file_path] = [];
252
+ byFile[c.file_path].push(c);
253
+ }
254
+
255
+ const lines = ['Please address these code review comments:\n'];
256
+
257
+ for (const [filePath, fileComments] of Object.entries(byFile)) {
258
+ lines.push(`## ${filePath}\n`);
259
+ for (const c of fileComments) {
260
+ const lineRef = c.line_end && c.line_end !== c.line_start
261
+ ? `Lines ${c.line_start}-${c.line_end}`
262
+ : `Line ${c.line_start}`;
263
+ const severity = c.severity !== 'comment' ? ` [${c.severity}]` : '';
264
+ lines.push(`**${lineRef}**${severity}: ${c.body}\n`);
265
+ }
266
+ }
267
+
268
+ if (review.summary) {
269
+ lines.push(`\n## Overall\n\n${review.summary}`);
270
+ }
271
+
272
+ return lines.join('\n');
273
+ }
274
+
275
+ module.exports = { handleReviewApi, checkForChanges };