create-walle 0.9.3 → 0.9.4

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 (75) hide show
  1. package/README.md +2 -1
  2. package/package.json +1 -1
  3. package/template/claude-task-manager/db.js +5 -1
  4. package/template/claude-task-manager/public/css/walle.css +317 -0
  5. package/template/claude-task-manager/public/index.html +404 -101
  6. package/template/claude-task-manager/public/js/walle.js +1256 -86
  7. package/template/claude-task-manager/server.js +189 -14
  8. package/template/docs/site/api/README.md +146 -0
  9. package/template/docs/site/skills/README.md +99 -5
  10. package/template/package.json +1 -1
  11. package/template/wall-e/agent.js +54 -0
  12. package/template/wall-e/api-walle.js +452 -3
  13. package/template/wall-e/brain.js +45 -1
  14. package/template/wall-e/channels/telegram-channel.js +96 -0
  15. package/template/wall-e/chat.js +61 -2
  16. package/template/wall-e/coding-context.js +252 -0
  17. package/template/wall-e/coding-orchestrator.js +625 -0
  18. package/template/wall-e/coding-review.js +189 -0
  19. package/template/wall-e/core-tasks.js +12 -3
  20. package/template/wall-e/deploy.sh +4 -4
  21. package/template/wall-e/fly.toml +2 -2
  22. package/template/wall-e/package.json +4 -1
  23. package/template/wall-e/skills/_bundled/coding-agent/SKILL.md +17 -0
  24. package/template/wall-e/skills/_bundled/coding-agent/run.js +142 -0
  25. package/template/wall-e/skills/_bundled/email-sync/SKILL.md +12 -7
  26. package/template/wall-e/skills/_bundled/email-sync/mail-reader.jxa +76 -46
  27. package/template/wall-e/skills/_bundled/email-sync/run.js +42 -2
  28. package/template/wall-e/skills/_bundled/glean-team-sync/SKILL.md +57 -0
  29. package/template/wall-e/skills/_bundled/glean-team-sync/run.js +254 -0
  30. package/template/wall-e/skills/_bundled/slack-mentions/SKILL.md +1 -1
  31. package/template/wall-e/skills/_bundled/slack-mentions/run.js +268 -121
  32. package/template/wall-e/skills/_templates/data-fetcher.md +27 -0
  33. package/template/wall-e/skills/_templates/manual-action.md +19 -0
  34. package/template/wall-e/skills/_templates/periodic-checker.md +29 -0
  35. package/template/wall-e/skills/_templates/script-runner.md +21 -0
  36. package/template/wall-e/skills/claude-code-reader.js +16 -4
  37. package/template/wall-e/skills/skill-executor.js +23 -1
  38. package/template/wall-e/skills/skill-validator.js +73 -0
  39. package/template/wall-e/tests/brain.test.js +3 -3
  40. package/template/wall-e/tests/coding-agent-integration.test.js +240 -0
  41. package/template/wall-e/tests/coding-context.test.js +212 -0
  42. package/template/wall-e/tests/coding-orchestrator.test.js +303 -0
  43. package/template/wall-e/tests/coding-review.test.js +141 -0
  44. package/template/claude-task-manager/package-lock.json +0 -1607
  45. package/template/claude-task-manager/tests/test-ai-search.js +0 -61
  46. package/template/claude-task-manager/tests/test-editor-ux.js +0 -76
  47. package/template/claude-task-manager/tests/test-editor-ux2.js +0 -51
  48. package/template/claude-task-manager/tests/test-features-v2.js +0 -127
  49. package/template/claude-task-manager/tests/test-insights-cached.js +0 -78
  50. package/template/claude-task-manager/tests/test-insights.js +0 -124
  51. package/template/claude-task-manager/tests/test-permissions-v2.js +0 -127
  52. package/template/claude-task-manager/tests/test-permissions.js +0 -122
  53. package/template/claude-task-manager/tests/test-pin.js +0 -51
  54. package/template/claude-task-manager/tests/test-prompts.js +0 -164
  55. package/template/claude-task-manager/tests/test-recent-sessions.js +0 -96
  56. package/template/claude-task-manager/tests/test-review.js +0 -104
  57. package/template/claude-task-manager/tests/test-send-dropdown.js +0 -76
  58. package/template/claude-task-manager/tests/test-send-final.js +0 -30
  59. package/template/claude-task-manager/tests/test-send-fixes.js +0 -76
  60. package/template/claude-task-manager/tests/test-send-integration.js +0 -107
  61. package/template/claude-task-manager/tests/test-send-visual.js +0 -34
  62. package/template/claude-task-manager/tests/test-session-create.js +0 -147
  63. package/template/claude-task-manager/tests/test-sidebar-ux.js +0 -83
  64. package/template/claude-task-manager/tests/test-url-hash.js +0 -68
  65. package/template/claude-task-manager/tests/test-ux-crop.js +0 -34
  66. package/template/claude-task-manager/tests/test-ux-review.js +0 -130
  67. package/template/claude-task-manager/tests/test-zoom-card.js +0 -76
  68. package/template/claude-task-manager/tests/test-zoom.js +0 -92
  69. package/template/claude-task-manager/tests/test-zoom2.js +0 -67
  70. package/template/docs/openclaw-vs-walle-comparison.md +0 -103
  71. package/template/docs/ux-improvement-plan.md +0 -84
  72. package/template/wall-e/docs/specs/2026-04-01-publish-plan.md +0 -112
  73. package/template/wall-e/docs/specs/SKILL-FORMAT.md +0 -326
  74. package/template/wall-e/package-lock.json +0 -533
  75. package/template/wall-e/skills/_bundled/slack-mentions/.watermark.json +0 -4
@@ -18,6 +18,16 @@ function ensureBrainInit() {
18
18
  }
19
19
  }
20
20
 
21
+ function getCodingDefaultCwd() {
22
+ try {
23
+ const cfgPath = require('path').join(__dirname, 'wall-e-config.json');
24
+ const cfg = JSON.parse(require('fs').readFileSync(cfgPath, 'utf8'));
25
+ return (cfg.coding_agent && cfg.coding_agent.default_cwd) || process.cwd();
26
+ } catch {
27
+ return process.cwd();
28
+ }
29
+ }
30
+
21
31
  async function chat(message, opts = {}) {
22
32
  ensureBrainInit();
23
33
  const channel = opts.channel || 'ctm';
@@ -72,7 +82,7 @@ async function chat(message, opts = {}) {
72
82
  },
73
83
  {
74
84
  name: 'run_skill',
75
- description: 'Run one of my skills to fetch data or perform an action. Use this when the user asks me to do something actionable.',
85
+ description: `Run one of my skills to fetch data or perform an action. PREFER skills over raw mcp_call when a matching skill exists skills parse data correctly and store results in brain. Available skills: ${(() => { try { const { loadAllSkills } = require('./skills/skill-loader'); const bundled = loadAllSkills().map(s => s.name + ': ' + (s.description || '').slice(0, 60)); const db = brain.listSkills({ enabled: 1 }).map(s => s.name + ': ' + (s.description || '').slice(0, 60)); const all = [...new Set([...bundled, ...db])]; return all.join('; ') || 'none'; } catch { return 'unknown'; } })()}`,
76
86
  input_schema: { type: 'object', properties: { skill_name: { type: 'string', description: 'Name of the skill to run' } }, required: ['skill_name'] },
77
87
  },
78
88
  {
@@ -176,6 +186,19 @@ async function chat(message, opts = {}) {
176
186
  required: ['title', 'description'],
177
187
  },
178
188
  },
189
+ {
190
+ name: 'start_coding',
191
+ description: 'Start an autonomous coding task. Wall-E will plan the implementation, execute subtasks via Claude Code, run tests, review code, and commit. Use when the user asks you to build, fix, add, or refactor something in a codebase.',
192
+ input_schema: {
193
+ type: 'object',
194
+ properties: {
195
+ request: { type: 'string', description: 'What to build, fix, or change — the user\'s request in their own words' },
196
+ cwd: { type: 'string', description: 'Project directory path. Defaults to the current workspace.' },
197
+ delivery: { type: 'string', enum: ['commit', 'push', 'pr'], description: 'What to do after completion. Default: commit (local only)' },
198
+ },
199
+ required: ['request'],
200
+ },
201
+ },
179
202
  {
180
203
  name: 'list_tasks',
181
204
  description: 'List current tasks and their status.',
@@ -199,7 +222,20 @@ async function chat(message, opts = {}) {
199
222
  return { acknowledged: true };
200
223
  }
201
224
  if (name === 'run_skill') {
202
- const skill = brain.getSkillByName(input.skill_name);
225
+ // Check bundled/user skills first (script-based), then DB skills (agent-based)
226
+ const { findSkill } = require('./skills/skill-loader');
227
+ const bundledSkill = findSkill(input.skill_name);
228
+ if (bundledSkill && bundledSkill.execution === 'script') {
229
+ try {
230
+ const { executeSkill } = require('./loops/tasks');
231
+ const fakeTaskId = `chat-${Date.now()}`;
232
+ const result = await executeSkill(fakeTaskId, { id: fakeTaskId, skill: input.skill_name, title: bundledSkill.name });
233
+ return { success: true, output: result.slice(0, 5000) };
234
+ } catch (err) {
235
+ return { error: err.message };
236
+ }
237
+ }
238
+ const skill = bundledSkill || brain.getSkillByName(input.skill_name);
203
239
  if (!skill) return { error: `Skill "${input.skill_name}" not found` };
204
240
  try {
205
241
  const { runSkill } = require('./skills/skill-executor');
@@ -409,6 +445,29 @@ async function chat(message, opts = {}) {
409
445
  });
410
446
  return { created: true, id: result.id, title: input.title, status: 'pending', due_at: input.due_at || 'immediate' };
411
447
  }
448
+ if (name === 'start_coding') {
449
+ ensureBrainInit();
450
+ const result = brain.insertTask({
451
+ title: input.request.slice(0, 100),
452
+ description: `Coding request: ${input.request}`,
453
+ priority: 'normal',
454
+ type: 'once',
455
+ execution: 'skill',
456
+ skill: 'coding-agent',
457
+ skill_config: JSON.stringify({
458
+ request: input.request,
459
+ cwd: input.cwd || getCodingDefaultCwd(),
460
+ options: { delivery: input.delivery || 'commit' },
461
+ }),
462
+ source: 'chat',
463
+ source_ref: opts.session_id || '',
464
+ });
465
+ return {
466
+ created: true,
467
+ task_id: result.id,
468
+ message: 'Coding task created. Wall-E will plan and execute autonomously. Track progress in the Tasks tab.',
469
+ };
470
+ }
412
471
  if (name === 'list_tasks') {
413
472
  const tasks = brain.listTasks({ status: input.status, limit: 20 });
414
473
  return { count: tasks.length, tasks: tasks.map(t => ({ id: t.id, title: t.title, status: t.status, priority: t.priority, due_at: t.due_at, created_at: t.created_at, result: t.result?.slice(0, 200) })) };
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const fsp = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { execFile } = require('node:child_process');
6
+ const { promisify } = require('node:util');
7
+
8
+ const execFileAsync = promisify(execFile);
9
+
10
+ const IGNORED_DIRS = new Set([
11
+ 'node_modules', '.git', '.next', 'dist', 'build', '.superpowers', '__pycache__',
12
+ ]);
13
+
14
+ const MAX_FILE_CONTENT = 50 * 1024; // 50KB
15
+
16
+ /**
17
+ * Returns file tree as newline-separated relative paths.
18
+ * Directories have trailing `/`. Excludes ignored dirs and dotfiles.
19
+ */
20
+ async function getFileTree(cwd, maxDepth = 5) {
21
+ const lines = [];
22
+
23
+ async function walk(dir, depth) {
24
+ if (depth > maxDepth) return;
25
+ let entries;
26
+ try {
27
+ entries = await fsp.readdir(dir, { withFileTypes: true });
28
+ } catch {
29
+ return;
30
+ }
31
+ // Sort for deterministic output
32
+ entries.sort((a, b) => a.name.localeCompare(b.name));
33
+ for (const entry of entries) {
34
+ const name = entry.name;
35
+ // Skip dotfiles and ignored dirs
36
+ if (name.startsWith('.')) continue;
37
+ if (entry.isDirectory()) {
38
+ if (IGNORED_DIRS.has(name)) continue;
39
+ const rel = path.relative(cwd, path.join(dir, name)) + '/';
40
+ lines.push(rel);
41
+ await walk(path.join(dir, name), depth + 1);
42
+ } else {
43
+ const rel = path.relative(cwd, path.join(dir, name));
44
+ lines.push(rel);
45
+ }
46
+ }
47
+ }
48
+
49
+ await walk(cwd, 1);
50
+ return lines.join('\n');
51
+ }
52
+
53
+ /**
54
+ * Reads CLAUDE.md from project dir. Returns content or empty string.
55
+ */
56
+ async function readClaudeMd(cwd) {
57
+ try {
58
+ return await fsp.readFile(path.join(cwd, 'CLAUDE.md'), 'utf8');
59
+ } catch {
60
+ return '';
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Runs `git log --oneline -N`. Returns string or empty.
66
+ */
67
+ async function getGitLog(cwd, count = 20) {
68
+ try {
69
+ const { stdout } = await execFileAsync('git', ['log', '--oneline', `-${count}`], { cwd });
70
+ return stdout.trim();
71
+ } catch {
72
+ return '';
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Detects test command from package.json or Makefile.
78
+ */
79
+ async function detectTestCommand(cwd) {
80
+ // Check package.json
81
+ try {
82
+ const pkgRaw = await fsp.readFile(path.join(cwd, 'package.json'), 'utf8');
83
+ const pkg = JSON.parse(pkgRaw);
84
+ if (pkg.scripts && pkg.scripts.test) {
85
+ return 'npm test';
86
+ }
87
+ } catch {
88
+ // no package.json or invalid
89
+ }
90
+
91
+ // Check Makefile
92
+ try {
93
+ const makefile = await fsp.readFile(path.join(cwd, 'Makefile'), 'utf8');
94
+ if (/^test:/m.test(makefile)) {
95
+ return 'make test';
96
+ }
97
+ } catch {
98
+ // no Makefile
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ /**
105
+ * Splits request into keywords, scores files by keyword matches in filename,
106
+ * reads top matches. Returns { filename: content } map.
107
+ */
108
+ async function findRelevantFiles(cwd, request, maxFiles = 10, existingTree = null) {
109
+ const keywords = request
110
+ .toLowerCase()
111
+ .split(/[\s,.\-_/]+/)
112
+ .filter(w => w.length > 2);
113
+
114
+ if (keywords.length === 0) return {};
115
+
116
+ // Reuse existing tree if provided, otherwise fetch
117
+ const tree = existingTree || await getFileTree(cwd);
118
+ const files = tree.split('\n').filter(f => f && !f.endsWith('/'));
119
+
120
+ // Score each file
121
+ const scored = files.map(f => {
122
+ const lower = f.toLowerCase();
123
+ let score = 0;
124
+ for (const kw of keywords) {
125
+ if (lower.includes(kw)) score++;
126
+ }
127
+ return { file: f, score };
128
+ });
129
+
130
+ // Filter and sort
131
+ const matches = scored
132
+ .filter(s => s.score > 0)
133
+ .sort((a, b) => b.score - a.score)
134
+ .slice(0, maxFiles);
135
+
136
+ const result = {};
137
+ for (const { file } of matches) {
138
+ try {
139
+ let content = await fsp.readFile(path.join(cwd, file), 'utf8');
140
+ if (content.length > MAX_FILE_CONTENT) {
141
+ content = content.slice(0, MAX_FILE_CONTENT);
142
+ }
143
+ result[file] = content;
144
+ } catch {
145
+ // skip unreadable files
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+
151
+ /**
152
+ * Search brain knowledge base for relevant context.
153
+ * Uses findKnowledge (SQL LIKE) since there's no FTS on memories.
154
+ */
155
+ function searchBrain(brain, request, limit = 10) {
156
+ if (!brain || typeof brain.findKnowledge !== 'function') return [];
157
+ try {
158
+ // Extract meaningful keywords from the request
159
+ const keywords = request
160
+ .toLowerCase()
161
+ .split(/[\s,.\-_/]+/)
162
+ .filter(w => w.length > 3);
163
+ if (keywords.length === 0) return [];
164
+
165
+ // Search knowledge by subject for each keyword, deduplicate
166
+ const seen = new Set();
167
+ const results = [];
168
+ for (const kw of keywords.slice(0, 5)) {
169
+ const found = brain.findKnowledge({ subject: kw, status: 'active' });
170
+ for (const item of found) {
171
+ if (!seen.has(item.id) && results.length < limit) {
172
+ seen.add(item.id);
173
+ results.push(item);
174
+ }
175
+ }
176
+ }
177
+ return results;
178
+ } catch {
179
+ return [];
180
+ }
181
+ }
182
+
183
+ /**
184
+ * If mcpClient provided, list tools, find search-type tools, call up to 3.
185
+ * Return [{ tool, result }]. Otherwise return [].
186
+ */
187
+ async function queryMcp(mcpClient, request) {
188
+ if (!mcpClient || typeof mcpClient.listTools !== 'function') return [];
189
+ try {
190
+ const tools = await mcpClient.listTools();
191
+ const searchTools = tools.filter(t => {
192
+ const name = (t.name || '').toLowerCase();
193
+ const desc = (t.description || '').toLowerCase();
194
+ return name.includes('search') || name.includes('query') || name.includes('find')
195
+ || desc.includes('search') || desc.includes('query') || desc.includes('find');
196
+ });
197
+
198
+ const selected = searchTools.slice(0, 3);
199
+ const settled = await Promise.allSettled(
200
+ selected.map(tool =>
201
+ mcpClient.callTool(tool.name, { query: request })
202
+ .then(result => ({ tool: tool.name, result }))
203
+ )
204
+ );
205
+ return settled
206
+ .filter(s => s.status === 'fulfilled')
207
+ .map(s => s.value);
208
+ } catch {
209
+ return [];
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Calls all context-gathering functions in parallel.
215
+ * Returns combined context object.
216
+ */
217
+ async function assembleContext(request, cwd, { brain, mcpClient } = {}) {
218
+ // Gather independent data in parallel (file tree fetched once, reused for relevantFiles)
219
+ const [fileTree, claudeMd, gitLog, testCommand, brainMemories, mcpResults] =
220
+ await Promise.all([
221
+ getFileTree(cwd),
222
+ readClaudeMd(cwd),
223
+ getGitLog(cwd),
224
+ detectTestCommand(cwd),
225
+ Promise.resolve(searchBrain(brain, request)),
226
+ queryMcp(mcpClient, request),
227
+ ]);
228
+
229
+ // Pass fileTree to avoid re-traversal
230
+ const relevantFiles = await findRelevantFiles(cwd, request, 10, fileTree);
231
+
232
+ return {
233
+ fileTree,
234
+ claudeMd,
235
+ gitLog,
236
+ testCommand,
237
+ relevantFiles,
238
+ brainMemories,
239
+ mcpResults,
240
+ };
241
+ }
242
+
243
+ module.exports = {
244
+ getFileTree,
245
+ readClaudeMd,
246
+ getGitLog,
247
+ detectTestCommand,
248
+ findRelevantFiles,
249
+ searchBrain,
250
+ queryMcp,
251
+ assembleContext,
252
+ };