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
@@ -1,61 +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
- page.on('console', msg => { if (msg.type() === 'error') console.log('[CONSOLE]', msg.text()); });
10
- await page.setViewport({ width: 1920, height: 1080 });
11
- await page.goto(`http://localhost:3456/prompts.html?token=${TOKEN}`, { waitUntil: 'networkidle2', timeout: 15000 });
12
- await new Promise(r => setTimeout(r, 1000));
13
-
14
- const sidebar = await page.$('#prompt-sidebar');
15
-
16
- // Screenshot 1: Before AI mode
17
- if (sidebar) await sidebar.screenshot({ path: 'tests/screenshots/ai-01-normal.png' });
18
-
19
- // Click AI button to activate
20
- await page.click('#ai-search-btn');
21
- await new Promise(r => setTimeout(r, 300));
22
- if (sidebar) await sidebar.screenshot({ path: 'tests/screenshots/ai-02-mode-active.png' });
23
-
24
- // Type search query
25
- await page.type('#prompt-search', 'email sending logic');
26
- await new Promise(r => setTimeout(r, 200));
27
- if (sidebar) await sidebar.screenshot({ path: 'tests/screenshots/ai-03-typing.png' });
28
-
29
- // Press Enter to search
30
- await page.keyboard.press('Enter');
31
- // Show spinner
32
- await new Promise(r => setTimeout(r, 500));
33
- if (sidebar) await sidebar.screenshot({ path: 'tests/screenshots/ai-04-searching.png' });
34
-
35
- // Wait for results (up to 15s)
36
- await page.waitForFunction(() => {
37
- const list = document.getElementById('prompt-list');
38
- return list && !list.querySelector('.ai-searching');
39
- }, { timeout: 20000 });
40
- await new Promise(r => setTimeout(r, 300));
41
- if (sidebar) await sidebar.screenshot({ path: 'tests/screenshots/ai-05-results.png' });
42
-
43
- // Full page screenshot
44
- await page.screenshot({ path: 'tests/screenshots/ai-06-full.png' });
45
-
46
- // Verify results have relevance scores
47
- const scores = await page.$$eval('.ai-result-score', els => els.map(e => e.textContent));
48
- const reasons = await page.$$eval('.ai-result-reason', els => els.map(e => e.textContent));
49
- console.log('Scores:', scores);
50
- console.log('Reasons:', reasons);
51
-
52
- // Click AI button again to deactivate
53
- await page.click('#ai-search-btn');
54
- await new Promise(r => setTimeout(r, 500));
55
- if (sidebar) await sidebar.screenshot({ path: 'tests/screenshots/ai-07-deactivated.png' });
56
-
57
- await browser.close();
58
- console.log('\nAll screenshots saved.');
59
- }
60
-
61
- 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
- // Click on "Email Cron v2" prompt
14
- const items = await page.$$('.prompt-item');
15
- let clicked = false;
16
- for (const item of items) {
17
- const title = await item.$eval('.prompt-title', el => el.textContent.trim());
18
- if (title.includes('Email Cron v2') || title.includes('Email Cron')) {
19
- await item.click();
20
- clicked = true;
21
- console.log('Clicked:', title);
22
- break;
23
- }
24
- }
25
- if (!clicked && items.length > 0) {
26
- const title = await items[0].$eval('.prompt-title', el => el.textContent.trim());
27
- await items[0].click();
28
- console.log('Clicked first item:', title);
29
- }
30
- await new Promise(r => setTimeout(r, 800));
31
-
32
- // Full page screenshot
33
- await page.screenshot({ path: 'tests/screenshots/editor-ux-full.png' });
34
-
35
- // Editor area only
36
- const editorArea = await page.$('#editor-area');
37
- if (editorArea) {
38
- await editorArea.screenshot({ path: 'tests/screenshots/editor-ux-editor-area.png' });
39
- }
40
-
41
- // Get editor HTML content for analysis
42
- const editorHtml = await page.$eval('#editor', el => el.innerHTML);
43
- console.log('\n--- Editor HTML (first 2000 chars) ---');
44
- console.log(editorHtml.substring(0, 2000));
45
-
46
- // Get computed styles
47
- const styles = await page.evaluate(() => {
48
- const editor = document.getElementById('editor');
49
- const wrapper = document.querySelector('.editor-wrapper');
50
- const cs = getComputedStyle(editor);
51
- const ws = getComputedStyle(wrapper);
52
- return {
53
- editor: {
54
- fontFamily: cs.fontFamily,
55
- fontSize: cs.fontSize,
56
- lineHeight: cs.lineHeight,
57
- color: cs.color,
58
- padding: cs.padding,
59
- maxWidth: cs.maxWidth,
60
- },
61
- wrapper: {
62
- padding: ws.padding,
63
- fontSize: ws.fontSize,
64
- maxWidth: ws.maxWidth,
65
- background: ws.backgroundColor,
66
- }
67
- };
68
- });
69
- console.log('\n--- Computed Styles ---');
70
- console.log('Editor:', JSON.stringify(styles.editor, null, 2));
71
- console.log('Wrapper:', JSON.stringify(styles.wrapper, null, 2));
72
-
73
- await browser.close();
74
- }
75
-
76
- runTest();
@@ -1,51 +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
- // Click on "Email Cron v2" prompt
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')) {
18
- await item.click();
19
- break;
20
- }
21
- }
22
- await new Promise(r => setTimeout(r, 800));
23
-
24
- // Crop to just the editor content area - centered content
25
- const editorEl = await page.$('#editor');
26
- if (editorEl) {
27
- const box = await editorEl.boundingBox();
28
- // Capture the editor content with some surrounding padding
29
- const clip = {
30
- x: Math.max(0, box.x - 20),
31
- y: Math.max(0, box.y - 10),
32
- width: Math.min(box.width + 40, 850),
33
- height: Math.min(box.height + 20, 700),
34
- };
35
- await page.screenshot({ path: 'tests/screenshots/editor-ux-content.png', clip });
36
- }
37
-
38
- // Full page at normal resolution
39
- await page.screenshot({ path: 'tests/screenshots/editor-ux-full2.png' });
40
-
41
- // Also scroll down to see if there's more content
42
- await page.evaluate(() => {
43
- document.querySelector('.editor-wrapper').scrollTop = 200;
44
- });
45
- await new Promise(r => setTimeout(r, 200));
46
- await page.screenshot({ path: 'tests/screenshots/editor-ux-scrolled.png' });
47
-
48
- await browser.close();
49
- }
50
-
51
- runTest();
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env node
2
- const puppeteer = require('puppeteer');
3
- const path = require('path');
4
- const fs = require('fs');
5
-
6
- const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
7
- const BASE_URL = `http://localhost:3456/?token=${TOKEN}`;
8
- const SCREENSHOT_DIR = path.join(__dirname, 'screenshots');
9
- if (!fs.existsSync(SCREENSHOT_DIR)) fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
10
-
11
- async function screenshot(page, name) {
12
- const filePath = path.join(SCREENSHOT_DIR, `${name}.png`);
13
- await page.screenshot({ path: filePath, fullPage: false });
14
- console.log(` Screenshot: ${name}.png`);
15
- }
16
-
17
- async function runTest() {
18
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
19
- const page = await browser.newPage();
20
- await page.setViewport({ width: 1920, height: 1080 });
21
- page.on('pageerror', err => console.error(` [PAGE ERROR] ${err.message}`));
22
-
23
- try {
24
- console.log('Step 1: Load page and wait for sessions...');
25
- await page.goto(BASE_URL, { waitUntil: 'networkidle2', timeout: 15000 });
26
- await new Promise(r => setTimeout(r, 2000));
27
- await screenshot(page, 'v2-01-loaded');
28
-
29
- // Check recent sessions loaded
30
- const recentItems = await page.$$('.recent-item');
31
- console.log(` Recent items: ${recentItems.length}`);
32
- if (recentItems.length === 0) throw new Error('No recent sessions');
33
- console.log(' PASS: Sessions loaded');
34
-
35
- console.log('\nStep 2: Check titles are shown by default...');
36
- const showTitlesChecked = await page.$eval('#show-titles', el => el.checked);
37
- console.log(` "Show titles" checked: ${showTitlesChecked}`);
38
- const firstDisplay = await page.$eval('.recent-item .recent-msg', el => el.textContent);
39
- console.log(` First display text: "${firstDisplay.slice(0, 60)}"`);
40
- console.log(' PASS: Titles displayed');
41
-
42
- console.log('\nStep 3: Toggle to show prompt text...');
43
- await page.click('#show-titles');
44
- await new Promise(r => setTimeout(r, 300));
45
- const firstDisplayAfter = await page.$eval('.recent-item .recent-msg', el => el.textContent);
46
- console.log(` After toggle: "${firstDisplayAfter.slice(0, 60)}"`);
47
- await screenshot(page, 'v2-02-prompt-mode');
48
- // Toggle back
49
- await page.click('#show-titles');
50
- console.log(' PASS: Toggle works');
51
-
52
- console.log('\nStep 4: Test filter chips...');
53
- // Click "Has Content"
54
- const filterChips = await page.$$('.filter-chip');
55
- console.log(` Filter chips found: ${filterChips.length}`);
56
- await filterChips[1].click(); // "Has Content"
57
- await new Promise(r => setTimeout(r, 300));
58
- const filteredItems = await page.$$('.recent-item');
59
- const hasEmpty = await page.evaluate(() => {
60
- return Array.from(document.querySelectorAll('.recent-item .recent-msg')).some(el => el.textContent.includes('[empty]'));
61
- });
62
- console.log(` After "Has Content" filter: ${filteredItems.length} items, has empty: ${hasEmpty}`);
63
- await screenshot(page, 'v2-03-has-content-filter');
64
- if (hasEmpty) throw new Error('Empty sessions still showing with Has Content filter');
65
- console.log(' PASS: Has Content filter works');
66
-
67
- // Click "Empty"
68
- await filterChips[2].click();
69
- await new Promise(r => setTimeout(r, 300));
70
- const emptyItems = await page.$$('.recent-item');
71
- console.log(` After "Empty" filter: ${emptyItems.length} items`);
72
- await screenshot(page, 'v2-04-empty-filter');
73
- console.log(' PASS: Empty filter works');
74
-
75
- // Back to All
76
- await filterChips[0].click();
77
- await new Promise(r => setTimeout(r, 300));
78
-
79
- console.log('\nStep 5: Check delete button on hover...');
80
- const firstItem = await page.$('.recent-item');
81
- await firstItem.hover();
82
- await new Promise(r => setTimeout(r, 300));
83
- const deleteBtn = await page.$('.recent-item .mini-btn.delete');
84
- console.log(` Delete button visible: ${!!deleteBtn}`);
85
- await screenshot(page, 'v2-05-hover-actions');
86
- console.log(' PASS: Delete button shows on hover');
87
-
88
- console.log('\nStep 6: Check Clean Empty button exists...');
89
- const cleanBtn = await page.evaluate(() => {
90
- const btns = Array.from(document.querySelectorAll('button'));
91
- return btns.some(b => b.textContent.includes('Clean Empty'));
92
- });
93
- console.log(` Clean Empty button found: ${cleanBtn}`);
94
- console.log(' PASS: Clean Empty button exists');
95
-
96
- console.log('\nStep 7: Check AI search toggle...');
97
- const aiBtn = await page.$('#ai-search-btn');
98
- console.log(` AI button found: ${!!aiBtn}`);
99
- await aiBtn.click();
100
- await new Promise(r => setTimeout(r, 200));
101
- const placeholder = await page.$eval('#recent-search', el => el.placeholder);
102
- console.log(` Search placeholder after AI toggle: "${placeholder}"`);
103
- const aiBtnActive = await page.$eval('#ai-search-btn', el => el.classList.contains('active'));
104
- console.log(` AI button active: ${aiBtnActive}`);
105
- await screenshot(page, 'v2-06-ai-search-mode');
106
- // Toggle back
107
- await aiBtn.click();
108
- console.log(' PASS: AI search toggle works');
109
-
110
- console.log('\nStep 8: Check message count in meta...');
111
- const hasMsgCount = await page.evaluate(() => {
112
- return Array.from(document.querySelectorAll('.recent-meta')).some(el => el.textContent.includes('msgs'));
113
- });
114
- console.log(` Message counts shown: ${hasMsgCount}`);
115
- console.log(' PASS: Message counts displayed');
116
-
117
- console.log('\n=== ALL TESTS PASSED ===');
118
- } catch (error) {
119
- console.error(`\n FAIL: ${error.message}`);
120
- await screenshot(page, 'v2-error');
121
- process.exitCode = 1;
122
- } finally {
123
- await browser.close();
124
- }
125
- }
126
-
127
- runTest();
@@ -1,78 +0,0 @@
1
- #!/usr/bin/env node
2
- const puppeteer = require('puppeteer');
3
- const path = require('path');
4
- const fs = require('fs');
5
-
6
- const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
7
- const BASE_URL = `http://localhost:3456/?token=${TOKEN}`;
8
- const SCREENSHOT_DIR = path.join(__dirname, 'screenshots');
9
- if (!fs.existsSync(SCREENSHOT_DIR)) fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
10
-
11
- async function screenshot(page, name) {
12
- const fp = path.join(SCREENSHOT_DIR, `${name}.png`);
13
- await page.screenshot({ path: fp });
14
- console.log(` Screenshot: ${name}.png`);
15
- }
16
-
17
- async function runTest() {
18
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
19
- const page = await browser.newPage();
20
- await page.setViewport({ width: 1920, height: 1080 });
21
- page.on('pageerror', err => console.error(` [PAGE ERROR] ${err.message}`));
22
-
23
- try {
24
- console.log('Step 1: Load page...');
25
- await page.goto(BASE_URL, { waitUntil: 'networkidle2', timeout: 15000 });
26
- await new Promise(r => setTimeout(r, 1000));
27
-
28
- console.log('\nStep 2: Open Insights panel (should load cached)...');
29
- const insightsBtn = await page.evaluateHandle(() =>
30
- Array.from(document.querySelectorAll('.btn')).find(b => b.textContent === 'Insights')
31
- );
32
- await insightsBtn.click();
33
- await new Promise(r => setTimeout(r, 1500));
34
- await screenshot(page, 'insights-cached-01');
35
-
36
- // Verify cached results render
37
- const groupCards = await page.$$('.group-card');
38
- const skillCards = await page.$$('.skill-card');
39
- const insightBullets = await page.$$('.insight-bullet');
40
- const meta = await page.$eval('#insights-meta', el => el.textContent);
41
- const btnText = await page.$eval('#analyze-btn', el => el.textContent);
42
-
43
- console.log(` Groups: ${groupCards.length}`);
44
- console.log(` Skills: ${skillCards.length}`);
45
- console.log(` Insights: ${insightBullets.length}`);
46
- console.log(` Meta: "${meta}"`);
47
- console.log(` Button: "${btnText}"`);
48
-
49
- if (groupCards.length > 0) {
50
- const firstGroupTitle = await page.$eval('.group-card .card-title', el => el.textContent);
51
- console.log(` First group: "${firstGroupTitle}"`);
52
- }
53
- if (skillCards.length > 0) {
54
- const firstSkillTitle = await page.$eval('.skill-card .card-title', el => el.textContent);
55
- console.log(` First skill: "${firstSkillTitle}"`);
56
- const createBtns = await page.$$('.create-skill-btn');
57
- console.log(` Create Skill buttons: ${createBtns.length}`);
58
- }
59
-
60
- await screenshot(page, 'insights-cached-02-scrolled');
61
-
62
- if (groupCards.length > 0 && skillCards.length > 0) {
63
- console.log('\n PASS: Cached analysis rendered immediately');
64
- } else {
65
- console.log('\n WARNING: No cached data rendered');
66
- }
67
-
68
- console.log('\n=== ALL TESTS PASSED ===');
69
- } catch (error) {
70
- console.error(`\n FAIL: ${error.message}`);
71
- await screenshot(page, 'insights-cached-error');
72
- process.exitCode = 1;
73
- } finally {
74
- await browser.close();
75
- }
76
- }
77
-
78
- runTest();
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env node
2
- const puppeteer = require('puppeteer');
3
- const path = require('path');
4
- const fs = require('fs');
5
-
6
- const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
7
- const BASE_URL = `http://localhost:3456/?token=${TOKEN}`;
8
- const SCREENSHOT_DIR = path.join(__dirname, 'screenshots');
9
- if (!fs.existsSync(SCREENSHOT_DIR)) fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
10
-
11
- async function screenshot(page, name) {
12
- const fp = path.join(SCREENSHOT_DIR, `${name}.png`);
13
- await page.screenshot({ path: fp });
14
- console.log(` Screenshot: ${name}.png`);
15
- }
16
-
17
- async function runTest() {
18
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
19
- const page = await browser.newPage();
20
- await page.setViewport({ width: 1920, height: 1080 });
21
- page.on('pageerror', err => console.error(` [PAGE ERROR] ${err.message}`));
22
-
23
- try {
24
- console.log('Step 1: Load page...');
25
- await page.goto(BASE_URL, { waitUntil: 'networkidle2', timeout: 15000 });
26
- await new Promise(r => setTimeout(r, 1000));
27
-
28
- console.log('\nStep 2: Click Insights button in top bar...');
29
- const insightsBtn = await page.evaluateHandle(() => {
30
- return Array.from(document.querySelectorAll('.btn')).find(b => b.textContent === 'Insights');
31
- });
32
- if (!insightsBtn) throw new Error('Insights button not found');
33
- await insightsBtn.click();
34
- await new Promise(r => setTimeout(r, 500));
35
- await screenshot(page, 'insights-01-panel');
36
-
37
- const panelActive = await page.$eval('#insights-panel', el => el.classList.contains('active'));
38
- console.log(` Insights panel active: ${panelActive}`);
39
- if (!panelActive) throw new Error('Insights panel not active');
40
-
41
- // Check tab
42
- const tabTexts = await page.$$eval('#tabbar .tab span:first-child', els => els.map(e => e.textContent));
43
- console.log(` Tab labels: ${tabTexts.join(', ')}`);
44
- if (!tabTexts.includes('Insights')) throw new Error('Insights tab not created');
45
- console.log(' PASS: Insights panel and tab created');
46
-
47
- // Check Analyze button exists
48
- const analyzeBtn = await page.$('#analyze-btn');
49
- console.log(` Analyze button found: ${!!analyzeBtn}`);
50
- console.log(' PASS: Analyze button present');
51
-
52
- // Check initial state
53
- const contentText = await page.$eval('#insights-content', el => el.textContent);
54
- console.log(` Initial content: "${contentText.trim().slice(0, 80)}..."`);
55
-
56
- console.log('\nStep 3: Run analysis (this will call Claude - may take time)...');
57
- await analyzeBtn.click();
58
- const btnText = await page.$eval('#analyze-btn', el => el.textContent);
59
- console.log(` Button text after click: "${btnText}"`);
60
- if (btnText !== 'Analyzing...') throw new Error('Button did not change to Analyzing...');
61
- console.log(' PASS: Analysis started');
62
-
63
- // Wait for analysis (up to 120s)
64
- console.log(' Waiting for analysis to complete (up to 120s)...');
65
- let completed = false;
66
- for (let i = 0; i < 60; i++) {
67
- await new Promise(r => setTimeout(r, 2000));
68
- const text = await page.$eval('#analyze-btn', el => el.textContent);
69
- const hasGroups = await page.$('.group-card');
70
- const hasProgress = await page.$('.progress-log');
71
- if (hasProgress) {
72
- const progressText = await page.$eval('.progress-log', el => el.textContent);
73
- if (progressText.includes('complete')) {
74
- console.log(' Analysis complete signal received');
75
- }
76
- }
77
- if (text === 'Re-analyze') {
78
- completed = true;
79
- console.log(` Analysis finished at ${i * 2}s`);
80
- break;
81
- }
82
- }
83
-
84
- await screenshot(page, 'insights-02-results');
85
-
86
- if (!completed) {
87
- console.log(' WARNING: Analysis timed out, checking partial results...');
88
- }
89
-
90
- console.log('\nStep 4: Verify results...');
91
- const groupCards = await page.$$('.group-card');
92
- console.log(` Group cards: ${groupCards.length}`);
93
-
94
- const skillCards = await page.$$('.skill-card');
95
- console.log(` Skill suggestion cards: ${skillCards.length}`);
96
-
97
- const insightBullets = await page.$$('.insight-bullet');
98
- console.log(` Insight bullets: ${insightBullets.length}`);
99
-
100
- const createBtns = await page.$$('.create-skill-btn');
101
- console.log(` Create Skill buttons: ${createBtns.length}`);
102
-
103
- const meta = await page.$eval('#insights-meta', el => el.textContent);
104
- console.log(` Meta: "${meta}"`);
105
-
106
- if (groupCards.length > 0 || skillCards.length > 0) {
107
- console.log('\n PASS: Analysis produced results');
108
- } else {
109
- console.log('\n WARNING: No results rendered (may be a timeout or API issue)');
110
- }
111
-
112
- await screenshot(page, 'insights-03-final');
113
-
114
- console.log('\n=== TESTS COMPLETED ===');
115
- } catch (error) {
116
- console.error(`\n FAIL: ${error.message}`);
117
- await screenshot(page, 'insights-error');
118
- process.exitCode = 1;
119
- } finally {
120
- await browser.close();
121
- }
122
- }
123
-
124
- runTest();
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env node
2
- const puppeteer = require('puppeteer');
3
- const TOKEN = process.env.CTM_TEST_TOKEN || 'test-token-placeholder';
4
- const BASE = `http://localhost:3456/?token=${TOKEN}`;
5
-
6
- async function runTest() {
7
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
8
- const page = await browser.newPage();
9
- page.on('pageerror', err => console.error('[PAGE ERROR]', err.message));
10
- await page.setViewport({ width: 1920, height: 1080 });
11
-
12
- await page.goto(BASE, { waitUntil: 'networkidle2', timeout: 15000 });
13
- await new Promise(r => setTimeout(r, 2000));
14
-
15
- // Open permissions panel
16
- const btns = await page.$$('.topbar button, .topbar .btn');
17
- for (const btn of btns) {
18
- const text = await page.evaluate(el => el.textContent, btn);
19
- if (text.includes('Permissions')) { await btn.click(); break; }
20
- }
21
- await new Promise(r => setTimeout(r, 1000));
22
-
23
- // Click Scan Sessions
24
- await page.evaluate(() => {
25
- const btns = document.querySelectorAll('button');
26
- for (const b of btns) { if (b.textContent.includes('Scan')) { b.click(); break; } }
27
- });
28
- await new Promise(r => setTimeout(r, 3000));
29
-
30
- // Screenshot 1: Full panel with risk badges
31
- await page.screenshot({ path: 'tests/screenshots/perm-v2-01-scanned.png', fullPage: false });
32
- console.log('1. Scanned - check risk badges');
33
-
34
- // Check risk badges exist
35
- const riskBadges = await page.evaluate(() => {
36
- const badges = document.querySelectorAll('.perm-risk');
37
- const counts = { high: 0, medium: 0, low: 0 };
38
- badges.forEach(b => { if (counts[b.textContent] !== undefined) counts[b.textContent]++; });
39
- return counts;
40
- });
41
- console.log(' Risk badges:', JSON.stringify(riskBadges));
42
-
43
- // Check search bar visible
44
- const searchVisible = await page.evaluate(() => {
45
- return document.getElementById('perm-search-bar').style.display !== 'none';
46
- });
47
- console.log('2. Search bar visible:', searchVisible);
48
-
49
- // Check bulk bar visible
50
- const bulkVisible = await page.evaluate(() => {
51
- return document.getElementById('perm-bulk-bar').style.display !== 'none';
52
- });
53
- console.log(' Bulk bar visible:', bulkVisible);
54
-
55
- // Test search filter
56
- await page.type('#perm-search', 'bash');
57
- await new Promise(r => setTimeout(r, 300));
58
- const visibleAfterSearch = await page.evaluate(() => {
59
- const groups = document.querySelectorAll('.perm-tool-group:not(.hidden-by-filter)');
60
- return groups.length;
61
- });
62
- const hiddenAfterSearch = await page.evaluate(() => {
63
- return document.querySelectorAll('.perm-tool-group.hidden-by-filter').length;
64
- });
65
- console.log('3. Search "bash" - visible:', visibleAfterSearch, 'hidden:', hiddenAfterSearch);
66
-
67
- await page.screenshot({ path: 'tests/screenshots/perm-v2-02-search.png', fullPage: false });
68
-
69
- // Clear search
70
- await page.evaluate(() => { document.getElementById('perm-search').value = ''; });
71
- await page.evaluate(() => filterPermTools());
72
-
73
- // Test risk filter - high only
74
- await page.select('#perm-risk-filter', 'high');
75
- await new Promise(r => setTimeout(r, 300));
76
- const highOnly = await page.evaluate(() => {
77
- return document.querySelectorAll('.perm-tool-group:not(.hidden-by-filter)').length;
78
- });
79
- console.log('4. Risk filter "high" - visible:', highOnly);
80
-
81
- await page.screenshot({ path: 'tests/screenshots/perm-v2-03-risk-filter.png', fullPage: false });
82
-
83
- // Reset risk filter
84
- await page.select('#perm-risk-filter', '');
85
- await page.evaluate(() => filterPermTools());
86
-
87
- // Test status filter
88
- await page.select('#perm-status-filter', 'unapproved');
89
- await new Promise(r => setTimeout(r, 300));
90
- const unapprovedCount = await page.evaluate(() => {
91
- return document.querySelectorAll('.perm-tool-group:not(.hidden-by-filter)').length;
92
- });
93
- console.log('5. Status filter "unapproved" - visible:', unapprovedCount);
94
-
95
- // Reset
96
- await page.select('#perm-status-filter', '');
97
- await page.evaluate(() => filterPermTools());
98
-
99
- // Check bulk summary text
100
- const summaryText = await page.evaluate(() => {
101
- return document.getElementById('perm-bulk-summary')?.textContent || '';
102
- });
103
- console.log('6. Bulk summary:', summaryText);
104
-
105
- // Test Select All with low risk filter (safe for testing)
106
- await page.select('#perm-risk-filter', 'low');
107
- await page.evaluate(() => filterPermTools());
108
- await new Promise(r => setTimeout(r, 200));
109
-
110
- const lowVisibleCount = await page.evaluate(() => {
111
- return document.querySelectorAll('.perm-tool-group:not(.hidden-by-filter)').length;
112
- });
113
- console.log('7. Low risk tools visible:', lowVisibleCount);
114
-
115
- await page.screenshot({ path: 'tests/screenshots/perm-v2-04-low-risk.png', fullPage: false });
116
-
117
- // Test Select All button exists
118
- const selectAllExists = await page.evaluate(() => {
119
- return !!document.querySelector('.perm-bulk-bar button');
120
- });
121
- console.log('8. Select All button exists:', selectAllExists);
122
-
123
- await browser.close();
124
- console.log('\nPermission Manager v2 tests complete.');
125
- }
126
-
127
- runTest();