@caddis/cli 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -1
  3. package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
  4. package/bundles/antigravity-plugin/agents/preflight.md +53 -0
  5. package/bundles/antigravity-plugin/guard_agy.py +338 -0
  6. package/bundles/antigravity-plugin/hooks.json +36 -0
  7. package/bundles/antigravity-plugin/mcp_config.json +8 -0
  8. package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
  9. package/bundles/antigravity-plugin/plugin.json +5 -0
  10. package/bundles/antigravity-plugin/session_end_agy.py +57 -0
  11. package/bundles/antigravity-plugin/skills/_registry.md +115 -0
  12. package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
  13. package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
  14. package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
  15. package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
  16. package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
  17. package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
  18. package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
  19. package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
  20. package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
  21. package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
  22. package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
  23. package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
  24. package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
  25. package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
  26. package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
  27. package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
  28. package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
  29. package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
  30. package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
  31. package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
  32. package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
  33. package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
  34. package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
  35. package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
  36. package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
  37. package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
  38. package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
  39. package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
  40. package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
  41. package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
  42. package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
  43. package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
  44. package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
  45. package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
  46. package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
  47. package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
  48. package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
  49. package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
  50. package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
  51. package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
  52. package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
  53. package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
  54. package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
  55. package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
  56. package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
  57. package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
  58. package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
  59. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
  60. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
  61. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
  62. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
  63. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
  64. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
  65. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
  66. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
  67. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
  68. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
  69. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
  70. package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
  71. package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
  72. package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
  73. package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
  74. package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
  75. package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
  76. package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
  77. package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
  78. package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
  79. package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
  80. package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
  81. package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
  82. package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
  83. package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
  84. package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
  85. package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
  86. package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
  87. package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
  88. package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
  89. package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
  90. package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
  91. package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
  92. package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
  93. package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
  94. package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
  95. package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
  96. package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
  97. package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
  98. package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
  99. package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
  100. package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
  101. package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
  102. package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
  103. package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
  104. package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
  105. package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
  106. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
  107. package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
  108. package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
  109. package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
  110. package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
  111. package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
  112. package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
  113. package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  114. package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  115. package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
  116. package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
  117. package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  118. package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
  119. package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
  120. package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
  121. package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
  122. package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
  123. package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
  124. package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
  125. package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
  126. package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
  127. package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
  128. package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
  129. package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
  130. package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
  131. package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
  132. package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
  133. package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
  134. package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
  135. package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
  136. package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
  137. package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
  138. package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
  139. package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
  140. package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
  141. package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
  142. package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
  143. package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
  144. package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
  145. package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
  146. package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
  147. package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
  148. package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
  149. package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
  150. package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
  151. package/bundles/manifest.json +6 -0
  152. package/dist/cli.js +5363 -0
  153. package/package.json +61 -4
@@ -0,0 +1,441 @@
1
+ // playwright-helpers.js
2
+ // Reusable utility functions for Playwright automation
3
+
4
+ const { chromium, firefox, webkit } = require('playwright');
5
+
6
+ /**
7
+ * Parse extra HTTP headers from environment variables.
8
+ * Supports two formats:
9
+ * - PW_HEADER_NAME + PW_HEADER_VALUE: Single header (simple, common case)
10
+ * - PW_EXTRA_HEADERS: JSON object for multiple headers (advanced)
11
+ * Single header format takes precedence if both are set.
12
+ * @returns {Object|null} Headers object or null if none configured
13
+ */
14
+ function getExtraHeadersFromEnv() {
15
+ const headerName = process.env.PW_HEADER_NAME;
16
+ const headerValue = process.env.PW_HEADER_VALUE;
17
+
18
+ if (headerName && headerValue) {
19
+ return { [headerName]: headerValue };
20
+ }
21
+
22
+ const headersJson = process.env.PW_EXTRA_HEADERS;
23
+ if (headersJson) {
24
+ try {
25
+ const parsed = JSON.parse(headersJson);
26
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
27
+ return parsed;
28
+ }
29
+ console.warn('PW_EXTRA_HEADERS must be a JSON object, ignoring...');
30
+ } catch (e) {
31
+ console.warn('Failed to parse PW_EXTRA_HEADERS as JSON:', e.message);
32
+ }
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Launch browser with standard configuration
40
+ * @param {string} browserType - 'chromium', 'firefox', or 'webkit'
41
+ * @param {Object} options - Additional launch options
42
+ */
43
+ async function launchBrowser(browserType = 'chromium', options = {}) {
44
+ const defaultOptions = {
45
+ headless: process.env.HEADLESS !== 'false',
46
+ slowMo: process.env.SLOW_MO ? parseInt(process.env.SLOW_MO) : 0,
47
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
48
+ };
49
+
50
+ const browsers = { chromium, firefox, webkit };
51
+ const browser = browsers[browserType];
52
+
53
+ if (!browser) {
54
+ throw new Error(`Invalid browser type: ${browserType}`);
55
+ }
56
+
57
+ return await browser.launch({ ...defaultOptions, ...options });
58
+ }
59
+
60
+ /**
61
+ * Create a new page with viewport and user agent
62
+ * @param {Object} context - Browser context
63
+ * @param {Object} options - Page options
64
+ */
65
+ async function createPage(context, options = {}) {
66
+ const page = await context.newPage();
67
+
68
+ if (options.viewport) {
69
+ await page.setViewportSize(options.viewport);
70
+ }
71
+
72
+ if (options.userAgent) {
73
+ await page.setExtraHTTPHeaders({
74
+ 'User-Agent': options.userAgent
75
+ });
76
+ }
77
+
78
+ // Set default timeout
79
+ page.setDefaultTimeout(options.timeout || 30000);
80
+
81
+ return page;
82
+ }
83
+
84
+ /**
85
+ * Smart wait for page to be ready
86
+ * @param {Object} page - Playwright page
87
+ * @param {Object} options - Wait options
88
+ */
89
+ async function waitForPageReady(page, options = {}) {
90
+ const waitOptions = {
91
+ waitUntil: options.waitUntil || 'networkidle',
92
+ timeout: options.timeout || 30000
93
+ };
94
+
95
+ try {
96
+ await page.waitForLoadState(waitOptions.waitUntil, {
97
+ timeout: waitOptions.timeout
98
+ });
99
+ } catch (e) {
100
+ console.warn('Page load timeout, continuing...');
101
+ }
102
+
103
+ // Additional wait for dynamic content if selector provided
104
+ if (options.waitForSelector) {
105
+ await page.waitForSelector(options.waitForSelector, {
106
+ timeout: options.timeout
107
+ });
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Safe click with retry logic
113
+ * @param {Object} page - Playwright page
114
+ * @param {string} selector - Element selector
115
+ * @param {Object} options - Click options
116
+ */
117
+ async function safeClick(page, selector, options = {}) {
118
+ const maxRetries = options.retries || 3;
119
+ const retryDelay = options.retryDelay || 1000;
120
+
121
+ for (let i = 0; i < maxRetries; i++) {
122
+ try {
123
+ await page.waitForSelector(selector, {
124
+ state: 'visible',
125
+ timeout: options.timeout || 5000
126
+ });
127
+ await page.click(selector, {
128
+ force: options.force || false,
129
+ timeout: options.timeout || 5000
130
+ });
131
+ return true;
132
+ } catch (e) {
133
+ if (i === maxRetries - 1) {
134
+ console.error(`Failed to click ${selector} after ${maxRetries} attempts`);
135
+ throw e;
136
+ }
137
+ console.log(`Retry ${i + 1}/${maxRetries} for clicking ${selector}`);
138
+ await page.waitForTimeout(retryDelay);
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Safe text input with clear before type
145
+ * @param {Object} page - Playwright page
146
+ * @param {string} selector - Input selector
147
+ * @param {string} text - Text to type
148
+ * @param {Object} options - Type options
149
+ */
150
+ async function safeType(page, selector, text, options = {}) {
151
+ await page.waitForSelector(selector, {
152
+ state: 'visible',
153
+ timeout: options.timeout || 10000
154
+ });
155
+
156
+ if (options.clear !== false) {
157
+ await page.fill(selector, '');
158
+ }
159
+
160
+ if (options.slow) {
161
+ await page.type(selector, text, { delay: options.delay || 100 });
162
+ } else {
163
+ await page.fill(selector, text);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Extract text from multiple elements
169
+ * @param {Object} page - Playwright page
170
+ * @param {string} selector - Elements selector
171
+ */
172
+ async function extractTexts(page, selector) {
173
+ await page.waitForSelector(selector, { timeout: 10000 });
174
+ return await page.$$eval(selector, elements =>
175
+ elements.map(el => el.textContent?.trim()).filter(Boolean)
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Take screenshot with timestamp
181
+ * @param {Object} page - Playwright page
182
+ * @param {string} name - Screenshot name
183
+ * @param {Object} options - Screenshot options
184
+ */
185
+ async function takeScreenshot(page, name, options = {}) {
186
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
187
+ const filename = `${name}-${timestamp}.png`;
188
+
189
+ await page.screenshot({
190
+ path: filename,
191
+ fullPage: options.fullPage !== false,
192
+ ...options
193
+ });
194
+
195
+ console.log(`Screenshot saved: ${filename}`);
196
+ return filename;
197
+ }
198
+
199
+ /**
200
+ * Handle authentication
201
+ * @param {Object} page - Playwright page
202
+ * @param {Object} credentials - Username and password
203
+ * @param {Object} selectors - Login form selectors
204
+ */
205
+ async function authenticate(page, credentials, selectors = {}) {
206
+ const defaultSelectors = {
207
+ username: 'input[name="username"], input[name="email"], #username, #email',
208
+ password: 'input[name="password"], #password',
209
+ submit: 'button[type="submit"], input[type="submit"], button:has-text("Login"), button:has-text("Sign in")'
210
+ };
211
+
212
+ const finalSelectors = { ...defaultSelectors, ...selectors };
213
+
214
+ await safeType(page, finalSelectors.username, credentials.username);
215
+ await safeType(page, finalSelectors.password, credentials.password);
216
+ await safeClick(page, finalSelectors.submit);
217
+
218
+ // Wait for navigation or success indicator
219
+ await Promise.race([
220
+ page.waitForNavigation({ waitUntil: 'networkidle' }),
221
+ page.waitForSelector(selectors.successIndicator || '.dashboard, .user-menu, .logout', { timeout: 10000 })
222
+ ]).catch(() => {
223
+ console.log('Login might have completed without navigation');
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Scroll page
229
+ * @param {Object} page - Playwright page
230
+ * @param {string} direction - 'down', 'up', 'top', 'bottom'
231
+ * @param {number} distance - Pixels to scroll (for up/down)
232
+ */
233
+ async function scrollPage(page, direction = 'down', distance = 500) {
234
+ switch (direction) {
235
+ case 'down':
236
+ await page.evaluate(d => window.scrollBy(0, d), distance);
237
+ break;
238
+ case 'up':
239
+ await page.evaluate(d => window.scrollBy(0, -d), distance);
240
+ break;
241
+ case 'top':
242
+ await page.evaluate(() => window.scrollTo(0, 0));
243
+ break;
244
+ case 'bottom':
245
+ await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
246
+ break;
247
+ }
248
+ await page.waitForTimeout(500); // Wait for scroll animation
249
+ }
250
+
251
+ /**
252
+ * Extract table data
253
+ * @param {Object} page - Playwright page
254
+ * @param {string} tableSelector - Table selector
255
+ */
256
+ async function extractTableData(page, tableSelector) {
257
+ await page.waitForSelector(tableSelector);
258
+
259
+ return await page.evaluate((selector) => {
260
+ const table = document.querySelector(selector);
261
+ if (!table) return null;
262
+
263
+ const headers = Array.from(table.querySelectorAll('thead th')).map(th =>
264
+ th.textContent?.trim()
265
+ );
266
+
267
+ const rows = Array.from(table.querySelectorAll('tbody tr')).map(tr => {
268
+ const cells = Array.from(tr.querySelectorAll('td'));
269
+ if (headers.length > 0) {
270
+ return cells.reduce((obj, cell, index) => {
271
+ obj[headers[index] || `column_${index}`] = cell.textContent?.trim();
272
+ return obj;
273
+ }, {});
274
+ } else {
275
+ return cells.map(cell => cell.textContent?.trim());
276
+ }
277
+ });
278
+
279
+ return { headers, rows };
280
+ }, tableSelector);
281
+ }
282
+
283
+ /**
284
+ * Wait for and dismiss cookie banners
285
+ * @param {Object} page - Playwright page
286
+ * @param {number} timeout - Max time to wait
287
+ */
288
+ async function handleCookieBanner(page, timeout = 3000) {
289
+ const commonSelectors = [
290
+ 'button:has-text("Accept")',
291
+ 'button:has-text("Accept all")',
292
+ 'button:has-text("OK")',
293
+ 'button:has-text("Got it")',
294
+ 'button:has-text("I agree")',
295
+ '.cookie-accept',
296
+ '#cookie-accept',
297
+ '[data-testid="cookie-accept"]'
298
+ ];
299
+
300
+ for (const selector of commonSelectors) {
301
+ try {
302
+ const element = await page.waitForSelector(selector, {
303
+ timeout: timeout / commonSelectors.length,
304
+ state: 'visible'
305
+ });
306
+ if (element) {
307
+ await element.click();
308
+ console.log('Cookie banner dismissed');
309
+ return true;
310
+ }
311
+ } catch (e) {
312
+ // Continue to next selector
313
+ }
314
+ }
315
+
316
+ return false;
317
+ }
318
+
319
+ /**
320
+ * Retry a function with exponential backoff
321
+ * @param {Function} fn - Function to retry
322
+ * @param {number} maxRetries - Maximum retry attempts
323
+ * @param {number} initialDelay - Initial delay in ms
324
+ */
325
+ async function retryWithBackoff(fn, maxRetries = 3, initialDelay = 1000) {
326
+ let lastError;
327
+
328
+ for (let i = 0; i < maxRetries; i++) {
329
+ try {
330
+ return await fn();
331
+ } catch (error) {
332
+ lastError = error;
333
+ const delay = initialDelay * Math.pow(2, i);
334
+ console.log(`Attempt ${i + 1} failed, retrying in ${delay}ms...`);
335
+ await new Promise(resolve => setTimeout(resolve, delay));
336
+ }
337
+ }
338
+
339
+ throw lastError;
340
+ }
341
+
342
+ /**
343
+ * Create browser context with common settings
344
+ * @param {Object} browser - Browser instance
345
+ * @param {Object} options - Context options
346
+ */
347
+ async function createContext(browser, options = {}) {
348
+ const envHeaders = getExtraHeadersFromEnv();
349
+
350
+ // Merge environment headers with any passed in options
351
+ const mergedHeaders = {
352
+ ...envHeaders,
353
+ ...options.extraHTTPHeaders
354
+ };
355
+
356
+ const defaultOptions = {
357
+ viewport: { width: 1280, height: 720 },
358
+ userAgent: options.mobile
359
+ ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1'
360
+ : undefined,
361
+ permissions: options.permissions || [],
362
+ geolocation: options.geolocation,
363
+ locale: options.locale || 'en-US',
364
+ timezoneId: options.timezoneId || 'America/New_York',
365
+ // Only include extraHTTPHeaders if we have any
366
+ ...(Object.keys(mergedHeaders).length > 0 && { extraHTTPHeaders: mergedHeaders })
367
+ };
368
+
369
+ return await browser.newContext({ ...defaultOptions, ...options });
370
+ }
371
+
372
+ /**
373
+ * Detect running dev servers on common ports
374
+ * @param {Array<number>} customPorts - Additional ports to check
375
+ * @returns {Promise<Array>} Array of detected server URLs
376
+ */
377
+ async function detectDevServers(customPorts = []) {
378
+ const http = require('http');
379
+
380
+ // Common dev server ports
381
+ const commonPorts = [3000, 3001, 3002, 5173, 8080, 8000, 4200, 5000, 9000, 1234];
382
+ const allPorts = [...new Set([...commonPorts, ...customPorts])];
383
+
384
+ const detectedServers = [];
385
+
386
+ console.log('🔍 Checking for running dev servers...');
387
+
388
+ for (const port of allPorts) {
389
+ try {
390
+ await new Promise((resolve, reject) => {
391
+ const req = http.request({
392
+ hostname: 'localhost',
393
+ port: port,
394
+ path: '/',
395
+ method: 'HEAD',
396
+ timeout: 500
397
+ }, (res) => {
398
+ if (res.statusCode < 500) {
399
+ detectedServers.push(`http://localhost:${port}`);
400
+ console.log(` ✅ Found server on port ${port}`);
401
+ }
402
+ resolve();
403
+ });
404
+
405
+ req.on('error', () => resolve());
406
+ req.on('timeout', () => {
407
+ req.destroy();
408
+ resolve();
409
+ });
410
+
411
+ req.end();
412
+ });
413
+ } catch (e) {
414
+ // Port not available, continue
415
+ }
416
+ }
417
+
418
+ if (detectedServers.length === 0) {
419
+ console.log(' ❌ No dev servers detected');
420
+ }
421
+
422
+ return detectedServers;
423
+ }
424
+
425
+ module.exports = {
426
+ launchBrowser,
427
+ createPage,
428
+ waitForPageReady,
429
+ safeClick,
430
+ safeType,
431
+ extractTexts,
432
+ takeScreenshot,
433
+ authenticate,
434
+ scrollPage,
435
+ extractTableData,
436
+ handleCookieBanner,
437
+ retryWithBackoff,
438
+ createContext,
439
+ detectDevServers,
440
+ getExtraHeadersFromEnv
441
+ };
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "playwright-skill",
3
+ "version": "4.1.0",
4
+ "description": "General-purpose browser automation with Playwright for Claude Code with auto-detection and smart test management",
5
+ "author": "lackeyjb",
6
+ "main": "run.js",
7
+ "scripts": {
8
+ "setup": "npm install && npx playwright install chromium",
9
+ "install-all-browsers": "npx playwright install chromium firefox webkit"
10
+ },
11
+ "keywords": [
12
+ "playwright",
13
+ "automation",
14
+ "browser-testing",
15
+ "web-automation",
16
+ "claude-skill",
17
+ "general-purpose"
18
+ ],
19
+ "dependencies": {
20
+ "playwright": "^1.57.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=14.0.0"
24
+ },
25
+ "license": "MIT"
26
+ }
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Universal Playwright Executor for Claude Code
4
+ *
5
+ * Executes Playwright automation code from:
6
+ * - File path: node run.js script.js
7
+ * - Inline code: node run.js 'await page.goto("...")'
8
+ * - Stdin: cat script.js | node run.js
9
+ *
10
+ * Ensures proper module resolution by running from skill directory.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { execSync } = require('child_process');
16
+
17
+ // Change to skill directory for proper module resolution
18
+ process.chdir(__dirname);
19
+
20
+ /**
21
+ * Check if Playwright is installed
22
+ */
23
+ function checkPlaywrightInstalled() {
24
+ try {
25
+ require.resolve('playwright');
26
+ return true;
27
+ } catch (e) {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Install Playwright if missing
34
+ */
35
+ function installPlaywright() {
36
+ console.log('📦 Playwright not found. Installing...');
37
+ try {
38
+ execSync('npm install', { stdio: 'inherit', cwd: __dirname });
39
+ execSync('npx playwright install chromium', { stdio: 'inherit', cwd: __dirname });
40
+ console.log('✅ Playwright installed successfully');
41
+ return true;
42
+ } catch (e) {
43
+ console.error('❌ Failed to install Playwright:', e.message);
44
+ console.error('Please run manually: cd', __dirname, '&& npm run setup');
45
+ return false;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Get code to execute from various sources
51
+ */
52
+ function getCodeToExecute() {
53
+ const args = process.argv.slice(2);
54
+
55
+ // Case 1: File path provided
56
+ if (args.length > 0 && fs.existsSync(args[0])) {
57
+ const filePath = path.resolve(args[0]);
58
+ console.log(`📄 Executing file: ${filePath}`);
59
+ return fs.readFileSync(filePath, 'utf8');
60
+ }
61
+
62
+ // Case 2: Inline code provided as argument
63
+ if (args.length > 0) {
64
+ console.log('⚡ Executing inline code');
65
+ return args.join(' ');
66
+ }
67
+
68
+ // Case 3: Code from stdin
69
+ if (!process.stdin.isTTY) {
70
+ console.log('📥 Reading from stdin');
71
+ return fs.readFileSync(0, 'utf8');
72
+ }
73
+
74
+ // No input
75
+ console.error('❌ No code to execute');
76
+ console.error('Usage:');
77
+ console.error(' node run.js script.js # Execute file');
78
+ console.error(' node run.js "code here" # Execute inline');
79
+ console.error(' cat script.js | node run.js # Execute from stdin');
80
+ process.exit(1);
81
+ }
82
+
83
+ /**
84
+ * Clean up old temporary execution files from previous runs
85
+ */
86
+ function cleanupOldTempFiles() {
87
+ try {
88
+ const files = fs.readdirSync(__dirname);
89
+ const tempFiles = files.filter(f => f.startsWith('.temp-execution-') && f.endsWith('.js'));
90
+
91
+ if (tempFiles.length > 0) {
92
+ tempFiles.forEach(file => {
93
+ const filePath = path.join(__dirname, file);
94
+ try {
95
+ fs.unlinkSync(filePath);
96
+ } catch (e) {
97
+ // Ignore errors - file might be in use or already deleted
98
+ }
99
+ });
100
+ }
101
+ } catch (e) {
102
+ // Ignore directory read errors
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Wrap code in async IIFE if not already wrapped
108
+ */
109
+ function wrapCodeIfNeeded(code) {
110
+ // Check if code already has require() and async structure
111
+ const hasRequire = code.includes('require(');
112
+ const hasAsyncIIFE = code.includes('(async () => {') || code.includes('(async()=>{');
113
+
114
+ // If it's already a complete script, return as-is
115
+ if (hasRequire && hasAsyncIIFE) {
116
+ return code;
117
+ }
118
+
119
+ // If it's just Playwright commands, wrap in full template
120
+ if (!hasRequire) {
121
+ return `
122
+ const { chromium, firefox, webkit, devices } = require('playwright');
123
+ const helpers = require('./lib/helpers');
124
+
125
+ // Extra headers from environment variables (if configured)
126
+ const __extraHeaders = helpers.getExtraHeadersFromEnv();
127
+
128
+ /**
129
+ * Utility to merge environment headers into context options.
130
+ * Use when creating contexts with raw Playwright API instead of helpers.createContext().
131
+ * @param {Object} options - Context options
132
+ * @returns {Object} Options with extraHTTPHeaders merged in
133
+ */
134
+ function getContextOptionsWithHeaders(options = {}) {
135
+ if (!__extraHeaders) return options;
136
+ return {
137
+ ...options,
138
+ extraHTTPHeaders: {
139
+ ...__extraHeaders,
140
+ ...(options.extraHTTPHeaders || {})
141
+ }
142
+ };
143
+ }
144
+
145
+ (async () => {
146
+ try {
147
+ ${code}
148
+ } catch (error) {
149
+ console.error('❌ Automation error:', error.message);
150
+ if (error.stack) {
151
+ console.error(error.stack);
152
+ }
153
+ process.exit(1);
154
+ }
155
+ })();
156
+ `;
157
+ }
158
+
159
+ // If has require but no async wrapper
160
+ if (!hasAsyncIIFE) {
161
+ return `
162
+ (async () => {
163
+ try {
164
+ ${code}
165
+ } catch (error) {
166
+ console.error('❌ Automation error:', error.message);
167
+ if (error.stack) {
168
+ console.error(error.stack);
169
+ }
170
+ process.exit(1);
171
+ }
172
+ })();
173
+ `;
174
+ }
175
+
176
+ return code;
177
+ }
178
+
179
+ /**
180
+ * Main execution
181
+ */
182
+ async function main() {
183
+ console.log('🎭 Playwright Skill - Universal Executor\n');
184
+
185
+ // Clean up old temp files from previous runs
186
+ cleanupOldTempFiles();
187
+
188
+ // Check Playwright installation
189
+ if (!checkPlaywrightInstalled()) {
190
+ const installed = installPlaywright();
191
+ if (!installed) {
192
+ process.exit(1);
193
+ }
194
+ }
195
+
196
+ // Get code to execute
197
+ const rawCode = getCodeToExecute();
198
+ const code = wrapCodeIfNeeded(rawCode);
199
+
200
+ // Create temporary file for execution
201
+ const tempFile = path.join(__dirname, `.temp-execution-${Date.now()}.js`);
202
+
203
+ try {
204
+ // Write code to temp file
205
+ fs.writeFileSync(tempFile, code, 'utf8');
206
+
207
+ // Execute the code
208
+ console.log('🚀 Starting automation...\n');
209
+ require(tempFile);
210
+
211
+ // Note: Temp file will be cleaned up on next run
212
+ // This allows long-running async operations to complete safely
213
+
214
+ } catch (error) {
215
+ console.error('❌ Execution failed:', error.message);
216
+ if (error.stack) {
217
+ console.error('\n📋 Stack trace:');
218
+ console.error(error.stack);
219
+ }
220
+ process.exit(1);
221
+ }
222
+ }
223
+
224
+ // Run main function
225
+ main().catch(error => {
226
+ console.error('❌ Fatal error:', error.message);
227
+ process.exit(1);
228
+ });