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.
- package/README.md +2 -1
- package/package.json +1 -1
- package/template/claude-task-manager/db.js +5 -1
- package/template/claude-task-manager/public/css/walle.css +317 -0
- package/template/claude-task-manager/public/index.html +404 -101
- package/template/claude-task-manager/public/js/walle.js +1256 -86
- package/template/claude-task-manager/server.js +189 -14
- package/template/docs/site/api/README.md +146 -0
- package/template/docs/site/skills/README.md +99 -5
- package/template/package.json +1 -1
- package/template/wall-e/agent.js +54 -0
- package/template/wall-e/api-walle.js +452 -3
- package/template/wall-e/brain.js +45 -1
- package/template/wall-e/channels/telegram-channel.js +96 -0
- package/template/wall-e/chat.js +61 -2
- package/template/wall-e/coding-context.js +252 -0
- package/template/wall-e/coding-orchestrator.js +625 -0
- package/template/wall-e/coding-review.js +189 -0
- package/template/wall-e/core-tasks.js +12 -3
- package/template/wall-e/deploy.sh +4 -4
- package/template/wall-e/fly.toml +2 -2
- package/template/wall-e/package.json +4 -1
- package/template/wall-e/skills/_bundled/coding-agent/SKILL.md +17 -0
- package/template/wall-e/skills/_bundled/coding-agent/run.js +142 -0
- package/template/wall-e/skills/_bundled/email-sync/SKILL.md +12 -7
- package/template/wall-e/skills/_bundled/email-sync/mail-reader.jxa +76 -46
- package/template/wall-e/skills/_bundled/email-sync/run.js +42 -2
- package/template/wall-e/skills/_bundled/glean-team-sync/SKILL.md +57 -0
- package/template/wall-e/skills/_bundled/glean-team-sync/run.js +254 -0
- package/template/wall-e/skills/_bundled/slack-mentions/SKILL.md +1 -1
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +268 -121
- package/template/wall-e/skills/_templates/data-fetcher.md +27 -0
- package/template/wall-e/skills/_templates/manual-action.md +19 -0
- package/template/wall-e/skills/_templates/periodic-checker.md +29 -0
- package/template/wall-e/skills/_templates/script-runner.md +21 -0
- package/template/wall-e/skills/claude-code-reader.js +16 -4
- package/template/wall-e/skills/skill-executor.js +23 -1
- package/template/wall-e/skills/skill-validator.js +73 -0
- package/template/wall-e/tests/brain.test.js +3 -3
- package/template/wall-e/tests/coding-agent-integration.test.js +240 -0
- package/template/wall-e/tests/coding-context.test.js +212 -0
- package/template/wall-e/tests/coding-orchestrator.test.js +303 -0
- package/template/wall-e/tests/coding-review.test.js +141 -0
- package/template/claude-task-manager/package-lock.json +0 -1607
- package/template/claude-task-manager/tests/test-ai-search.js +0 -61
- package/template/claude-task-manager/tests/test-editor-ux.js +0 -76
- package/template/claude-task-manager/tests/test-editor-ux2.js +0 -51
- package/template/claude-task-manager/tests/test-features-v2.js +0 -127
- package/template/claude-task-manager/tests/test-insights-cached.js +0 -78
- package/template/claude-task-manager/tests/test-insights.js +0 -124
- package/template/claude-task-manager/tests/test-permissions-v2.js +0 -127
- package/template/claude-task-manager/tests/test-permissions.js +0 -122
- package/template/claude-task-manager/tests/test-pin.js +0 -51
- package/template/claude-task-manager/tests/test-prompts.js +0 -164
- package/template/claude-task-manager/tests/test-recent-sessions.js +0 -96
- package/template/claude-task-manager/tests/test-review.js +0 -104
- package/template/claude-task-manager/tests/test-send-dropdown.js +0 -76
- package/template/claude-task-manager/tests/test-send-final.js +0 -30
- package/template/claude-task-manager/tests/test-send-fixes.js +0 -76
- package/template/claude-task-manager/tests/test-send-integration.js +0 -107
- package/template/claude-task-manager/tests/test-send-visual.js +0 -34
- package/template/claude-task-manager/tests/test-session-create.js +0 -147
- package/template/claude-task-manager/tests/test-sidebar-ux.js +0 -83
- package/template/claude-task-manager/tests/test-url-hash.js +0 -68
- package/template/claude-task-manager/tests/test-ux-crop.js +0 -34
- package/template/claude-task-manager/tests/test-ux-review.js +0 -130
- package/template/claude-task-manager/tests/test-zoom-card.js +0 -76
- package/template/claude-task-manager/tests/test-zoom.js +0 -92
- package/template/claude-task-manager/tests/test-zoom2.js +0 -67
- package/template/docs/openclaw-vs-walle-comparison.md +0 -103
- package/template/docs/ux-improvement-plan.md +0 -84
- package/template/wall-e/docs/specs/2026-04-01-publish-plan.md +0 -112
- package/template/wall-e/docs/specs/SKILL-FORMAT.md +0 -326
- package/template/wall-e/package-lock.json +0 -533
- package/template/wall-e/skills/_bundled/slack-mentions/.watermark.json +0 -4
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const puppeteer = require('puppeteer');
|
|
3
|
-
const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
|
|
4
|
-
|
|
5
|
-
async function runTest() {
|
|
6
|
-
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
7
|
-
const page = await browser.newPage();
|
|
8
|
-
page.on('pageerror', err => console.error('[PAGE ERROR]', err.message));
|
|
9
|
-
await page.setViewport({ width: 1920, height: 1080 });
|
|
10
|
-
await page.goto(`http://localhost:3456/prompts.html?token=${TOKEN}`, { waitUntil: 'networkidle2', timeout: 15000 });
|
|
11
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
12
|
-
|
|
13
|
-
// Screenshot 1: Empty/welcome state
|
|
14
|
-
await page.screenshot({ path: 'tests/screenshots/ux-01-welcome.png' });
|
|
15
|
-
|
|
16
|
-
// Click on "Email Cron v2" prompt
|
|
17
|
-
const items = await page.$$('.prompt-item');
|
|
18
|
-
for (const item of items) {
|
|
19
|
-
const title = await item.$eval('.prompt-title', el => el.textContent.trim());
|
|
20
|
-
if (title.includes('Email Cron')) {
|
|
21
|
-
await item.click();
|
|
22
|
-
console.log('Clicked:', title);
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
await new Promise(r => setTimeout(r, 800));
|
|
27
|
-
|
|
28
|
-
// Screenshot 2: Full page with editor open
|
|
29
|
-
await page.screenshot({ path: 'tests/screenshots/ux-02-editor-full.png' });
|
|
30
|
-
|
|
31
|
-
// Screenshot 3: Editor content area (cropped)
|
|
32
|
-
const editorWrapper = await page.$('.editor-wrapper');
|
|
33
|
-
if (editorWrapper) {
|
|
34
|
-
await editorWrapper.screenshot({ path: 'tests/screenshots/ux-03-editor-content.png' });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Screenshot 4: Meta bar and toolbar area
|
|
38
|
-
const metaBar = await page.$('.meta-bar');
|
|
39
|
-
const toolbar = await page.$('.editor-toolbar');
|
|
40
|
-
if (metaBar && toolbar) {
|
|
41
|
-
const mBox = await metaBar.boundingBox();
|
|
42
|
-
const tBox = await toolbar.boundingBox();
|
|
43
|
-
const clip = {
|
|
44
|
-
x: mBox.x,
|
|
45
|
-
y: mBox.y,
|
|
46
|
-
width: Math.max(mBox.width, tBox.width),
|
|
47
|
-
height: (tBox.y + tBox.height) - mBox.y
|
|
48
|
-
};
|
|
49
|
-
await page.screenshot({ path: 'tests/screenshots/ux-04-meta-toolbar.png', clip });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Screenshot 5: Sidebar
|
|
53
|
-
const sidebar = await page.$('#prompt-sidebar');
|
|
54
|
-
if (sidebar) {
|
|
55
|
-
await sidebar.screenshot({ path: 'tests/screenshots/ux-05-sidebar.png' });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Screenshot 6: Scroll down to see more content
|
|
59
|
-
await page.evaluate(() => {
|
|
60
|
-
const w = document.querySelector('.editor-wrapper');
|
|
61
|
-
if (w) w.scrollTop = w.scrollHeight;
|
|
62
|
-
});
|
|
63
|
-
await new Promise(r => setTimeout(r, 300));
|
|
64
|
-
await page.screenshot({ path: 'tests/screenshots/ux-06-scrolled-bottom.png' });
|
|
65
|
-
|
|
66
|
-
// Screenshot 7: Mobile-ish width
|
|
67
|
-
await page.setViewport({ width: 768, height: 1024 });
|
|
68
|
-
await new Promise(r => setTimeout(r, 500));
|
|
69
|
-
await page.screenshot({ path: 'tests/screenshots/ux-07-tablet.png' });
|
|
70
|
-
|
|
71
|
-
// Check some computed styles for the analysis
|
|
72
|
-
await page.setViewport({ width: 1920, height: 1080 });
|
|
73
|
-
await new Promise(r => setTimeout(r, 300));
|
|
74
|
-
|
|
75
|
-
const styles = await page.evaluate(() => {
|
|
76
|
-
const editor = document.getElementById('editor');
|
|
77
|
-
const wrapper = document.querySelector('.editor-wrapper');
|
|
78
|
-
const metaBar = document.querySelector('.meta-bar');
|
|
79
|
-
const titleInput = document.getElementById('prompt-title-input');
|
|
80
|
-
const toolbar = document.querySelector('.editor-toolbar');
|
|
81
|
-
|
|
82
|
-
const get = el => {
|
|
83
|
-
if (!el) return null;
|
|
84
|
-
const cs = getComputedStyle(el);
|
|
85
|
-
return {
|
|
86
|
-
fontFamily: cs.fontFamily,
|
|
87
|
-
fontSize: cs.fontSize,
|
|
88
|
-
lineHeight: cs.lineHeight,
|
|
89
|
-
color: cs.color,
|
|
90
|
-
background: cs.backgroundColor,
|
|
91
|
-
padding: cs.padding,
|
|
92
|
-
maxWidth: cs.maxWidth,
|
|
93
|
-
margin: cs.margin,
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Check link styles
|
|
98
|
-
const links = editor.querySelectorAll('a');
|
|
99
|
-
const linkStyles = links.length ? getComputedStyle(links[0]) : null;
|
|
100
|
-
|
|
101
|
-
// Check strong/bold styles
|
|
102
|
-
const strongs = editor.querySelectorAll('strong');
|
|
103
|
-
const strongStyle = strongs.length ? getComputedStyle(strongs[0]) : null;
|
|
104
|
-
|
|
105
|
-
// Check paragraphs
|
|
106
|
-
const paragraphs = editor.querySelectorAll('p');
|
|
107
|
-
const pStyle = paragraphs.length ? getComputedStyle(paragraphs[0]) : null;
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
editor: get(editor),
|
|
111
|
-
wrapper: get(wrapper),
|
|
112
|
-
metaBar: get(metaBar),
|
|
113
|
-
titleInput: get(titleInput),
|
|
114
|
-
toolbar: get(toolbar),
|
|
115
|
-
link: linkStyles ? { color: linkStyles.color, textDecoration: linkStyles.textDecoration } : null,
|
|
116
|
-
strong: strongStyle ? { fontWeight: strongStyle.fontWeight, fontSize: strongStyle.fontSize } : null,
|
|
117
|
-
paragraph: pStyle ? { marginBottom: pStyle.marginBottom, lineHeight: pStyle.lineHeight } : null,
|
|
118
|
-
editorWidth: editor.getBoundingClientRect().width,
|
|
119
|
-
wrapperWidth: wrapper.getBoundingClientRect().width,
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
console.log('\n=== Computed Styles ===');
|
|
124
|
-
console.log(JSON.stringify(styles, null, 2));
|
|
125
|
-
|
|
126
|
-
await browser.close();
|
|
127
|
-
console.log('\nScreenshots saved to tests/screenshots/ux-*.png');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
runTest();
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const puppeteer = require('puppeteer');
|
|
3
|
-
const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
|
|
4
|
-
|
|
5
|
-
async function runTest() {
|
|
6
|
-
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
7
|
-
const page = await browser.newPage();
|
|
8
|
-
page.on('pageerror', err => console.error('[PAGE ERROR]', err.message));
|
|
9
|
-
await page.setViewport({ width: 1920, height: 1080 });
|
|
10
|
-
await page.goto(`http://localhost:3456/prompts.html?token=${TOKEN}`, { waitUntil: 'networkidle2', timeout: 15000 });
|
|
11
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
12
|
-
|
|
13
|
-
// Open Email Cron v2
|
|
14
|
-
const items = await page.$$('.prompt-item');
|
|
15
|
-
for (const item of items) {
|
|
16
|
-
const title = await item.$eval('.prompt-title', el => el.textContent.trim());
|
|
17
|
-
if (title.includes('Email Cron')) { await item.click(); break; }
|
|
18
|
-
}
|
|
19
|
-
await new Promise(r => setTimeout(r, 800));
|
|
20
|
-
|
|
21
|
-
// Helper: crop to editor area
|
|
22
|
-
async function cropEditor(filename) {
|
|
23
|
-
const wrapper = await page.$('.editor-wrapper');
|
|
24
|
-
if (wrapper) {
|
|
25
|
-
const box = await wrapper.boundingBox();
|
|
26
|
-
const clip = {
|
|
27
|
-
x: box.x,
|
|
28
|
-
y: box.y,
|
|
29
|
-
width: Math.min(box.width, 1400),
|
|
30
|
-
height: Math.min(box.height, 700),
|
|
31
|
-
};
|
|
32
|
-
await page.screenshot({ path: `tests/screenshots/${filename}`, clip });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 100% zoom
|
|
37
|
-
console.log('Testing 100% zoom...');
|
|
38
|
-
await cropEditor('zoom-card-100.png');
|
|
39
|
-
|
|
40
|
-
// 75% zoom
|
|
41
|
-
await page.select('#zoom-select', '75');
|
|
42
|
-
await new Promise(r => setTimeout(r, 300));
|
|
43
|
-
console.log('Testing 75% zoom...');
|
|
44
|
-
await cropEditor('zoom-card-75.png');
|
|
45
|
-
|
|
46
|
-
// 150% zoom
|
|
47
|
-
await page.select('#zoom-select', '150');
|
|
48
|
-
await new Promise(r => setTimeout(r, 300));
|
|
49
|
-
console.log('Testing 150% zoom...');
|
|
50
|
-
await cropEditor('zoom-card-150.png');
|
|
51
|
-
|
|
52
|
-
// 200% zoom
|
|
53
|
-
await page.select('#zoom-select', '200');
|
|
54
|
-
await new Promise(r => setTimeout(r, 300));
|
|
55
|
-
console.log('Testing 200% zoom...');
|
|
56
|
-
await cropEditor('zoom-card-200.png');
|
|
57
|
-
|
|
58
|
-
// Check computed properties
|
|
59
|
-
const info = await page.evaluate(() => {
|
|
60
|
-
const editor = document.getElementById('editor');
|
|
61
|
-
const wrapper = document.querySelector('.editor-wrapper');
|
|
62
|
-
return {
|
|
63
|
-
editorZoom: editor.style.zoom,
|
|
64
|
-
editorWidth: editor.getBoundingClientRect().width,
|
|
65
|
-
editorHeight: editor.getBoundingClientRect().height,
|
|
66
|
-
wrapperScrollWidth: wrapper.scrollWidth,
|
|
67
|
-
wrapperClientWidth: wrapper.clientWidth,
|
|
68
|
-
wrapperScrollHeight: wrapper.scrollHeight,
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
console.log('\nAt 200% zoom:', JSON.stringify(info, null, 2));
|
|
72
|
-
|
|
73
|
-
await browser.close();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
runTest();
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const puppeteer = require('puppeteer');
|
|
3
|
-
|
|
4
|
-
const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
|
|
5
|
-
const BASE_URL = `http://localhost:3456/prompts.html?token=${TOKEN}`;
|
|
6
|
-
|
|
7
|
-
async function runTest() {
|
|
8
|
-
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
9
|
-
const page = await browser.newPage();
|
|
10
|
-
page.on('pageerror', err => console.error('[PAGE ERROR]', err.message));
|
|
11
|
-
page.on('console', msg => { if (msg.type() === 'error') console.log('[CONSOLE ERROR]', msg.text()); });
|
|
12
|
-
await page.setViewport({ width: 1920, height: 1080 });
|
|
13
|
-
await page.goto(BASE_URL, { waitUntil: 'networkidle2', timeout: 15000 });
|
|
14
|
-
await new Promise(r => setTimeout(r, 800));
|
|
15
|
-
|
|
16
|
-
// Open first prompt
|
|
17
|
-
const items = await page.$$('.prompt-item');
|
|
18
|
-
if (items.length) await items[0].click();
|
|
19
|
-
await new Promise(r => setTimeout(r, 500));
|
|
20
|
-
|
|
21
|
-
// Check toolbar visibility
|
|
22
|
-
const toolbarDisplay = await page.$eval('#editor-toolbar', el => getComputedStyle(el).display);
|
|
23
|
-
console.log('Toolbar computed display:', toolbarDisplay);
|
|
24
|
-
|
|
25
|
-
// Check zoom select
|
|
26
|
-
const selectExists = !!(await page.$('#zoom-select'));
|
|
27
|
-
console.log('Zoom select exists:', selectExists);
|
|
28
|
-
const selectVal = await page.$eval('#zoom-select', el => el.value);
|
|
29
|
-
console.log('Zoom select value:', selectVal);
|
|
30
|
-
|
|
31
|
-
// List all toolbar buttons with their onclick
|
|
32
|
-
const btns = await page.$$eval('.toolbar-btn', els => els.map(e => ({
|
|
33
|
-
text: e.textContent.trim(),
|
|
34
|
-
onclick: e.getAttribute('onclick'),
|
|
35
|
-
visible: getComputedStyle(e).display !== 'none',
|
|
36
|
-
})));
|
|
37
|
-
console.log('\nAll toolbar buttons:');
|
|
38
|
-
btns.forEach(b => console.log(` "${b.text}" → onclick="${b.onclick}" visible=${b.visible}`));
|
|
39
|
-
|
|
40
|
-
// Find + and - buttons
|
|
41
|
-
const plusBtn = btns.find(b => b.text === '+');
|
|
42
|
-
const minusBtn = btns.find(b => b.onclick && b.onclick.includes('zoomEditor(-1)'));
|
|
43
|
-
console.log('\nPlus button:', plusBtn ? 'FOUND' : 'NOT FOUND');
|
|
44
|
-
console.log('Minus button:', minusBtn ? 'FOUND' : 'NOT FOUND');
|
|
45
|
-
|
|
46
|
-
// Test: click + button
|
|
47
|
-
console.log('\n--- Test: Click + button ---');
|
|
48
|
-
const valBefore = await page.$eval('#zoom-select', el => el.value);
|
|
49
|
-
console.log('Before:', valBefore);
|
|
50
|
-
|
|
51
|
-
await page.evaluate(() => {
|
|
52
|
-
const btn = Array.from(document.querySelectorAll('.toolbar-btn')).find(b => b.getAttribute('onclick') === 'zoomEditor(1)');
|
|
53
|
-
console.log('Found btn:', btn);
|
|
54
|
-
if (btn) btn.click();
|
|
55
|
-
});
|
|
56
|
-
await new Promise(r => setTimeout(r, 200));
|
|
57
|
-
|
|
58
|
-
const valAfter = await page.$eval('#zoom-select', el => el.value);
|
|
59
|
-
console.log('After click +:', valAfter);
|
|
60
|
-
|
|
61
|
-
// Test: direct JS call
|
|
62
|
-
console.log('\n--- Test: Direct zoomEditor(1) ---');
|
|
63
|
-
try {
|
|
64
|
-
await page.evaluate(() => zoomEditor(1));
|
|
65
|
-
const valAfter2 = await page.$eval('#zoom-select', el => el.value);
|
|
66
|
-
console.log('After direct call:', valAfter2);
|
|
67
|
-
} catch (e) {
|
|
68
|
-
console.log('ERROR calling zoomEditor:', e.message);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Test: check if zoomEditor is defined
|
|
72
|
-
const fnExists = await page.evaluate(() => typeof zoomEditor);
|
|
73
|
-
console.log('\ntypeof zoomEditor:', fnExists);
|
|
74
|
-
const setZoomExists = await page.evaluate(() => typeof setZoom);
|
|
75
|
-
console.log('typeof setZoom:', setZoomExists);
|
|
76
|
-
const applyZoomExists = await page.evaluate(() => typeof applyZoom);
|
|
77
|
-
console.log('typeof applyZoom:', applyZoomExists);
|
|
78
|
-
|
|
79
|
-
// Test: select dropdown
|
|
80
|
-
console.log('\n--- Test: Select 200% from dropdown ---');
|
|
81
|
-
await page.select('#zoom-select', '200');
|
|
82
|
-
await new Promise(r => setTimeout(r, 200));
|
|
83
|
-
const wrapperFontSize = await page.$eval('#editor-wrapper', el => el.style.fontSize);
|
|
84
|
-
console.log('Wrapper fontSize:', wrapperFontSize);
|
|
85
|
-
|
|
86
|
-
await page.screenshot({ path: 'tests/screenshots/zoom-debug.png' });
|
|
87
|
-
console.log('\nScreenshot saved');
|
|
88
|
-
|
|
89
|
-
await browser.close();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
runTest();
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const puppeteer = require('puppeteer');
|
|
3
|
-
const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
|
|
4
|
-
|
|
5
|
-
async function runTest() {
|
|
6
|
-
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
7
|
-
const page = await browser.newPage();
|
|
8
|
-
page.on('pageerror', err => console.error('[PAGE ERROR]', err.message));
|
|
9
|
-
await page.setViewport({ width: 1920, height: 1080 });
|
|
10
|
-
await page.goto(`http://localhost:3456/prompts.html?token=${TOKEN}`, { waitUntil: 'networkidle2', timeout: 15000 });
|
|
11
|
-
await new Promise(r => setTimeout(r, 800));
|
|
12
|
-
|
|
13
|
-
// Open first prompt
|
|
14
|
-
const items = await page.$$('.prompt-item');
|
|
15
|
-
if (items.length) await items[0].click();
|
|
16
|
-
await new Promise(r => setTimeout(r, 500));
|
|
17
|
-
|
|
18
|
-
// Crop to editor area for comparison
|
|
19
|
-
const clip = { x: 220, y: 90, width: 900, height: 400 };
|
|
20
|
-
|
|
21
|
-
// 100%
|
|
22
|
-
console.log('Screenshot at 100%...');
|
|
23
|
-
await page.screenshot({ path: 'tests/screenshots/zoom-crop-100.png', clip });
|
|
24
|
-
|
|
25
|
-
// 150%
|
|
26
|
-
await page.select('#zoom-select', '150');
|
|
27
|
-
await new Promise(r => setTimeout(r, 300));
|
|
28
|
-
const fs150 = await page.$eval('#editor-wrapper', el => el.style.fontSize);
|
|
29
|
-
console.log('fontSize at 150%:', fs150);
|
|
30
|
-
await page.screenshot({ path: 'tests/screenshots/zoom-crop-150.png', clip });
|
|
31
|
-
|
|
32
|
-
// 200%
|
|
33
|
-
await page.select('#zoom-select', '200');
|
|
34
|
-
await new Promise(r => setTimeout(r, 300));
|
|
35
|
-
const fs200 = await page.$eval('#editor-wrapper', el => el.style.fontSize);
|
|
36
|
-
console.log('fontSize at 200%:', fs200);
|
|
37
|
-
await page.screenshot({ path: 'tests/screenshots/zoom-crop-200.png', clip });
|
|
38
|
-
|
|
39
|
-
// 75%
|
|
40
|
-
await page.select('#zoom-select', '75');
|
|
41
|
-
await new Promise(r => setTimeout(r, 300));
|
|
42
|
-
const fs75 = await page.$eval('#editor-wrapper', el => el.style.fontSize);
|
|
43
|
-
console.log('fontSize at 75%:', fs75);
|
|
44
|
-
await page.screenshot({ path: 'tests/screenshots/zoom-crop-75.png', clip });
|
|
45
|
-
|
|
46
|
-
// Check actual computed font size of editor text
|
|
47
|
-
const computed100 = await page.evaluate(() => {
|
|
48
|
-
document.getElementById('editor-wrapper').style.fontSize = '100%';
|
|
49
|
-
return getComputedStyle(document.getElementById('editor')).fontSize;
|
|
50
|
-
});
|
|
51
|
-
const computed200 = await page.evaluate(() => {
|
|
52
|
-
document.getElementById('editor-wrapper').style.fontSize = '200%';
|
|
53
|
-
return getComputedStyle(document.getElementById('editor')).fontSize;
|
|
54
|
-
});
|
|
55
|
-
console.log('\nComputed font-size at 100%:', computed100);
|
|
56
|
-
console.log('Computed font-size at 200%:', computed200);
|
|
57
|
-
|
|
58
|
-
if (computed100 === computed200) {
|
|
59
|
-
console.log('\nFAIL: Zoom has no visual effect! The font-size% on wrapper is not cascading to the editor content.');
|
|
60
|
-
} else {
|
|
61
|
-
console.log('\nPASS: Zoom changes computed font size.');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
await browser.close();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
runTest();
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# OpenClaw vs. Wall-E: Intelligence Comparison
|
|
2
|
-
|
|
3
|
-
*Date: 2026-04-03*
|
|
4
|
-
|
|
5
|
-
## The Fundamental Architectural Difference
|
|
6
|
-
|
|
7
|
-
**OpenClaw is a *routing plane*. Wall-E is a *cognitive loop*.**
|
|
8
|
-
|
|
9
|
-
They solve different problems, and that shapes everything about how "intelligent" they feel.
|
|
10
|
-
|
|
11
|
-
| | OpenClaw | Wall-E |
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| **Core metaphor** | Gateway / message router | Digital twin / brain |
|
|
14
|
-
| **Intelligence source** | Claude (or any LLM) on every turn | Claude for extraction + pre-computed brain state |
|
|
15
|
-
| **Proactivity** | Cron jobs -> spawn agent turn | Ingest/think/reflect loops + skill planner |
|
|
16
|
-
| **Memory** | Vector embeddings + session transcripts | SPO knowledge triples + raw memories + FTS |
|
|
17
|
-
| **Identity** | Configurable name/emoji prefix | Deep identity from 28k+ ingested memories |
|
|
18
|
-
| **Channels** | 23+ messaging platforms | iMessage, Slack DM, CTM chat |
|
|
19
|
-
| **Architecture** | ~200k+ lines TypeScript monorepo | ~10k lines Node.js |
|
|
20
|
-
|
|
21
|
-
## Where OpenClaw Feels "Smarter"
|
|
22
|
-
|
|
23
|
-
### 1. The Gateway is the brain -- every message gets a full Claude turn
|
|
24
|
-
|
|
25
|
-
OpenClaw's Pi agent runtime (`pi-embedded-runner.ts`) runs a full LLM call for every single incoming message. The agent has access to tools (bash, file read/write, web search, canvas, image gen, cron management, subagent spawning, session management) and Claude decides what to do. This means:
|
|
26
|
-
|
|
27
|
-
- It can reason about *anything* in real-time
|
|
28
|
-
- It can chain tool calls naturally (search -> read -> respond)
|
|
29
|
-
- It doesn't need pre-computed knowledge -- it reasons on the fly
|
|
30
|
-
|
|
31
|
-
Wall-E, by contrast, pre-computes knowledge in batch loops (think every 2 min, reflect every hour). The chat handler builds a static system prompt from brain state. This means Wall-E's "intelligence" at chat time is limited to what's already been extracted and stored.
|
|
32
|
-
|
|
33
|
-
### 2. Subagent orchestration
|
|
34
|
-
|
|
35
|
-
OpenClaw can spawn child agents (`subagent-spawn.ts`) -- isolated sessions with their own tools, models, and timeouts. This enables multi-step autonomous workflows where one agent delegates to another. Wall-E has no equivalent -- it's a single-threaded daemon.
|
|
36
|
-
|
|
37
|
-
### 3. Cron = proactive muscle
|
|
38
|
-
|
|
39
|
-
OpenClaw's cron system (`server-cron.ts`) spawns full agent turns on a schedule. The agent gets the same tools as a chat turn -- so a cron job can browse the web, check APIs, write files, and send messages autonomously.
|
|
40
|
-
|
|
41
|
-
Wall-E's `runDueSkills()` is similar in concept but far simpler -- it runs predefined prompt templates through Claude tool-use. The prompts are static, not adaptive.
|
|
42
|
-
|
|
43
|
-
### 4. Compaction and context management
|
|
44
|
-
|
|
45
|
-
OpenClaw has a sophisticated compaction system (`compaction.ts`) that progressively summarizes conversation history, preserves identifiers and decisions, drops old chunks first, and handles tool-result pairing. This means long conversations stay coherent.
|
|
46
|
-
|
|
47
|
-
Wall-E has no compaction -- chat context is built fresh each time from brain state. Long conversations will eventually hit token limits and simply fail.
|
|
48
|
-
|
|
49
|
-
### 5. Memory search with embeddings
|
|
50
|
-
|
|
51
|
-
OpenClaw's `memory-search.ts` uses hybrid search: vector embeddings + full-text with MMR (Maximal Marginal Relevance) and temporal decay. This means it can find semantically relevant memories, not just keyword matches.
|
|
52
|
-
|
|
53
|
-
Wall-E uses BM25 full-text search only (via SQLite FTS5). Good for exact matches, but misses semantic connections.
|
|
54
|
-
|
|
55
|
-
## Where Wall-E Has Unique Strengths
|
|
56
|
-
|
|
57
|
-
### 1. Deep identity model
|
|
58
|
-
|
|
59
|
-
Wall-E has something OpenClaw doesn't: a *persistent cognitive identity*. The knowledge triples (subject-predicate-object), relationship graph with trust levels, behavioral patterns, and 28k+ memories create a model of *who Juncao is*. The system prompt in `chat.js` includes actual Slack messages as voice samples, people interaction summaries, and topic frequencies.
|
|
60
|
-
|
|
61
|
-
OpenClaw's identity is a configurable name/emoji + optional SOUL.md persona file. It has no self-model.
|
|
62
|
-
|
|
63
|
-
### 2. The think loop is genuine cognition
|
|
64
|
-
|
|
65
|
-
Wall-E's `think.js` does something unique: it takes raw memories and *extracts structured knowledge* (SPO triples), detects contradictions between new and existing knowledge, and generates questions when it's uncertain. This is closer to how a brain actually learns -- not just retrieving, but *processing and integrating*.
|
|
66
|
-
|
|
67
|
-
OpenClaw has no equivalent batch learning process.
|
|
68
|
-
|
|
69
|
-
### 3. Autonomy tiers
|
|
70
|
-
|
|
71
|
-
Wall-E's graduated trust system (Tier 1-4 per domain, with demotion on rejection) is a thoughtful approach to autonomous action. OpenClaw has approval mechanisms, but they're per-tool policy, not a learned trust model.
|
|
72
|
-
|
|
73
|
-
## Why Wall-E Feels "Pulled Back"
|
|
74
|
-
|
|
75
|
-
### 1. Static system prompt, no dynamic reasoning chain
|
|
76
|
-
|
|
77
|
-
Wall-E builds one big system prompt with everything pre-loaded (knowledge, people, memories, skills). Claude sees a snapshot, not a live workspace. OpenClaw's agent gets tools and *discovers* what it needs on each turn.
|
|
78
|
-
|
|
79
|
-
### 2. No autonomous action loop
|
|
80
|
-
|
|
81
|
-
Wall-E's daemon loops (ingest/think/reflect) are *passive processing*. They crunch data but never take initiative. The skill planner runs due skills on a timer, but the skills are pre-defined prompt templates. There's no "look at what's happening and decide to do something" loop.
|
|
82
|
-
|
|
83
|
-
OpenClaw's cron + subagent spawning means it can proactively decide "it's Monday morning, let me check your calendar, summarize your Slack, and send you a briefing."
|
|
84
|
-
|
|
85
|
-
### 3. No multi-turn tool orchestration
|
|
86
|
-
|
|
87
|
-
Wall-E's chat does tool-use (search_memories, run_skill, mcp_call), but each chat turn is essentially independent. There's no concept of a long-running task that spans multiple turns. OpenClaw's session model preserves full conversation state and allows agent turns to build on each other.
|
|
88
|
-
|
|
89
|
-
### 4. No event-driven triggers
|
|
90
|
-
|
|
91
|
-
Wall-E runs on fixed timers. OpenClaw can react to webhooks, channel events, and cron triggers. If someone mentions you in Slack, OpenClaw can immediately spawn a response. Wall-E would only notice on its next poll cycle.
|
|
92
|
-
|
|
93
|
-
## Key Upgrades to Make Wall-E More Proactive
|
|
94
|
-
|
|
95
|
-
1. **Event-driven wake**: Instead of polling every 60s, react to channel events immediately.
|
|
96
|
-
2. **Autonomous reasoning turn**: Periodic "what should I do?" turn where Wall-E decides whether to take action.
|
|
97
|
-
3. **Persistent conversation context**: Maintain session state with compaction instead of fresh system prompt each time.
|
|
98
|
-
4. **Dynamic tool discovery**: Let Wall-E discover what it needs via tool calls instead of pre-loading everything.
|
|
99
|
-
5. **Background tasks / subagents**: Spawn long-running tasks that run independently.
|
|
100
|
-
|
|
101
|
-
## Summary
|
|
102
|
-
|
|
103
|
-
OpenClaw is a mature *infrastructure* project -- it solves the "connect to everything, route to Claude, let Claude figure it out" problem extremely well. Wall-E is a more ambitious *cognitive* project -- it's trying to actually *be* you, not just respond on your behalf. The gap is that Wall-E has the brain but not the muscles. It knows you deeply but doesn't act on that knowledge. The fix isn't to copy OpenClaw's architecture wholesale, but to give Wall-E's brain an *agency layer* -- the ability to decide, act, and iterate autonomously.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# CTM & Wall-E UX Improvement Plan
|
|
2
|
-
|
|
3
|
-
Date: 2026-04-03
|
|
4
|
-
|
|
5
|
-
## Phase 1: First Impressions (New User Experience)
|
|
6
|
-
|
|
7
|
-
### 1.1 Simplify Navigation
|
|
8
|
-
- Group nav into primary (Sessions, Prompts, WALL-E) and secondary
|
|
9
|
-
- Move Insights, Permissions, Review, Rules, Backups behind "More" dropdown
|
|
10
|
-
- Keeps nav clean for new users, power users still have access
|
|
11
|
-
|
|
12
|
-
### 1.2 Better Empty State
|
|
13
|
-
- Sessions welcome: 1 clear CTA "Start a Claude Session" with brief explanation
|
|
14
|
-
- Remove keyboard shortcuts from welcome (move to ? help overlay)
|
|
15
|
-
- Add "What can I do?" quick examples
|
|
16
|
-
|
|
17
|
-
### 1.3 Hide Queue Builder by Default
|
|
18
|
-
- Queue Builder panel takes ~25% screen width, always open
|
|
19
|
-
- Make it a toggle/drawer, collapsed by default
|
|
20
|
-
- Show "Queue" button in toolbar that opens it
|
|
21
|
-
|
|
22
|
-
### 1.4 Wall-E Onboarding
|
|
23
|
-
- First-time visitor sees intro card: "I'm Wall-E" with 3-4 example actions
|
|
24
|
-
- Disappears after first interaction or dismiss
|
|
25
|
-
|
|
26
|
-
### 1.5 Reduce Wall-E Sub-tabs
|
|
27
|
-
- Current: Chat, Tasks, Brain, Actions, Skills, Timeline, Questions, Status (8 tabs)
|
|
28
|
-
- Proposed: Chat, Tasks, Skills as primary; Brain/Actions/Timeline/Questions/Status behind "More"
|
|
29
|
-
|
|
30
|
-
## Phase 2: Information Hierarchy & Readability
|
|
31
|
-
|
|
32
|
-
### 2.1 Task Cards Cleanup
|
|
33
|
-
- Collapse output by default (click to expand)
|
|
34
|
-
- Add colored status dots (green=running, yellow=pending, red=failed, gray=paused)
|
|
35
|
-
- Reduce metadata noise — show schedule + last run, hide others until hover/expand
|
|
36
|
-
|
|
37
|
-
### 2.2 Prompts Toolbar Simplification
|
|
38
|
-
- Essential always visible: bold, italic, lists, headings
|
|
39
|
-
- Advanced in overflow: font selector, zoom, chains/templates/patterns/harvest/copilot sub-tabs
|
|
40
|
-
- Move sub-tabs into prompt editor sidebar or contextual menu
|
|
41
|
-
|
|
42
|
-
### 2.3 Insights Action-First Layout
|
|
43
|
-
- Put the action item (green arrow text) FIRST, then explanation
|
|
44
|
-
- Add category icons for workflow/prompt-effectiveness/skill-gap/cost-saving
|
|
45
|
-
|
|
46
|
-
### 2.4 Permissions Explainer
|
|
47
|
-
- Add brief card at top: "Permissions control what Claude Code can do automatically"
|
|
48
|
-
- Better risk color coding (red=high, yellow=medium, green=low)
|
|
49
|
-
|
|
50
|
-
## Phase 3: Visual Polish & Consistency
|
|
51
|
-
|
|
52
|
-
### 3.1 Unified Status Indicators
|
|
53
|
-
- Colored dots + icons for all states across tasks, sessions, integrations
|
|
54
|
-
- Running=green pulse, Pending=yellow, Failed=red, Paused=gray, Connected=green, Disconnected=red
|
|
55
|
-
|
|
56
|
-
### 3.2 Chat Improvements
|
|
57
|
-
- Date separators between conversations
|
|
58
|
-
- Softer "YOU" label (lowercase or muted)
|
|
59
|
-
- Proper markdown table rendering in chat
|
|
60
|
-
|
|
61
|
-
### 3.3 Setup Page Polish
|
|
62
|
-
- Move Data Storage into collapsible "Advanced" section
|
|
63
|
-
- Add step progress indicator
|
|
64
|
-
|
|
65
|
-
### 3.4 Consistent Card Styling
|
|
66
|
-
- Unify border radius, padding, shadow across all pages
|
|
67
|
-
|
|
68
|
-
## Phase 4: Progressive Disclosure
|
|
69
|
-
|
|
70
|
-
### 4.1 Contextual Tooltips
|
|
71
|
-
- First-time hints for Shadow Approver, Queue Builder, Harvest, Copilot
|
|
72
|
-
|
|
73
|
-
### 4.2 Keyboard Shortcuts Overlay
|
|
74
|
-
- Press ? to see all shortcuts (like GitHub)
|
|
75
|
-
|
|
76
|
-
### 4.3 Collapsible Sidebar Filters
|
|
77
|
-
- Sessions filters collapse to single row
|
|
78
|
-
|
|
79
|
-
## Implementation Notes
|
|
80
|
-
|
|
81
|
-
- Test all changes on dev instance (port 4000), never restart primary (port 3456)
|
|
82
|
-
- Use /ctm-dev skill for testing
|
|
83
|
-
- Each phase can be implemented independently
|
|
84
|
-
- Phase 1 has highest impact for new users
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Publishing Plan: CTM & Wall-E
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-04-01
|
|
4
|
-
**Author:** Wall-E Team
|
|
5
|
-
**Domain:** https://walle.sh
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
Publish CTM (Claude Task Manager) and Wall-E (personal digital twin agent) as open-source tools that others can discover, install, and run locally.
|
|
10
|
-
|
|
11
|
-
## Phases
|
|
12
|
-
|
|
13
|
-
### Phase 1: Code Cleanup & Open Source (Current)
|
|
14
|
-
|
|
15
|
-
**Goal:** Make the codebase safe and portable for public GitHub.
|
|
16
|
-
|
|
17
|
-
#### Security Cleanup
|
|
18
|
-
|
|
19
|
-
| Issue | Severity | Files | Fix |
|
|
20
|
-
|---|---|---|---|
|
|
21
|
-
| Hardcoded Slack User ID `YOUR_SLACK_USER_ID` | Critical | 5 files in `scripts/`, `slack-ingest.js` | Env var `SLACK_OWNER_USER_ID` |
|
|
22
|
-
| `owner@example.com` in SKILL.md examples | High | `email-sync/SKILL.md`, `google-calendar/SKILL.md` | Replace with `owner@example.com` |
|
|
23
|
-
| Hardcoded Slack handle in queries | High | `slack-backfill.js`, `slack-channel-history.js`, `morning-briefing/run.js` | Read from config/brain |
|
|
24
|
-
| `OWNER_NAME` owner name constant | High | `pull-slack-via-claude.js`, `slack-ingest.js` | Use `brain.getOwnerName()` |
|
|
25
|
-
| Hardcoded `.walle/data` | Medium | `server.js`, `db.js`, `brain.js`, `api-walle.js` | Default to `~/.walle/data` |
|
|
26
|
-
|
|
27
|
-
#### Repo Hygiene
|
|
28
|
-
|
|
29
|
-
- [ ] `.env.example` documenting all env vars
|
|
30
|
-
- [ ] `.gitignore` excludes `.db`, `.env`, compiled binaries, oauth tokens
|
|
31
|
-
- [ ] MIT `LICENSE` file
|
|
32
|
-
- [ ] Scrub git history or start fresh public repo (if secrets were ever committed)
|
|
33
|
-
|
|
34
|
-
#### Already Good
|
|
35
|
-
|
|
36
|
-
- API keys (ANTHROPIC_API_KEY, SLACK_TOKEN) read from env vars
|
|
37
|
-
- `.db` files not in repo
|
|
38
|
-
- `CTM_DATA_DIR` / `WALL_E_DATA_DIR` env vars already supported
|
|
39
|
-
|
|
40
|
-
### Phase 2: Distribution & Discovery
|
|
41
|
-
|
|
42
|
-
**Goal:** Make it easy for others to find and install.
|
|
43
|
-
|
|
44
|
-
| Channel | Effort | Reach | Priority |
|
|
45
|
-
|---|---|---|---|
|
|
46
|
-
| GitHub repo + README | Low | Devs who search GitHub | P0 |
|
|
47
|
-
| `npx create-walle` scaffolder | Medium | Anyone with Node.js | P1 |
|
|
48
|
-
| Claude Code skill registry | Low | Claude Code users | P1 |
|
|
49
|
-
| Homebrew tap | Medium | macOS users | P2 |
|
|
50
|
-
| walle.sh website | High | Broadest | P2 |
|
|
51
|
-
|
|
52
|
-
#### README Requirements
|
|
53
|
-
|
|
54
|
-
- One-paragraph pitch
|
|
55
|
-
- Screenshot / demo GIF
|
|
56
|
-
- "Try it in 2 minutes" quickstart
|
|
57
|
-
- Architecture diagram (CTM <-> Wall-E <-> Brain)
|
|
58
|
-
- List of bundled skills (calendar, slack, email, morning briefing)
|
|
59
|
-
- Configuration reference
|
|
60
|
-
|
|
61
|
-
#### npx Scaffolder
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
npx create-walle
|
|
65
|
-
# Clones repo, runs npm install, creates ~/.walle/data,
|
|
66
|
-
# prompts for ANTHROPIC_API_KEY, owner name, optional Slack token
|
|
67
|
-
# Starts CTM + Wall-E daemon
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Phase 3: Website — walle.sh
|
|
71
|
-
|
|
72
|
-
**Goal:** Landing page, docs, and eventually auth + cloud hosting.
|
|
73
|
-
|
|
74
|
-
| Sub-phase | What | Tech |
|
|
75
|
-
|---|---|---|
|
|
76
|
-
| 3a | Static landing page: pitch, screenshots, install guide | Cloudflare Pages (free, deploys from repo) |
|
|
77
|
-
| 3b | Interactive docs: skill catalog, API reference | Same, or Astro/Starlight |
|
|
78
|
-
| 3c | Auth + cloud DB | Fly.io (already deployed), Turso/Libsql for SQLite-compat cloud DB, Clerk or Passkeys for auth |
|
|
79
|
-
| 3d | Web-based setup wizard | Create account -> configure integrations -> download CLI |
|
|
80
|
-
|
|
81
|
-
#### Domain DNS Roadmap
|
|
82
|
-
|
|
83
|
-
| Record | Now | Later |
|
|
84
|
-
|---|---|---|
|
|
85
|
-
| `walle.sh` | `127.0.0.1` (local alias) | Cloudflare Pages (landing) |
|
|
86
|
-
| `app.walle.sh` | — | Fly.io (cloud CTM + Wall-E) |
|
|
87
|
-
| `api.walle.sh` | — | Fly.io (public API) |
|
|
88
|
-
| `docs.walle.sh` | — | Cloudflare Pages (documentation) |
|
|
89
|
-
|
|
90
|
-
### Phase 4: Multi-User & Marketplace
|
|
91
|
-
|
|
92
|
-
**Goal:** Let others contribute skills; host as SaaS.
|
|
93
|
-
|
|
94
|
-
- Per-user data isolation (separate brain DBs)
|
|
95
|
-
- Skill marketplace: browse, install, rate community skills
|
|
96
|
-
- Usage-based pricing or free tier + pro
|
|
97
|
-
- Telemetry opt-in for usage insights
|
|
98
|
-
- Rate limiting / cost management (Wall-E daemon burns API tokens)
|
|
99
|
-
|
|
100
|
-
## Branding
|
|
101
|
-
|
|
102
|
-
- **Public name:** Wall-E (memorable, the face of the project)
|
|
103
|
-
- **CTM** is the task manager component (less catchy, keep as internal name)
|
|
104
|
-
- **Domain:** walle.sh
|
|
105
|
-
- **Tagline ideas:** "Your personal digital twin" / "An AI that knows you"
|
|
106
|
-
|
|
107
|
-
## Open Questions
|
|
108
|
-
|
|
109
|
-
1. Start fresh public repo or clean history of current repo?
|
|
110
|
-
2. Pricing model for cloud version? Free tier limits?
|
|
111
|
-
3. Skill contribution guidelines and review process?
|
|
112
|
-
4. Should the Slack/Email/Calendar sync skills require specific setup guides per-platform?
|