opengstack 0.13.10 → 0.14.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 (151) hide show
  1. package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +0 -16
  2. package/{skills/benchmark/SKILL.md → commands/benchmark.md} +0 -17
  3. package/{skills/browse/SKILL.md → commands/browse.md} +0 -17
  4. package/{skills/ship/SKILL.md → commands/canary.md} +0 -18
  5. package/{skills/careful/SKILL.md → commands/careful.md} +0 -20
  6. package/{skills/canary/SKILL.md → commands/codex.md} +0 -17
  7. package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +0 -15
  8. package/commands/cso.md +72 -0
  9. package/commands/design-consultation.md +72 -0
  10. package/commands/design-review.md +72 -0
  11. package/commands/design-shotgun.md +72 -0
  12. package/commands/document-release.md +72 -0
  13. package/{skills/freeze/SKILL.md → commands/freeze.md} +0 -26
  14. package/{skills/gstack-upgrade/SKILL.md → commands/gstack-upgrade.md} +0 -14
  15. package/{skills/guard/SKILL.md → commands/guard.md} +0 -31
  16. package/commands/investigate.md +72 -0
  17. package/commands/land-and-deploy.md +72 -0
  18. package/commands/office-hours.md +72 -0
  19. package/commands/plan-ceo-review.md +72 -0
  20. package/commands/plan-design-review.md +72 -0
  21. package/commands/plan-eng-review.md +72 -0
  22. package/commands/qa-only.md +72 -0
  23. package/commands/qa.md +72 -0
  24. package/commands/retro.md +72 -0
  25. package/commands/review.md +72 -0
  26. package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +0 -14
  27. package/commands/setup-deploy.md +72 -0
  28. package/commands/ship.md +72 -0
  29. package/{skills/unfreeze/SKILL.md → commands/unfreeze.md} +0 -12
  30. package/package.json +4 -4
  31. package/scripts/install-commands.js +45 -0
  32. package/skills/autoplan/SKILL.md +0 -96
  33. package/skills/autoplan/SKILL.md.tmpl +0 -694
  34. package/skills/benchmark/SKILL.md.tmpl +0 -222
  35. package/skills/browse/SKILL.md.tmpl +0 -131
  36. package/skills/browse/bin/find-browse +0 -21
  37. package/skills/browse/bin/remote-slug +0 -14
  38. package/skills/browse/scripts/build-node-server.sh +0 -48
  39. package/skills/browse/src/activity.ts +0 -208
  40. package/skills/browse/src/browser-manager.ts +0 -959
  41. package/skills/browse/src/buffers.ts +0 -137
  42. package/skills/browse/src/bun-polyfill.cjs +0 -109
  43. package/skills/browse/src/cli.ts +0 -678
  44. package/skills/browse/src/commands.ts +0 -128
  45. package/skills/browse/src/config.ts +0 -150
  46. package/skills/browse/src/cookie-import-browser.ts +0 -625
  47. package/skills/browse/src/cookie-picker-routes.ts +0 -230
  48. package/skills/browse/src/cookie-picker-ui.ts +0 -688
  49. package/skills/browse/src/find-browse.ts +0 -61
  50. package/skills/browse/src/meta-commands.ts +0 -550
  51. package/skills/browse/src/platform.ts +0 -17
  52. package/skills/browse/src/read-commands.ts +0 -358
  53. package/skills/browse/src/server.ts +0 -1192
  54. package/skills/browse/src/sidebar-agent.ts +0 -280
  55. package/skills/browse/src/sidebar-utils.ts +0 -21
  56. package/skills/browse/src/snapshot.ts +0 -407
  57. package/skills/browse/src/url-validation.ts +0 -95
  58. package/skills/browse/src/write-commands.ts +0 -364
  59. package/skills/browse/test/activity.test.ts +0 -120
  60. package/skills/browse/test/adversarial-security.test.ts +0 -32
  61. package/skills/browse/test/browser-manager-unit.test.ts +0 -17
  62. package/skills/browse/test/bun-polyfill.test.ts +0 -72
  63. package/skills/browse/test/commands.test.ts +0 -2075
  64. package/skills/browse/test/compare-board.test.ts +0 -342
  65. package/skills/browse/test/config.test.ts +0 -316
  66. package/skills/browse/test/cookie-import-browser.test.ts +0 -519
  67. package/skills/browse/test/cookie-picker-routes.test.ts +0 -260
  68. package/skills/browse/test/file-drop.test.ts +0 -271
  69. package/skills/browse/test/find-browse.test.ts +0 -50
  70. package/skills/browse/test/findport.test.ts +0 -191
  71. package/skills/browse/test/fixtures/basic.html +0 -33
  72. package/skills/browse/test/fixtures/cursor-interactive.html +0 -22
  73. package/skills/browse/test/fixtures/dialog.html +0 -15
  74. package/skills/browse/test/fixtures/empty.html +0 -2
  75. package/skills/browse/test/fixtures/forms.html +0 -55
  76. package/skills/browse/test/fixtures/iframe.html +0 -30
  77. package/skills/browse/test/fixtures/network-idle.html +0 -30
  78. package/skills/browse/test/fixtures/qa-eval-checkout.html +0 -108
  79. package/skills/browse/test/fixtures/qa-eval-spa.html +0 -98
  80. package/skills/browse/test/fixtures/qa-eval.html +0 -51
  81. package/skills/browse/test/fixtures/responsive.html +0 -49
  82. package/skills/browse/test/fixtures/snapshot.html +0 -55
  83. package/skills/browse/test/fixtures/spa.html +0 -24
  84. package/skills/browse/test/fixtures/states.html +0 -17
  85. package/skills/browse/test/fixtures/upload.html +0 -25
  86. package/skills/browse/test/gstack-config.test.ts +0 -138
  87. package/skills/browse/test/gstack-update-check.test.ts +0 -514
  88. package/skills/browse/test/handoff.test.ts +0 -235
  89. package/skills/browse/test/path-validation.test.ts +0 -91
  90. package/skills/browse/test/platform.test.ts +0 -37
  91. package/skills/browse/test/server-auth.test.ts +0 -65
  92. package/skills/browse/test/sidebar-agent-roundtrip.test.ts +0 -226
  93. package/skills/browse/test/sidebar-agent.test.ts +0 -199
  94. package/skills/browse/test/sidebar-integration.test.ts +0 -320
  95. package/skills/browse/test/sidebar-unit.test.ts +0 -96
  96. package/skills/browse/test/snapshot.test.ts +0 -467
  97. package/skills/browse/test/state-ttl.test.ts +0 -35
  98. package/skills/browse/test/test-server.ts +0 -57
  99. package/skills/browse/test/url-validation.test.ts +0 -72
  100. package/skills/browse/test/watch.test.ts +0 -129
  101. package/skills/canary/SKILL.md.tmpl +0 -212
  102. package/skills/careful/SKILL.md.tmpl +0 -56
  103. package/skills/careful/bin/check-careful.sh +0 -112
  104. package/skills/codex/SKILL.md +0 -90
  105. package/skills/codex/SKILL.md.tmpl +0 -417
  106. package/skills/connect-chrome/SKILL.md.tmpl +0 -195
  107. package/skills/cso/ACKNOWLEDGEMENTS.md +0 -14
  108. package/skills/cso/SKILL.md +0 -93
  109. package/skills/cso/SKILL.md.tmpl +0 -606
  110. package/skills/design-consultation/SKILL.md +0 -94
  111. package/skills/design-consultation/SKILL.md.tmpl +0 -415
  112. package/skills/design-review/SKILL.md +0 -94
  113. package/skills/design-review/SKILL.md.tmpl +0 -290
  114. package/skills/design-shotgun/SKILL.md +0 -91
  115. package/skills/design-shotgun/SKILL.md.tmpl +0 -285
  116. package/skills/document-release/SKILL.md +0 -91
  117. package/skills/document-release/SKILL.md.tmpl +0 -359
  118. package/skills/freeze/SKILL.md.tmpl +0 -77
  119. package/skills/freeze/bin/check-freeze.sh +0 -79
  120. package/skills/gstack-upgrade/SKILL.md.tmpl +0 -222
  121. package/skills/guard/SKILL.md.tmpl +0 -77
  122. package/skills/investigate/SKILL.md +0 -105
  123. package/skills/investigate/SKILL.md.tmpl +0 -194
  124. package/skills/land-and-deploy/SKILL.md.tmpl +0 -881
  125. package/skills/office-hours/SKILL.md +0 -96
  126. package/skills/office-hours/SKILL.md.tmpl +0 -645
  127. package/skills/plan-ceo-review/SKILL.md +0 -94
  128. package/skills/plan-ceo-review/SKILL.md.tmpl +0 -811
  129. package/skills/plan-design-review/SKILL.md +0 -92
  130. package/skills/plan-design-review/SKILL.md.tmpl +0 -446
  131. package/skills/plan-eng-review/SKILL.md +0 -93
  132. package/skills/plan-eng-review/SKILL.md.tmpl +0 -303
  133. package/skills/qa/SKILL.md +0 -95
  134. package/skills/qa/SKILL.md.tmpl +0 -316
  135. package/skills/qa/references/issue-taxonomy.md +0 -85
  136. package/skills/qa/templates/qa-report-template.md +0 -126
  137. package/skills/qa-only/SKILL.md +0 -89
  138. package/skills/qa-only/SKILL.md.tmpl +0 -101
  139. package/skills/retro/SKILL.md +0 -89
  140. package/skills/retro/SKILL.md.tmpl +0 -820
  141. package/skills/review/SKILL.md +0 -92
  142. package/skills/review/SKILL.md.tmpl +0 -281
  143. package/skills/review/TODOS-format.md +0 -62
  144. package/skills/review/checklist.md +0 -220
  145. package/skills/review/design-checklist.md +0 -132
  146. package/skills/review/greptile-triage.md +0 -220
  147. package/skills/setup-browser-cookies/SKILL.md.tmpl +0 -81
  148. package/skills/setup-deploy/SKILL.md +0 -92
  149. package/skills/setup-deploy/SKILL.md.tmpl +0 -215
  150. package/skills/ship/SKILL.md.tmpl +0 -636
  151. package/skills/unfreeze/SKILL.md.tmpl +0 -36
@@ -1,222 +0,0 @@
1
- ---
2
- name: benchmark
3
- preamble-tier: 1
4
- version: 1.0.0
5
- description: |
6
- Performance regression detection using the browse daemon. Establishes
7
- baselines for page load times, Core Web Vitals, and resource sizes.
8
- Compares before/after on every PR. Tracks performance trends over time.
9
- Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals",
10
- "bundle size", "load time".
11
- allowed-tools:
12
- - Bash
13
- - Read
14
- - Write
15
- - Glob
16
- - AskUserQuestion
17
- ---
18
-
19
- {{PREAMBLE}}
20
-
21
- {{BROWSE_SETUP}}
22
-
23
- # /benchmark — Performance Regression Detection
24
-
25
- You are a **Performance Engineer** who has optimized apps serving millions of requests. You know that performance doesn't degrade in one big regression — it dies by a thousand paper cuts. Each PR adds 50ms here, 20KB there, and one day the app takes 8 seconds to load and nobody knows when it got slow.
26
-
27
- Your job is to measure, baseline, compare, and alert. You use the browse daemon's `perf` command and JavaScript evaluation to gather real performance data from running pages.
28
-
29
- ## User-invocable
30
- When the user types `/benchmark`, run this skill.
31
-
32
- ## Arguments
33
- - `/benchmark <url>` — full performance audit with baseline comparison
34
- - `/benchmark <url> --baseline` — capture baseline (run before making changes)
35
- - `/benchmark <url> --quick` — single-pass timing check (no baseline needed)
36
- - `/benchmark <url> --pages /,/dashboard,/api/health` — specify pages
37
- - `/benchmark --diff` — benchmark only pages affected by current branch
38
- - `/benchmark --trend` — show performance trends from historical data
39
-
40
- ## Instructions
41
-
42
- ### Phase 1: Setup
43
-
44
- ```bash
45
- eval "$(~/.claude/skills/opengstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown")"
46
- mkdir -p .gstack/benchmark-reports
47
- mkdir -p .gstack/benchmark-reports/baselines
48
-
49
- ### Phase 2: Page Discovery
50
-
51
- Same as /canary — auto-discover from navigation or use `--pages`.
52
-
53
- If `--diff` mode:
54
- ```bash
55
- git diff $(gh pr view --json baseRefName -q .baseRefName 2>/dev/null || gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo main)...HEAD --name-only
56
-
57
- ### Phase 3: Performance Data Collection
58
-
59
- For each page, collect comprehensive performance metrics:
60
-
61
- ```bash
62
- $B goto <page-url>
63
- $B perf
64
-
65
- Then gather detailed metrics via JavaScript:
66
-
67
- ```bash
68
- $B eval "JSON.stringify(performance.getEntriesByType('navigation')[0])"
69
-
70
- Extract key metrics:
71
- - **TTFB** (Time to First Byte): `responseStart - requestStart`
72
- - **FCP** (First Contentful Paint): from PerformanceObserver or `paint` entries
73
- - **LCP** (Largest Contentful Paint): from PerformanceObserver
74
- - **DOM Interactive**: `domInteractive - navigationStart`
75
- - **DOM Complete**: `domComplete - navigationStart`
76
- - **Full Load**: `loadEventEnd - navigationStart`
77
-
78
- Resource analysis:
79
- ```bash
80
- $B eval "JSON.stringify(performance.getEntriesByType('resource').map(r => ({name: r.name.split('/').pop().split('?')[0], type: r.initiatorType, size: r.transferSize, duration: Math.round(r.duration)})).sort((a,b) => b.duration - a.duration).slice(0,15))"
81
-
82
- Bundle size check:
83
- ```bash
84
- $B eval "JSON.stringify(performance.getEntriesByType('resource').filter(r => r.initiatorType === 'script').map(r => ({name: r.name.split('/').pop().split('?')[0], size: r.transferSize})))"
85
- $B eval "JSON.stringify(performance.getEntriesByType('resource').filter(r => r.initiatorType === 'css').map(r => ({name: r.name.split('/').pop().split('?')[0], size: r.transferSize})))"
86
-
87
- Network summary:
88
- ```bash
89
- $B eval "(() => { const r = performance.getEntriesByType('resource'); return JSON.stringify({total_requests: r.length, total_transfer: r.reduce((s,e) => s + (e.transferSize||0), 0), by_type: Object.entries(r.reduce((a,e) => { a[e.initiatorType] = (a[e.initiatorType]||0) + 1; return a; }, {})).sort((a,b) => b[1]-a[1])})})()"
90
-
91
- ### Phase 4: Baseline Capture (--baseline mode)
92
-
93
- Save metrics to baseline file:
94
-
95
- ```json
96
- {
97
- "url": "<url>",
98
- "timestamp": "<ISO>",
99
- "branch": "<branch>",
100
- "pages": {
101
- "/": {
102
- "ttfb_ms": 120,
103
- "fcp_ms": 450,
104
- "lcp_ms": 800,
105
- "dom_interactive_ms": 600,
106
- "dom_complete_ms": 1200,
107
- "full_load_ms": 1400,
108
- "total_requests": 42,
109
- "total_transfer_bytes": 1250000,
110
- "js_bundle_bytes": 450000,
111
- "css_bundle_bytes": 85000,
112
- "largest_resources": [
113
- {"name": "main.js", "size": 320000, "duration": 180},
114
- {"name": "vendor.js", "size": 130000, "duration": 90}
115
- ]
116
- }
117
- }
118
- }
119
-
120
- Write to `.gstack/benchmark-reports/baselines/baseline.json`.
121
-
122
- ### Phase 5: Comparison
123
-
124
- If baseline exists, compare current metrics against it:
125
-
126
-
127
- PERFORMANCE REPORT — [url]
128
- ══════════════════════════
129
- Branch: [current-branch] vs baseline ([baseline-branch])
130
-
131
- Page: /
132
- ─────────────────────────────────────────────────────
133
- Metric Baseline Current Delta Status
134
- ──────── ──────── ─────── ───── ──────
135
- TTFB 120ms 135ms +15ms OK
136
- FCP 450ms 480ms +30ms OK
137
- LCP 800ms 1600ms +800ms REGRESSION
138
- DOM Interactive 600ms 650ms +50ms OK
139
- DOM Complete 1200ms 1350ms +150ms WARNING
140
- Full Load 1400ms 2100ms +700ms REGRESSION
141
- Total Requests 42 58 +16 WARNING
142
- Transfer Size 1.2MB 1.8MB +0.6MB REGRESSION
143
- JS Bundle 450KB 720KB +270KB REGRESSION
144
- CSS Bundle 85KB 88KB +3KB OK
145
-
146
- REGRESSIONS DETECTED: 3
147
- [1] LCP doubled (800ms → 1600ms) — likely a large new image or blocking resource
148
- [2] Total transfer +50% (1.2MB → 1.8MB) — check new JS bundles
149
- [3] JS bundle +60% (450KB → 720KB) — new dependency or missing tree-shaking
150
-
151
- **Regression thresholds:**
152
- - Timing metrics: >50% increase OR >500ms absolute increase = REGRESSION
153
- - Timing metrics: >20% increase = WARNING
154
- - Bundle size: >25% increase = REGRESSION
155
- - Bundle size: >10% increase = WARNING
156
- - Request count: >30% increase = WARNING
157
-
158
- ### Phase 6: Slowest Resources
159
-
160
-
161
- TOP 10 SLOWEST RESOURCES
162
- ═════════════════════════
163
- # Resource Type Size Duration
164
- 1 vendor.chunk.js script 320KB 480ms
165
- 2 main.js script 250KB 320ms
166
- 3 hero-image.webp img 180KB 280ms
167
- 4 analytics.js script 45KB 250ms ← third-party
168
- 5 fonts/inter-var.woff2 font 95KB 180ms
169
- ...
170
-
171
- RECOMMENDATIONS:
172
- - vendor.chunk.js: Consider code-splitting — 320KB is large for initial load
173
- - analytics.js: Load async/defer — blocks rendering for 250ms
174
- - hero-image.webp: Add width/height to prevent CLS, consider lazy loading
175
-
176
- ### Phase 7: Performance Budget
177
-
178
- Check against industry budgets:
179
-
180
-
181
- PERFORMANCE BUDGET CHECK
182
- ════════════════════════
183
- Metric Budget Actual Status
184
- ──────── ────── ────── ──────
185
- FCP < 1.8s 0.48s PASS
186
- LCP < 2.5s 1.6s PASS
187
- Total JS < 500KB 720KB FAIL
188
- Total CSS < 100KB 88KB PASS
189
- Total Transfer < 2MB 1.8MB WARNING (90%)
190
- HTTP Requests < 50 58 FAIL
191
-
192
- Grade: B (4/6 passing)
193
-
194
- ### Phase 8: Trend Analysis (--trend mode)
195
-
196
- Load historical baseline files and show trends:
197
-
198
-
199
- PERFORMANCE TRENDS (last 5 benchmarks)
200
- ══════════════════════════════════════
201
- Date FCP LCP Bundle Requests Grade
202
- 2026-03-10 420ms 750ms 380KB 38 A
203
- 2026-03-12 440ms 780ms 410KB 40 A
204
- 2026-03-14 450ms 800ms 450KB 42 A
205
- 2026-03-16 460ms 850ms 520KB 48 B
206
- 2026-03-18 480ms 1600ms 720KB 58 B
207
-
208
- TREND: Performance degrading. LCP doubled in 8 days.
209
- JS bundle growing 50KB/week. Investigate.
210
-
211
- ### Phase 9: Save Report
212
-
213
- Write to `.gstack/benchmark-reports/{date}-benchmark.md` and `.gstack/benchmark-reports/{date}-benchmark.json`.
214
-
215
- ## Important Rules
216
-
217
- - **Measure, don't guess.** Use actual performance.getEntries() data, not estimates.
218
- - **Baseline is essential.** Without a baseline, you can report absolute numbers but can't detect regressions. Always encourage baseline capture.
219
- - **Relative thresholds, not absolute.** 2000ms load time is fine for a complex dashboard, terrible for a landing page. Compare against YOUR baseline.
220
- - **Third-party scripts are context.** Flag them, but the user can't fix Google Analytics being slow. Focus recommendations on first-party resources.
221
- - **Bundle size is the leading indicator.** Load time varies with network. Bundle size is deterministic. Track it religiously.
222
- - **Read-only.** Produce the report. Don't modify code unless explicitly asked.
@@ -1,131 +0,0 @@
1
- ---
2
- name: browse
3
- preamble-tier: 1
4
- version: 1.1.0
5
- description: |
6
- Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with
7
- elements, verify page state, diff before/after actions, take annotated screenshots, check
8
- responsive layouts, test forms and uploads, handle dialogs, and assert element states.
9
- ~100ms per command. Use when you need to test a feature, verify a deployment, dogfood a
10
- user flow, or file a bug with evidence. Use when asked to "open in browser", "test the
11
- site", "take a screenshot", or "dogfood this".
12
- allowed-tools:
13
- - Bash
14
- - Read
15
- - AskUserQuestion
16
-
17
- ---
18
-
19
- {{PREAMBLE}}
20
-
21
- # browse: QA Testing & Dogfooding
22
-
23
- Persistent headless Chromium. First call auto-starts (~3s), then ~100ms per command.
24
- State persists between calls (cookies, tabs, login sessions).
25
-
26
- {{BROWSE_SETUP}}
27
-
28
- ## Core QA Patterns
29
-
30
- ### 1. Verify a page loads correctly
31
- ```bash
32
- $B goto https://yourapp.com
33
- $B text # content loads?
34
- $B console # JS errors?
35
- $B network # failed requests?
36
- $B is visible ".main-content" # key elements present?
37
-
38
- ### 2. Test a user flow
39
- ```bash
40
- $B goto https://app.com/login
41
- $B snapshot -i # see all interactive elements
42
- $B fill @e3 "user@test.com"
43
- $B fill @e4 "password"
44
- $B click @e5 # submit
45
- $B snapshot -D # diff: what changed after submit?
46
- $B is visible ".dashboard" # success state present?
47
-
48
- ### 3. Verify an action worked
49
- ```bash
50
- $B snapshot # baseline
51
- $B click @e3 # do something
52
- $B snapshot -D # unified diff shows exactly what changed
53
-
54
- ### 4. Visual evidence for bug reports
55
- ```bash
56
- $B snapshot -i -a -o /tmp/annotated.png # labeled screenshot
57
- $B screenshot /tmp/bug.png # plain screenshot
58
- $B console # error log
59
-
60
- ### 5. Find all clickable elements (including non-ARIA)
61
- ```bash
62
- $B snapshot -C # finds divs with cursor:pointer, onclick, tabindex
63
- $B click @c1 # interact with them
64
-
65
- ### 6. Assert element states
66
- ```bash
67
- $B is visible ".modal"
68
- $B is enabled "#submit-btn"
69
- $B is disabled "#submit-btn"
70
- $B is checked "#agree-checkbox"
71
- $B is editable "#name-field"
72
- $B is focused "#search-input"
73
- $B js "document.body.textContent.includes('Success')"
74
-
75
- ### 7. Test responsive layouts
76
- ```bash
77
- $B responsive /tmp/layout # mobile + tablet + desktop screenshots
78
- $B viewport 375x812 # or set specific viewport
79
- $B screenshot /tmp/mobile.png
80
-
81
- ### 8. Test file uploads
82
- ```bash
83
- $B upload "#file-input" /path/to/file.pdf
84
- $B is visible ".upload-success"
85
-
86
- ### 9. Test dialogs
87
- ```bash
88
- $B dialog-accept "yes" # set up handler
89
- $B click "#delete-button" # trigger dialog
90
- $B dialog # see what appeared
91
- $B snapshot -D # verify deletion happened
92
-
93
- ### 10. Compare environments
94
- ```bash
95
- $B diff https://staging.app.com https://prod.app.com
96
-
97
- ### 11. Show screenshots to the user
98
- After `$B screenshot`, `$B snapshot -a -o`, or `$B responsive`, always use the Read tool on the output PNG(s) so the user can see them. Without this, screenshots are invisible.
99
-
100
- ## User Handoff
101
-
102
- When you hit something you can't handle in headless mode (CAPTCHA, complex auth, multi-factor
103
- login), hand off to the user:
104
-
105
- ```bash
106
- # 1. Open a visible Chrome at the current page
107
- $B handoff "Stuck on CAPTCHA at login page"
108
-
109
- # 2. Tell the user what happened (via AskUserQuestion)
110
- # "I've opened Chrome at the login page. Please solve the CAPTCHA
111
- # and let me know when you're done."
112
-
113
- # 3. When user says "done", re-snapshot and continue
114
- $B resume
115
-
116
- **When to use handoff:**
117
- - CAPTCHAs or bot detection
118
- - Multi-factor authentication (SMS, authenticator app)
119
- - OAuth flows that require user interaction
120
- - Complex interactions the AI can't handle after 3 attempts
121
-
122
- The browser preserves all state (cookies, localStorage, tabs) across the handoff.
123
- After `resume`, you get a fresh snapshot of wherever the user left off.
124
-
125
- ## Snapshot Flags
126
-
127
- {{SNAPSHOT_FLAGS}}
128
-
129
- ## Full Command List
130
-
131
- {{COMMAND_REFERENCE}}
@@ -1,21 +0,0 @@
1
- #!/bin/bash
2
- # Shim: delegates to compiled find-browse binary, falls back to basic discovery.
3
- # The compiled binary handles git root detection for workspace-local installs.
4
- DIR="$(cd "$(dirname "$0")/.." && pwd)/dist"
5
- if test -x "$DIR/find-browse"; then
6
- exec "$DIR/find-browse" "$@"
7
- fi
8
- # Fallback: basic discovery with priority chain
9
- ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
10
- for MARKER in .codex .agents .claude; do
11
- if [ -n "$ROOT" ] && test -x "$ROOT/$MARKER/skills/gstack/browse/dist/browse"; then
12
- echo "$ROOT/$MARKER/skills/gstack/browse/dist/browse"
13
- exit 0
14
- fi
15
- if test -x "$HOME/$MARKER/skills/gstack/browse/dist/browse"; then
16
- echo "$HOME/$MARKER/skills/gstack/browse/dist/browse"
17
- exit 0
18
- fi
19
- done
20
- echo "ERROR: browse binary not found. Run: cd <skill-dir> && ./setup" >&2
21
- exit 1
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Output the remote slug (owner-repo) for the current git repo.
3
- # Used by SKILL.md files to derive project-specific paths in ~/.gstack/projects/.
4
- set -e
5
- URL=$(git remote get-url origin 2>/dev/null || true)
6
- if [ -n "$URL" ]; then
7
- # Strip trailing .git if present, then extract owner/repo
8
- URL="${URL%.git}"
9
- # Handle both SSH (git@host:owner/repo) and HTTPS (https://host/owner/repo)
10
- OWNER_REPO=$(echo "$URL" | sed -E 's#.*[:/]([^/]+)/([^/]+)$#\1-\2#')
11
- echo "$OWNER_REPO"
12
- else
13
- basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
14
- fi
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Build a Node.js-compatible server bundle for Windows.
3
- #
4
- # On Windows, Bun can't launch or connect to Playwright's Chromium
5
- # (oven-sh/bun#4253, #9911). This script produces a server bundle
6
- # that runs under Node.js with Bun API polyfills.
7
-
8
- set -e
9
-
10
- GSTACK_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
11
- SRC_DIR="$GSTACK_DIR/browse/src"
12
- DIST_DIR="$GSTACK_DIR/browse/dist"
13
-
14
- echo "Building Node-compatible server bundle..."
15
-
16
- # Step 1: Transpile server.ts to a single .mjs bundle (externalize runtime deps)
17
- bun build "$SRC_DIR/server.ts" \
18
- --target=node \
19
- --outfile "$DIST_DIR/server-node.mjs" \
20
- --external playwright \
21
- --external playwright-core \
22
- --external diff \
23
- --external "bun:sqlite"
24
-
25
- # Step 2: Post-process
26
- # Replace import.meta.dir with a resolvable reference
27
- perl -pi -e 's/import\.meta\.dir/__browseNodeSrcDir/g' "$DIST_DIR/server-node.mjs"
28
- # Stub out bun:sqlite (macOS-only cookie import, not needed on Windows)
29
- perl -pi -e 's|import { Database } from "bun:sqlite";|const Database = null; // bun:sqlite stubbed on Node|g' "$DIST_DIR/server-node.mjs"
30
-
31
- # Step 3: Create the final file with polyfill header injected after the first line
32
- {
33
- head -1 "$DIST_DIR/server-node.mjs"
34
- echo '// ── Windows Node.js compatibility (auto-generated) ──'
35
- echo 'import { fileURLToPath as _ftp } from "node:url";'
36
- echo 'import { dirname as _dn } from "node:path";'
37
- echo 'const __browseNodeSrcDir = _dn(_dn(_ftp(import.meta.url))) + "/src";'
38
- echo '{ const _r = createRequire(import.meta.url); _r("./bun-polyfill.cjs"); }'
39
- echo '// ── end compatibility ──'
40
- tail -n +2 "$DIST_DIR/server-node.mjs"
41
- } > "$DIST_DIR/server-node.tmp.mjs"
42
-
43
- mv "$DIST_DIR/server-node.tmp.mjs" "$DIST_DIR/server-node.mjs"
44
-
45
- # Step 4: Copy polyfill to dist/
46
- cp "$SRC_DIR/bun-polyfill.cjs" "$DIST_DIR/bun-polyfill.cjs"
47
-
48
- echo "Node server bundle ready: $DIST_DIR/server-node.mjs"
@@ -1,208 +0,0 @@
1
- /**
2
- * Activity streaming — real-time feed of browse commands for the Chrome extension Side Panel
3
- *
4
- * Architecture:
5
- * handleCommand() ──► emitActivity(command_start)
6
- * ──► emitActivity(command_end)
7
- * wirePageEvents() ──► emitActivity(navigation)
8
- *
9
- * GET /activity/stream?after=ID ──► SSE via ReadableStream
10
- * GET /activity/history?limit=N ──► REST fallback
11
- *
12
- * Privacy: filterArgs() redacts passwords, auth tokens, and sensitive query params.
13
- * Backpressure: subscribers notified via queueMicrotask (never blocks command path).
14
- * Gap detection: client sends ?after=ID, server detects if ring buffer overflowed.
15
- */
16
-
17
- import { CircularBuffer } from './buffers';
18
-
19
- // ─── Types ──────────────────────────────────────────────────────
20
-
21
- export interface ActivityEntry {
22
- id: number;
23
- timestamp: number;
24
- type: 'command_start' | 'command_end' | 'navigation' | 'error';
25
- command?: string;
26
- args?: string[];
27
- url?: string;
28
- duration?: number;
29
- status?: 'ok' | 'error';
30
- error?: string;
31
- result?: string;
32
- tabs?: number;
33
- mode?: string;
34
- }
35
-
36
- // ─── Buffer & Subscribers ───────────────────────────────────────
37
-
38
- const BUFFER_CAPACITY = 1000;
39
- const activityBuffer = new CircularBuffer<ActivityEntry>(BUFFER_CAPACITY);
40
- let nextId = 1;
41
-
42
- type ActivitySubscriber = (entry: ActivityEntry) => void;
43
- const subscribers = new Set<ActivitySubscriber>();
44
-
45
- // ─── Privacy Filtering ─────────────────────────────────────────
46
-
47
- const SENSITIVE_COMMANDS = new Set(['fill', 'type', 'cookie', 'header']);
48
- const SENSITIVE_PARAM_PATTERN = /\b(password|token|secret|key|auth|bearer|api[_-]?key)\b/i;
49
-
50
- /**
51
- * Redact sensitive data from command args before streaming.
52
- */
53
- export function filterArgs(command: string, args: string[]): string[] {
54
- if (!args || args.length === 0) return args;
55
-
56
- // fill: redact the value (last arg) for password-type fields
57
- if (command === 'fill' && args.length >= 2) {
58
- const selector = args[0];
59
- // If the selector suggests a password field, redact the value
60
- if (/password|passwd|secret|token/i.test(selector)) {
61
- return [selector, '[REDACTED]'];
62
- }
63
- return args;
64
- }
65
-
66
- // header: redact Authorization and other sensitive headers
67
- if (command === 'header' && args.length >= 1) {
68
- const headerLine = args[0];
69
- if (/^(authorization|x-api-key|cookie|set-cookie)/i.test(headerLine)) {
70
- const colonIdx = headerLine.indexOf(':');
71
- if (colonIdx > 0) {
72
- return [headerLine.substring(0, colonIdx + 1) + '[REDACTED]'];
73
- }
74
- }
75
- return args;
76
- }
77
-
78
- // cookie: redact cookie values
79
- if (command === 'cookie' && args.length >= 1) {
80
- const cookieStr = args[0];
81
- const eqIdx = cookieStr.indexOf('=');
82
- if (eqIdx > 0) {
83
- return [cookieStr.substring(0, eqIdx + 1) + '[REDACTED]'];
84
- }
85
- return args;
86
- }
87
-
88
- // type: always redact (could be a password field)
89
- if (command === 'type') {
90
- return ['[REDACTED]'];
91
- }
92
-
93
- // URL args: redact sensitive query params
94
- return args.map(arg => {
95
- if (arg.startsWith('http://') || arg.startsWith('https://')) {
96
- try {
97
- const url = new URL(arg);
98
- let redacted = false;
99
- for (const key of url.searchParams.keys()) {
100
- if (SENSITIVE_PARAM_PATTERN.test(key)) {
101
- url.searchParams.set(key, '[REDACTED]');
102
- redacted = true;
103
- }
104
- }
105
- return redacted ? url.toString() : arg;
106
- } catch {
107
- return arg;
108
- }
109
- }
110
- return arg;
111
- });
112
- }
113
-
114
- /**
115
- * Truncate result text for streaming (max 200 chars).
116
- */
117
- function truncateResult(result: string | undefined): string | undefined {
118
- if (!result) return undefined;
119
- if (result.length <= 200) return result;
120
- return result.substring(0, 200) + '...';
121
- }
122
-
123
- // ─── Public API ─────────────────────────────────────────────────
124
-
125
- /**
126
- * Emit an activity event. Backpressure-safe: subscribers notified asynchronously.
127
- */
128
- export function emitActivity(entry: Omit<ActivityEntry, 'id' | 'timestamp'>): ActivityEntry {
129
- const full: ActivityEntry = {
130
- ...entry,
131
- id: nextId++,
132
- timestamp: Date.now(),
133
- args: entry.args ? filterArgs(entry.command || '', entry.args) : undefined,
134
- result: truncateResult(entry.result),
135
- };
136
- activityBuffer.push(full);
137
-
138
- // Notify subscribers asynchronously — never block the command path
139
- for (const notify of subscribers) {
140
- queueMicrotask(() => {
141
- try { notify(full); } catch { /* subscriber error — don't crash */ }
142
- });
143
- }
144
-
145
- return full;
146
- }
147
-
148
- /**
149
- * Subscribe to live activity events. Returns unsubscribe function.
150
- */
151
- export function subscribe(fn: ActivitySubscriber): () => void {
152
- subscribers.add(fn);
153
- return () => subscribers.delete(fn);
154
- }
155
-
156
- /**
157
- * Get recent activity entries after the given cursor ID.
158
- * Returns entries and gap info if the buffer has overflowed.
159
- */
160
- export function getActivityAfter(afterId: number): {
161
- entries: ActivityEntry[];
162
- gap: boolean;
163
- gapFrom?: number;
164
- availableFrom?: number;
165
- totalAdded: number;
166
- } {
167
- const total = activityBuffer.totalAdded;
168
- const allEntries = activityBuffer.toArray();
169
-
170
- if (afterId === 0) {
171
- return { entries: allEntries, gap: false, totalAdded: total };
172
- }
173
-
174
- // Check for gap: if afterId is too old and has been evicted
175
- const oldestId = allEntries.length > 0 ? allEntries[0].id : nextId;
176
- if (afterId < oldestId) {
177
- return {
178
- entries: allEntries,
179
- gap: true,
180
- gapFrom: afterId + 1,
181
- availableFrom: oldestId,
182
- totalAdded: total,
183
- };
184
- }
185
-
186
- // Filter to entries after the cursor
187
- const filtered = allEntries.filter(e => e.id > afterId);
188
- return { entries: filtered, gap: false, totalAdded: total };
189
- }
190
-
191
- /**
192
- * Get the N most recent activity entries.
193
- */
194
- export function getActivityHistory(limit: number = 50): {
195
- entries: ActivityEntry[];
196
- totalAdded: number;
197
- } {
198
- const allEntries = activityBuffer.toArray();
199
- const sliced = limit < allEntries.length ? allEntries.slice(-limit) : allEntries;
200
- return { entries: sliced, totalAdded: activityBuffer.totalAdded };
201
- }
202
-
203
- /**
204
- * Get subscriber count (for debugging/health).
205
- */
206
- export function getSubscriberCount(): number {
207
- return subscribers.size;
208
- }