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
@@ -25,9 +25,11 @@ const JXA_SCRIPT = path.join(SKILL_DIR, 'mail-reader.jxa');
25
25
  const args = process.argv.slice(2);
26
26
  let daysBack = 3;
27
27
  let syncInbox = false;
28
+ let incremental = false;
28
29
  for (let i = 0; i < args.length; i++) {
29
30
  if (args[i] === '--days-back' && args[i + 1]) daysBack = parseInt(args[i + 1], 10);
30
31
  if (args[i] === '--sync-inbox') syncInbox = true;
32
+ if (args[i] === '--incremental') incremental = true;
31
33
  }
32
34
 
33
35
  // Task-level config overrides (passed via WALL_E_SKILL_CONFIG env var)
@@ -36,8 +38,34 @@ try {
36
38
  ? JSON.parse(process.env.WALL_E_SKILL_CONFIG) : {};
37
39
  if (envConfig.days_back != null) daysBack = envConfig.days_back;
38
40
  if (envConfig.sync_inbox != null) syncInbox = !!envConfig.sync_inbox;
41
+ if (envConfig.incremental != null) incremental = !!envConfig.incremental;
39
42
  } catch { /* ignore parse errors */ }
40
43
 
44
+ // Incremental mode: compute days_back from the latest email already in the brain
45
+ if (incremental) {
46
+ try {
47
+ const brainPath = path.resolve(SKILL_DIR, '..', '..', '..', 'brain.js');
48
+ const brain = require(brainPath);
49
+ brain.initDb();
50
+ const row = brain.getDb().prepare(
51
+ "SELECT max(timestamp) as ts FROM memories WHERE source = 'email'"
52
+ ).get();
53
+ brain.closeDb();
54
+ if (row?.ts) {
55
+ const latestDate = new Date(row.ts);
56
+ const now = new Date();
57
+ // Go back 1 extra day for overlap/safety margin
58
+ const computedDays = Math.ceil((now - latestDate) / (24 * 60 * 60 * 1000)) + 1;
59
+ console.error(`[email-sync] Incremental: latest email is ${row.ts}, fetching ${computedDays}d back`);
60
+ daysBack = computedDays;
61
+ } else {
62
+ console.error(`[email-sync] Incremental: no existing emails, falling back to ${daysBack}d`);
63
+ }
64
+ } catch (err) {
65
+ console.error(`[email-sync] Incremental check failed: ${err.message}, using days_back=${daysBack}`);
66
+ }
67
+ }
68
+
41
69
  // ── Step 1: Read emails via JXA ──────────────────────────────────────
42
70
 
43
71
  function readEmails() {
@@ -191,8 +219,20 @@ async function main() {
191
219
  return;
192
220
  } catch (err) {
193
221
  const errMsg = (err.stderr || err.message || '').toString();
194
- const isPermDenied = errMsg.includes('not allowed') || errMsg.includes('assistive access')
195
- || errMsg.includes('-1743') || errMsg.includes('-1744') || errMsg.includes('AppleEvent');
222
+ const isTimeout = errMsg.includes('-1712') || errMsg.includes('timed out');
223
+ const isPermDenied = !isTimeout && (errMsg.includes('not allowed') || errMsg.includes('assistive access')
224
+ || errMsg.includes('-1743') || errMsg.includes('-1744'));
225
+
226
+ if (isTimeout) {
227
+ console.error(`[email-sync] Mail app timed out (attempt ${attempt + 1}/${MAX_RETRIES + 1}). Mail may be busy, not running, or the query is too large.`);
228
+ if (attempt < MAX_RETRIES) {
229
+ console.error('[email-sync] Retrying in 10s — make sure Mail.app is open and responsive...');
230
+ await sleep(10_000);
231
+ continue;
232
+ }
233
+ console.error('[email-sync] Still timing out. Try: 1) Open Mail.app and wait for it to finish syncing, 2) Run with a smaller days_back value.');
234
+ process.exit(1);
235
+ }
196
236
 
197
237
  if (isPermDenied && attempt < MAX_RETRIES) {
198
238
  console.error(`[email-sync] Mail access denied (attempt ${attempt + 1}/${MAX_RETRIES + 1}).`);
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: glean-team-sync
3
+ description: >
4
+ Sync team structure from Glean people directory into WALL-E's brain.
5
+ Reads the owner's profile, their manager, peers, and direct reports,
6
+ then recursively maps each person's contact info, location, and role.
7
+ version: 1.0.0
8
+ author: wall-e
9
+ execution: script
10
+ entry: run.js
11
+ trigger:
12
+ type: manual
13
+ config:
14
+ owner_name:
15
+ type: string
16
+ default: ""
17
+ description: "Name of the person whose team to sync (defaults to WALLE_OWNER_NAME)"
18
+ depth:
19
+ type: number
20
+ default: 2
21
+ description: "How many levels of org tree to traverse (1=direct team, 2=skip-level reports)"
22
+ tags: [glean, team, people, directory, org, contacts]
23
+ permissions:
24
+ - glean:read
25
+ - brain:write
26
+ ---
27
+ # Glean Team Sync
28
+
29
+ ## What This Skill Does
30
+
31
+ Syncs team structure from Glean's people directory into WALL-E's brain.
32
+ Starting from the owner, it discovers the org tree (manager, peers, direct
33
+ reports) and stores each person's profile as a memory.
34
+
35
+ ## How It Works
36
+
37
+ 1. Search Glean for the owner by name via `employee_search` MCP tool
38
+ 2. Parse the profile: name, title, department, location, email, manager, reports
39
+ 3. Recursively search each team member up to configured depth
40
+ 4. Dedup by `source_id` = `glean:person:{email}` — skip unchanged, update modified
41
+ 5. Store as memories with `source: 'glean'`, `memory_type: 'team_member'`
42
+
43
+ ## Memory Format
44
+
45
+ Each person is stored as a memory:
46
+ - **source**: `glean`
47
+ - **source_id**: `glean:person:{email}`
48
+ - **source_channel**: department or team name
49
+ - **memory_type**: `team_member`
50
+ - **subject**: person's full name
51
+ - **content**: human-readable profile with title, location, contact, org relationship
52
+ - **metadata**: JSON with structured fields (email, title, department, location, manager, reports, relationship)
53
+ - **importance**: 0.5 (team context)
54
+
55
+ ## Output
56
+
57
+ Returns: `{ synced, updated, skipped, errors, people: [...names] }`
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * glean-team-sync — reads team structure from Glean people directory
5
+ * and stores each person's profile in WALL-E's brain.
6
+ *
7
+ * Uses the Glean MCP `employee_search` tool to find people, then
8
+ * recursively traverses the org tree.
9
+ *
10
+ * Usage: node run.js [--owner "Name"] [--depth N]
11
+ */
12
+ const path = require('path');
13
+
14
+ const SKILL_DIR = __dirname;
15
+ const PAUSE_MS = 600;
16
+
17
+ // Parse CLI args
18
+ const args = process.argv.slice(2);
19
+ let ownerName = process.env.WALLE_OWNER_NAME || '';
20
+ let maxDepth = 2;
21
+ for (let i = 0; i < args.length; i++) {
22
+ if (args[i] === '--owner' && args[i + 1]) ownerName = args[i + 1];
23
+ if (args[i] === '--depth' && args[i + 1]) maxDepth = parseInt(args[i + 1], 10);
24
+ }
25
+
26
+ // Task-level config overrides
27
+ try {
28
+ const envConfig = process.env.WALL_E_SKILL_CONFIG
29
+ ? JSON.parse(process.env.WALL_E_SKILL_CONFIG) : {};
30
+ if (envConfig.owner_name) ownerName = envConfig.owner_name;
31
+ if (envConfig.depth != null) maxDepth = envConfig.depth;
32
+ } catch { /* ignore */ }
33
+
34
+ function sleep(ms) {
35
+ return new Promise(resolve => setTimeout(resolve, ms));
36
+ }
37
+
38
+ // ── Glean MCP ───────────────────────────────────────────────────────
39
+
40
+ let mcpClient;
41
+ let gleanServerName;
42
+
43
+ async function initGlean() {
44
+ mcpClient = require(path.resolve(SKILL_DIR, '..', '..', '..', 'skills', 'mcp-client'));
45
+ const configs = mcpClient.loadMcpConfigs();
46
+ gleanServerName = Object.keys(configs).find(k => k.includes('glean'));
47
+ if (!gleanServerName) throw new Error('Glean MCP not configured');
48
+ }
49
+
50
+ async function searchPerson(query) {
51
+ const result = await mcpClient.callMcpTool(gleanServerName, 'employee_search', { query });
52
+ // Extract raw text from MCP response
53
+ let raw;
54
+ if (result && result.content) {
55
+ raw = result.content.map(c => c.text || JSON.stringify(c)).join('\n');
56
+ } else {
57
+ raw = JSON.stringify(result);
58
+ }
59
+
60
+ // Parse JSON response — Glean returns { entities: [{ person: {...} }] }
61
+ try {
62
+ const data = JSON.parse(raw);
63
+ if (data.entities && data.entities.length > 0) {
64
+ return data.entities.map(e => e.person).filter(Boolean);
65
+ }
66
+ } catch { /* not JSON, try text parsing below */ }
67
+
68
+ // Fallback: already an array of person objects
69
+ if (Array.isArray(raw)) return raw;
70
+
71
+ return [];
72
+ }
73
+
74
+ // ── Build readable profile ──────────────────────────────────────────
75
+
76
+ function buildProfile(person, relationship) {
77
+ const parts = [person.name];
78
+ if (person.title) parts.push(`Title: ${person.title}`);
79
+ if (person.department) parts.push(`Department: ${person.department}`);
80
+ if (person.type) parts.push(`Type: ${person.type}`);
81
+ if (person.location) parts.push(`Location: ${person.location}`);
82
+ if (person.email) parts.push(`Email: ${person.email}`);
83
+ if (person.startDate) parts.push(`Start Date: ${person.startDate}`);
84
+ if (person.pronoun && person.pronoun !== 'None') parts.push(`Pronouns: ${person.pronoun}`);
85
+ if (person.manager) {
86
+ const mgr = typeof person.manager === 'object' ? person.manager.name : person.manager;
87
+ parts.push(`Manager: ${mgr}`);
88
+ }
89
+ if (person.directReportsCount) parts.push(`Direct Reports: ${person.directReportsCount}`);
90
+ if (person.totalReportsCount) parts.push(`Total Reports: ${person.totalReportsCount}`);
91
+ if (person.directReports && person.directReports.length > 0) {
92
+ const names = person.directReports.map(r => typeof r === 'object' ? r.name : r);
93
+ parts.push(`Direct Report Names: ${names.join(', ')}`);
94
+ }
95
+ if (person.teams && person.teams.length > 0) {
96
+ parts.push(`Teams: ${person.teams.join(', ')}`);
97
+ }
98
+ if (person.datasourceToProfileLink) {
99
+ const links = person.datasourceToProfileLink;
100
+ if (links.SLACK) parts.push(`Slack: ${links.SLACK}`);
101
+ }
102
+ if (relationship) parts.push(`Relationship: ${relationship}`);
103
+ return parts.join('\n');
104
+ }
105
+
106
+ // ── Brain storage ───────────────────────────────────────────────────
107
+
108
+ function storePerson(brain, person, relationship) {
109
+ const email = person.email || `unknown-${person.name.replace(/\s+/g, '-').toLowerCase()}`;
110
+ const sourceId = `glean:person:${email}`;
111
+ const content = buildProfile(person, relationship);
112
+
113
+ // Strip circular/large fields from metadata
114
+ const meta = { ...person, relationship };
115
+ // Flatten manager to just name+email
116
+ if (meta.manager && typeof meta.manager === 'object') {
117
+ meta.managerName = meta.manager.name;
118
+ meta.managerEmail = meta.manager.email;
119
+ delete meta.manager;
120
+ }
121
+ // Flatten direct reports to names
122
+ if (meta.directReports) {
123
+ meta.directReportNames = meta.directReports.map(r => typeof r === 'object' ? r.name : r);
124
+ delete meta.directReports;
125
+ }
126
+ const metadata = JSON.stringify(meta);
127
+
128
+ // Check if exists and needs update
129
+ const existing = brain.getDb().prepare(
130
+ 'SELECT id, content FROM memories WHERE source = ? AND source_id = ?'
131
+ ).get('glean', sourceId);
132
+
133
+ if (existing) {
134
+ if (existing.content !== content) {
135
+ brain.getDb().prepare(
136
+ 'UPDATE memories SET content = ?, metadata = ?, timestamp = ? WHERE id = ?'
137
+ ).run(content, metadata, new Date().toISOString(), existing.id);
138
+ return 'updated';
139
+ }
140
+ return 'skipped';
141
+ }
142
+
143
+ const result = brain.insertMemory({
144
+ source: 'glean',
145
+ source_id: sourceId,
146
+ source_channel: person.department || 'unknown',
147
+ memory_type: 'team_member',
148
+ subject: person.name,
149
+ content,
150
+ metadata,
151
+ importance: 0.5,
152
+ timestamp: new Date().toISOString(),
153
+ });
154
+ return result ? 'inserted' : 'skipped';
155
+ }
156
+
157
+ // ── Recursive crawl ─────────────────────────────────────────────────
158
+
159
+ async function main() {
160
+ await initGlean();
161
+
162
+ const brainPath = path.resolve(SKILL_DIR, '..', '..', '..', 'brain.js');
163
+ const brain = require(brainPath);
164
+ brain.initDb();
165
+
166
+ // Resolve owner name from brain if not set via env/args
167
+ if (!ownerName) {
168
+ ownerName = brain.getOwnerName() || '';
169
+ }
170
+ if (!ownerName) {
171
+ console.error('[glean-team-sync] No owner name configured (set WALLE_OWNER_NAME or --owner)');
172
+ process.exit(1);
173
+ }
174
+
175
+ const stats = { synced: 0, updated: 0, skipped: 0, errors: 0, people: [] };
176
+ const visited = new Set(); // by email
177
+
178
+ async function crawlPerson(name, relationship, depth) {
179
+ console.error(`[glean-team-sync] Searching: ${name} (${relationship}, depth=${depth})`);
180
+
181
+ try {
182
+ const people = await searchPerson(name);
183
+ await sleep(PAUSE_MS);
184
+
185
+ if (people.length === 0) {
186
+ console.error(`[glean-team-sync] No results for: ${name}`);
187
+ stats.errors++;
188
+ return;
189
+ }
190
+
191
+ // Find best match
192
+ const normalized = name.trim().toLowerCase();
193
+ const person = people.find(p => p.name && p.name.toLowerCase() === normalized)
194
+ || people.find(p => p.name && p.name.toLowerCase().includes(normalized))
195
+ || people[0];
196
+
197
+ if (!person || !person.name) {
198
+ console.error(`[glean-team-sync] No valid profile for: ${name}`);
199
+ stats.errors++;
200
+ return;
201
+ }
202
+
203
+ const email = (person.email || '').toLowerCase();
204
+ if (visited.has(email || person.name.toLowerCase())) return;
205
+ visited.add(email || person.name.toLowerCase());
206
+
207
+ // Store
208
+ const result = storePerson(brain, person, relationship);
209
+ if (result === 'inserted') stats.synced++;
210
+ else if (result === 'updated') stats.updated++;
211
+ else stats.skipped++;
212
+ stats.people.push(person.name);
213
+
214
+ console.error(`[glean-team-sync] ${result}: ${person.name} — ${person.title || 'no title'}`);
215
+
216
+ // Recurse into org tree
217
+ if (depth < maxDepth) {
218
+ // Manager
219
+ if (person.manager) {
220
+ const mgrName = typeof person.manager === 'object' ? person.manager.name : person.manager;
221
+ if (mgrName) {
222
+ await crawlPerson(mgrName, `manager of ${person.name}`, depth + 1);
223
+ }
224
+ }
225
+
226
+ // Direct reports
227
+ if (person.directReports && person.directReports.length > 0) {
228
+ for (const report of person.directReports) {
229
+ const reportName = typeof report === 'object' ? report.name : report;
230
+ if (reportName) {
231
+ await crawlPerson(reportName, `reports to ${person.name}`, depth + 1);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ } catch (err) {
237
+ console.error(`[glean-team-sync] Error searching ${name}: ${err.message}`);
238
+ stats.errors++;
239
+ }
240
+ }
241
+
242
+ await crawlPerson(ownerName, 'self (owner)', 0);
243
+
244
+ brain.closeDb();
245
+ mcpClient.disconnectAll();
246
+
247
+ console.log(JSON.stringify(stats));
248
+ console.error(`[glean-team-sync] Done: ${stats.synced} new, ${stats.updated} updated, ${stats.skipped} unchanged, ${stats.errors} errors (${stats.people.length} people)`);
249
+ }
250
+
251
+ main().catch(err => {
252
+ console.error(`[glean-team-sync] Fatal: ${err.message}`);
253
+ process.exit(1);
254
+ });
@@ -22,7 +22,7 @@ permissions:
22
22
 
23
23
  ## What This Skill Does
24
24
 
25
- Polls Slack for messages mentioning @walle or @wall-e from the owner (Juncao).
25
+ Polls Slack for messages mentioning @walle or @wall-e from the owner.
26
26
  Classifies each mention as either a task assignment or a question, then:
27
27
 
28
28
  - **Task**: Creates a brain task with `source: 'slack'` and `source_ref` linking