Full Code of steel-dev/steel-browser for AI

main c1a55edbb5f1 cached
281 files
54.9 MB
341.6k tokens
637 symbols
1 requests
Download .txt
Showing preview only (1,503K chars total). Download the full file or copy to clipboard to get everything.
Repository: steel-dev/steel-browser
Branch: main
Commit: c1a55edbb5f1
Files: 281
Total size: 54.9 MB

Directory structure:
gitextract_z2lghh3p/

├── .dockerignore
├── .env.example
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   ├── feature_request.md
│   │   └── question.md
│   ├── auto-assign.yml
│   ├── labeler.yml
│   ├── labels.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── auto-assign.yml
│       ├── build-docker.yml
│       ├── check-build.yml
│       ├── pr-checks.yml
│       ├── release.yml
│       └── welcome.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── api/
│   ├── .dockerignore
│   ├── .env.example
│   ├── .gitattributes
│   ├── .gitignore
│   ├── .prettierignore
│   ├── .prettierrc
│   ├── .puppeteerrc.cjs
│   ├── Dockerfile
│   ├── entrypoint.sh
│   ├── extensions/
│   │   └── recorder/
│   │       ├── .gitignore
│   │       ├── manifest.json
│   │       ├── package.json
│   │       ├── src/
│   │       │   ├── background.js
│   │       │   └── inject.js
│   │       └── webpack.config.mjs
│   ├── nginx.conf
│   ├── openapi/
│   │   ├── generate.ts
│   │   └── schemas.json
│   ├── package.json
│   ├── selenium/
│   │   ├── driver/
│   │   │   ├── LICENSE.chromedriver
│   │   │   ├── THIRD_PARTY_NOTICES.chromedriver
│   │   │   └── chromedriver2
│   │   └── server/
│   │       └── selenium-server.jar
│   ├── src/
│   │   ├── config.ts
│   │   ├── env.ts
│   │   ├── index.ts
│   │   ├── modules/
│   │   │   ├── actions/
│   │   │   │   ├── actions.controller.ts
│   │   │   │   ├── actions.routes.ts
│   │   │   │   └── actions.schema.ts
│   │   │   ├── cdp/
│   │   │   │   ├── cdp.routes.ts
│   │   │   │   └── cdp.schemas.ts
│   │   │   ├── files/
│   │   │   │   ├── files.controller.ts
│   │   │   │   ├── files.routes.ts
│   │   │   │   └── files.schema.ts
│   │   │   ├── logs/
│   │   │   │   ├── logs.routes.ts
│   │   │   │   └── logs.schema.ts
│   │   │   ├── selenium/
│   │   │   │   ├── selenium.routes.ts
│   │   │   │   └── selenium.schema.ts
│   │   │   └── sessions/
│   │   │       ├── sessions.controller.ts
│   │   │       ├── sessions.routes.ts
│   │   │       └── sessions.schema.ts
│   │   ├── plugins/
│   │   │   ├── browser-session.ts
│   │   │   ├── browser-socket/
│   │   │   │   ├── browser-socket.ts
│   │   │   │   ├── casting.handler.ts
│   │   │   │   └── handlers/
│   │   │   │       ├── cast.handler.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── logs.handler.ts
│   │   │   │       ├── pageId.handler.ts
│   │   │   │       └── recording.handler.ts
│   │   │   ├── browser.ts
│   │   │   ├── custom-body-parser.ts
│   │   │   ├── file-storage.ts
│   │   │   ├── request-logger.ts
│   │   │   ├── scalar-theme.ts
│   │   │   ├── schemas.ts
│   │   │   ├── selenium.ts
│   │   │   └── ui-plugin.ts
│   │   ├── routes.ts
│   │   ├── scripts/
│   │   │   ├── fingerprint.js
│   │   │   └── index.ts
│   │   ├── services/
│   │   │   ├── cdp/
│   │   │   │   ├── cdp.service.ts
│   │   │   │   ├── errors/
│   │   │   │   │   └── launch-errors.ts
│   │   │   │   ├── instrumentation/
│   │   │   │   │   ├── browser-logger.test.ts
│   │   │   │   │   ├── browser-logger.ts
│   │   │   │   │   ├── cdp-events.ts
│   │   │   │   │   ├── extension-events.ts
│   │   │   │   │   ├── page-console.ts
│   │   │   │   │   ├── page-events.ts
│   │   │   │   │   ├── storage/
│   │   │   │   │   │   ├── duckdb-storage.ts
│   │   │   │   │   │   ├── in-memory-storage.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── log-storage.interface.ts
│   │   │   │   │   │   └── safe-json.ts
│   │   │   │   │   ├── target-manager.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── worker-events.ts
│   │   │   │   ├── plugins/
│   │   │   │   │   ├── core/
│   │   │   │   │   │   ├── base-plugin.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── plugin-manager.ts
│   │   │   │   │   └── pptr-extensions.d.ts
│   │   │   │   └── utils/
│   │   │   │       ├── error-handlers.ts
│   │   │   │       └── validation.ts
│   │   │   ├── context/
│   │   │   │   ├── chrome-context.service.ts
│   │   │   │   └── types.ts
│   │   │   ├── file.service.ts
│   │   │   ├── leveldb/
│   │   │   │   ├── localstorage.ts
│   │   │   │   └── sessionstorage.ts
│   │   │   ├── selenium.service.ts
│   │   │   ├── session.service.ts
│   │   │   ├── timezone-fetcher.service.ts
│   │   │   └── websocket-registry.service.ts
│   │   ├── steel-browser-plugin.ts
│   │   ├── telemetry/
│   │   │   ├── noop.ts
│   │   │   └── tracer.ts
│   │   ├── templates/
│   │   │   └── live-session-streamer.ejs
│   │   ├── types/
│   │   │   ├── browser.ts
│   │   │   ├── casting.ts
│   │   │   ├── enums.ts
│   │   │   ├── fastify.d.ts
│   │   │   ├── index.ts
│   │   │   ├── turndown.d.ts
│   │   │   └── websocket.ts
│   │   └── utils/
│   │       ├── browser.ts
│   │       ├── casting.ts
│   │       ├── context.ts
│   │       ├── errors.ts
│   │       ├── extensions.ts
│   │       ├── leveldb.ts
│   │       ├── logging.ts
│   │       ├── passthough-proxy.ts
│   │       ├── proxy.ts
│   │       ├── requests.ts
│   │       ├── retry.ts
│   │       ├── schema.ts
│   │       ├── scrape/
│   │       │   ├── cleanHtml.ts
│   │       │   ├── htmlToMarkdown.ts
│   │       │   ├── index.ts
│   │       │   ├── pdfToHtml.ts
│   │       │   ├── plugins/
│   │       │   │   ├── highlightedCodeBlock.ts
│   │       │   │   ├── inlineLink.ts
│   │       │   │   ├── strikethrough.ts
│   │       │   │   ├── table.ts
│   │       │   │   ├── taskListItems.ts
│   │       │   │   └── utilities.ts
│   │       │   ├── readability.ts
│   │       │   ├── safeGoTo.ts
│   │       │   └── transformHtml.ts
│   │       ├── size.ts
│   │       ├── text.ts
│   │       └── url.ts
│   ├── tsconfig.json
│   └── tsconfig.test.json
├── commitlint.config.cjs
├── docker-compose.dev.yml
├── docker-compose.yml
├── docs/
│   ├── ARCHITECTURE.md
│   ├── DEVELOPMENT_SETUP.md
│   ├── PLUGIN_DEVELOPMENT.md
│   ├── README.md
│   └── TROUBLESHOOTING.md
├── nginx.conf
├── package.json
├── render.yaml
├── repl/
│   ├── README.md
│   ├── package.json
│   └── src/
│       └── script.ts
└── ui/
    ├── .dockerignore
    ├── .eslintrc.cjs
    ├── .gitignore
    ├── Dockerfile
    ├── README.md
    ├── components.json
    ├── entrypoint.sh
    ├── index.html
    ├── nginx.conf.template
    ├── openapi-ts.config.ts
    ├── package.json
    ├── postcss.config.js
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   ├── badges/
    │   │   │   ├── proxy-badge.tsx
    │   │   │   ├── user-agent-badge.tsx
    │   │   │   └── websocket-url-badge.tsx
    │   │   ├── header/
    │   │   │   ├── header.tsx
    │   │   │   └── index.tsx
    │   │   ├── icons/
    │   │   │   ├── ChromeIcon.tsx
    │   │   │   ├── DeleteIcon.tsx
    │   │   │   ├── GlobeIcon.tsx
    │   │   │   ├── GlowingGreenDot.tsx
    │   │   │   ├── KeyIcon.tsx
    │   │   │   ├── LoadingSpinner.tsx
    │   │   │   ├── NinjaIcon.tsx
    │   │   │   ├── SessionIcon.tsx
    │   │   │   └── SettingsIcon.tsx
    │   │   ├── illustrations/
    │   │   │   ├── command-line.tsx
    │   │   │   └── globe.tsx
    │   │   ├── loading/
    │   │   │   ├── Loading.styles.tsx
    │   │   │   ├── Loading.tsx
    │   │   │   └── index.tsx
    │   │   ├── sessions/
    │   │   │   ├── release-session-dialog.tsx
    │   │   │   ├── session-console/
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── session-details.tsx
    │   │   │   │   ├── session-devtools.tsx
    │   │   │   │   └── session-logs.tsx
    │   │   │   └── session-viewer/
    │   │   │       ├── empty-state.tsx
    │   │   │       ├── example-events/
    │   │   │       │   ├── example-events.json
    │   │   │       │   └── test.json
    │   │   │       ├── index.tsx
    │   │   │       ├── live-empty-state.tsx
    │   │   │       ├── session-viewer-controls.css
    │   │   │       └── session-viewer.tsx
    │   │   ├── theme-provider.tsx
    │   │   └── ui/
    │   │       ├── avatar.tsx
    │   │       ├── badge.tsx
    │   │       ├── button.tsx
    │   │       ├── card.tsx
    │   │       ├── checkbox.tsx
    │   │       ├── dialog.tsx
    │   │       ├── form.tsx
    │   │       ├── input.tsx
    │   │       ├── label.tsx
    │   │       ├── pagination.tsx
    │   │       ├── popover.tsx
    │   │       ├── select.tsx
    │   │       ├── separator.tsx
    │   │       ├── table.tsx
    │   │       ├── tabs.tsx
    │   │       ├── toast.tsx
    │   │       └── toaster.tsx
    │   ├── containers/
    │   │   └── session-container.tsx
    │   ├── contexts/
    │   │   └── sessions-context/
    │   │       ├── index.tsx
    │   │       ├── sessions-context.tsx
    │   │       └── sessions-context.types.ts
    │   ├── env.ts
    │   ├── fonts/
    │   │   ├── Geist/
    │   │   │   ├── Geist-Black.otf
    │   │   │   ├── Geist-Bold.otf
    │   │   │   ├── Geist-Light.otf
    │   │   │   ├── Geist-Medium.otf
    │   │   │   ├── Geist-Regular.otf
    │   │   │   ├── Geist-SemiBold.otf
    │   │   │   ├── Geist-Thin.otf
    │   │   │   ├── Geist-UltraBlack.otf
    │   │   │   ├── Geist-UltraLight.otf
    │   │   │   └── LICENSE.TXT
    │   │   └── GeistMono/
    │   │       ├── GeistMono-Black.otf
    │   │       ├── GeistMono-Bold.otf
    │   │       ├── GeistMono-Light.otf
    │   │       ├── GeistMono-Medium.otf
    │   │       ├── GeistMono-Regular.otf
    │   │       ├── GeistMono-SemiBold.otf
    │   │       ├── GeistMono-Thin.otf
    │   │       ├── GeistMono-UltraBlack.otf
    │   │       ├── GeistMono-UltraLight.otf
    │   │       └── LICENSE.TXT
    │   ├── hooks/
    │   │   ├── use-sessions-context.ts
    │   │   └── use-toast.ts
    │   ├── index.css
    │   ├── lib/
    │   │   ├── query-client.ts
    │   │   └── utils.ts
    │   ├── main.tsx
    │   ├── root-layout.tsx
    │   ├── steel-client/
    │   │   ├── index.ts
    │   │   ├── schemas.gen.ts
    │   │   ├── services.gen.ts
    │   │   └── types.gen.ts
    │   ├── styles/
    │   │   ├── common.styles.tsx
    │   │   └── theme.ts
    │   ├── types/
    │   │   ├── cdp.ts
    │   │   └── props.ts
    │   ├── utils/
    │   │   ├── formatting.ts
    │   │   └── toasts.ts
    │   └── vite-env.d.ts
    ├── tailwind.config.js
    ├── tsconfig.json
    ├── tsconfig.node.json
    ├── vercel.json
    └── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
**/node_modules/
build/
**/.env
**/.env.local
Dockerfile
docker-compose.yml
api/extensions/**/dist


================================================
FILE: .env.example
================================================
VITE_API_URL=http://HOST:3000
VITE_WS_URL=ws://HOST:3000

================================================
FILE: .gitattributes
================================================
* text=auto
*.sh text eol=lf
*.conf text eol=lf


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] TITLE"
labels: ''
assignees: ''

---

Issue tracker is **ONLY** used for reporting bugs. New features should be discussed in our [Discord server](https://discord.gg/steel-dev).

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 💬 Discord Community
    url: https://discord.gg/steel-dev
    about: Join our Discord server for real-time help, discussions, and community support
  - name: 📚 Documentation
    url: https://docs.steel.dev/
    about: Check our comprehensive documentation for guides and API reference
  - name: 🍳 Steel Cookbook
    url: https://github.com/steel-dev/steel-cookbook
    about: Browse code examples and common use cases
  - name: 🔒 Security Issues
    url: mailto:security@steel.dev
    about: Please report security vulnerabilities privately via email
  - name: 🤖 Automated Issue Creation
    url: https://github.com/steel-dev/steel-browser/issues/new/choose
    about: Use our templates for better issue tracking 

================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for Steel Browser
title: "[FEATURE] "
labels: 'enhancement'
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Use case**
Describe the specific use case or scenario where this feature would be helpful.

**Implementation ideas (optional)**
If you have ideas about how this could be implemented, please share them.

**Additional context**
Add any other context, screenshots, or examples about the feature request here.

**Would you be willing to contribute this feature?**
- [ ] Yes, I'd like to work on this
- [ ] Yes, with guidance from maintainers
- [ ] No, but I'd be happy to test it
- [ ] No, just suggesting the idea 

================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Ask a question about using Steel Browser
title: "[QUESTION] "
labels: 'question'
assignees: ''

---

**Before asking your question**
- [ ] I've checked the [documentation](https://docs.steel.dev/)
- [ ] I've searched existing issues
- [ ] I've looked at the [Steel Cookbook](https://github.com/steel-dev/steel-cookbook) for examples

**What are you trying to do?**
A clear description of what you're trying to accomplish.

**What have you tried?**
Describe what you've already attempted and what happened.

**Code example (if applicable)**
```typescript
// Include relevant code snippets here
```

**Environment**
- Steel Browser version: 
- Node.js version:
- Operating System:
- Browser: Chrome/Chromium version

**Additional context**
Add any other context, error messages, or screenshots that might help.

---

💡 **Tip**: For real-time help and community discussion, consider joining our [Discord server](https://discord.gg/steel-dev)! 

================================================
FILE: .github/auto-assign.yml
================================================
# Auto-assign configuration for Steel Browser
# This file is used by the kentaro-m/auto-assign-action

# Add reviewers to pull requests
addReviewers: true

# Add assignees to pull requests  
addAssignees: false

# Number of reviewers to add to each pull request
numberOfReviewers: 1

# Reviewers to be added to pull requests (GitHub usernames)
reviewers:
  - fukouda

# Skip draft pull requests
skipDraftPR: true

# Only assign reviewers if no reviewers are already assigned
assignWhenNoReviewers: true 

================================================
FILE: .github/labeler.yml
================================================
# GitHub Labeler Configuration for Steel Browser
# This file is used by the actions/labeler@v5 action in pr-checks.yml

# Component labels based on file paths
api:
- changed-files:
  - any-glob-to-any-file: 'api/**/*'
  
ui:
- changed-files:
  - any-glob-to-any-file: 'ui/**/*'
  
documentation:
- changed-files:
  - any-glob-to-any-file: 
    - 'docs/**/*'
    - '*.md'
    - 'README.md'
    - 'CONTRIBUTING.md'
  
plugin-system:
- changed-files:
  - any-glob-to-any-file: 'api/src/services/cdp/plugins/**/*'
  
cdp:
- changed-files:
  - any-glob-to-any-file:
    - 'api/src/services/cdp/**/*'
    - 'api/src/modules/cdp/**/*'
  
session-management:
- changed-files:
  - any-glob-to-any-file:
    - 'api/src/services/session.service.ts'
    - 'api/src/modules/sessions/**/*'
  
file-storage:
- changed-files:
  - any-glob-to-any-file:
    - 'api/src/services/file.service.ts'
    - 'api/src/modules/files/**/*'

docker:
- changed-files:
  - any-glob-to-any-file:
    - 'docker-compose*.yml'
    - 'api/Dockerfile'
    - 'ui/Dockerfile'
    - '.dockerignore'

dependencies:
- changed-files:
  - any-glob-to-any-file:
    - 'package-lock.json'
    - '**/package-lock.json'
    - 'package.json'
    - '**/package.json'

github-actions:
- changed-files:
  - any-glob-to-any-file:
    - '.github/workflows/**/*'
    - '.github/**/*'

testing:
- changed-files:
  - any-glob-to-any-file:
    - '**/*.test.ts'
    - '**/*.test.js'
    - '**/*.spec.ts'
    - '**/*.spec.js'
    - 'tsconfig.test.json' 

================================================
FILE: .github/labels.yml
================================================
# GitHub Labels Configuration for Steel Browser
# This file can be used with the github-labels CLI tool to sync labels

# Type labels
- name: "bug"
  color: "d73a4a"
  description: "Something isn't working"

- name: "enhancement"
  color: "a2eeef"
  description: "New feature or request"

- name: "documentation"
  color: "0075ca"
  description: "Improvements or additions to documentation"

- name: "question"
  color: "d876e3"
  description: "Further information is requested"

# Priority labels
- name: "priority: critical"
  color: "b60205"
  description: "Critical issue that needs immediate attention"

- name: "priority: high"
  color: "d93f0b"
  description: "High priority issue"

- name: "priority: medium"
  color: "fbca04"
  description: "Medium priority issue"

- name: "priority: low"
  color: "0e8a16"
  description: "Low priority issue"

# Difficulty labels
- name: "good first issue"
  color: "7057ff"
  description: "Good for newcomers"

- name: "help wanted"
  color: "008672"
  description: "Extra attention is needed"

- name: "difficulty: easy"
  color: "c2e0c6"
  description: "Easy to implement"

- name: "difficulty: medium"
  color: "fef2c0"
  description: "Moderate difficulty"

- name: "difficulty: hard"
  color: "f9d0c4"
  description: "Hard to implement"

# Component labels
- name: "api"
  color: "1f77b4"
  description: "Related to the API backend"

- name: "ui"
  color: "ff7f0e"
  description: "Related to the frontend UI"

- name: "plugin-system"
  color: "2ca02c"
  description: "Related to the plugin architecture"

- name: "cdp"
  color: "d62728"
  description: "Related to Chrome DevTools Protocol"

- name: "session-management"
  color: "9467bd"
  description: "Related to browser session handling"

- name: "file-storage"
  color: "8c564b"
  description: "Related to file upload/download functionality"

- name: "docker"
  color: "0db7ed"
  description: "Related to Docker configuration"

- name: "testing"
  color: "17becf"
  description: "Related to testing infrastructure"

# Status labels
- name: "status: blocked"
  color: "b60205"
  description: "Blocked by another issue or external dependency"

- name: "status: in progress"
  color: "fbca04"
  description: "Currently being worked on"

- name: "status: needs review"
  color: "0052cc"
  description: "Needs code review"

- name: "status: needs testing"
  color: "1d76db"
  description: "Needs testing before merge"

- name: "status: ready to merge"
  color: "0e8a16"
  description: "Ready to be merged"

# Breaking change labels
- name: "breaking change"
  color: "b60205"
  description: "Introduces breaking changes"

- name: "backwards compatible"
  color: "0e8a16"
  description: "Backwards compatible changes"

# Special labels
- name: "duplicate"
  color: "cfd3d7"
  description: "This issue or pull request already exists"

- name: "invalid"
  color: "e4e669"
  description: "This doesn't seem right"

- name: "wontfix"
  color: "ffffff"
  description: "This will not be worked on"

- name: "dependencies"
  color: "0366d6"
  description: "Pull requests that update a dependency file"

- name: "security"
  color: "d73a4a"
  description: "Security-related issue"

- name: "performance"
  color: "ff9500"
  description: "Performance improvement"

- name: "refactor"
  color: "5319e7"
  description: "Code refactoring"

- name: "chore"
  color: "fef2c0"
  description: "Maintenance tasks" 

================================================
FILE: .github/pull_request_template.md
================================================
## Description

Brief description of the changes in this PR.

## Type of Change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Code refactoring
- [ ] Performance improvement
- [ ] Test addition/update

## Related Issues

Closes #(issue number)
Related to #(issue number)

## Changes Made

- [ ] List specific changes made
- [ ] Include any new files or major modifications
- [ ] Mention any removed functionality

## Testing

- [ ] I have tested this locally
- [ ] I have added/updated unit tests
- [ ] I have added/updated integration tests
- [ ] I have tested with Docker
- [ ] All existing tests pass

## Documentation

- [ ] I have updated relevant documentation
- [ ] I have added JSDoc comments for new public APIs
- [ ] I have updated the README if needed
- [ ] I have updated the CHANGELOG if needed

## Code Quality

- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings or errors

## Breaking Changes

If this is a breaking change, please describe:

1. What breaks:
2. How users should migrate:
3. Why this change is necessary:

## Screenshots (if applicable)

Include screenshots or GIFs for UI changes.

## Additional Notes

Any additional information, concerns, or context for reviewers.

---

## Reviewer Checklist

- [ ] Code follows project conventions and style
- [ ] Changes are well-tested
- [ ] Documentation is updated appropriately
- [ ] No security concerns
- [ ] Performance impact is acceptable
- [ ] Breaking changes are properly documented 

================================================
FILE: .github/workflows/auto-assign.yml
================================================
# .github/workflows/auto-assign.yml
name: Auto Assign

on:
  pull_request:
    types: [opened, ready_for_review]

jobs:
  assign:
    runs-on: ubuntu-latest
    steps:
      - name: Auto Assign
        uses: kentaro-m/auto-assign-action@v1.2.5
        with:
          configuration-path: ".github/auto-assign.yml"


================================================
FILE: .github/workflows/build-docker.yml
================================================
name: Build and Push Latest Docker Image to GHCR

on:
  push:
    branches:
      - main

jobs:
  push_to_registry:
    name: Build and Push Latest Docker Image to GHCR
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ secrets.GH_USERNAME }}
          password: ${{ secrets.GH_TOKEN }}

      - name: Build and push the latest Steel Browser image
        run: |
          docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/steel-dev/steel-browser:latest .

      - name: Build and push the latest Steel Browser API image
        run: |
          docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/steel-dev/steel-browser-api:latest . -f ./api/Dockerfile

      - name: Build and push the latest Steel Browser UI image
        run: |
          docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/steel-dev/steel-browser-ui:latest . -f ./ui/Dockerfile


================================================
FILE: .github/workflows/check-build.yml
================================================
name: Check Docker Build

on:
  pull_request:
    branches:
      - main

jobs:
  check-docker-build:
    name: Check Docker Build
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v3

      - name: Build the latest Steel Browser image
        run: |
          docker build -t steel-browser -f ./Dockerfile .

      - name: Build the latest Steel Browser API image
        run: |
          docker build -t steel-browser-api -f ./api/Dockerfile .
      - name: Build the latest Steel Browser UI image
        run: |
          docker build -t steel-browser-ui -f ./ui/Dockerfile .


================================================
FILE: .github/workflows/pr-checks.yml
================================================
# .github/workflows/pr-checks.yml
name: PR Quality Checks

on:
  pull_request_target:
    branches: [main]

permissions:
  contents: read
  pull-requests: write

jobs:
  validate-pr:
    runs-on: ubuntu-latest
    steps:
      # Check PR title follows conventional commits
      - name: Validate PR Title
        uses: amannn/action-semantic-pull-request@v5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Auto-assign labels based on files changed
      - name: Label PR
        uses: actions/labeler@v5

      # Check for breaking changes
      - name: Check Breaking Changes
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Get list of changed files in this PR
          CHANGED_FILES=$(gh pr view ${{ github.event.pull_request.number }} --json files --jq '.files[].path' | tr '\n' ' ')
          echo "Changed files: $CHANGED_FILES"

          # Check for potential breaking changes in specific files
          if echo "$CHANGED_FILES" | grep -E "(api/src/types/|api/src/steel-browser-plugin.ts|api/src/services/cdp/plugins/core/)"; then
            echo "::warning::Potential breaking changes detected in core APIs"
          fi

          # Check for package.json changes
          if echo "$CHANGED_FILES" | grep -E "package\.json$"; then
            echo "::warning::Package.json changes detected - review dependencies carefully"
          fi


================================================
FILE: .github/workflows/release.yml
================================================
name: Automatic Release

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      # Automatic semantic version bump (major/minor/patch from commit messages)
      - name: Bump version and push tag
        id: bump_version
        uses: phips28/gh-action-bump-version@v11.0.3
        with:
          tag-prefix: "v"
          tag-suffix: "-beta"
          skip-commit: true
          patch-wording: "patch,fix,fixes,docs,feat,feature,minor"
          minor-wording: ""
          major-wording: "breaking,breaking-change,major"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Generate changelog file from previous commits
      - name: Generate changelog
        uses: mikepenz/release-changelog-builder-action@v5
        id: changelog
        with:
          mode: "COMMIT"
          configurationJson: |
            {
                "template": "#{{CHANGELOG}}",
                "commit_template": "- [`#{{MERGE_SHA_SUBSTRING}}`](${{ github.server_url }}/${{ github.repository }}/commit/#{{MERGE_SHA}}): #{{TITLE}} (@#{{AUTHOR}})",
                "custom_placeholders": [
                    {
                        "name": "MERGE_SHA_SUBSTRING",
                        "source": "MERGE_SHA",
                        "transformer": {
                            "pattern": "^(.{6})",
                            "method": "regexr",
                            "target": "$1"
                        }
                    }
                ],
                "categories": [
                    {
                        "title": "## Improvements",
                        "labels": [
                            "feat",
                            "feature"
                        ]
                    },
                    {
                        "title": "## Bug Fixes",
                        "labels": [
                            "fix",
                            "bug"
                        ]
                    },
                    {
                        "title": "## Documentation",
                        "labels": [
                            "docs"
                        ]
                    },
                    {
                        "title": "## Housekeeping",
                        "labels": []
                    }
                ],
                "sort": {
                    "order": "ASC",
                    "on_property": "mergedAt"
                },
                "label_extractor": [
                    {
                        "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: ([\\w ])+([\\s\\S]*)",
                        "on_property": "title",
                        "target": "$1"
                    }
                ]
            }
          toTag: ${{ steps.bump_version.outputs.newTag }}
          fromTag: ""

      # Create automatic GitHub release
      - name: Create GitHub Release
        uses: ncipollo/release-action@v1.18.0
        with:
          token: "${{ secrets.GITHUB_TOKEN }}"
          tag: ${{ steps.bump_version.outputs.newTag }}
          prerelease: false
          name: "Release ${{ steps.bump_version.outputs.newTag }}"
          body: |
            ${{ steps.changelog.outputs.changelog }}

            ---

            ![release-image](https://raw.githubusercontent.com/steel-dev/.github/refs/heads/main/profile/github_hero.png)

            ## Come Hang Out
            - Questions? Join us on [Discord](https://discord.gg/gPpvhNvc5R)
            - Found a bug? Open an issue on [GitHub](https://github.com/steel-dev/steel-browser/issues)


================================================
FILE: .github/workflows/welcome.yml
================================================
# .github/workflows/welcome.yml
name: Welcome

on:
  issues:
    types: [opened]
  pull_request:
    types: [opened]

jobs:
  welcome:
    runs-on: ubuntu-latest
    steps:
      - name: Welcome new contributors
        uses: actions/first-interaction@v1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          issue-message: |
            Thanks for opening your first issue! 🎉 
            Please check our [Contributing Guide](CONTRIBUTING.md) and [Troubleshooting Guide](docs/TROUBLESHOOTING.md).
          pr-message: |
            Thanks for your first contribution! 🚀 
            Please ensure you've followed our [Contributing Guidelines](CONTRIBUTING.md).


================================================
FILE: .gitignore
================================================
node_modules
.pnp
.pnp.js
coverage
.DS_Store
*.pem
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.env
.env.local
.env.development.local
.test.env.local
.env.production.local
!.env.example
production.env
.turbo
build
db/data
*.tsbuildinfo
out
.idea
*.env
dist
.aider*
extensions/*


================================================
FILE: .husky/commit-msg
================================================
npx --no-install commitlint --edit "$1"

================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# Code formatting and linting
echo "🎨 Running code formatting..."
npm run pretty -w api

echo "🔍 Running linting..."
npm run lint -w ui --fix

# Type checking
echo "🔧 Running type checking..."
npm run build

# Add formatted files back to staging
git add -u

echo "✅ Pre-commit checks passed!" 

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Steel Browser

Welcome to Steel Browser! 🎉 We're excited that you're interested in contributing to our open-source browser API. This guide will help you get started and make your first contribution.

## 🚀 Quick Start

### Prerequisites

- **Node.js**: Version 22 or higher
- **npm**: Version 10 or higher  
- **Docker**: For containerized development (optional but recommended)
- **Git**: For version control
- **Chrome/Chromium**: Required for browser automation

### Development Setup

1. **Fork and Clone**
   ```bash
   git clone https://github.com/steel-dev/steel-browser.git
   cd steel-browser
   ```

2. **Install Dependencies**
   ```bash
   npm install
   ```

3. **Start Development Environment**
   ```bash
   # Start both API and UI in development mode
   npm run dev
   
   # Or start individually:
   npm run dev -w api    # API server on http://localhost:3000
   npm run dev -w ui     # UI server on http://localhost:5173
   ```

4. **Verify Setup**
   - API: Visit http://localhost:3000/documentation
   - UI: Visit http://localhost:5173
   - Test REPL: `cd repl && npm start`

### Docker Development (Alternative)

```bash
# Build and run with Docker Compose
docker-compose -f docker-compose.dev.yml up --build

# Or use production images
docker-compose up
```

## 📁 Project Structure

```
steel-browser/
├── api/                    # Backend API (Fastify + Puppeteer)
│   ├── src/
│   │   ├── modules/        # API modules (actions, sessions, etc.)
│   │   ├── plugins/        # Fastify plugins
│   │   ├── services/       # Core services (CDP, file, session)
│   │   └── types/          # TypeScript type definitions
│   └── extensions/         # Browser extensions (must pass name as param to session creation)
├── ui/                     # Frontend UI (React + Vite)
│   └── src/
│       ├── components/     # Reusable UI components
│       ├── containers/     # Page containers
│       └── contexts/       # React contexts
├── repl/                   # Interactive REPL for testing
└── docs/                   # Documentation
```

## 🏗️ Architecture Overview

Steel Browser follows a plugin-based architecture:

### Core Components

1. **Steel Browser Plugin** (`api/src/steel-browser-plugin.ts`)
   - Registers all the necessary services, routes, and hooks
   - Can be used as a standalone plugin or integrated into your own application
   - Provides the core functionality of Steel Browser

2. **CDP Service** (`api/src/services/cdp/cdp.service.ts`)
   - Manages Chrome DevTools Protocol connections
   - Handles browser lifecycle and page management
   - Supports plugin system for extensibility

2. **CDP Plugin System** (`api/src/services/cdp/plugins/`)
   - **BasePlugin**: Abstract base class for all plugins
   - **PluginManager**: Manages plugin lifecycle and events
   - Plugins can hook into browser events (launch, page creation, navigation, etc.)

3. **Session Management** (`api/src/services/session.service.ts`)
   - Manages browser sessions and their state
   - Handles session persistence and cleanup

4. **File Storage** (`api/src/services/file.service.ts`)
   - Manages file uploads, downloads, and storage
   - Supports session-scoped file management


### Using Steel Browser as a Plugin

```typescript
import Fastify from 'fastify';
import steelBrowserPlugin, { SteelBrowserConfig } from './api/src/steel-browser-plugin.js';

const fastify = Fastify({ logger: true });

// Register Steel Browser plugin with configuration
const config: SteelBrowserConfig = {
  fileStorage: {
    maxSizePerSession: 100 * 1024 * 1024, // 100MB
  },
  customWsHandlers: [
    // Your custom WebSocket handlers
  ],
};

await fastify.register(steelBrowserPlugin, config);

// Your additional routes and plugins
await fastify.register(myCustomPlugin);

await fastify.listen({ port: 3000 });
```

### Configuration Options

The `SteelBrowserConfig` interface allows you to customize:

- **fileStorage**: Configure file storage limits per session
- **customWsHandlers**: Add custom WebSocket handlers for real-time features

### CDP Plugin Development

Using the CDP Plugin System, you can create plugins that hook into browser lifecycle events:

```typescript
import { BasePlugin, PluginOptions } from './api/src/services/cdp/plugins/core/base-plugin.js';
import { Browser, Page } from 'puppeteer-core';

export class MyCustomPlugin extends BasePlugin {
  constructor(options: PluginOptions) {
    super({ name: 'my-custom-plugin', ...options });
  }

  async onBrowserLaunch(browser: Browser): Promise<void> {
    this.cdpService?.logger.info('Custom plugin: Browser launched');
    // Your custom logic here
  }

  async onPageCreated(page: Page): Promise<void> {
    this.cdpService?.logger.info('Custom plugin: New page created');
    // Handle new page creation
    await page.setUserAgent('MyCustomBot/1.0');
  }

  async onPageNavigate(page: Page): Promise<void> {
    // Handle page navigation
    const url = page.url();
    this.cdpService?.logger.info(`Custom plugin: Navigated to ${url}`);
  }

  async onBrowserClose(browser: Browser): Promise<void> {
    // Cleanup when browser closes
    this.cdpService?.logger.info('Custom plugin: Browser closed');
  }

  async onShutdown(): Promise<void> {
    // Cleanup when service shuts down
    this.cdpService?.logger.info('Custom plugin: Service shutting down');
  }
}

// Register the plugin
fastify.cdpService.registerPlugin(new MyCustomPlugin({}));
```

### Available Plugin Hooks

The `BasePlugin` class provides these lifecycle hooks:

- `onBrowserLaunch(browser)`: Called when browser instance starts
- `onPageCreated(page)`: Called when a new page is created
- `onPageNavigate(page)`: Called when a page navigates to a new URL
- `onPageUnload(page)`: Called when a page is about to unload
- `onBeforePageClose(page)`: Called before a page is closed
- `onBrowserClose(browser)`: Called when browser instance closes
- `onShutdown()`: Called during service shutdown
- `onSessionEnd(sessionConfig)`: Called when a session ends

## 🛠️ Development Workflow

### Branch Naming Convention

- `feature/description` - New features
- `fix/description` - Bug fixes  
- `docs/description` - Documentation updates
- `refactor/description` - Code refactoring
- `test/description` - Test additions/updates

### Commit Message Format

We use [Conventional Commits](https://conventionalcommits.org/):

```
type(scope): description

[optional body]

[optional footer]
```

**Types:**
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `style`: Code style changes (formatting, etc.)
- `refactor`: Code refactoring
- `test`: Adding or updating tests
- `chore`: Maintenance tasks

**Examples:**
```
feat(api): add session timeout configuration
fix(ui): resolve session list refresh issue  
docs: update plugin development guide
test(api): add CDP service unit tests
```

### Code Style & Formatting

We use automated formatting and linting:

```bash
# Format code (API)
npm run pretty -w api

# Lint code (UI)  
npm run lint -w ui

# These run automatically on commit via Husky
```

**Style Guidelines:**
- Use TypeScript for all new code
- Follow existing patterns and conventions
- Add JSDoc comments for public APIs
- Use descriptive variable and function names
- Keep functions small and focused

### Testing

> **Note**: We're currently building out our test suite! This is a great area for contributions.

```bash
# Tests are currently being set up - for now run these checks:
npm run build  # Type checking for both API and UI
npm run lint -w ui  # UI linting  
npm run pretty -w api  # API code formatting

# When tests become available:
# npm test -w api
# npm test -w ui
```

**Testing Guidelines:**
- Write unit tests for new functions and classes
- Add integration tests for API endpoints
- Include end-to-end tests for critical user flows
- Mock external dependencies appropriately
- Aim for meaningful test coverage, not just high percentages

## 🔄 Pull Request Process

### Before Submitting

1. **Create an Issue** (for non-trivial changes)
   - Describe the problem or feature request
   - Discuss the approach with maintainers
   - Reference the issue in your PR

2. **Test Your Changes**
   ```bash
   # Build and test locally
   npm run build
   # npm test  # tests coming soon
   
   # Test with Docker
   docker-compose -f docker-compose.dev.yml up --build
   ```

3. **Update Documentation**
   - Update relevant README sections
   - Add/update JSDoc comments
   - Update API documentation if needed

### PR Checklist

- [ ] Branch is up-to-date with main
- [ ] Code follows project style guidelines
- [ ] Tests pass (when available)
- [ ] Documentation is updated
- [ ] Commit messages follow conventional format
- [ ] PR description clearly explains changes
- [ ] Breaking changes are documented

### PR Template

```markdown
## Description
Brief description of changes

## Type of Change
- [ ] Bug fix
- [ ] New feature  
- [ ] Breaking change
- [ ] Documentation update

## Testing
- [ ] Tested locally
- [ ] Added/updated tests
- [ ] Tested with Docker

## Related Issues
Fixes #(issue number)
```

## 🐛 Reporting Issues

### Bug Reports

Use our [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include:

- Clear description of the issue
- Steps to reproduce
- Expected vs actual behavior
- Environment details (OS, Node version, etc.)
- Screenshots/logs if applicable

### Feature Requests

- Check existing issues first
- Describe the use case and motivation
- Provide examples of how it would work
- Consider implementation complexity

## 🌟 Good First Issues

Looking for ways to contribute? Check out issues labeled:

- `good first issue` - Perfect for newcomers
- `help wanted` - We'd love community help
- `documentation` - Improve our docs
- `testing` - Help build our test suite

## 📚 Resources

### Learning Resources

- [Puppeteer Documentation](https://pptr.dev/)
- [Fastify Documentation](https://www.fastify.io/)
- [React Documentation](https://react.dev/)
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)

### Project Resources

- [API Documentation](http://localhost:3000/documentation)
- [Steel Cookbook](https://github.com/steel-dev/steel-cookbook) - Usage examples
- [Discord Community](https://discord.gg/steel-dev) - Get help and discuss

## 🤝 Community Guidelines

### Code of Conduct

We are committed to providing a welcoming and inclusive environment for all contributors. Please:

- Be respectful and constructive in discussions
- Help newcomers and answer questions
- Provide helpful feedback in code reviews
- Report any unacceptable behavior to maintainers

### Getting Help

- **Discord**: Join our [Discord server](https://discord.gg/steel-dev) for real-time help
- **GitHub Issues**: For bug reports and feature requests
- **GitHub Discussions**: For questions and general discussion

### Recognition

We appreciate all contributions! Contributors are recognized:

- In our README contributors section
- In our Discord server + Changelog announcements
- Through GitHub's contribution tracking
- In release notes for significant contributions
- Potential invitation to join the core team

## 🔧 Advanced Development

### Environment Variables

Key environment variables for development:

```bash
# API Configuration
NODE_ENV=development
HOST=0.0.0.0
PORT=3000
CHROME_HEADLESS=false  # For debugging
ENABLE_CDP_LOGGING=true  # For detailed logs

# UI Configuration  
API_URL=http://localhost:3000
```

### Debugging

```bash
# Debug API with Chrome DevTools
node --inspect ./api/build/index.js

# Debug with VS Code
# Use the provided launch configurations

# Enable verbose logging
ENABLE_VERBOSE_LOGGING=true npm run dev -w api
```

## 📝 Documentation

### Writing Documentation

- Use clear, concise language
- Include code examples
- Add screenshots for UI features
- Keep examples up-to-date
- Follow markdown best practices

### Documentation Structure

- **README.md**: Project overview and quick start
- **CONTRIBUTING.md**: This file - contribution guidelines
- **API docs**: Auto-generated from OpenAPI schemas
- **Architecture docs**: High-level system design
- **Plugin docs**: Plugin development guides

## 🚀 Release Process

### Automated Releases

We use automated semantic versioning based on conventional commits:

- **Automatic Version Bumping**: Versions are automatically bumped based on commit messages
  - `patch`: Commits with `patch`, `fix`, `fixes`, or `docs` 
  - `minor`: Commits with `feat`, `feature`, or `minor`
  - `major`: Commits with `breaking`, `breaking-change`, or `major`

- **Beta Releases**: All releases are tagged with `-beta` suffix initially
- **Automatic Changelog**: Generated from commit history with categorized changes
- **GitHub Releases**: Automatically created with changelog and community links

### Versioning

We follow [Semantic Versioning](https://semver.org/):

- **MAJOR**: Breaking changes (triggered by `breaking`, `breaking-change`, `major` in commits)
- **MINOR**: New features, backwards compatible (triggered by `feat`, `feature`, `minor`)
- **PATCH**: Bug fixes, backwards compatible (triggered by `patch`, `fix`, `fixes`, `docs`)

### Release Workflow

The release process is fully automated via GitHub Actions:

1. **Push to main**: Any push to the main branch triggers the release workflow
2. **Version bump**: Automatically determines version based on commit messages
3. **Changelog generation**: Creates categorized changelog from commits
4. **GitHub release**: Creates release with changelog and community links
5. **Tag creation**: Tags the release with the new version

### Manual Release Steps (if needed)

If manual intervention is required:

1. Ensure commit messages follow conventional format
2. Push changes to main branch
3. Monitor the GitHub Actions workflow
4. Verify the release was created successfully
5. Announce on Discord/social media

---

## Thank You! 🙏

Thank you for contributing to Steel Browser! Your contributions help make browser automation more accessible and powerful for developers worldwide.

**Happy hacking!** 🎉 

================================================
FILE: Dockerfile
================================================
ARG NODE_VERSION=22.13.0

FROM node:${NODE_VERSION} AS base

WORKDIR /app

ENV NODE_ENV="production" \
    PUPPETEER_CACHE_DIR=/app/.cache \
    DISPLAY=:10 \
    PATH="/usr/bin:/app/selenium/driver:${PATH}" \
    CHROME_BIN=/usr/bin/chromium \
    CHROME_PATH=/usr/bin/chromium

LABEL org.opencontainers.image.source="https://github.com/steel-dev/steel-browser"

# Install dependencies
RUN rm -f /etc/apt/apt.conf.d/docker-clean; \
    echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache; \
    apt-get update -qq && \
    DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade

# Stage 1: Build UI
FROM node:${NODE_VERSION} AS ui-build

WORKDIR /app

# Copy root workspace files for UI build
COPY --link package.json package-lock.json ./
COPY --link ui/ ./ui/

# Install UI dependencies and build with correct base path
RUN npm ci --include=dev -w ui --ignore-scripts
RUN VITE_API_URL="" VITE_WS_URL="" npm run build -w ui -- --base=/ui

# Stage 2: Build API
FROM base AS api-build

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    build-essential \
    pkg-config \
    python-is-python3 \
    xvfb

# Copy root workspace files for API build
COPY --link package.json package-lock.json ./

# Remove or override the prepare script to avoid husky in Docker
RUN npm pkg set scripts.prepare="echo skip husky"

COPY --link api/ ./api/

# Install dependencies for API
RUN npm ci --include=dev --workspace=api

# Install dependencies for recorder extension separately
RUN cd api/extensions/recorder && npm ci --include=dev && cd -

# Build the API package
RUN npm run build -w api

# Build the recorder extension
RUN cd api/extensions/recorder && \
    npm run build && \
    cd -

# Prune dev dependencies
RUN npm prune --omit=dev -w api
RUN cd api/extensions/recorder && npm prune --omit=dev && cd -

# Stage 3: Production
FROM base AS production

# Install production dependencies
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    wget \
    nginx \
    gnupg \
    fonts-ipafont-gothic \
    fonts-wqy-zenhei \
    fonts-thai-tlwg \
    fonts-kacst \
    fonts-freefont-ttf \
    libxss1 \
    xvfb \
    curl \
    unzip \
    default-jre \
    dbus \
    dbus-x11 \
    procps \
    x11-xserver-utils

# Install Chrome and ChromeDriver
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    wget \
    ca-certificates \
    curl \
    unzip \
    # Download and install Chromium
    && apt-get install -y chromium chromium-driver \
    # Clean up
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/cache/apt/*

RUN mkdir -p /files

# Copy the built API from api-build stage
COPY --from=api-build /app /app

# Copy the built UI from ui-build stage into the API container
COPY --from=ui-build /app/ui/dist /app/ui/dist

# Copy entrypoint script
COPY --chmod=755 api/entrypoint.sh /app/api/entrypoint.sh

EXPOSE 3000 9223

ENV HOST_IP=localhost \
    DBUS_SESSION_BUS_ADDRESS=autolaunch:

ENTRYPOINT ["/app/api/entrypoint.sh"]


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<br />
<p align="center">
<a href="https://steel.dev">
  <img src="images/steel_header_logo.png" alt="Steel Logo" width="100">
</a>
</p>



<h3 align="center"><b>Steel</b></h3>
<p align="center">
    <b>The open-source browser API for AI agents & apps.</b> <br />
    The best way to build live web agents and browser automation tools.
</p>

<div align="center">

[![Commit Activity](https://img.shields.io/github/commit-activity/m/steel-dev/steel-browser?color=yellow)](https://github.com/steel-dev/steel-browser/commits/main)
[![License](https://img.shields.io/github/license/steel-dev/steel-browser?color=yellow)](https://github.com/steel-dev/steel-browser/blob/main/LICENSE)
[![Discord](https://img.shields.io/discord/1285696350117167226?label=discord)](https://discord.gg/steel-dev)
[![Twitter Follow](https://img.shields.io/twitter/follow/steeldotdev)](https://twitter.com/steeldotdev)
[![GitHub stars](https://img.shields.io/github/stars/steel-dev/steel-browser)](https://github.com/steel-dev/steel-browser)

</div>

<h4 align="center">
    <a href="https://app.steel.dev/sign-up" target="_blank">
      Get Started
  </a>  ·
    <a href="https://docs.steel.dev/" target="_blank">
      Documentation
  </a>  ·
  <a href="https://steel.dev/" target="_blank">
      Website
  </a> ·
  <a href="https://github.com/steel-dev/steel-cookbook" target="_blank">
      Cookbook
  </a>
</h4>

<p align="center">
  <img src="images/demo.gif" alt="Steel Demo" width="600">
</p>

## ✨ Highlights

[Steel.dev](https://steel.dev) is an open-source browser API that makes it easy to build AI apps and agents that interact with the web. Instead of building automation infrastructure from scratch, you can focus on your AI application while Steel handles the complexity.

Under the hood, it manages sessions, pages, and browser processes, allowing you to perform complex browsing tasks programmatically without any of the headaches:
- **Full Browser Control**: Uses Puppeteer and CDP for complete control over Chrome instances -- allowing you to connect using Puppeteer, Playwright, or Selenium.
- **Session Management**: Maintains browser state, cookies, and local storage across requests
- **Proxy Support**: Built-in proxy chain management for IP rotation
- **Extension Support**: Load custom Chrome extensions for enhanced functionality
- **Debugging Tools**: Built-in request logging and a UI to view/debug sessions with
- **Anti-Detection**: Includes stealth plugins and fingerprint management
- **Resource Management**: Automatic cleanup and browser lifecycle management
- **Browser Tools**: Exposes APIs to quick convert pages to markdown, readability, screenshots, or PDFs.


For detailed API documentation and examples, check out our [API reference](https://docs.steel.dev/api-reference) or explore the Swagger UI directly at `http://0.0.0.0:3000/documentation`.

> Steel is in public beta and evolving every day. Your suggestions, ideas, and reported bugs help us immensely. Do not hesitate to join in the conversation on [Discord](https://discord.gg/steel-dev) or raise a GitHub issue. We read everything, respond to most, and love you.

If you love open-source, AI, and dev tools, [we're hiring across the stack](https://jobs.ashbyhq.com/steel)!

### Make sure to give us a star ⭐

<img width="200" alt="Start us on Github!" src="images/star_img.png">

## 🛠️ Getting Started
The easiest way to get started with Steel is by creating a [Steel Cloud](https://app.steel.dev) account. Otherwise, you can deploy this Steel browser instance to a cloud provider or run it locally.

## ⚡ Quick Deploy
If you're looking to deploy to a cloud provider, we've got you covered.

| Deployment methods | Link |
| -------------------- | ----- |
| Pre-built Docker Image (combined API + UI) | [![Deploy with Github Container Registry](https://img.shields.io/badge/GHCR-478CFF?style=for-the-badge&labelColor=478CFF&logo=github&logoColor=white)](https://github.com/steel-dev/steel-browser/pkgs/container/steel-browser) |
| 1-click deploy to Railway | [![Deploy on Railway](https://img.shields.io/badge/Railway-B039CB?style=for-the-badge&labelColor=B039CB&logo=railway&logoColor=white)](https://railway.app/deploy/steelbrowser) |
| 1-click deploy to Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy) |


## 💻 Running Locally

### Docker

The simplest way to deploy/run a Steel browser instance locally is to run the pre-built Docker image:

```bash
# Pull and run the Docker image
docker run -p 3000:3000 -p 9223:9223 ghcr.io/steel-dev/steel-browser
```

This will start the Steel browser server on port 3000 (http://localhost:3000) and the UI at http://localhost:3000/ui. The 9223 port is used for the console debugger.

You can now create sessions, scrape pages, take screenshots, and more. Jump to the [Usage](#usage) section for some quick examples on how you can do that.

Alternatively, you can run the API and UI separately with docker compose:

```bash
docker compose up
```

For Mac Silicon users, you will need to pass this env flag to the Docker compose command to run the images on the correct platform:
```bash
DOCKER_DEFAULT_PLATFORM=linux/arm64 docker compose up
```

## Quickstart for Contributors
When developing locally, you will need to run the [`docker-compose.dev.yml`](./docker-compose.dev.yml) file instead of the default [`docker-compose.yml`](./docker-compose.yml) file so that your local changes are reflected. Doing this will build the Docker images from the [`api`](./api) and [`ui`](./ui) directories and run the server and UI on port 3000 and 5173 respectively.

```bash
docker compose -f docker-compose.dev.yml up
```

You will also need to run it with `--build` to ensure the Docker images are re-built every time you make changes:

```bash
docker compose -f docker-compose.dev.yml up --build
```

If you run on a custom host, create a `.env` file (see `docs/DEVELOPMENT_SETUP.md` for variables) or modify the environment variables used by `docker-compose.dev.yml` to use your host.

### Node.js
Alternatively, if you have Node.js and Chrome installed, you can run both the server and the UI directly:

```bash
npm install
npm run dev
```

This will also start the Steel server on port 3000 and the UI on port 5173.

Make sure you have the Chrome executable installed and in one of these paths:

- **Linux**:
  `/usr/bin/google-chrome`

- **MacOS**:
  `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`

- **Windows**:
  - `C:\Program Files\Google\Chrome\Application\chrome.exe` OR
  - `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`

#### Custom Chrome Executable

If you have a custom Chrome executable or a different path, you can set the `CHROME_EXECUTABLE_PATH` environment variable to the path of your Chrome executable:

```bash
export CHROME_EXECUTABLE_PATH=/path/to/your/chrome
npm run dev
```

For more details on where this is checked look at [`api/src/utils/browser.ts`](./api/src/utils/browser.ts).

## 🏄🏽‍♂️ Usage
> If you're looking for quick examples on how to use Steel, check out the [Cookbook](https://github.com/steel-dev/steel-cookbook).
>
> Alternatively you can play with the [REPL package](./repl/README.md) too `cd repl` and `npm run start`

There are two main ways to interact with the Steel browser API:
1. [Using Sessions](#sessions)
2. [Using the Quick Actions Endpoints](#quick-actions-api)

In these examples, we assume your custom Steel API endpoint is `http://localhost:3000`.

The full REST OpenAPI documentation can be found [on our site](https://docs.steel.dev/api-reference) and on your local Steel instance at `http://localhost:3000/documentation`.

#### Using the SDKs
If you prefer to use the our Python and Node SDKs, you can install the `steel-sdk` package for Node or Python.

These SDKs are built on top of the REST API and provide a more convenient way to interact with the Steel browser API. They are fully typed, and are compatible with both Steel Cloud and self-hosted Steel instances (changeable using the `baseURL` option on Node and `base_url` on Python).

For more details on installing and using the SDKs, please see the [Node SDK Reference](https://github.com/steel-dev/steel-node/blob/main/api.md) and the [Python SDK Reference](https://github.com/steel-dev/steel-python/blob/main/api.md).


### Sessions
The `/sessions` endpoint lets you relaunch the browser with custom options or extensions (e.g. with a custom proxy) and also reset the browser state. Perfect for complex, stateful workflows that need fine-grained control.

Once you have a session, you can use the session ID or the root URL to interact with the browser. To do this, you will need to use Puppeteer or Playwright. You can find some examples of how to use Puppeteer and Playwright with Steel in the docs below:
* [Puppeteer Integration](https://docs.steel.dev/overview/guides/puppeteer)
* [Playwright with Node](https://docs.steel.dev/overview/guides/playwright-node)
* [Playwright with Python](https://docs.steel.dev/overview/guides/playwright-python)

<details open>
<summary><b>Creating a Session using the Node SDK</b></summary>
<br>

```typescript
import Steel from 'steel-sdk';

const client = new Steel({
  baseURL: "http://localhost:3000", // Custom API Base URL override
});

(async () => {
  try {
    // Create a new browser session with current API fields
    const session = await client.sessions.create({
      blockAds: true,
      proxyUrl: "user:pass@host:port", // optional
      dimensions: { width: 1280, height: 800 }, // optional
    });
    console.log("Created session with ID:", session.id);
  } catch (error) {
    console.error("Error creating session:", error);
  }
})();
````
</details>

<details>
<summary><b>Creating a Session using the Python SDK</b></summary>
<br>

````python
import os
from steel import Steel

client = Steel(
    base_url="http://localhost:3000",  # Custom API Base URL override
)

try:
    # Create a new browser session with custom options
    session = client.sessions.create(
        block_ads=True,
        proxy_url="user:pass@host:port",  # optional
        dimensions={"width": 1280, "height": 800},  # optional
    )
    print("Created session with ID:", session.id)
except Exception as e:
    print("Error creating session:", e)
````
</details>

<details>
<summary><b>Creating a Session using Curl</b></summary>
<br>

```bash
# Launch a new browser session
curl -X POST http://localhost:3000/v1/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "proxyUrl": "user:pass@host:port",
    "blockAds": true,
    "dimensions": { "width": 1280, "height": 800 }
  }'
```
</details>


#### Selenium Sessions
>**Note:** This integration does not support all the features of the CDP-based browser sessions API.

For teams with existing Selenium workflows, the Steel browser provides a drop-in replacement that adds enhanced features while maintaining compatibility. You can simply use the `isSelenium` option to create a Selenium session:

```typescript
// Using the Node SDK
const session = await client.sessions.create({ isSelenium: true });
```
```python
# Using the Python SDK
session = client.sessions.create(is_selenium=True)
```
<details>
<summary><b>Using Curl</b></summary>
<br>

```bash
# Launch a Selenium session
curl -X POST http://localhost:3000/v1/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "isSelenium": true
  }'
```
</details>
<br>

The Selenium API is fully compatible with Selenium's WebDriver protocol, so you can use any existing Selenium clients to connect to the Steel browser. **For more details on using Selenium with Steel, refer to the [Selenium Docs](https://docs.steel.dev/overview/guides/selenium).**

### Quick Actions API
The `/scrape`, `/screenshot`, and `/pdf` endpoints let you quickly extract clean, well-formatted data from any webpage using the running Steel server. Ideal for simple, read-only, on-demand jobs:

<details open>
<summary><b>Scrape a Web Page</b></summary>
<br>

Extract the HTML content of a web page.

```bash
# Example using the Actions API
curl -X POST http://0.0.0.0:3000/v1/scrape \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "delay": 1000
  }'
```
</details>

<details>
<summary><b>Take a Screenshot</b></summary>
<br>

Take a screenshot of a web page.
```bash
# Example using the Actions API
curl -X POST http://0.0.0.0:3000/v1/screenshot \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "fullPage": true
  }' --output screenshot.png
```
</details>

<details>
<summary><b>Download a PDF</b></summary>
<br>

Download a PDF of a web page.
```bash
# Example using the Actions API
curl -X POST http://0.0.0.0:3000/v1/pdf \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com"
  }' --output output.pdf
```
</details>

## Get involved
Steel browser is an open-source project, and we welcome contributions!
- Questions/ideas/feedback? Come hangout on [Discord](https://discord.gg/steel-dev)
- Found a bug? Open an issue on [GitHub](https://github.com/steel-dev/steel-browser/issues)

## License
[Apache 2.0](./LICENSE)

---

Made with ❤️ by the Steel team.


================================================
FILE: api/.dockerignore
================================================
node_modules/
build/
**/.env
**/.env.local
Dockerfile
docker-compose.yml
api/extensions/**/dist


================================================
FILE: api/.env.example
================================================
# Server configuration
NODE_ENV=development
HOST=0.0.0.0
PORT=3000
# Use DOMAIN if you want to specify a full domain name instead of HOST:PORT
# DOMAIN=example.com

# Set to true to use HTTPS/WSS instead of HTTP/WS
USE_SSL=false

# Chrome/CDP configuration
CHROME_HEADLESS=true
CHROME_EXECUTABLE_PATH=
CHROME_ARGS=
CDP_REDIRECT_PORT=9223
# CDP_DOMAIN=example.com:9223

# Optional proxy configuration
PROXY_URL=

# Logging
LOG_LEVEL=warn # fatal, error, warn, info, debug, trace
ENABLE_CDP_LOGGING=false
LOG_CUSTOM_EMIT_EVENTS=false
ENABLE_VERBOSE_LOGGING=false

# Other configuration options
SKIP_FINGERPRINT_INJECTION=false
DEFAULT_TIMEZONE=
DEFAULT_HEADERS=


================================================
FILE: api/.gitattributes
================================================
src/scripts/* linguist-vendored
extensions/* linguist-vendored


================================================
FILE: api/.gitignore
================================================
node_modules
.pnp
.pnp.js
coverage
.DS_Store
*.pem
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.env
.env.local
.env.development.local
.test.env.local
.env.production.local
!.env.example
production.env
.turbo
build
db/data
*.tsbuildinfo
out
.idea
*.env
dist
.aider*
extensions/*
!extensions/recorder
files/*
static/*
# Ignore future changes to this file
src/services/cdp-lifecycle.service.ts

================================================
FILE: api/.prettierignore
================================================
# Ignore artifacts:
build
coverage


================================================
FILE: api/.prettierrc
================================================
{
  "printWidth": 100,
  "tabWidth": 2,
  "semi": true,
  "singleQuote": false,
  "bracketSpacing": true,
  "arrowParens": "always"
}

================================================
FILE: api/.puppeteerrc.cjs
================================================
const { join } = require("path");

/**
 * @type {import("puppeteer").Configuration}
 */
module.exports = {
  defaultProduct: "chrome",
  cacheDirectory: join(__dirname, ".cache", "puppeteer"),
};


================================================
FILE: api/Dockerfile
================================================
ARG NODE_VERSION=22.13.0

FROM node:${NODE_VERSION}-slim AS base

WORKDIR /app

ENV NODE_ENV="production" \
    PUPPETEER_CACHE_DIR=/app/.cache \
    DISPLAY=:10 \
    PATH="/usr/bin:/app/selenium/driver:${PATH}" \
    CHROME_BIN=/usr/bin/chromium \
    CHROME_PATH=/usr/bin/chromium

LABEL org.opencontainers.image.source="https://github.com/steel-dev/steel-browser"

# Install dependencies
RUN rm -f /etc/apt/apt.conf.d/docker-clean; \
    echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache; \
    apt-get update -qq && \
    DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
    apt-get install -y --no-install-recommends \
    expat \
    libxslt1.1 \
    libpam0g \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

FROM base AS build

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    build-essential \
    pkg-config \
    python-is-python3 \
    xvfb

# Copy root workspace files first
COPY --link package.json package-lock.json ./

# Remove or override the prepare script to avoid husky in Docker
RUN npm pkg set scripts.prepare="echo skip husky"

COPY --link api/ ./api/

# Install dependencies for api
RUN npm ci --include=dev --workspace=api

# Install dependencies for recorder extension separately
RUN cd api/extensions/recorder && npm ci --include=dev && cd -

# Build the api package
RUN npm run build -w api

RUN cd api/extensions/recorder && \
    npm run build && \
    cd -

# Prune dev dependencies
RUN npm prune --omit=dev -w api
RUN cd api/extensions/recorder && npm prune --omit=dev && cd -

FROM base AS production
# Install dependencies
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    wget \
    nginx \
    gnupg \
    fonts-ipafont-gothic \
    fonts-wqy-zenhei \
    fonts-thai-tlwg \
    fonts-kacst \
    fonts-freefont-ttf \
    libxss1 \
    xvfb \
    curl \
    unzip \
    default-jre \
    dbus \
    dbus-x11 \
    procps \
    x11-xserver-utils

# Install Chrome and ChromeDriver
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    wget \
    ca-certificates \
    curl \
    unzip \
    # Download and install Chromium
    && apt-get install -y chromium chromium-driver \
    # Clean up
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/cache/apt/*

RUN mkdir -p /files

COPY --chmod=755 api/entrypoint.sh /app/api/entrypoint.sh

EXPOSE 3000 9223

ENV HOST_IP=localhost \
    DBUS_SESSION_BUS_ADDRESS=autolaunch:

ENTRYPOINT ["/app/api/entrypoint.sh"]

COPY --from=build /app /app


================================================
FILE: api/entrypoint.sh
================================================
#!/bin/sh
set -e  # Exit on error

# Function to log with timestamp
log() {
    if [ "$DEBUG" = "true" ]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    fi
}

# Initialize DBus
init_dbus() {
    log "Initializing DBus..."
    mkdir -p /var/run/dbus

    if [ -e /var/run/dbus/pid ]; then
        rm -f /var/run/dbus/pid
    fi

    dbus-daemon --system --fork
    sleep 2  # Give DBus time to initialize

    if dbus-send --system --print-reply --dest=org.freedesktop.DBus \
        /org/freedesktop/DBus org.freedesktop.DBus.ListNames >/dev/null 2>&1; then
        log "DBus initialized successfully"
        return 0
    else
        log "ERROR: DBus failed to initialize"
        return 1
    fi
}

# Verify Chrome and ChromeDriver installation
verify_chrome() {
    log "Verifying Chrome installation..."

    # Check Chrome binary and version
    if [ ! -f "/usr/bin/chromium" ] && [ -z "$CHROME_EXECUTABLE_PATH" ]; then
        log "ERROR: Chrome binary not found at /usr/bin/chromium and CHROME_EXECUTABLE_PATH not set"
        return 1
    fi

    if [ -f "/usr/bin/chromium" ]; then
        chrome_version=$(chromium --version 2>/dev/null || echo "unknown")
    elif [ -n "$CHROME_EXECUTABLE_PATH" ] && [ -f "$CHROME_EXECUTABLE_PATH" ]; then
        chrome_version=$("$CHROME_EXECUTABLE_PATH" --version 2>/dev/null || echo "unknown")
    else
        chrome_version="unknown"
    fi
    log "Chrome version: $chrome_version"

    # Check ChromeDriver binary and version
    if [ ! -f "/usr/bin/chromedriver" ]; then
        log "ERROR: ChromeDriver not found at /usr/bin/chromedriver"
        return 1
    fi

    chromedriver_version=$(chromedriver --version 2>/dev/null || echo "unknown")
    log "ChromeDriver version: $chromedriver_version"

    log "Chrome environment configured successfully"
    return 0
}

# Start nginx with better error handling
start_nginx() {
    if [ "$START_NGINX" = "true" ]; then
        log "Starting nginx..."
        nginx -c /app/api/nginx.conf
        
        # Wait for nginx to start
        max_attempts=10
        attempt=1
        while [ $attempt -le $max_attempts ]; do
            if nginx -t >/dev/null 2>&1; then
                log "Nginx started successfully"
                return 0
            fi
            log "Attempt $attempt/$max_attempts: Waiting for nginx..."
            attempt=$((attempt + 1))
            sleep 1
        done
        log "ERROR: Nginx failed to start properly"
        return 1
    else
        log "Skipping nginx startup (--no-nginx flag detected)"
        return 0
    fi
}

# Main execution
main() {
    # Parse arguments
    START_NGINX=true
    for arg in "$@"; do
        if [ "$arg" = "--no-nginx" ]; then
            START_NGINX=false
            break
        fi
    done
    
    if [ "$DEBUG" = "true" ]; then
        init_dbus || exit 1
        verify_chrome || exit 1
    fi
    start_nginx || exit 1
    
    # Set required environment variables
    export CDP_REDIRECT_PORT=9223
    export DISPLAY=:10
    
    # Log environment state
    log "Environment configuration:"
    log "HOST=$HOST"
    log "CDP_REDIRECT_PORT=$CDP_REDIRECT_PORT"
    log "NODE_ENV=$NODE_ENV"
    
    # Start the application
    # Run the `npm run start` command but without npm.
    # NPM will introduce its own signal handling
    # which will prevent the container from waiting
    # for a session to be released before stopping gracefully
    log "Starting Steel Browser API..."
    exec node ./api/build/index.js
}

main "$@"

================================================
FILE: api/extensions/recorder/.gitignore
================================================
node_modules/
dist/


================================================
FILE: api/extensions/recorder/manifest.json
================================================
{
  "manifest_version": 3,
  "name": "Steel Recording Extension",
  "version": "1.0",
  "permissions": [
    "scripting",
    "activeTab"
  ],
  "host_permissions": [
    "http://localhost:3000/*",
    "http://0.0.0.0:3000/*"
  ],
  "background": {
    "service_worker": "dist/background.js"
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "dist/inject.js"
      ]
    }
  ]
}

================================================
FILE: api/extensions/recorder/package.json
================================================
{
  "name": "steel-recording-extension",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "dependencies": {
    "rrweb": "^2.0.0-alpha.4",
    "@rrweb/packer": "^2.0.0-alpha.4"
  },
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4"
  }
}

================================================
FILE: api/extensions/recorder/src/background.js
================================================
const LOCAL_API_URL = "http://localhost:3000/v1/events";
const FALLBACK_API_URL = "http://0.0.0.0:3000/v1/events"; // Need to point to 0.0.0.0 in some deploys
let currentApiUrl = LOCAL_API_URL;

async function injectScript(tabId, changeInfo, tab) {
  if (changeInfo.status === "complete" && tab.url) {
    try {
      await chrome.scripting.executeScript({
        target: { tabId },
        files: ["inject.js"],
      });
    } catch (error) {
      console.error("Script injection failed:", error);
    }
  }
}

// Listen for tab updates
chrome.tabs.onUpdated.addListener(injectScript);

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type !== "SAVE_EVENTS") {
    return false;
  }

  console.log("[Recorder Background] Saving events to", currentApiUrl);

  const sendEvents = async (url) => {
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(message),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      sendResponse({ success: true });
    } catch (error) {
      if (url === LOCAL_API_URL) {
        // Retry with fallback URL
        currentApiUrl = FALLBACK_API_URL;
        return sendEvents(FALLBACK_API_URL);
      }
      sendResponse({ success: false, error: error.message });
    }
  };

  sendEvents(currentApiUrl);
  return true;
});


================================================
FILE: api/extensions/recorder/src/inject.js
================================================
import { record } from "rrweb";
import { pack } from "@rrweb/packer";

record({
  emit: (event) => {
    chrome.runtime.sendMessage(
      {
        type: "SAVE_EVENTS",
        events: [event],
      },
      (response) => {
        if (!response.success) {
          console.error("[Recorder] Failed to save events:", response.error);
        }
      },
    );
  },
  packFn: pack,
  sampling: {
    media: 800,
  },
  inlineImages: true,
  collectFonts: true,
  recordCrossOriginIframes: true,
  recordCanvas: true,
});

const enableWebRtcSites = ["meet.google.com", "zoom.us", "discord.com"];

try {
  const hostname = new URL(window.location.href).hostname;
  const shouldDisableWebRtc = !enableWebRtcSites.includes(hostname);

  if (shouldDisableWebRtc) {
    navigator.mediaDevices.getUserMedia =
      navigator.webkitGetUserMedia =
      navigator.mozGetUserMedia =
      navigator.getUserMedia =
      webkitRTCPeerConnection =
      RTCPeerConnection =
      MediaStreamTrack =
        undefined;

    Object.defineProperty(window, "RTCPeerConnection", {
      get: () => {
        return {};
      },
    });
    Object.defineProperty(window, "RTCDataChannel", {
      get: () => {
        return {};
      },
    });
  }
} catch (e) {
  console.error(`Error processing URL for WebRTC blocking: ${e}`);
}


================================================
FILE: api/extensions/recorder/webpack.config.mjs
================================================
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default {
  mode: "production",
  entry: {
    inject: path.resolve(__dirname, "src/inject.js"),
    background: path.resolve(__dirname, "src/background.js"),
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
  optimization: {
    minimize: false,
  },
};


================================================
FILE: api/nginx.conf
================================================
events {
    worker_connections 1024;
}

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      keep-alive;
    }
    server {
        listen 9223;
        
        location / {
            proxy_pass http://127.0.0.1:9222;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $host;
            gzip off;
            proxy_set_header Accept-Encoding "";
            proxy_read_timeout 86400;
            proxy_send_timeout 86400;
            proxy_buffering off;
            proxy_request_buffering off;
            chunked_transfer_encoding on;
        }
    }
} 

================================================
FILE: api/openapi/generate.ts
================================================
import { writeFileSync } from "fs";
import { server } from "../src";
import { env } from "../src/env.js";

interface OpenAPIServer {
  url: string;
  description?: string;
}

interface OpenAPIDocument {
  servers?: OpenAPIServer[];
  [key: string]: any;
}

server.ready(() => {
  let openApiJSON = server.swagger() as OpenAPIDocument;

  // Add server URL from environment variables.
  const serverUrl = `http://${env.HOST}:${env.PORT}`;
  if (!openApiJSON.servers) {
    openApiJSON.servers = [];
  }
  openApiJSON.servers.push({
    url: serverUrl,
    description: "Local server from env variables",
  });

  writeFileSync("./openapi/schemas.json", JSON.stringify(openApiJSON, null, 2), "utf-8");
  console.log("OpenAPI JSON has been written to schemas.json");

  server.close(() => {
    console.log("Server closed after generating schemas.");
    process.exit(0);
  });
});


================================================
FILE: api/openapi/schemas.json
================================================
{
  "openapi": "3.0.3",
  "info": {
    "title": "Steel Browser Instance API",
    "description": "Documentation for controlling a single instance of Steel Browser",
    "version": "0.0.1"
  },
  "components": {
    "securitySchemes": {},
    "schemas": {
      "ScrapeRequest": {
        "title": "ScrapeRequest",
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "format": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "html",
                "readability",
                "cleaned_html",
                "markdown"
              ]
            }
          },
          "screenshot": {
            "type": "boolean"
          },
          "pdf": {
            "type": "boolean"
          },
          "proxyUrl": {
            "type": "string",
            "nullable": true,
            "description": "Proxy URL to use for the scrape. Provide `null` to disable proxy. If not provided, current session proxy settings will be used."
          },
          "delay": {
            "type": "number"
          },
          "logUrl": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ScrapeResponse": {
        "title": "ScrapeResponse",
        "type": "object",
        "properties": {
          "content": {
            "type": "object",
            "additionalProperties": {}
          },
          "metadata": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "language": {
                "type": "string"
              },
              "urlSource": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              },
              "description": {
                "type": "string"
              },
              "keywords": {
                "type": "string"
              },
              "author": {
                "type": "string"
              },
              "ogTitle": {
                "type": "string"
              },
              "ogDescription": {
                "type": "string"
              },
              "ogImage": {
                "type": "string"
              },
              "ogUrl": {
                "type": "string"
              },
              "ogSiteName": {
                "type": "string"
              },
              "articleAuthor": {
                "type": "string"
              },
              "publishedTime": {
                "type": "string"
              },
              "modifiedTime": {
                "type": "string"
              },
              "canonical": {
                "type": "string"
              },
              "favicon": {
                "type": "string"
              },
              "jsonLd": {},
              "statusCode": {
                "type": "integer"
              }
            },
            "required": [
              "statusCode"
            ],
            "additionalProperties": false
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              },
              "required": [
                "url",
                "text"
              ],
              "additionalProperties": false
            }
          },
          "screenshot": {
            "type": "string"
          },
          "pdf": {
            "type": "string"
          }
        },
        "required": [
          "content",
          "metadata",
          "links"
        ],
        "additionalProperties": false
      },
      "ScreenshotRequest": {
        "title": "ScreenshotRequest",
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "proxyUrl": {
            "type": "string",
            "nullable": true,
            "description": "Proxy URL to use for the scrape. Provide `null` to disable proxy. If not provided, current session proxy settings will be used."
          },
          "delay": {
            "type": "number"
          },
          "fullPage": {
            "type": "boolean"
          },
          "logUrl": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ScreenshotResponse": {
        "title": "ScreenshotResponse"
      },
      "PDFRequest": {
        "title": "PDFRequest",
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "proxyUrl": {
            "type": "string",
            "nullable": true,
            "description": "Proxy URL to use for the scrape. Provide `null` to disable proxy. If not provided, current session proxy settings will be used."
          },
          "delay": {
            "type": "number"
          },
          "logUrl": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "PDFResponse": {
        "title": "PDFResponse"
      },
      "CreateSession": {
        "title": "CreateSession",
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the session"
          },
          "proxyUrl": {
            "type": "string",
            "description": "Proxy URL to use for the session"
          },
          "userAgent": {
            "type": "string",
            "description": "User agent string to use for the session"
          },
          "sessionContext": {
            "type": "object",
            "properties": {
              "cookies": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "The name of the cookie"
                    },
                    "value": {
                      "type": "string",
                      "description": "The value of the cookie"
                    },
                    "url": {
                      "type": "string",
                      "description": "The URL of the cookie"
                    },
                    "domain": {
                      "type": "string",
                      "description": "The domain of the cookie"
                    },
                    "path": {
                      "type": "string",
                      "description": "The path of the cookie"
                    },
                    "secure": {
                      "type": "boolean",
                      "description": "Whether the cookie is secure"
                    },
                    "httpOnly": {
                      "type": "boolean",
                      "description": "Whether the cookie is HTTP only"
                    },
                    "sameSite": {
                      "type": "string",
                      "enum": [
                        "Strict",
                        "Lax",
                        "None"
                      ],
                      "description": "The same site attribute of the cookie"
                    },
                    "size": {
                      "type": "number",
                      "description": "The size of the cookie"
                    },
                    "expires": {
                      "type": "number",
                      "description": "The expiration date of the cookie"
                    },
                    "partitionKey": {
                      "type": "object",
                      "properties": {
                        "topLevelSite": {
                          "type": "string",
                          "description": "The site of the top-level URL the browser was visiting at the start of the request to the endpoint that set the cookie."
                        },
                        "hasCrossSiteAncestor": {
                          "type": "boolean",
                          "description": "Indicates if the cookie has any ancestors that are cross-site to the topLevelSite."
                        }
                      },
                      "required": [
                        "topLevelSite",
                        "hasCrossSiteAncestor"
                      ],
                      "additionalProperties": false,
                      "description": "The partition key of the cookie"
                    },
                    "session": {
                      "type": "boolean",
                      "description": "Whether the cookie is a session cookie"
                    },
                    "priority": {
                      "type": "string",
                      "enum": [
                        "Low",
                        "Medium",
                        "High"
                      ],
                      "description": "The priority of the cookie"
                    },
                    "sameParty": {
                      "type": "boolean",
                      "description": "Whether the cookie is a same party cookie"
                    },
                    "sourceScheme": {
                      "type": "string",
                      "enum": [
                        "Unset",
                        "NonSecure",
                        "Secure"
                      ],
                      "description": "The source scheme of the cookie"
                    },
                    "sourcePort": {
                      "type": "number",
                      "description": "The source port of the cookie"
                    }
                  },
                  "required": [
                    "name",
                    "value"
                  ],
                  "additionalProperties": false
                },
                "description": "Cookies to initialize in the session"
              },
              "localStorage": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "description": "Domain-specific localStorage items to initialize in the session"
              },
              "sessionStorage": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "description": "Domain-specific sessionStorage items to initialize in the session"
              },
              "indexedDB": {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "number"
                      },
                      "name": {
                        "type": "string"
                      },
                      "data": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "number"
                            },
                            "name": {
                              "type": "string"
                            },
                            "records": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {},
                                  "value": {},
                                  "blobFiles": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "blobNumber": {
                                          "type": "number"
                                        },
                                        "mimeType": {
                                          "type": "string"
                                        },
                                        "size": {
                                          "type": "number"
                                        },
                                        "filename": {
                                          "type": "string"
                                        },
                                        "lastModified": {
                                          "type": "string",
                                          "format": "date-time"
                                        },
                                        "path": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "blobNumber",
                                        "mimeType",
                                        "size"
                                      ],
                                      "additionalProperties": false
                                    }
                                  }
                                },
                                "additionalProperties": false
                              }
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "records"
                          ],
                          "additionalProperties": false
                        }
                      }
                    },
                    "required": [
                      "id",
                      "name",
                      "data"
                    ],
                    "additionalProperties": false
                  }
                },
                "description": "Domain-specific indexedDB items to initialize in the session"
              }
            },
            "additionalProperties": false,
            "description": "Session context data to be used in the created session"
          },
          "isSelenium": {
            "type": "boolean",
            "description": "Indicates if Selenium is used in the session"
          },
          "blockAds": {
            "type": "boolean",
            "description": "Flag to indicate if ads should be blocked in the session"
          },
          "optimizeBandwidth": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "blockImages": {
                    "type": "boolean"
                  },
                  "blockMedia": {
                    "type": "boolean"
                  },
                  "blockStylesheets": {
                    "type": "boolean"
                  },
                  "blockHosts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "blockUrlPatterns": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "additionalProperties": false
              }
            ],
            "description": "Enable bandwidth optimizations. Passing true enables all flags (except hosts/patterns). Object allows granular control."
          },
          "skipFingerprintInjection": {
            "type": "boolean",
            "description": "Flag to indicate if fingerprint injection should be skipped for this session."
          },
          "deviceConfig": {
            "type": "object",
            "properties": {
              "device": {
                "type": "string",
                "enum": [
                  "desktop",
                  "mobile"
                ],
                "default": "desktop"
              }
            },
            "additionalProperties": false,
            "description": "Device configuration for the session. Specify 'mobile' for mobile device fingerprints and configurations."
          },
          "logSinkUrl": {
            "type": "string",
            "description": "Deprecated: Log sink URL to use for the session"
          },
          "extensions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Extensions to use for the session"
          },
          "persist": {
            "type": "boolean",
            "description": "Flag to indicate if session should be persisted"
          },
          "userDataDir": {
            "type": "string",
            "description": "User data directory path to use for the session"
          },
          "timezone": {
            "type": "string",
            "description": "Timezone to use for the session"
          },
          "dimensions": {
            "type": "object",
            "properties": {
              "width": {
                "type": "number"
              },
              "height": {
                "type": "number"
              }
            },
            "required": [
              "width",
              "height"
            ],
            "additionalProperties": false,
            "description": "Dimensions to use for the session"
          },
          "userPreferences": {
            "type": "object",
            "additionalProperties": {},
            "description": "Chrome user preferences to customize browser behavior (e.g., font size, popup blocking, notification settings)"
          },
          "extra": {
            "type": "object",
            "additionalProperties": {},
            "description": "Extra metadata to help initialize the session"
          },
          "credentials": {
            "type": "object",
            "properties": {
              "autoSubmit": {
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "not": {}
                  }
                ]
              },
              "blurFields": {
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "not": {}
                  }
                ]
              },
              "exactOrigin": {
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "not": {}
                  }
                ]
              }
            },
            "additionalProperties": false,
            "description": "Configuration for session credentials"
          },
          "headless": {
            "type": "boolean",
            "description": "Headless mode for the session"
          }
        },
        "additionalProperties": false
      },
      "SessionDetails": {
        "title": "SessionDetails",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the session"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the session started"
          },
          "status": {
            "type": "string",
            "enum": [
              "idle",
              "live",
              "released",
              "failed"
            ],
            "description": "Status of the session"
          },
          "duration": {
            "type": "integer",
            "description": "Duration of the session in milliseconds"
          },
          "eventCount": {
            "type": "integer",
            "description": "Number of events processed in the session"
          },
          "dimensions": {
            "type": "object",
            "properties": {
              "width": {
                "type": "number"
              },
              "height": {
                "type": "number"
              }
            },
            "required": [
              "width",
              "height"
            ],
            "additionalProperties": false,
            "description": "Dimensions used for the session"
          },
          "timeout": {
            "type": "integer",
            "description": "Session timeout duration in milliseconds"
          },
          "creditsUsed": {
            "type": "integer",
            "description": "Amount of credits consumed by the session"
          },
          "websocketUrl": {
            "type": "string",
            "description": "URL for the session's WebSocket connection"
          },
          "debugUrl": {
            "type": "string",
            "description": "URL for a viewing the live browser instance for the session"
          },
          "debuggerUrl": {
            "type": "string",
            "description": "URL for debugging the session"
          },
          "sessionViewerUrl": {
            "type": "string",
            "description": "URL to view session details"
          },
          "userAgent": {
            "type": "string",
            "description": "User agent string used in the session"
          },
          "proxy": {
            "type": "string",
            "description": "Proxy server used for the session"
          },
          "proxyTxBytes": {
            "type": "integer",
            "minimum": 0,
            "description": "Amount of data transmitted through the proxy"
          },
          "proxyRxBytes": {
            "type": "integer",
            "minimum": 0,
            "description": "Amount of data received through the proxy"
          },
          "solveCaptcha": {
            "type": "boolean",
            "description": "Indicates if captcha solving is enabled"
          },
          "isSelenium": {
            "type": "boolean",
            "description": "Indicates if Selenium is used in the session"
          }
        },
        "required": [
          "id",
          "createdAt",
          "status",
          "duration",
          "eventCount",
          "timeout",
          "creditsUsed",
          "websocketUrl",
          "debugUrl",
          "debuggerUrl",
          "sessionViewerUrl",
          "proxyTxBytes",
          "proxyRxBytes"
        ],
        "additionalProperties": false
      },
      "MultipleSessions": {
        "title": "MultipleSessions",
        "type": "object",
        "properties": {
          "sessions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Unique identifier for the session"
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Timestamp when the session started"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "idle",
                    "live",
                    "released",
                    "failed"
                  ],
                  "description": "Status of the session"
                },
                "duration": {
                  "type": "integer",
                  "description": "Duration of the session in milliseconds"
                },
                "eventCount": {
                  "type": "integer",
                  "description": "Number of events processed in the session"
                },
                "dimensions": {
                  "type": "object",
                  "properties": {
                    "width": {
                      "type": "number"
                    },
                    "height": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "width",
                    "height"
                  ],
                  "additionalProperties": false,
                  "description": "Dimensions used for the session"
                },
                "timeout": {
                  "type": "integer",
                  "description": "Session timeout duration in milliseconds"
                },
                "creditsUsed": {
                  "type": "integer",
                  "description": "Amount of credits consumed by the session"
                },
                "websocketUrl": {
                  "type": "string",
                  "description": "URL for the session's WebSocket connection"
                },
                "debugUrl": {
                  "type": "string",
                  "description": "URL for a viewing the live browser instance for the session"
                },
                "debuggerUrl": {
                  "type": "string",
                  "description": "URL for debugging the session"
                },
                "sessionViewerUrl": {
                  "type": "string",
                  "description": "URL to view session details"
                },
                "userAgent": {
                  "type": "string",
                  "description": "User agent string used in the session"
                },
                "proxy": {
                  "type": "string",
                  "description": "Proxy server used for the session"
                },
                "proxyTxBytes": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Amount of data transmitted through the proxy"
                },
                "proxyRxBytes": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Amount of data received through the proxy"
                },
                "solveCaptcha": {
                  "type": "boolean",
                  "description": "Indicates if captcha solving is enabled"
                },
                "isSelenium": {
                  "type": "boolean",
                  "description": "Indicates if Selenium is used in the session"
                }
              },
              "required": [
                "id",
                "createdAt",
                "status",
                "duration",
                "eventCount",
                "timeout",
                "creditsUsed",
                "websocketUrl",
                "debugUrl",
                "debuggerUrl",
                "sessionViewerUrl",
                "proxyTxBytes",
                "proxyRxBytes"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "sessions"
        ],
        "additionalProperties": false
      },
      "SessionContextSchema": {
        "title": "SessionContextSchema",
        "type": "object",
        "properties": {
          "cookies": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "The name of the cookie"
                },
                "value": {
                  "type": "string",
                  "description": "The value of the cookie"
                },
                "url": {
                  "type": "string",
                  "description": "The URL of the cookie"
                },
                "domain": {
                  "type": "string",
                  "description": "The domain of the cookie"
                },
                "path": {
                  "type": "string",
                  "description": "The path of the cookie"
                },
                "secure": {
                  "type": "boolean",
                  "description": "Whether the cookie is secure"
                },
                "httpOnly": {
                  "type": "boolean",
                  "description": "Whether the cookie is HTTP only"
                },
                "sameSite": {
                  "type": "string",
                  "enum": [
                    "Strict",
                    "Lax",
                    "None"
                  ],
                  "description": "The same site attribute of the cookie"
                },
                "size": {
                  "type": "number",
                  "description": "The size of the cookie"
                },
                "expires": {
                  "type": "number",
                  "description": "The expiration date of the cookie"
                },
                "partitionKey": {
                  "type": "object",
                  "properties": {
                    "topLevelSite": {
                      "type": "string",
                      "description": "The site of the top-level URL the browser was visiting at the start of the request to the endpoint that set the cookie."
                    },
                    "hasCrossSiteAncestor": {
                      "type": "boolean",
                      "description": "Indicates if the cookie has any ancestors that are cross-site to the topLevelSite."
                    }
                  },
                  "required": [
                    "topLevelSite",
                    "hasCrossSiteAncestor"
                  ],
                  "additionalProperties": false,
                  "description": "The partition key of the cookie"
                },
                "session": {
                  "type": "boolean",
                  "description": "Whether the cookie is a session cookie"
                },
                "priority": {
                  "type": "string",
                  "enum": [
                    "Low",
                    "Medium",
                    "High"
                  ],
                  "description": "The priority of the cookie"
                },
                "sameParty": {
                  "type": "boolean",
                  "description": "Whether the cookie is a same party cookie"
                },
                "sourceScheme": {
                  "type": "string",
                  "enum": [
                    "Unset",
                    "NonSecure",
                    "Secure"
                  ],
                  "description": "The source scheme of the cookie"
                },
                "sourcePort": {
                  "type": "number",
                  "description": "The source port of the cookie"
                }
              },
              "required": [
                "name",
                "value"
              ],
              "additionalProperties": false
            },
            "description": "Cookies to initialize in the session"
          },
          "localStorage": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            },
            "description": "Domain-specific localStorage items to initialize in the session"
          },
          "sessionStorage": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            },
            "description": "Domain-specific sessionStorage items to initialize in the session"
          },
          "indexedDB": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "number"
                  },
                  "name": {
                    "type": "string"
                  },
                  "data": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "number"
                        },
                        "name": {
                          "type": "string"
                        },
                        "records": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {},
                              "value": {},
                              "blobFiles": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "blobNumber": {
                                      "type": "number"
                                    },
                                    "mimeType": {
                                      "type": "string"
                                    },
                                    "size": {
                                      "type": "number"
                                    },
                                    "filename": {
                                      "type": "string"
                                    },
                                    "lastModified": {
                                      "type": "string",
                                      "format": "date-time"
                                    },
                                    "path": {
                                      "type": "string"
                                    }
                                  },
                                  "required": [
                                    "blobNumber",
                                    "mimeType",
                                    "size"
                                  ],
                                  "additionalProperties": false
                                }
                              }
                            },
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "records"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "id",
                  "name",
                  "data"
                ],
                "additionalProperties": false
              }
            },
            "description": "Domain-specific indexedDB items to initialize in the session"
          }
        },
        "additionalProperties": false
      },
      "RecordedEvents": {
        "title": "RecordedEvents",
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "description": "Events to emit"
          }
        },
        "required": [
          "events"
        ],
        "additionalProperties": false
      },
      "ReleaseSession": {
        "title": "ReleaseSession",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the session"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the session started"
          },
          "status": {
            "type": "string",
            "enum": [
              "idle",
              "live",
              "released",
              "failed"
            ],
            "description": "Status of the session"
          },
          "duration": {
            "type": "integer",
            "description": "Duration of the session in milliseconds"
          },
          "eventCount": {
            "type": "integer",
            "description": "Number of events processed in the session"
          },
          "dimensions": {
            "type": "object",
            "properties": {
              "width": {
                "type": "number"
              },
              "height": {
                "type": "number"
              }
            },
            "required": [
              "width",
              "height"
            ],
            "additionalProperties": false,
            "description": "Dimensions used for the session"
          },
          "timeout": {
            "type": "integer",
            "description": "Session timeout duration in milliseconds"
          },
          "creditsUsed": {
            "type": "integer",
            "description": "Amount of credits consumed by the session"
          },
          "websocketUrl": {
            "type": "string",
            "description": "URL for the session's WebSocket connection"
          },
          "debugUrl": {
            "type": "string",
            "description": "URL for a viewing the live browser instance for the session"
          },
          "debuggerUrl": {
            "type": "string",
            "description": "URL for debugging the session"
          },
          "sessionViewerUrl": {
            "type": "string",
            "description": "URL to view session details"
          },
          "userAgent": {
            "type": "string",
            "description": "User agent string used in the session"
          },
          "proxy": {
            "type": "string",
            "description": "Proxy server used for the session"
          },
          "proxyTxBytes": {
            "type": "integer",
            "minimum": 0,
            "description": "Amount of data transmitted through the proxy"
          },
          "proxyRxBytes": {
            "type": "integer",
            "minimum": 0,
            "description": "Amount of data received through the proxy"
          },
          "solveCaptcha": {
            "type": "boolean",
            "description": "Indicates if captcha solving is enabled"
          },
          "isSelenium": {
            "type": "boolean",
            "description": "Indicates if Selenium is used in the session"
          },
          "success": {
            "type": "boolean",
            "description": "Indicates if the session was successfully released"
          }
        },
        "required": [
          "id",
          "createdAt",
          "status",
          "duration",
          "eventCount",
          "timeout",
          "creditsUsed",
          "websocketUrl",
          "debugUrl",
          "debuggerUrl",
          "sessionViewerUrl",
          "proxyTxBytes",
          "proxyRxBytes",
          "success"
        ],
        "additionalProperties": false
      },
      "SessionStreamQuery": {
        "title": "SessionStreamQuery",
        "type": "object",
        "properties": {
          "showControls": {
            "type": "boolean",
            "default": true,
            "description": "Show controls in the browser iframe"
          },
          "theme": {
            "type": "string",
            "enum": [
              "dark",
              "light"
            ],
            "default": "dark",
            "description": "Theme of the browser iframe"
          },
          "interactive": {
            "type": "boolean",
            "default": true,
            "description": "Make the browser iframe interactive"
          },
          "pageId": {
            "type": "string",
            "description": "Page ID to connect to"
          },
          "pageIndex": {
            "type": "string",
            "description": "Page index (or tab index) to connect to"
          }
        },
        "additionalProperties": false
      },
      "SessionStreamResponse": {
        "title": "SessionStreamResponse",
        "type": "string",
        "description": "HTML content for the session streamer view"
      },
      "SessionLiveDetailsResponse": {
        "title": "SessionLiveDetailsResponse",
        "type": "object",
        "properties": {
          "sessionViewerUrl": {
            "type": "string"
          },
          "sessionViewerFullscreenUrl": {
            "type": "string"
          },
          "websocketUrl": {
            "type": "string"
          },
          "pages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "favicon": {
                  "type": "string",
                  "nullable": true
                }
              },
              "required": [
                "id",
                "url",
                "title",
                "favicon"
              ],
              "additionalProperties": false
            }
          },
          "browserState": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "idle",
                  "live",
                  "released",
                  "failed"
                ]
              },
              "userAgent": {
                "type": "string"
              },
              "browserVersion": {
                "type": "string"
              },
              "initialDimensions": {
                "type": "object",
                "properties": {
                  "width": {
                    "type": "number"
                  },
                  "height": {
                    "type": "number"
                  }
                },
                "required": [
                  "width",
                  "height"
                ],
                "additionalProperties": false
              },
              "pageCount": {
                "type": "number"
              }
            },
            "required": [
              "status",
              "userAgent",
              "browserVersion",
              "initialDimensions",
              "pageCount"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "sessionViewerUrl",
          "sessionViewerFullscreenUrl",
          "websocketUrl",
          "pages",
          "browserState"
        ],
        "additionalProperties": false
      },
      "LogQuerySchema": {
        "title": "LogQuerySchema",
        "type": "object",
        "properties": {
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "eventTypes": {
            "type": "string"
          },
          "pageId": {
            "type": "string"
          },
          "targetType": {
            "type": "string"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "default": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "default": 0
          }
        },
        "additionalProperties": false
      },
      "LogStatsSchema": {
        "title": "LogStatsSchema",
        "type": "object",
        "properties": {
          "totalEvents": {
            "type": "number"
          },
          "oldestEvent": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "newestEvent": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sizeBytes": {
            "type": "number"
          }
        },
        "required": [
          "totalEvents",
          "oldestEvent",
          "newestEvent",
          "sizeBytes"
        ],
        "additionalProperties": false
      },
      "LogQueryResultSchema": {
        "title": "LogQueryResultSchema",
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {}
            }
          },
          "total": {
            "type": "number"
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "required": [
          "events",
          "total",
          "hasMore"
        ],
        "additionalProperties": false
      },
      "ExportLogsSchema": {
        "title": "ExportLogsSchema",
        "type": "object",
        "properties": {
          "query": {
            "type": "object",
            "properties": {
              "startTime": {
                "type": "string",
                "format": "date-time"
              },
              "endTime": {
                "type": "string",
                "format": "date-time"
              },
              "eventTypes": {
                "type": "string"
              },
              "pageId": {
                "type": "string"
              },
              "targetType": {
                "type": "string"
              },
              "limit": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000,
                "default": 100
              },
              "offset": {
                "type": "integer",
                "minimum": 0,
                "default": 0
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "GetDevtoolsUrlSchema": {
        "title": "GetDevtoolsUrlSchema",
        "type": "object",
        "properties": {
          "pageId": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "LaunchRequest": {
        "title": "LaunchRequest",
        "type": "object",
        "properties": {
          "options": {
            "type": "object",
            "properties": {
              "args": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "chromiumSandbox": {
                "type": "boolean"
              },
              "devtools": {
                "type": "boolean"
              },
              "downloadsPath": {
                "type": "string"
              },
              "headless": {
                "type": "boolean"
              },
              "ignoreDefaultArgs": {
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                ]
              },
              "proxyUrl": {
                "type": "string"
              },
              "timeout": {
                "type": "number"
              },
              "tracesDir": {
                "type": "string"
              }
            },
            "additionalProperties": false
          },
          "req": {},
          "stealth": {
            "type": "boolean"
          },
          "cookies": {
            "type": "array"
          },
          "userAgent": {
            "type": "string"
          },
          "extensions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "logSinkUrl": {
            "type": "string",
            "description": "Deprecated"
          },
          "customHeaders": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "timezone": {
            "type": "string"
          },
          "dimensions": {
            "type": "object",
            "properties": {
              "width": {
                "type": "number"
              },
              "height": {
                "type": "number"
              }
            },
            "required": [
              "width",
              "height"
            ],
            "additionalProperties": false,
            "nullable": true
          }
        },
        "required": [
          "options"
        ],
        "additionalProperties": false
      },
      "LaunchResponse": {
        "title": "LaunchResponse",
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          }
        },
        "required": [
          "success"
        ],
        "additionalProperties": false
      },
      "FileUploadRequest": {
        "title": "FileUploadRequest",
        "type": "object",
        "properties": {
          "file": {
            "description": "The file to upload (binary) or URL string to download from"
          },
          "path": {
            "type": "string",
            "description": "Path to the file in the storage system"
          }
        },
        "additionalProperties": false
      },
      "FileDetails": {
        "title": "FileDetails",
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Path to the file in the storage system"
          },
          "size": {
            "type": "number",
            "description": "Size of the file in bytes"
          },
          "lastModified": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the file was last updated"
          }
        },
        "required": [
          "path",
          "size",
          "lastModified"
        ],
        "additionalProperties": false
      },
      "MultipleFiles": {
        "title": "MultipleFiles",
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Path to the file in the storage system"
                },
                "size": {
                  "type": "number",
                  "description": "Size of the file in bytes"
                },
                "lastModified": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Timestamp when the file was last updated"
                }
              },
              "required": [
                "path",
                "size",
                "lastModified"
              ],
              "additionalProperties": false
            },
            "description": "Array of files for the current page"
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      }
    }
  },
  "paths": {
    "/v1/scrape": {
      "post": {
        "operationId": "scrape",
        "summary": "Scrape a URL",
        "tags": [
          "Browser Actions"
        ],
        "description": "Scrape a URL",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScrapeResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/screenshot": {
      "post": {
        "operationId": "screenshot",
        "summary": "Take a screenshot",
        "tags": [
          "Browser Actions"
        ],
        "description": "Take a screenshot",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScreenshotRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScreenshotResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/pdf": {
      "post": {
        "operationId": "pdf",
        "summary": "Get the PDF content of a page",
        "tags": [
          "Browser Actions"
        ],
        "description": "Get the PDF content of a page",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PDFRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PDFResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "summary": "Check if the server and browser are running",
        "tags": [
          "Health"
        ],
        "description": "Check if the server and browser are running",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/sessions": {
      "post": {
        "operationId": "launch_browser_session",
        "summary": "Launch a browser session",
        "tags": [
          "Sessions"
        ],
        "description": "Launch a browser session",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSession"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDetails"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "get_sessions",
        "summary": "Get all sessions",
        "tags": [
          "Sessions"
        ],
        "description": "Get all sessions",
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MultipleSessions"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{sessionId}": {
      "get": {
        "operationId": "get_session_details",
        "summary": "Get session details",
        "tags": [
          "Sessions"
        ],
        "description": "Get session details",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{sessionId}/context": {
      "get": {
        "operationId": "get_browser_context",
        "summary": "Get a browser context",
        "tags": [
          "Sessions"
        ],
        "description": "Get a browser context",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionContextSchema"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{sessionId}/release": {
      "post": {
        "operationId": "release_browser_session",
        "summary": "Release a browser session",
        "tags": [
          "Sessions"
        ],
        "description": "Release a browser session",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseSession"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/release": {
      "post": {
        "operationId": "release_browser_sessions",
        "summary": "Release browser sessions",
        "tags": [
          "Sessions"
        ],
        "description": "Release browser sessions",
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseSession"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/debug": {
      "get": {
        "operationId": "get_session_debugger_stream",
        "summary": "Get session debugger view",
        "tags": [
          "Sessions"
        ],
        "description": "Returns an HTML page with a live debugger view of the session",
        "parameters": [
          {
            "schema": {
              "type": "boolean",
              "default": true
            },
            "in": "query",
            "name": "showControls",
            "required": false,
            "description": "Show controls in the browser iframe"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "dark",
                "light"
              ],
              "default": "dark"
            },
            "in": "query",
            "name": "theme",
            "required": false,
            "description": "Theme of the browser iframe"
          },
          {
            "schema": {
              "type": "boolean",
              "default": true
            },
            "in": "query",
            "name": "interactive",
            "required": false,
            "description": "Make the browser iframe interactive"
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "pageId",
            "required": false,
            "description": "Page ID to connect to"
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "pageIndex",
            "required": false,
            "description": "Page index (or tab index) to connect to"
          }
        ],
        "responses": {
          "200": {
            "description": "HTML content for the session streamer view",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionStreamResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/events": {
      "post": {
        "operationId": "receive_events",
        "summary": "Receive recorded events from the browser",
        "tags": [
          "Sessions"
        ],
        "description": "Receive recorded events from the browser",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordedEvents"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/sessions/{id}/live-details": {
      "get": {
        "operationId": "get_session_live_details",
        "summary": "Get session live details",
        "tags": [
          "Sessions"
        ],
        "description": "Returns the live state of the session, including pages, tabs, and browser state",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionLiveDetailsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/scrape": {
      "post": {
        "operationId": "scrape_session",
        "summary": "Scrape Current Session",
        "tags": [
          "Sessions"
        ],
        "description": "Scrape Current Session",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScrapeResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/screenshot": {
      "post": {
        "operationId": "screenshot_session",
        "summary": "Take Screenshot of Current Session",
        "tags": [
          "Sessions"
        ],
        "description": "Take Screenshot of Current Session",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScreenshotRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScreenshotResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/pdf": {
      "post": {
        "operationId": "pdf_session",
        "summary": "Generate PDF of Current Session",
        "tags": [
          "Sessions"
        ],
        "description": "Generate PDF of Current Session",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PDFRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PDFResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/devtools/inspector.html": {
      "get": {
        "operationId": "getDevtoolsUrl",
        "summary": "Get the URL for the DevTools inspector",
        "tags": [
          "CDP"
        ],
        "description": "Get the URL for the DevTools inspector",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "pageId",
            "required": false
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/sessions/{sessionId}/files": {
      "post": {
        "operationId": "upload_file",
        "summary": "Upload a file",
        "tags": [
          "Files"
        ],
        "description": "Uploads a file to a session via `multipart/form-data` with a `file` field that accepts either binary data or a URL string to download from, and an optional `path` field for the file storage path.",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/FileUploadRequest"
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileDetails"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "list_files",
        "summary": "List files",
        "tags": [
          "Files"
        ],
        "description": "List all files from the session in descending order.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MultipleFiles"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete_all_files",
        "summary": "Delete all files",
        "tags": [
          "Files"
        ],
        "description": "Delete all files from a session",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          }
        }
      }
    },
    "/v1/sessions/{sessionId}/files/{*}": {
      "get": {
        "operationId": "download_file",
        "summary": "Download a file",
        "tags": [
          "Files"
        ],
        "description": "Download a file from a session",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "*",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "delete": {
        "operationId": "delete_file",
        "summary": "Delete a file",
        "tags": [
          "Files"
        ],
        "description": "Delete a file from a session",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "*",
            "required": true
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          }
        }
      }
    },
    "/v1/sessions/{sessionId}/files.zip": {
      "get": {
        "operationId": "download_archive",
        "summary": "Download archive",
        "tags": [
          "Files"
        ],
        "description": "Download all files from the session as a zip archive.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "sessionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/logs/query": {
      "get": {
        "tags": [
          "Logs"
        ],
        "description": "Query browser logs from local storage",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "startTime",
            "required": false
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "endTime",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "eventTypes",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "pageId",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "targetType",
            "required": false
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            },
            "in": "query",
            "name": "limit",
            "required": false
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "in": "query",
            "name": "offset",
            "required": false
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/logs/stats": {
      "get": {
        "tags": [
          "Logs"
        ],
        "description": "Get statistics about stored browser logs",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/logs/stream": {
      "get": {
        "tags": [
          "Logs"
        ],
        "description": "Stream browser logs in real-time using SSE",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/logs/export": {
      "post": {
        "tags": [
          "Logs"
        ],
        "description": "Export browser logs to Parquet format",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "startTime",
            "required": false
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "endTime",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "eventTypes",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "pageId",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "targetType",
            "required": false
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            },
            "in": "query",
            "name": "limit",
            "required": false
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "in": "query",
            "name": "offset",
            "required": false
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/v1/logs/": {
      "delete": {
        "tags": [
          "Logs"
        ],
        "description": "Clear all browser logs from storage",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "http://0.0.0.0:3000/",
      "description": "Local server"
    },
    {
      "url": "http://0.0.0.0:3000",
      "description": "Local server from env variables"
    }
  ]
}

================================================
FILE: api/package.json
================================================
{
  "name": "@steel-browser/api",
  "version": "0.5.1",
  "description": "",
  "main": "index.js",
  "type": "module",
  "private": true,
  "exports": {
    "./plugin": {
      "import": {
        "types": "./build/steel-browser-plugin.d.ts",
        "default": "./build/steel-browser-plugin.js"
      }
    },
    "./cdp-plugin": {
      "import": {
        "types": "./build/services/cdp/plugins/core/base-plugin.d.ts",
        "default": "./build/services/cdp/plugins/core/base-plugin.js"
      }
    },
    "./logger": {
      "import": {
        "types": "./build/plugins/logging/browser-logger.d.ts",
        "default": "./build/plugins/logging/browser-logger.js"
      }
    },
    "./storage": {
      "import": {
        "types": "./build/services/cdp/instrumentation/storage/log-storage.interface.d.ts",
        "default": "./build/services/cdp/instrumentation/storage/log-storage.interface.js"
      }
    }
  },
  "scripts": {
    "start": "node ./build/index.js",
    "build": "tsc && npm run copy:templates && npm run copy:fingerprint",
    "lint": "eslint . --ext ts --report-unused-disable-directives --max-warnings 10",
    "copy:templates": "mkdir -p build/templates && cp -r src/templates/* build/templates/",
    "copy:fingerprint": "cp src/scripts/fingerprint.js build/scripts/fingerprint.js",
    "prepare:recorder": "cd extensions/recorder && npm install && npm run build",
    "dev": "npm run prepare:recorder && tsx watch src/index.ts",
    "test": "echo \"Error: no test specified\" && exit 1",
    "pretty": "prettier --write \"src/**/*.ts\"",
    "generate:openapi": "tsx ./openapi/generate.ts"
  },
  "author": "Nasr Mohamed",
  "devDependencies": {
    "@opentelemetry/api": "1.9.0",
    "@types/archiver": "^6.0.3",
    "@types/iconv-lite": "^0.0.1",
    "@types/json-stringify-safe": "^5.0.3",
    "@types/lodash-es": "^4.17.12",
    "@types/markdownlint": "^0.13.0",
    "@types/mime-types": "^2.1.4",
    "@types/node": "^22.14.1",
    "@types/turndown": "^5.0.5",
    "@types/ws": "^8.5.14",
    "fastify": "^5.2.1",
    "prettier": "3.0.3",
    "ts-node": "^10.9.2",
    "tsx": "^4.19.3",
    "typescript": "^5.7.3",
    "vite": "^6.3.5",
    "vitest": "^3.2.4"
  },
  "dependencies": {
    "@adobe/css-tools": "^4.4.3",
    "@fastify/cors": "^10.0.2",
    "@fastify/multipart": "^9.0.3",
    "@fastify/reply-from": "^12.0.2",
    "@fastify/sensible": "^6.0.3",
    "@fastify/static": "^8.1.1",
    "@fastify/swagger": "^9.4.2",
    "@fastify/view": "10.0.2",
    "@joplin/turndown": "^4.0.80",
    "@scalar/fastify-api-reference": "^1.25.116",
    "archiver": "^7.0.1",
    "axios": "^1.12.0",
    "cheerio": "^1.1.2",
    "chokidar": "^4.0.3",
    "defuddle": "^0.6.4",
    "dotenv": "^16.4.7",
    "duckdb-async": "^1.1.3",
    "ejs": "^3.1.10",
    "fastify-plugin": "^5.0.1",
    "file-type": "^20.4.1",
    "fingerprint-generator": "2.1.72",
    "fingerprint-injector": "2.1.72",
    "http-proxy": "^1.18.1",
    "https-proxy-agent": "^7.0.6",
    "iconv-lite": "^0.6.3",
    "jsdom": "^24.1.3",
    "json-stringify-safe": "^5.0.1",
    "level": "^9.0.0",
    "lodash-es": "^4.17.21",
    "markdownlint": "^0.38.0",
    "mime-types": "^2.1.35",
    "pdf2html": "^4.4.0",
    "pino": "^9.6.0",
    "pino-pretty": "^13.0.0",
    "proxy-chain": "^2.5.6",
    "puppeteer-core": "23.6.0",
    "socks-proxy-agent": "^8.0.5",
    "uuid": "^11.0.5",
    "zod": "^3.24.2",
    "zod-to-json-schema": "^3.24.1"
  },
  "overrides": {
    "cross-spawn": "^7.0.6",
    "tar-fs": ">=3.1.1"
  },
  "peerDependencies": {
    "@opentelemetry/api": "1.9.0",
    "fastify": "^5.0.0"
  },
  "peerDependenciesMeta": {
    "fastify": {
      "optional": false
    },
    "@opentelemetry/api": {
      "optional": true
    }
  }
}

================================================
FILE: api/selenium/driver/LICENSE.chromedriver
================================================
// Copyright 2015 The Chromium Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: api/selenium/driver/THIRD_PARTY_NOTICES.chromedriver
================================================
--------------------
License notice for Google Double Conversion
--------------------
Copyright 2006-2011, the V8 project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.
    * Neither the name of Google Inc. nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

--------------------
License notice for QUICHE
--------------------
// Copyright 2015 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

--------------------
License notice for Abseil
--------------------

                                 Apache License
                           Version 2.0, January 2004
                        https://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       https://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


--------------------
License notice for Implementation of WebDriver BiDi standard
--------------------
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
--------------------
License notice for mitt
--------------------
MIT License

Copyright (c) 2021 Jason Miller

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

--------------------
License notice for urlpattern-polyfill
--------------------
Copyright 2020 Intel Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

--------------------
License notice for zod
--------------------
MIT License

Copyright (c) 2020 Colin McDonnell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

--------------------
License notice for WebKit
--------------------
(WebKit doesn't distribute an explicit license.  This LICENSE is derived from
license text in the source.)

Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2006, 2007 Alexander Kellett, Alexey Proskuryakov, Alex Mathews, Allan
Sandfeld Jensen, Alp Toker, Anders Carlsson, Andrew Wellington, Antti
Koivisto, Apple Inc., Arthur Langereis, Baron Schwartz, Bjoern Graf,
Brent Fulgham, Cameron Zwarich, Charles Samuels, Christian Dywan,
Collabora Ltd., Cyrus Patel, Daniel Molkentin, Dave Maclachlan, David
Smith, Dawit Alemayehu, Dirk Mueller, Dirk Schulze, Don Gibson, Enrico
Ros, Eric Seidel, Frederik Holljen, Frerich Raabe, Friedmann Kleint,
George Staikos, Google Inc., Graham Dennis, Harri Porten, Henry Mason,
Hiroyuki Ikezoe, Holger Hans Peter Freyther, IBM, James G. Speth, Jan
Alonzo, Jean-Loup Gailly, John Reis, Jonas Witt, Jon Shier, Jonas
Witt, Julien Chaffraix, Justin Haygood, Kevin Ollivier, Kevin Watters,
Kimmo Kinnunen, Kouhei Sutou, Krzysztof Kowalczyk, Lars Knoll, Luca
Bruno, Maks Orlovich, Malte Starostik, Mark Adler, Martin Jones,
Marvin Decker, Matt Lilek, Michael Emmel, Mitz Pettel, mozilla.org,
Netscape Communications Corporation, Nicholas Shanks, Nikolas
Zimmermann, Nokia, Oliver Hunt, Opened Hand, Paul Johnston, Peter
Kelly, Pioneer Research Center USA, Rich Moore, Rob Buis, Robin Dunn,
Ronald Tschalär, Samuel Weinig, Simon Hausmann, Staikos Computing
Services Inc., Stefan Schimanski, Symantec Corporation, The Dojo
Foundation, The Karbon Developers, Thomas Boyer, Tim Copperfield,
Tobias Anton, Torben Weis, Trolltech, University of Cambridge, Vaclav
Slavik, Waldo Bastian, Xan Lopez, Zack Rusin

The terms and conditions vary from file to file, but are one of:

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.

*OR*

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.
3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
   its contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY

OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


                  GNU LIBRARY GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1991 Free Software Foundation, Inc.
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the library GPL.  It is
 numbered 2 because it goes with version 2 of the ordinary GPL.]

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Library General Public License, applies to some
specially designated Free Software Foundation software, and to any
other libraries whose authors decide to use it.  You can use it for
your libraries, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if
you distribute copies of the library, or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link a program with the library, you must provide
complete object files to the recipients so that they can relink them
with the library, after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  Our method of protecting your rights has two steps: (1) copyright
the library, and (2) offer you this license which gives you legal
permission to copy, distribute and/or modify the library.

  Also, for each distributor's protection, we want to make certain
that everyone understands that there is no warranty for this free
library.  If the library is modified by someone else and passed on, we
want its recipients to know that what they have is not the original
version, so that any problems introduced by others will not reflect on
the original authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that companies distributing free
software will individually obtain patent licenses, thus in effect
transforming the program into proprietary software.  To prevent this,
we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.

  Most GNU software, including some libraries, is covered by the ordinary
GNU General Public License, which was designed for utility programs.  This
license, the GNU Library General Public License, applies to certain
designated libraries.  This license is quite different from the ordinary
one; be sure to read it in full, and don't assume that anything in it is
the same as in the ordinary license.

  The reason we have a separate public license for some libraries is that
they blur the distinction we usually make between modifying or adding to a
program and simply using it.  Linking a program with a library, without
changing the library, is in some sense simply using the library, and is
analogous to running a utility program or application program.  However, in
a textual and legal sense, the linked executable is a combined work, a
derivative of the original library, and the ordinary General Public License
treats it as such.

  Because of this blurred distinction, using the ordinary General
Public License for libraries did not effectively promote software
sharing, because most developers did not use the libraries.  We
concluded that weaker conditions might promote sharing better.

  However, unrestricted linking of non-free programs would deprive the
users of those programs of all benefit from the free status of the
libraries themselves.  This Library General Public License is intended to
permit developers of non-free programs to use free libraries, while
preserving your freedom as a user of such programs to change the free
libraries that are incorporated in them.  (We have not seen how to achieve
this as regards changes in header files, but we have achieved it as regards
changes in the actual functions of the Library.)  The hope is that this
will lead to faster development of free libraries.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, while the latter only
works together with the library.

  Note that it is possib
Download .txt
gitextract_z2lghh3p/

├── .dockerignore
├── .env.example
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   ├── feature_request.md
│   │   └── question.md
│   ├── auto-assign.yml
│   ├── labeler.yml
│   ├── labels.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── auto-assign.yml
│       ├── build-docker.yml
│       ├── check-build.yml
│       ├── pr-checks.yml
│       ├── release.yml
│       └── welcome.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── api/
│   ├── .dockerignore
│   ├── .env.example
│   ├── .gitattributes
│   ├── .gitignore
│   ├── .prettierignore
│   ├── .prettierrc
│   ├── .puppeteerrc.cjs
│   ├── Dockerfile
│   ├── entrypoint.sh
│   ├── extensions/
│   │   └── recorder/
│   │       ├── .gitignore
│   │       ├── manifest.json
│   │       ├── package.json
│   │       ├── src/
│   │       │   ├── background.js
│   │       │   └── inject.js
│   │       └── webpack.config.mjs
│   ├── nginx.conf
│   ├── openapi/
│   │   ├── generate.ts
│   │   └── schemas.json
│   ├── package.json
│   ├── selenium/
│   │   ├── driver/
│   │   │   ├── LICENSE.chromedriver
│   │   │   ├── THIRD_PARTY_NOTICES.chromedriver
│   │   │   └── chromedriver2
│   │   └── server/
│   │       └── selenium-server.jar
│   ├── src/
│   │   ├── config.ts
│   │   ├── env.ts
│   │   ├── index.ts
│   │   ├── modules/
│   │   │   ├── actions/
│   │   │   │   ├── actions.controller.ts
│   │   │   │   ├── actions.routes.ts
│   │   │   │   └── actions.schema.ts
│   │   │   ├── cdp/
│   │   │   │   ├── cdp.routes.ts
│   │   │   │   └── cdp.schemas.ts
│   │   │   ├── files/
│   │   │   │   ├── files.controller.ts
│   │   │   │   ├── files.routes.ts
│   │   │   │   └── files.schema.ts
│   │   │   ├── logs/
│   │   │   │   ├── logs.routes.ts
│   │   │   │   └── logs.schema.ts
│   │   │   ├── selenium/
│   │   │   │   ├── selenium.routes.ts
│   │   │   │   └── selenium.schema.ts
│   │   │   └── sessions/
│   │   │       ├── sessions.controller.ts
│   │   │       ├── sessions.routes.ts
│   │   │       └── sessions.schema.ts
│   │   ├── plugins/
│   │   │   ├── browser-session.ts
│   │   │   ├── browser-socket/
│   │   │   │   ├── browser-socket.ts
│   │   │   │   ├── casting.handler.ts
│   │   │   │   └── handlers/
│   │   │   │       ├── cast.handler.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── logs.handler.ts
│   │   │   │       ├── pageId.handler.ts
│   │   │   │       └── recording.handler.ts
│   │   │   ├── browser.ts
│   │   │   ├── custom-body-parser.ts
│   │   │   ├── file-storage.ts
│   │   │   ├── request-logger.ts
│   │   │   ├── scalar-theme.ts
│   │   │   ├── schemas.ts
│   │   │   ├── selenium.ts
│   │   │   └── ui-plugin.ts
│   │   ├── routes.ts
│   │   ├── scripts/
│   │   │   ├── fingerprint.js
│   │   │   └── index.ts
│   │   ├── services/
│   │   │   ├── cdp/
│   │   │   │   ├── cdp.service.ts
│   │   │   │   ├── errors/
│   │   │   │   │   └── launch-errors.ts
│   │   │   │   ├── instrumentation/
│   │   │   │   │   ├── browser-logger.test.ts
│   │   │   │   │   ├── browser-logger.ts
│   │   │   │   │   ├── cdp-events.ts
│   │   │   │   │   ├── extension-events.ts
│   │   │   │   │   ├── page-console.ts
│   │   │   │   │   ├── page-events.ts
│   │   │   │   │   ├── storage/
│   │   │   │   │   │   ├── duckdb-storage.ts
│   │   │   │   │   │   ├── in-memory-storage.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── log-storage.interface.ts
│   │   │   │   │   │   └── safe-json.ts
│   │   │   │   │   ├── target-manager.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── worker-events.ts
│   │   │   │   ├── plugins/
│   │   │   │   │   ├── core/
│   │   │   │   │   │   ├── base-plugin.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── plugin-manager.ts
│   │   │   │   │   └── pptr-extensions.d.ts
│   │   │   │   └── utils/
│   │   │   │       ├── error-handlers.ts
│   │   │   │       └── validation.ts
│   │   │   ├── context/
│   │   │   │   ├── chrome-context.service.ts
│   │   │   │   └── types.ts
│   │   │   ├── file.service.ts
│   │   │   ├── leveldb/
│   │   │   │   ├── localstorage.ts
│   │   │   │   └── sessionstorage.ts
│   │   │   ├── selenium.service.ts
│   │   │   ├── session.service.ts
│   │   │   ├── timezone-fetcher.service.ts
│   │   │   └── websocket-registry.service.ts
│   │   ├── steel-browser-plugin.ts
│   │   ├── telemetry/
│   │   │   ├── noop.ts
│   │   │   └── tracer.ts
│   │   ├── templates/
│   │   │   └── live-session-streamer.ejs
│   │   ├── types/
│   │   │   ├── browser.ts
│   │   │   ├── casting.ts
│   │   │   ├── enums.ts
│   │   │   ├── fastify.d.ts
│   │   │   ├── index.ts
│   │   │   ├── turndown.d.ts
│   │   │   └── websocket.ts
│   │   └── utils/
│   │       ├── browser.ts
│   │       ├── casting.ts
│   │       ├── context.ts
│   │       ├── errors.ts
│   │       ├── extensions.ts
│   │       ├── leveldb.ts
│   │       ├── logging.ts
│   │       ├── passthough-proxy.ts
│   │       ├── proxy.ts
│   │       ├── requests.ts
│   │       ├── retry.ts
│   │       ├── schema.ts
│   │       ├── scrape/
│   │       │   ├── cleanHtml.ts
│   │       │   ├── htmlToMarkdown.ts
│   │       │   ├── index.ts
│   │       │   ├── pdfToHtml.ts
│   │       │   ├── plugins/
│   │       │   │   ├── highlightedCodeBlock.ts
│   │       │   │   ├── inlineLink.ts
│   │       │   │   ├── strikethrough.ts
│   │       │   │   ├── table.ts
│   │       │   │   ├── taskListItems.ts
│   │       │   │   └── utilities.ts
│   │       │   ├── readability.ts
│   │       │   ├── safeGoTo.ts
│   │       │   └── transformHtml.ts
│   │       ├── size.ts
│   │       ├── text.ts
│   │       └── url.ts
│   ├── tsconfig.json
│   └── tsconfig.test.json
├── commitlint.config.cjs
├── docker-compose.dev.yml
├── docker-compose.yml
├── docs/
│   ├── ARCHITECTURE.md
│   ├── DEVELOPMENT_SETUP.md
│   ├── PLUGIN_DEVELOPMENT.md
│   ├── README.md
│   └── TROUBLESHOOTING.md
├── nginx.conf
├── package.json
├── render.yaml
├── repl/
│   ├── README.md
│   ├── package.json
│   └── src/
│       └── script.ts
└── ui/
    ├── .dockerignore
    ├── .eslintrc.cjs
    ├── .gitignore
    ├── Dockerfile
    ├── README.md
    ├── components.json
    ├── entrypoint.sh
    ├── index.html
    ├── nginx.conf.template
    ├── openapi-ts.config.ts
    ├── package.json
    ├── postcss.config.js
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   ├── badges/
    │   │   │   ├── proxy-badge.tsx
    │   │   │   ├── user-agent-badge.tsx
    │   │   │   └── websocket-url-badge.tsx
    │   │   ├── header/
    │   │   │   ├── header.tsx
    │   │   │   └── index.tsx
    │   │   ├── icons/
    │   │   │   ├── ChromeIcon.tsx
    │   │   │   ├── DeleteIcon.tsx
    │   │   │   ├── GlobeIcon.tsx
    │   │   │   ├── GlowingGreenDot.tsx
    │   │   │   ├── KeyIcon.tsx
    │   │   │   ├── LoadingSpinner.tsx
    │   │   │   ├── NinjaIcon.tsx
    │   │   │   ├── SessionIcon.tsx
    │   │   │   └── SettingsIcon.tsx
    │   │   ├── illustrations/
    │   │   │   ├── command-line.tsx
    │   │   │   └── globe.tsx
    │   │   ├── loading/
    │   │   │   ├── Loading.styles.tsx
    │   │   │   ├── Loading.tsx
    │   │   │   └── index.tsx
    │   │   ├── sessions/
    │   │   │   ├── release-session-dialog.tsx
    │   │   │   ├── session-console/
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── session-details.tsx
    │   │   │   │   ├── session-devtools.tsx
    │   │   │   │   └── session-logs.tsx
    │   │   │   └── session-viewer/
    │   │   │       ├── empty-state.tsx
    │   │   │       ├── example-events/
    │   │   │       │   ├── example-events.json
    │   │   │       │   └── test.json
    │   │   │       ├── index.tsx
    │   │   │       ├── live-empty-state.tsx
    │   │   │       ├── session-viewer-controls.css
    │   │   │       └── session-viewer.tsx
    │   │   ├── theme-provider.tsx
    │   │   └── ui/
    │   │       ├── avatar.tsx
    │   │       ├── badge.tsx
    │   │       ├── button.tsx
    │   │       ├── card.tsx
    │   │       ├── checkbox.tsx
    │   │       ├── dialog.tsx
    │   │       ├── form.tsx
    │   │       ├── input.tsx
    │   │       ├── label.tsx
    │   │       ├── pagination.tsx
    │   │       ├── popover.tsx
    │   │       ├── select.tsx
    │   │       ├── separator.tsx
    │   │       ├── table.tsx
    │   │       ├── tabs.tsx
    │   │       ├── toast.tsx
    │   │       └── toaster.tsx
    │   ├── containers/
    │   │   └── session-container.tsx
    │   ├── contexts/
    │   │   └── sessions-context/
    │   │       ├── index.tsx
    │   │       ├── sessions-context.tsx
    │   │       └── sessions-context.types.ts
    │   ├── env.ts
    │   ├── fonts/
    │   │   ├── Geist/
    │   │   │   ├── Geist-Black.otf
    │   │   │   ├── Geist-Bold.otf
    │   │   │   ├── Geist-Light.otf
    │   │   │   ├── Geist-Medium.otf
    │   │   │   ├── Geist-Regular.otf
    │   │   │   ├── Geist-SemiBold.otf
    │   │   │   ├── Geist-Thin.otf
    │   │   │   ├── Geist-UltraBlack.otf
    │   │   │   ├── Geist-UltraLight.otf
    │   │   │   └── LICENSE.TXT
    │   │   └── GeistMono/
    │   │       ├── GeistMono-Black.otf
    │   │       ├── GeistMono-Bold.otf
    │   │       ├── GeistMono-Light.otf
    │   │       ├── GeistMono-Medium.otf
    │   │       ├── GeistMono-Regular.otf
    │   │       ├── GeistMono-SemiBold.otf
    │   │       ├── GeistMono-Thin.otf
    │   │       ├── GeistMono-UltraBlack.otf
    │   │       ├── GeistMono-UltraLight.otf
    │   │       └── LICENSE.TXT
    │   ├── hooks/
    │   │   ├── use-sessions-context.ts
    │   │   └── use-toast.ts
    │   ├── index.css
    │   ├── lib/
    │   │   ├── query-client.ts
    │   │   └── utils.ts
    │   ├── main.tsx
    │   ├── root-layout.tsx
    │   ├── steel-client/
    │   │   ├── index.ts
    │   │   ├── schemas.gen.ts
    │   │   ├── services.gen.ts
    │   │   └── types.gen.ts
    │   ├── styles/
    │   │   ├── common.styles.tsx
    │   │   └── theme.ts
    │   ├── types/
    │   │   ├── cdp.ts
    │   │   └── props.ts
    │   ├── utils/
    │   │   ├── formatting.ts
    │   │   └── toasts.ts
    │   └── vite-env.d.ts
    ├── tailwind.config.js
    ├── tsconfig.json
    ├── tsconfig.node.json
    ├── vercel.json
    └── vite.config.ts
Download .txt
SYMBOL INDEX (637 symbols across 122 files)

FILE: api/extensions/recorder/src/background.js
  constant LOCAL_API_URL (line 1) | const LOCAL_API_URL = "http://localhost:3000/v1/events";
  constant FALLBACK_API_URL (line 2) | const FALLBACK_API_URL = "http://0.0.0.0:3000/v1/events";
  function injectScript (line 5) | async function injectScript(tabId, changeInfo, tab) {

FILE: api/openapi/generate.ts
  type OpenAPIServer (line 5) | interface OpenAPIServer {
  type OpenAPIDocument (line 10) | interface OpenAPIDocument {

FILE: api/src/config.ts
  type LoggingConfig (line 5) | interface LoggingConfig {
  method logMethod (line 21) | logMethod(inputArgs: any[], method: any) {

FILE: api/src/index.ts
  constant HOST (line 10) | const HOST = process.env.HOST ?? "0.0.0.0";
  constant PORT (line 11) | const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;

FILE: api/src/modules/actions/actions.routes.ts
  function routes (line 6) | async function routes(server: FastifyInstance) {

FILE: api/src/modules/actions/actions.schema.ts
  type ScrapeRequestBody (line 112) | type ScrapeRequestBody = z.infer<typeof ScrapeRequest>;
  type ScrapeRequest (line 113) | type ScrapeRequest = FastifyRequest<{ Body: ScrapeRequestBody }>;
  type ScreenshotRequestBody (line 115) | type ScreenshotRequestBody = z.infer<typeof ScreenshotRequest>;
  type ScreenshotRequest (line 116) | type ScreenshotRequest = FastifyRequest<{ Body: ScreenshotRequestBody }>;
  type PDFRequestBody (line 118) | type PDFRequestBody = z.infer<typeof PDFRequest>;
  type PDFRequest (line 119) | type PDFRequest = FastifyRequest<{ Body: PDFRequestBody }>;
  type SearchRequestBody (line 121) | type SearchRequestBody = z.infer<typeof SearchRequest>;
  type SearchRequest (line 122) | type SearchRequest = FastifyRequest<{ Body: SearchRequestBody }>;

FILE: api/src/modules/cdp/cdp.routes.ts
  function routes (line 6) | async function routes(server: FastifyInstance) {

FILE: api/src/modules/files/files.controller.ts
  class FilesController (line 17) | class FilesController {
    method constructor (line 18) | constructor(private fileService: FileService) {}
    method validatePath (line 20) | private validatePath(filePath: string): boolean {
    method handleFileUpload (line 41) | async handleFileUpload(
    method createStreamFromUrl (line 159) | private async createStreamFromUrl(
    method handleFileDownload (line 193) | async handleFileDownload(
    method handleFileHead (line 218) | async handleFileHead(
    method handleFileList (line 238) | async handleFileList(
    method handleFileDelete (line 261) | async handleFileDelete(
    method handleFileDeleteAll (line 277) | async handleFileDeleteAll(
    method handleDownloadArchive (line 291) | async handleDownloadArchive(

FILE: api/src/modules/files/files.routes.ts
  function routes (line 8) | async function routes(server: FastifyInstance) {

FILE: api/src/modules/files/files.schema.ts
  type FileDetails (line 18) | type FileDetails = z.infer<typeof FileDetails>;
  type MultipleFiles (line 19) | type MultipleFiles = z.infer<typeof MultipleFiles>;
  type FileUploadRequest (line 20) | type FileUploadRequest = z.infer<typeof FileUploadRequest>;

FILE: api/src/modules/logs/logs.schema.ts
  type LogQueryInput (line 30) | type LogQueryInput = z.infer<typeof LogQuerySchema>;
  type LogStatsOutput (line 31) | type LogStatsOutput = z.infer<typeof LogStatsSchema>;
  type LogQueryResultOutput (line 32) | type LogQueryResultOutput = z.infer<typeof LogQueryResultSchema>;
  type ExportLogsInput (line 33) | type ExportLogsInput = z.infer<typeof ExportLogsSchema>;

FILE: api/src/modules/selenium/selenium.routes.ts
  function routes (line 4) | async function routes(server: FastifyInstance) {

FILE: api/src/modules/selenium/selenium.schema.ts
  type LaunchRequest (line 36) | type LaunchRequest = z.infer<typeof LaunchRequest>;

FILE: api/src/modules/sessions/sessions.routes.ts
  function routes (line 23) | async function routes(server: FastifyInstance) {

FILE: api/src/modules/sessions/sessions.schema.ts
  type CredentialsOptions (line 10) | type CredentialsOptions = z.infer<typeof SessionCredentials>;
  type SessionsScrapeRequestBody (line 178) | type SessionsScrapeRequestBody = Omit<ScrapeRequestBody, "url">;
  type SessionsScrapeRequest (line 179) | type SessionsScrapeRequest = FastifyRequest<{ Body: SessionsScrapeReques...
  type SessionsScreenshotRequestBody (line 181) | type SessionsScreenshotRequestBody = Omit<ScreenshotRequestBody, "url">;
  type SessionsScreenshotRequest (line 182) | type SessionsScreenshotRequest = FastifyRequest<{ Body: SessionsScreensh...
  type SessionsPDFRequestBody (line 184) | type SessionsPDFRequestBody = Omit<PDFRequestBody, "url">;
  type SessionsPDFRequest (line 185) | type SessionsPDFRequest = FastifyRequest<{ Body: SessionsPDFRequestBody }>;
  type RecordedEvents (line 187) | type RecordedEvents = z.infer<typeof RecordedEvents>;
  type CreateSessionBody (line 188) | type CreateSessionBody = z.infer<typeof CreateSession>;
  type CreateSessionRequest (line 189) | type CreateSessionRequest = FastifyRequest<{ Body: CreateSessionBody }>;
  type SessionDetails (line 190) | type SessionDetails = z.infer<typeof SessionDetails>;
  type MultipleSessions (line 191) | type MultipleSessions = z.infer<typeof MultipleSessions>;
  type SessionStreamQuery (line 193) | type SessionStreamQuery = z.infer<typeof SessionStreamQuery>;
  type SessionStreamRequest (line 194) | type SessionStreamRequest = FastifyRequest<{ Querystring: SessionStreamQ...

FILE: api/src/plugins/browser-socket/browser-socket.ts
  type BrowserSocketOptions (line 8) | interface BrowserSocketOptions {

FILE: api/src/plugins/browser-socket/casting.handler.ts
  function handleCastSession (line 18) | async function handleCastSession(

FILE: api/src/plugins/browser-socket/handlers/logs.handler.ts
  function handleLogsWebSocket (line 7) | function handleLogsWebSocket(context: WebSocketHandlerContext, ws: WebSo...

FILE: api/src/plugins/browser-socket/handlers/pageId.handler.ts
  function handlePageIdWebSocket (line 6) | function handlePageIdWebSocket(context: WebSocketHandlerContext, ws: Web...

FILE: api/src/plugins/browser-socket/handlers/recording.handler.ts
  function handleRecordingWebSocket (line 7) | function handleRecordingWebSocket(context: WebSocketHandlerContext, ws: ...

FILE: api/src/plugins/browser.ts
  type FastifyInstance (line 15) | interface FastifyInstance {

FILE: api/src/plugins/request-logger.ts
  type FastifyReply (line 5) | interface FastifyReply {
  function now (line 11) | function now() {
  function roundMS (line 46) | function roundMS(num: number): number {
  function getClientIp (line 50) | function getClientIp(req: any): string {

FILE: api/src/plugins/schemas.ts
  constant SCHEMAS (line 16) | const SCHEMAS = {

FILE: api/src/plugins/ui-plugin.ts
  type UIPluginOptions (line 7) | interface UIPluginOptions {

FILE: api/src/scripts/fingerprint.js
  function _0x5610 (line 40) | function _0x5610(_0xa98ee6, _0x384755) {
  function _0x338a (line 55) | function _0x338a() {

FILE: api/src/scripts/index.ts
  constant SCRIPTS_DIR (line 5) | const SCRIPTS_DIR = path.join(dirname(fileURLToPath(import.meta.url)));
  constant FIXED_VERSION (line 12) | const FIXED_VERSION = "WebGL 1.0 (OpenGL ES 2.0 Chromium)";
  constant FIXED_SHADING_LANGUAGE_VERSION (line 13) | const FIXED_SHADING_LANGUAGE_VERSION = "WebGL GLSL ES 1.0 (OpenGL ES GLS...

FILE: api/src/services/cdp/cdp.service.ts
  class CDPService (line 83) | class CDPService extends EventEmitter {
    method constructor (line 115) | constructor(
    method getInstrumentationLogger (line 187) | public getInstrumentationLogger(): BrowserLogger {
    method getLogger (line 191) | public getLogger(name: string) {
    method setProxyWebSocketHandler (line 195) | public setProxyWebSocketHandler(
    method setDisconnectHandler (line 201) | public setDisconnectHandler(handler: () => Promise<void>): void {
    method getBrowserInstance (line 205) | public getBrowserInstance(): Browser | null {
    method getLaunchConfig (line 209) | public getLaunchConfig(): BrowserLauncherOptions | undefined {
    method getSessionContext (line 213) | public getSessionContext(): SessionData | null {
    method registerLaunchHook (line 217) | public registerLaunchHook(fn: (config: BrowserLauncherOptions) => Prom...
    method registerShutdownHook (line 221) | public registerShutdownHook(fn: (config: BrowserLauncherOptions | null...
    method removeAllHandlers (line 225) | private removeAllHandlers() {
    method isRunning (line 230) | public isRunning(): boolean {
    method getTargetId (line 234) | public getTargetId(page: Page) {
    method getPrimaryPage (line 239) | public async getPrimaryPage(): Promise<Page> {
    method getDebuggerBase (line 249) | private getDebuggerBase(): { baseUrl: string; protocol: string; wsProt...
    method getDebuggerUrl (line 256) | public getDebuggerUrl() {
    method getDebuggerWsUrl (line 261) | public getDebuggerWsUrl(pageId?: string) {
    method refreshPrimaryPage (line 268) | public async refreshPrimaryPage() {
    method registerPlugin (line 278) | public registerPlugin(plugin: BasePlugin) {
    method unregisterPlugin (line 282) | public unregisterPlugin(pluginName: string) {
    method handleTargetChange (line 286) | private async handleTargetChange(target: Target) {
    method handleNewTarget (line 316) | private async handleNewTarget(target: Target) {
    method handlePageRequest (line 388) | private async handlePageRequest(request: HTTPRequest, page: Page) {
    method createPage (line 445) | public async createPage(): Promise<Page> {
    method shutdownHook (line 452) | private async shutdownHook() {
    method shutdown (line 459) | public async shutdown(): Promise<void> {
    method getBrowserProcess (line 509) | public getBrowserProcess() {
    method createBrowserContext (line 513) | public async createBrowserContext(proxyUrl: string): Promise<BrowserCo...
    method launch (line 521) | public async launch(
    method launchInternal (line 552) | private async launchInternal(config?: BrowserLauncherOptions): Promise...
    method proxyWebSocket (line 1060) | public async proxyWebSocket(req: IncomingMessage, socket: Duplex, head...
    method getUserAgent (line 1121) | public getUserAgent() {
    method getDimensions (line 1127) | public getDimensions() {
    method getFingerprintData (line 1131) | public getFingerprintData(): BrowserFingerprintWithHeaders | null {
    method getCookies (line 1135) | public async getCookies(): Promise<Protocol.Network.Cookie[]> {
    method getBrowserState (line 1145) | public async getBrowserState(): Promise<SessionData> {
    method getExistingPageSessionData (line 1196) | private async getExistingPageSessionData(): Promise<SessionData> {
    method getAllPages (line 1258) | public async getAllPages() {
    method startNewSession (line 1263) | public async startNewSession(sessionConfig: BrowserLauncherOptions): P...
    method endSession (line 1278) | public async endSession(): Promise<void> {
    method onDisconnect (line 1302) | private async onDisconnect(): Promise<void> {
    method injectSessionContext (line 1313) | private async injectSessionContext(
    method injectFingerprintSafely (line 1366) | private async injectFingerprintSafely(
    method setupUserPreferences (line 1461) | private async setupUserPreferences(userDataDir: string, userPreference...

FILE: api/src/services/cdp/errors/launch-errors.ts
  type LaunchErrorType (line 6) | enum LaunchErrorType {
  type BrowserProcessState (line 19) | enum BrowserProcessState {
  type PluginName (line 27) | enum PluginName {
  type PluginOperation (line 33) | enum PluginOperation {
  type CleanupType (line 39) | enum CleanupType {
  type SessionContextType (line 44) | enum SessionContextType {
  type FingerprintStage (line 48) | enum FingerprintStage {
  type ResourceType (line 53) | enum ResourceType {
  type NetworkOperation (line 58) | enum NetworkOperation {
  type SystemOperation (line 64) | enum SystemOperation {
  type ConfigurationField (line 69) | enum ConfigurationField {
  type ErrorCategories (line 75) | enum ErrorCategories {}
  method constructor (line 82) | constructor(
  class LaunchTimeoutError (line 106) | class LaunchTimeoutError extends BaseLaunchError {
    method constructor (line 107) | constructor(timeoutMs: number = 30000, cause?: unknown) {
  class ConfigurationError (line 124) | class ConfigurationError extends BaseLaunchError {
    method constructor (line 125) | constructor(
  class ResourceError (line 148) | class ResourceError extends BaseLaunchError {
    method constructor (line 149) | constructor(
  class SystemError (line 169) | class SystemError extends BaseLaunchError {
    method constructor (line 170) | constructor(message: string, operation: SystemOperation, originalError...
  class NetworkError (line 188) | class NetworkError extends BaseLaunchError {
    method constructor (line 189) | constructor(message: string, networkOperation: NetworkOperation, cause...
  class FingerprintError (line 206) | class FingerprintError extends BaseLaunchError {
    method constructor (line 207) | constructor(message: string, stage: FingerprintStage, cause?: unknown) {
  class PluginError (line 224) | class PluginError extends BaseLaunchError {
    method constructor (line 225) | constructor(
  class CleanupError (line 249) | class CleanupError extends BaseLaunchError {
    method constructor (line 250) | constructor(message: string, cleanupType: CleanupType, cause?: unknown) {
  class BrowserProcessError (line 267) | class BrowserProcessError extends BaseLaunchError {
    method constructor (line 268) | constructor(
  class SessionContextError (line 291) | class SessionContextError extends BaseLaunchError {
    method constructor (line 292) | constructor(message: string, contextType: SessionContextType, cause?: ...
  function categorizeError (line 308) | function categorizeError(error: unknown, context?: string): BaseLaunchEr...

FILE: api/src/services/cdp/instrumentation/browser-logger.ts
  type Context (line 6) | type Context = Record<string, any>;
  type Logger (line 12) | interface Logger {
  type BrowserLogger (line 17) | interface BrowserLogger {
  type CreateBrowserLoggerOptions (line 30) | interface CreateBrowserLoggerOptions {
  function createBrowserLogger (line 37) | function createBrowserLogger(options: CreateBrowserLoggerOptions): Brows...

FILE: api/src/services/cdp/instrumentation/cdp-events.ts
  function attachCDPEvents (line 9) | function attachCDPEvents(session: CDPSession, logger: BrowserLogger): vo...

FILE: api/src/services/cdp/instrumentation/extension-events.ts
  function attachExtensionEvents (line 8) | async function attachExtensionEvents(

FILE: api/src/services/cdp/instrumentation/page-events.ts
  type AttachPageEventsOptions (line 6) | interface AttachPageEventsOptions {
  constant MAX_BODY_SIZE (line 10) | const MAX_BODY_SIZE = 1_048_576;
  constant TEXT_MIME_PREFIXES (line 11) | const TEXT_MIME_PREFIXES = ["text/", "application/json", "application/xm...
  function isTextMime (line 13) | function isTextMime(mime: string | undefined): boolean {
  function attachPageEvents (line 24) | async function attachPageEvents(

FILE: api/src/services/cdp/instrumentation/storage/duckdb-storage.ts
  type ParquetCompression (line 9) | type ParquetCompression = "zstd" | "snappy" | "gzip" | "none";
  type DuckDBStorageOptions (line 11) | interface DuckDBStorageOptions {
  class DuckDBStorage (line 48) | class DuckDBStorage implements LogStorage {
    method constructor (line 64) | constructor(options: DuckDBStorageOptions = {}) {
    method initialize (line 74) | async initialize(): Promise<void> {
    method startFlushTimer (line 118) | private startFlushTimer(): void {
    method stopFlushTimer (line 129) | private stopFlushTimer(): void {
    method flushWriteBuffer (line 136) | private async flushWriteBuffer(): Promise<void> {
    method write (line 153) | async write(event: BrowserEventUnion, context: Record<string, any>): P...
    method writeSingle (line 173) | private async writeSingle(event: BrowserEventUnion, context: Record<st...
    method writeBatch (line 193) | async writeBatch(
    method writeBatchInternal (line 217) | private async writeBatchInternal(
    method flush (line 247) | async flush(): Promise<void> {
    method query (line 252) | async query(query: LogQuery): Promise<LogQueryResult> {
    method supportsParquetExport (line 316) | supportsParquetExport(): boolean {
    method exportToParquet (line 320) | async exportToParquet(filePath: string, query?: LogQuery): Promise<str...
    method getStats (line 381) | async getStats(): Promise<{
    method clear (line 416) | async clear(options: { vacuum?: boolean } = {}): Promise<void> {
    method vacuum (line 441) | async vacuum(): Promise<void> {
    method close (line 448) | async close(): Promise<void> {
    method getBufferStats (line 472) | getBufferStats(): { bufferedEvents: number; isBufferingEnabled: boolea...

FILE: api/src/services/cdp/instrumentation/storage/in-memory-storage.ts
  type StoredEvent (line 4) | interface StoredEvent {
  class InMemoryStorage (line 14) | class InMemoryStorage implements LogStorage {
    method constructor (line 18) | constructor(maxEvents: number = 10000) {
    method initialize (line 22) | async initialize(): Promise<void> {
    method write (line 26) | async write(event: BrowserEventUnion, context: Record<string, any>): P...
    method writeBatch (line 39) | async writeBatch(
    method query (line 47) | async query(query: LogQuery): Promise<LogQueryResult> {
    method supportsParquetExport (line 89) | supportsParquetExport(): boolean {
    method exportToParquet (line 93) | async exportToParquet(filePath: string, query?: LogQuery): Promise<str...
    method getStats (line 97) | async getStats(): Promise<{
    method clear (line 113) | async clear(): Promise<void> {
    method flush (line 117) | async flush(): Promise<void> {
    method close (line 121) | async close(): Promise<void> {

FILE: api/src/services/cdp/instrumentation/storage/log-storage.interface.ts
  type LogQuery (line 3) | interface LogQuery {
  type LogQueryResult (line 13) | interface LogQueryResult {
  type LogStorage (line 19) | interface LogStorage {

FILE: api/src/services/cdp/instrumentation/storage/safe-json.ts
  function safeStringify (line 1) | function safeStringify(value: unknown): string {

FILE: api/src/services/cdp/instrumentation/target-manager.ts
  constant INTERNAL_EXTENSIONS (line 10) | const INTERNAL_EXTENSIONS = new Set<string>([
  class TargetInstrumentationManager (line 14) | class TargetInstrumentationManager {
    method constructor (line 20) | constructor(
    method attach (line 28) | async attach(target: Target, type: TargetType) {
    method detach (line 118) | detach(targetId: string) {
    method enableDomainsForTarget (line 129) | private async enableDomainsForTarget(

FILE: api/src/services/cdp/instrumentation/types.ts
  type BaseBrowserEvent (line 4) | interface BaseBrowserEvent {
  type RequestEvent (line 11) | interface RequestEvent extends BaseBrowserEvent {
  type ResponseEvent (line 22) | interface ResponseEvent extends BaseBrowserEvent {
  type ResponseBodyEvent (line 33) | interface ResponseBodyEvent extends BaseBrowserEvent {
  type NavigationEvent (line 42) | interface NavigationEvent extends BaseBrowserEvent {
  type ConsoleEvent (line 47) | interface ConsoleEvent extends BaseBrowserEvent {
  type ErrorEvent (line 52) | interface ErrorEvent extends BaseBrowserEvent {
  type RecordingEvent (line 61) | interface RecordingEvent extends BaseBrowserEvent {
  type CDPEvent (line 66) | interface CDPEvent extends BaseBrowserEvent {
  type CDPCommandEvent (line 74) | interface CDPCommandEvent extends BaseBrowserEvent {
  type CDPCommandResultEvent (line 83) | interface CDPCommandResultEvent extends BaseBrowserEvent {
  type ExtensionEvent (line 94) | interface ExtensionEvent extends BaseBrowserEvent {
  type BrowserEventUnion (line 104) | type BrowserEventUnion =

FILE: api/src/services/cdp/instrumentation/utils.ts
  function extractTargetId (line 4) | function extractTargetId(target: Target): string {
  function serializeRemoteObject (line 8) | function serializeRemoteObject(obj: Protocol.Runtime.RemoteObject): stri...
  function formatLocation (line 15) | function formatLocation(stackTrace?: Protocol.Runtime.StackTrace): strin...

FILE: api/src/services/cdp/instrumentation/worker-events.ts
  function attachWorkerEvents (line 6) | function attachWorkerEvents(

FILE: api/src/services/cdp/plugins/core/base-plugin.ts
  type PluginOptions (line 5) | interface PluginOptions {
  method constructor (line 15) | constructor(options: PluginOptions) {
  method setService (line 21) | public setService(service: CDPService): void {
  method onBrowserLaunch (line 26) | public async onBrowserLaunch(browser: Browser): Promise<void> {}
  method onBrowserReady (line 27) | public onBrowserReady(context: BrowserLauncherOptions): void | Promise<v...
  method onPageCreated (line 28) | public async onPageCreated(page: Page): Promise<void> {}
  method onPageNavigate (line 29) | public async onPageNavigate(page: Page): Promise<void> {}
  method onPageUnload (line 30) | public async onPageUnload(page: Page): Promise<void> {}
  method onBrowserClose (line 31) | public async onBrowserClose(browser: Browser): Promise<void> {}
  method onBeforePageClose (line 32) | public async onBeforePageClose(page: Page): Promise<void> {}
  method onShutdown (line 33) | public async onShutdown(): Promise<void> {}
  method onSessionEnd (line 34) | public async onSessionEnd(sessionConfig: BrowserLauncherOptions): Promis...

FILE: api/src/services/cdp/plugins/core/plugin-manager.ts
  class PluginManager (line 7) | class PluginManager {
    method constructor (line 12) | constructor(service: CDPService, logger: FastifyBaseLogger) {
    method register (line 21) | public register(plugin: BasePlugin): void {
    method unregister (line 34) | public unregister(pluginName: string): boolean {
    method getPlugin (line 47) | public getPlugin<T extends BasePlugin>(pluginName: string): T | undefi...
    method onBrowserLaunch (line 54) | public async onBrowserLaunch(browser: Browser): Promise<void> {
    method onBrowserReady (line 65) | public async onBrowserReady(context: BrowserLauncherOptions): Promise<...
    method onPageCreated (line 81) | public async onPageCreated(page: Page): Promise<void> {
    method onBrowserClose (line 95) | public async onBrowserClose(browser: Browser): Promise<void> {
    method onPageNavigate (line 109) | public async onPageNavigate(page: Page): Promise<void> {
    method onPageUnload (line 123) | public async onPageUnload(page: Page): Promise<void> {
    method onBeforePageClose (line 137) | public async onBeforePageClose(page: Page): Promise<void> {
    method onShutdown (line 151) | public async onShutdown(): Promise<void> {
    method onSessionEnd (line 165) | public async onSessionEnd(sessionConfig: BrowserLauncherOptions): Prom...

FILE: api/src/services/cdp/plugins/pptr-extensions.d.ts
  type Page (line 4) | interface Page {

FILE: api/src/services/cdp/utils/error-handlers.ts
  function executeCritical (line 18) | async function executeCritical<T>(
  function executeOptional (line 46) | async function executeOptional<T>(
  function executeBestEffort (line 76) | async function executeBestEffort<T>(

FILE: api/src/services/cdp/utils/validation.ts
  function comparePromiseValues (line 12) | async function comparePromiseValues<T>(
  function validateLaunchConfig (line 31) | function validateLaunchConfig(config: BrowserLauncherOptions): void {
  function validateTimezone (line 72) | async function validateTimezone(
  function isSimilarConfig (line 114) | async function isSimilarConfig(

FILE: api/src/services/context/chrome-context.service.ts
  class ChromeContextService (line 8) | class ChromeContextService extends EventEmitter {
    method constructor (line 11) | constructor(logger: FastifyBaseLogger) {
    method getSessionData (line 21) | public async getSessionData(userDataDir?: string): Promise<SessionData> {
    method extractLocalStorage (line 61) | private async extractLocalStorage(
    method extractSessionStorage (line 80) | private async extractSessionStorage(

FILE: api/src/services/context/types.ts
  type StorageProviderName (line 3) | enum StorageProviderName {
  type IndexedDBDatabaseWithOrigin (line 10) | interface IndexedDBDatabaseWithOrigin {
  type IndexedDBDatabase (line 17) | interface IndexedDBDatabase {
  type IndexedDBObjectStore (line 23) | interface IndexedDBObjectStore {
  type IndexedDBRecord (line 29) | interface IndexedDBRecord {
  type IndexedDBBlobFile (line 35) | interface IndexedDBBlobFile {
  type LocalStorageData (line 44) | type LocalStorageData = Record<string, string>;
  type SessionStorageData (line 45) | type SessionStorageData = Record<string, string>;
  type CookieData (line 46) | type CookieData = z.infer<typeof CDPCookieSchema>;
  type StorageProviderDataMap (line 48) | interface StorageProviderDataMap {
  type ProviderDataType (line 56) | type ProviderDataType<T extends StorageProviderName> = StorageProviderDa...
  type SessionData (line 58) | type SessionData = {
  class CorruptedSessionDataError (line 66) | class CorruptedSessionDataError extends Error {
    method constructor (line 67) | constructor(zodError: z.ZodError) {
  type CDPCookie (line 114) | type CDPCookie = z.infer<typeof CDPCookieSchema>;

FILE: api/src/services/file.service.ts
  type File (line 11) | interface File {
  class FileService (line 16) | class FileService {
    method constructor (line 26) | private constructor() {
    method getInstance (line 39) | public static getInstance() {
    method handleFileAdd (line 46) | private async handleFileAdd(filePath: string) {
    method handleFileDelete (line 51) | private handleFileDelete(filePath: string) {
    method handleDirChange (line 56) | private handleDirChange(filePath: string) {
    method initFileWatcher (line 61) | private initFileWatcher() {
    method getSafeFilePath (line 87) | private getSafeFilePath(relativePath: string) {
    method exists (line 98) | private async exists(filePath: string): Promise<boolean> {
    method saveFile (line 109) | public async saveFile({
    method downloadFile (line 149) | public async downloadFile({
    method getFile (line 180) | public async getFile({ filePath }: { filePath: string }): Promise<File> {
    method listFiles (line 203) | public async listFiles(): Promise<Array<{ path: string } & File>> {
    method deleteFile (line 252) | public async deleteFile({ filePath }: { filePath: string }): Promise<v...
    method cleanupFiles (line 280) | public async cleanupFiles(): Promise<void> {
    method getBaseFilesPath (line 331) | public getBaseFilesPath(): string {
    method getPrebuiltArchivePath (line 335) | public async getPrebuiltArchivePath(): Promise<string> {
    method _createArchive (line 339) | private _createArchive(): Promise<string | null> {

FILE: api/src/services/leveldb/localstorage.ts
  function decodeString (line 13) | function decodeString(raw: Buffer): { value: string; charset: string } {
  type StorageMetadata (line 35) | interface StorageMetadata {
  type LocalStorageRecord (line 41) | interface LocalStorageRecord {
  class LocalStoreDb (line 52) | class LocalStoreDb {
    method constructor (line 56) | private constructor(db: Level<Buffer, Buffer>) {
    method open (line 60) | public static async open(dir: string): Promise<LocalStoreDb> {
    method load (line 84) | public async load(): Promise<void> {
    method close (line 121) | public close(): void {
  class ChromeLocalStorageReader (line 127) | class ChromeLocalStorageReader {
    method readLocalStorage (line 132) | public static async readLocalStorage(

FILE: api/src/services/leveldb/sessionstorage.ts
  function decodeUTF16LE (line 12) | function decodeUTF16LE(raw: Buffer): string {
  type SessionStorageRecord (line 20) | interface SessionStorageRecord {
  class SessionStoreDb (line 32) | class SessionStoreDb {
    method constructor (line 37) | private constructor(db: Level<Buffer, Buffer>) {
    method open (line 41) | public static async open(dir: string): Promise<SessionStoreDb> {
    method loadNamespaceRecords (line 68) | private async loadNamespaceRecords(): Promise<void> {
    method load (line 93) | public async load(): Promise<void> {
    method close (line 135) | public close(): void {
  function processSessionRecord (line 144) | function processSessionRecord(record: SessionStorageRecord): SessionStor...
  class ChromeSessionStorageReader (line 204) | class ChromeSessionStorageReader {
    method readSessionStorage (line 209) | public static async readSessionStorage(

FILE: api/src/services/selenium.service.ts
  class SeleniumService (line 8) | class SeleniumService extends EventEmitter {
    method constructor (line 15) | constructor(logger: FastifyBaseLogger) {
    method getChromeArgs (line 20) | public async getChromeArgs(): Promise<string[]> {
    method launch (line 32) | public async launch(launchOptions: BrowserLauncherOptions): Promise<vo...
    method close (line 84) | public close(): void {
    method getSeleniumServerUrl (line 91) | public getSeleniumServerUrl(): string {
    method postLog (line 95) | private async postLog(browserLog: BrowserEvent) {

FILE: api/src/services/session.service.ts
  type Session (line 20) | type Session = SessionDetails & {
  type ProxyFactory (line 48) | type ProxyFactory = (proxyUrl: string) => Promise<IProxyServer> | IProxy...
  class SessionService (line 50) | class SessionService {
    method constructor (line 61) | constructor(config: {
    method startSession (line 85) | public async startSession(options: {
    method endSession (line 248) | public async endSession(): Promise<SessionDetails> {
    method resetSessionInfo (line 278) | private async resetSessionInfo(overrides?: Partial<SessionDetails>): P...
    method setProxyFactory (line 300) | public setProxyFactory(factory: ProxyFactory) {

FILE: api/src/services/timezone-fetcher.service.ts
  type TimezoneFetchResult (line 7) | interface TimezoneFetchResult {
  type TimezoneService (line 13) | interface TimezoneService {
  class TimezoneFetcher (line 19) | class TimezoneFetcher {
    method constructor (line 25) | constructor(logger: FastifyBaseLogger) {
    method startFetch (line 40) | private startFetch(proxyUrl?: string): Promise<TimezoneFetchResult> {
    method getTimezone (line 58) | public async getTimezone(proxyUrl?: string, fallback?: string): Promis...
    method fetchTimezoneInternal (line 91) | private async fetchTimezoneInternal(proxyUrl?: string): Promise<Timezo...

FILE: api/src/services/websocket-registry.service.ts
  class WebSocketRegistryService (line 3) | class WebSocketRegistryService implements WebSocketHandlerRegistry {
    method registerHandler (line 6) | registerHandler(handler: WebSocketHandler): void {
    method getHandler (line 10) | getHandler(path: string): WebSocketHandler | undefined {
    method matchHandler (line 14) | matchHandler(url: string): WebSocketHandler | undefined {

FILE: api/src/steel-browser-plugin.ts
  type FastifyInstance (line 32) | interface FastifyInstance {
  type LogStorageInterface (line 43) | interface LogStorageInterface extends LogStorage {}
  type SteelBrowserConfig (line 46) | interface SteelBrowserConfig {

FILE: api/src/telemetry/noop.ts
  method spanContext (line 4) | spanContext() {
  method setAttribute (line 12) | setAttribute() {
  method setAttributes (line 15) | setAttributes() {
  method addEvent (line 18) | addEvent() {
  method addLink (line 21) | addLink() {
  method addLinks (line 24) | addLinks() {
  method setStatus (line 27) | setStatus() {
  method updateName (line 30) | updateName() {
  method end (line 33) | end() {}
  method isRecording (line 34) | isRecording() {
  method recordException (line 37) | recordException() {}

FILE: api/src/telemetry/tracer.ts
  type TracerOptions (line 13) | interface TracerOptions extends SpanOptions {
  method startActiveSpan (line 19) | startActiveSpan<F extends (span: Span) => unknown>(
  method factory (line 64) | factory(tracerName: string) {
  function traceable (line 83) | function traceable(
  function createDecorator (line 97) | function createDecorator(opts?: TracerOptions) {
  function toKebabCase (line 124) | function toKebabCase(str: string) {

FILE: api/src/types/browser.ts
  type OptimizeBandwidthOptions (line 11) | type OptimizeBandwidthOptions = {
  type BrowserLauncherOptions (line 19) | interface BrowserLauncherOptions {
  type BrowserServerOptions (line 50) | interface BrowserServerOptions {
  type BrowserEvent (line 62) | type BrowserEvent = {

FILE: api/src/types/casting.ts
  type MouseEvent (line 1) | type MouseEvent = {
  type KeyEvent (line 16) | type KeyEvent = {
  type NavigationEvent (line 29) | type NavigationEvent = {
  type CloseTabEvent (line 38) | type CloseTabEvent = {
  type ClipboardWriteEvent (line 43) | type ClipboardWriteEvent = {
  type ClipboardReadEvent (line 51) | type ClipboardReadEvent = {
  type GetSelectedTextEvent (line 56) | type GetSelectedTextEvent = {
  type PageInfo (line 61) | type PageInfo = {

FILE: api/src/types/enums.ts
  type ScrapeFormat (line 3) | enum ScrapeFormat {
  type BrowserEventType (line 10) | enum BrowserEventType {
  type EmitEvent (line 27) | enum EmitEvent {

FILE: api/src/types/fastify.d.ts
  type FastifyRequest (line 9) | interface FastifyRequest {}
  type FastifyInstance (line 10) | interface FastifyInstance {

FILE: api/src/types/websocket.ts
  type WebSocketHandlerContext (line 6) | interface WebSocketHandlerContext {
  type WebSocketHandler (line 12) | interface WebSocketHandler {
  type WebSocketHandlerRegistry (line 22) | interface WebSocketHandlerRegistry {

FILE: api/src/utils/browser.ts
  function installMouseHelper (line 35) | async function installMouseHelper(page: Page, device: string) {
  function filterHeaders (line 148) | function filterHeaders(headers: Record<string, string>) {

FILE: api/src/utils/context.ts
  function extractStorageForPage (line 19) | async function extractStorageForPage(
  function groupSessionStorageByOrigin (line 360) | function groupSessionStorageByOrigin(
  function getProfilePath (line 415) | function getProfilePath(userDataDir: string, ...pathSegments: string[]):...
  function deepMerge (line 436) | function deepMerge<T = any>(target: T, source: Partial<T>): T {

FILE: api/src/utils/errors.ts
  function getErrors (line 1) | function getErrors(e: unknown) {

FILE: api/src/utils/extensions.ts
  function getExtensionPaths (line 4) | async function getExtensionPaths(extensionNames: string[]): Promise<stri...

FILE: api/src/utils/leveldb.ts
  function copyDirectory (line 7) | async function copyDirectory(src: string, dest: string): Promise<void> {

FILE: api/src/utils/passthough-proxy.ts
  type Result (line 73) | type Result<T> = [err: Error, result: null] | [err: null, result: T];

FILE: api/src/utils/proxy.ts
  type IProxyServer (line 6) | interface IProxyServer {
  class ProxyServer (line 15) | class ProxyServer extends Server implements IProxyServer {
    method constructor (line 22) | constructor(proxyUrl: string) {
    method listen (line 65) | async listen(): Promise<void> {

FILE: api/src/utils/requests.ts
  constant AD_HOSTS (line 1) | const AD_HOSTS = [
  constant RE_IMAGE_EXT (line 53) | const RE_IMAGE_EXT = /\.(jpg|jpeg|png|webp|svg|ico)(\?.*)?$/i;
  constant RE_VIDEO_EXT (line 54) | const RE_VIDEO_EXT = /\.(mp4|m4s|m3u8|ts|webm|gif)(\?.*)?$/i;
  constant RE_RANGE (line 55) | const RE_RANGE = /range=\d+-\d+/i;
  function tryParseUrl (line 57) | function tryParseUrl(url: string): URL | null {
  function isAdRequest (line 65) | function isAdRequest(parsed: URL): boolean {
  function isImageRequest (line 70) | function isImageRequest(parsed: URL): boolean {
  function isHeavyMediaRequest (line 74) | function isHeavyMediaRequest(parsed: URL): boolean {
  function isHostBlocked (line 81) | function isHostBlocked(parsed: URL, blockedHosts?: string[]): boolean {
  function compileUrlPatterns (line 87) | function compileUrlPatterns(patterns: string[]): RegExp[] {
  function isUrlMatchingPatterns (line 101) | function isUrlMatchingPatterns(url: string, compiledPatterns?: RegExp[])...

FILE: api/src/utils/retry.ts
  type RetryOptions (line 9) | interface RetryOptions {
  type RetryResult (line 17) | interface RetryResult<T> {
  class RetryError (line 23) | class RetryError extends Error {
    method constructor (line 28) | constructor(attempts: number, lastError: Error, allErrors: Error[]) {
  class RetryManager (line 40) | class RetryManager {
    method constructor (line 50) | constructor(logger: FastifyBaseLogger) {
    method executeWithRetry (line 57) | async executeWithRetry<T>(
    method isErrorRetryable (line 135) | private isErrorRetryable(error: Error): boolean {
    method sleep (line 152) | private sleep(ms: number): Promise<void> {
    method createRetryWrapper (line 156) | createRetryWrapper<T extends any[], R>(

FILE: api/src/utils/schema.ts
  type Models (line 4) | type Models<Key extends string = string> = {
  type BuildJsonSchemasOptions (line 8) | type BuildJsonSchemasOptions = {
  type SchemaKey (line 14) | type SchemaKey<M extends Models> = M extends Models<infer Key> ? Key & s...
  type SchemaKeyOrDescription (line 16) | type SchemaKeyOrDescription<M extends Models> =
  type $Ref (line 23) | type $Ref<M extends Models> = (key: SchemaKeyOrDescription<M>) => {
  type JsonSchema (line 28) | type JsonSchema = {
  type BuildJsonSchemasResult (line 32) | type BuildJsonSchemasResult<M extends Models> = {

FILE: api/src/utils/scrape/pdfToHtml.ts
  function parsePdfDate (line 3) | function parsePdfDate(pdfDate?: string | null): string | null {
  type HtmlLikeMetadata (line 30) | type HtmlLikeMetadata = {
  function extractLinksFromConvertedHtml (line 56) | function extractLinksFromConvertedHtml(html: string): { url: string; tex...
  function buildHtmlLikeMetadataFromPdf (line 67) | function buildHtmlLikeMetadataFromPdf(

FILE: api/src/utils/scrape/plugins/highlightedCodeBlock.ts
  function highlightedCodeBlock (line 7) | function highlightedCodeBlock(turndownService: TurndownService) {

FILE: api/src/utils/scrape/plugins/inlineLink.ts
  function inlineLink (line 5) | function inlineLink(turndownService: TurndownService) {

FILE: api/src/utils/scrape/plugins/strikethrough.ts
  function taskListItems (line 5) | function taskListItems(turndownService: TurndownService) {

FILE: api/src/utils/scrape/plugins/table.ts
  function getAlignment (line 16) | function getAlignment(node) {
  function getBorder (line 20) | function getBorder(alignment) {
  function getColumnAlignment (line 24) | function getColumnAlignment(table, columnIndex) {
  function extractTextFromCell (line 49) | function extractTextFromCell(cellNode: HTMLElement): string {
  function isHeadingRow (line 170) | function isHeadingRow(tr) {
  function isFirstTbody (line 182) | function isFirstTbody(element) {
  function cell (line 191) | function cell(content: string, node: Node, index: number | null = null) {
  function nodeContainsTable (line 202) | function nodeContainsTable(node) {
  function tableShouldBeSkipped (line 241) | function tableShouldBeSkipped(tableNode) {
  function tableShouldBeSkipped_ (line 251) | function tableShouldBeSkipped_(tableNode) {
  function nodeParentTable (line 259) | function nodeParentTable(node) {
  function handleColSpan (line 268) | function handleColSpan(content, node, emptyChar) {
  function tableColCount (line 276) | function tableColCount(node) {
  function tables (line 286) | function tables(turndownService: TurndownService) {

FILE: api/src/utils/scrape/plugins/taskListItems.ts
  function strikethrough (line 5) | function strikethrough(turndownService: TurndownService) {

FILE: api/src/utils/scrape/plugins/utilities.ts
  function isCodeBlockSpecialCase1 (line 5) | function isCodeBlockSpecialCase1(node: Node) {
  function isCodeBlockSpecialCase2 (line 16) | function isCodeBlockSpecialCase2(node: Node) {
  function isCodeBlock (line 35) | function isCodeBlock(node: Node) {

FILE: api/src/utils/scrape/safeGoTo.ts
  function safeGoto (line 11) | async function safeGoto(page: Page, url: string, options = {}) {

FILE: api/src/utils/text.ts
  function titleCase (line 1) | function titleCase(input: string): string {

FILE: api/src/utils/url.ts
  function getProtocol (line 8) | function getProtocol(protocolType: "http" | "ws"): string {
  function getBaseUrl (line 17) | function getBaseUrl(protocolType: "http" | "ws" = "http"): string {
  function getUrl (line 29) | function getUrl(path: string, protocolType: "http" | "ws" = "http"): str...
  function normalizeUrl (line 41) | function normalizeUrl(url: string): string | null {

FILE: repl/src/script.ts
  function run (line 3) | async function run() {

FILE: ui/src/App.tsx
  function App (line 11) | function App() {

FILE: ui/src/components/badges/proxy-badge.tsx
  function ProxyBadge (line 5) | function ProxyBadge({ proxy }: { proxy: string }) {

FILE: ui/src/components/badges/user-agent-badge.tsx
  function UserAgentBadge (line 6) | function UserAgentBadge({ userAgent }: { userAgent: string }) {

FILE: ui/src/components/badges/websocket-url-badge.tsx
  function WebsocketUrlBadge (line 5) | function WebsocketUrlBadge({ url }: { url: string }) {

FILE: ui/src/components/icons/ChromeIcon.tsx
  function ChromeIcon (line 3) | function ChromeIcon({

FILE: ui/src/components/icons/DeleteIcon.tsx
  function DeleteIcon (line 3) | function DeleteIcon({ width = 28, height = 28 }: IconProps) {

FILE: ui/src/components/icons/KeyIcon.tsx
  function KeyIcon (line 3) | function KeyIcon({

FILE: ui/src/components/icons/LoadingSpinner.tsx
  type LoadingSpinnerProps (line 3) | interface LoadingSpinnerProps {

FILE: ui/src/components/icons/NinjaIcon.tsx
  function NinjaIcon (line 3) | function NinjaIcon({ color = "#A1A1AA" }: IconProps) {

FILE: ui/src/components/illustrations/command-line.tsx
  function CommandLine (line 1) | function CommandLine() {

FILE: ui/src/components/illustrations/globe.tsx
  function Globe (line 1) | function Globe() {

FILE: ui/src/components/loading/Loading.tsx
  function Loading (line 3) | function Loading() {

FILE: ui/src/components/sessions/session-console/index.tsx
  type SessionConsoleProps (line 7) | interface SessionConsoleProps {
  function SessionConsole (line 11) | function SessionConsole({ id }: SessionConsoleProps) {

FILE: ui/src/components/sessions/session-console/session-details.tsx
  function SessionDetails (line 6) | function SessionDetails({ id }: { id: string | null }) {

FILE: ui/src/components/sessions/session-console/session-devtools.tsx
  function SessionDevTools (line 4) | function SessionDevTools() {

FILE: ui/src/components/sessions/session-console/session-logs.tsx
  function SessionLogs (line 4) | function SessionLogs({ id }: { id: string }) {

FILE: ui/src/components/sessions/session-viewer/empty-state.tsx
  function EmptyState (line 5) | function EmptyState() {

FILE: ui/src/components/sessions/session-viewer/live-empty-state.tsx
  function LiveEmptyState (line 7) | function LiveEmptyState({ session }: { session: SessionDetails }) {

FILE: ui/src/components/sessions/session-viewer/session-viewer.tsx
  type SessionViewerProps (line 6) | type SessionViewerProps = {
  function SessionViewer (line 12) | function SessionViewer({ id }: SessionViewerProps) {

FILE: ui/src/components/theme-provider.tsx
  type Theme (line 3) | type Theme = "dark" | "light" | "system";
  type ThemeProviderProps (line 5) | type ThemeProviderProps = {
  type ThemeProviderState (line 11) | type ThemeProviderState = {
  function ThemeProvider (line 23) | function ThemeProvider({

FILE: ui/src/components/ui/badge.tsx
  type BadgeProps (line 25) | interface BadgeProps
  function Badge (line 29) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: ui/src/components/ui/button.tsx
  type ButtonProps (line 34) | interface ButtonProps

FILE: ui/src/components/ui/form.tsx
  type FormFieldContextValue (line 18) | type FormFieldContextValue<
  type FormItemContextValue (line 63) | type FormItemContextValue = {

FILE: ui/src/components/ui/input.tsx
  type InputProps (line 5) | interface InputProps

FILE: ui/src/components/ui/pagination.tsx
  type PaginationLinkProps (line 41) | type PaginationLinkProps = {

FILE: ui/src/components/ui/toast.tsx
  type ToastProps (line 113) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
  type ToastActionElement (line 115) | type ToastActionElement = React.ReactElement<typeof ToastAction>

FILE: ui/src/components/ui/toaster.tsx
  function Toaster (line 11) | function Toaster() {

FILE: ui/src/containers/session-container.tsx
  function SessionContainer (line 9) | function SessionContainer() {

FILE: ui/src/contexts/sessions-context/sessions-context.tsx
  function SessionsProvider (line 24) | function SessionsProvider({

FILE: ui/src/contexts/sessions-context/sessions-context.types.ts
  type SessionsContextType (line 10) | type SessionsContextType = {
  type SessionsProviderProps (line 22) | type SessionsProviderProps = {

FILE: ui/src/hooks/use-sessions-context.ts
  function useSessionsContext (line 5) | function useSessionsContext(): SessionsContextType {

FILE: ui/src/hooks/use-toast.ts
  constant TOAST_LIMIT (line 11) | const TOAST_LIMIT = 1
  constant TOAST_REMOVE_DELAY (line 12) | const TOAST_REMOVE_DELAY = 1000000
  type ToasterToast (line 14) | type ToasterToast = ToastProps & {
  function genId (line 30) | function genId() {
  type ActionType (line 35) | type ActionType = typeof actionTypes
  type Action (line 37) | type Action =
  type State (line 55) | interface State {
  function dispatch (line 136) | function dispatch(action: Action) {
  type Toast (line 143) | type Toast = Omit<ToasterToast, "id">
  function toast (line 145) | function toast({ ...props }: Toast) {
  function useToast (line 174) | function useToast() {

FILE: ui/src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: ui/src/root-layout.tsx
  function RootLayout (line 9) | function RootLayout() {

FILE: ui/src/steel-client/types.gen.ts
  type ScrapeRequest (line 3) | type ScrapeRequest = {
  type ScrapeResponse (line 16) | type ScrapeResponse = {
  type ScreenshotRequest (line 49) | type ScreenshotRequest = {
  type ScreenshotResponse (line 60) | type ScreenshotResponse = unknown;
  type PDFRequest (line 62) | type PDFRequest = {
  type PDFResponse (line 72) | type PDFResponse = unknown;
  type CreateSession (line 74) | type CreateSession = {
  type device (line 294) | type device = "desktop" | "mobile";
  type SessionDetails (line 301) | type SessionDetails = {
  type status (line 382) | type status = "idle" | "live" | "released" | "failed";
  type MultipleSessions (line 394) | type MultipleSessions = {
  type SessionContextSchema (line 474) | type SessionContextSchema = {
  type RecordedEvents (line 596) | type RecordedEvents = {
  type ReleaseSession (line 603) | type ReleaseSession = {
  type SessionStreamQuery (line 685) | type SessionStreamQuery = {
  type theme (line 711) | type theme = "dark" | "light";
  type SessionStreamResponse (line 724) | type SessionStreamResponse = string;
  type SessionLiveDetailsResponse (line 726) | type SessionLiveDetailsResponse = {
  type LogQuerySchema (line 748) | type LogQuerySchema = {
  type LogStatsSchema (line 758) | type LogStatsSchema = {
  type LogQueryResultSchema (line 765) | type LogQueryResultSchema = {
  type ExportLogsSchema (line 773) | type ExportLogsSchema = {
  type GetDevtoolsUrlSchema (line 785) | type GetDevtoolsUrlSchema = {
  type LaunchRequest (line 789) | type LaunchRequest = {
  type LaunchResponse (line 820) | type LaunchResponse = {
  type FileUploadRequest (line 824) | type FileUploadRequest = {
  type FileDetails (line 835) | type FileDetails = {
  type MultipleFiles (line 850) | type MultipleFiles = {
  type ScrapeData (line 870) | type ScrapeData = {
  type ScrapeResponse2 (line 874) | type ScrapeResponse2 = ScrapeResponse;
  type ScrapeError (line 876) | type ScrapeError = unknown;
  type ScreenshotData (line 878) | type ScreenshotData = {
  type ScreenshotResponse2 (line 882) | type ScreenshotResponse2 = ScreenshotResponse;
  type ScreenshotError (line 884) | type ScreenshotError = unknown;
  type PdfData (line 886) | type PdfData = {
  type PdfResponse (line 890) | type PdfResponse = PDFResponse;
  type PdfError (line 892) | type PdfError = unknown;
  type HealthResponse (line 894) | type HealthResponse = unknown;
  type HealthError (line 896) | type HealthError = unknown;
  type LaunchBrowserSessionData (line 898) | type LaunchBrowserSessionData = {
  type LaunchBrowserSessionResponse (line 902) | type LaunchBrowserSessionResponse = SessionDetails;
  type LaunchBrowserSessionError (line 904) | type LaunchBrowserSessionError = unknown;
  type GetSessionsResponse (line 906) | type GetSessionsResponse = MultipleSessions;
  type GetSessionsError (line 908) | type GetSessionsError = unknown;
  type GetSessionDetailsData (line 910) | type GetSessionDetailsData = {
  type GetSessionDetailsResponse (line 916) | type GetSessionDetailsResponse = SessionDetails;
  type GetSessionDetailsError (line 918) | type GetSessionDetailsError = unknown;
  type GetBrowserContextData (line 920) | type GetBrowserContextData = {
  type GetBrowserContextResponse (line 926) | type GetBrowserContextResponse = SessionContextSchema;
  type GetBrowserContextError (line 928) | type GetBrowserContextError = unknown;
  type ReleaseBrowserSessionData (line 930) | type ReleaseBrowserSessionData = {
  type ReleaseBrowserSessionResponse (line 936) | type ReleaseBrowserSessionResponse = ReleaseSession;
  type ReleaseBrowserSessionError (line 938) | type ReleaseBrowserSessionError = unknown;
  type ReleaseBrowserSessionsResponse (line 940) | type ReleaseBrowserSessionsResponse = ReleaseSession;
  type ReleaseBrowserSessionsError (line 942) | type ReleaseBrowserSessionsError = unknown;
  type GetSessionDebuggerStreamData (line 944) | type GetSessionDebuggerStreamData = {
  type GetSessionDebuggerStreamResponse (line 969) | type GetSessionDebuggerStreamResponse = SessionStreamResponse;
  type GetSessionDebuggerStreamError (line 971) | type GetSessionDebuggerStreamError = unknown;
  type ReceiveEventsData (line 973) | type ReceiveEventsData = {
  type ReceiveEventsResponse (line 977) | type ReceiveEventsResponse = unknown;
  type ReceiveEventsError (line 979) | type ReceiveEventsError = unknown;
  type GetSessionLiveDetailsData (line 981) | type GetSessionLiveDetailsData = {
  type GetSessionLiveDetailsResponse (line 987) | type GetSessionLiveDetailsResponse = SessionLiveDetailsResponse;
  type GetSessionLiveDetailsError (line 989) | type GetSessionLiveDetailsError = unknown;
  type ScrapeSessionData (line 991) | type ScrapeSessionData = {
  type ScrapeSessionResponse (line 995) | type ScrapeSessionResponse = ScrapeResponse;
  type ScrapeSessionError (line 997) | type ScrapeSessionError = unknown;
  type ScreenshotSessionData (line 999) | type ScreenshotSessionData = {
  type ScreenshotSessionResponse (line 1003) | type ScreenshotSessionResponse = ScreenshotResponse;
  type ScreenshotSessionError (line 1005) | type ScreenshotSessionError = unknown;
  type PdfSessionData (line 1007) | type PdfSessionData = {
  type PdfSessionResponse (line 1011) | type PdfSessionResponse = PDFResponse;
  type PdfSessionError (line 1013) | type PdfSessionError = unknown;
  type GetDevtoolsUrlData (line 1015) | type GetDevtoolsUrlData = {
  type GetDevtoolsUrlResponse (line 1021) | type GetDevtoolsUrlResponse = unknown;
  type GetDevtoolsUrlError (line 1023) | type GetDevtoolsUrlError = unknown;
  type UploadFileData (line 1025) | type UploadFileData = {
  type UploadFileResponse (line 1032) | type UploadFileResponse = FileDetails;
  type UploadFileError (line 1034) | type UploadFileError = unknown;
  type ListFilesData (line 1036) | type ListFilesData = {
  type ListFilesResponse (line 1042) | type ListFilesResponse = MultipleFiles;
  type ListFilesError (line 1044) | type ListFilesError = unknown;
  type DeleteAllFilesData (line 1046) | type DeleteAllFilesData = {
  type DeleteAllFilesResponse (line 1052) | type DeleteAllFilesResponse = void;
  type DeleteAllFilesError (line 1054) | type DeleteAllFilesError = unknown;
  type DownloadFileData (line 1056) | type DownloadFileData = {
  type DownloadFileResponse (line 1063) | type DownloadFileResponse = unknown;
  type DownloadFileError (line 1065) | type DownloadFileError = unknown;
  type DeleteFileData (line 1067) | type DeleteFileData = {
  type DeleteFileResponse (line 1074) | type DeleteFileResponse = void;
  type DeleteFileError (line 1076) | type DeleteFileError = unknown;
  type DownloadArchiveData (line 1078) | type DownloadArchiveData = {
  type DownloadArchiveResponse (line 1084) | type DownloadArchiveResponse = unknown;
  type DownloadArchiveError (line 1086) | type DownloadArchiveError = unknown;
  type GetV1LogsQueryData (line 1088) | type GetV1LogsQueryData = {
  type GetV1LogsQueryResponse (line 1100) | type GetV1LogsQueryResponse = unknown;
  type GetV1LogsQueryError (line 1102) | type GetV1LogsQueryError = unknown;
  type GetV1LogsStatsResponse (line 1104) | type GetV1LogsStatsResponse = unknown;
  type GetV1LogsStatsError (line 1106) | type GetV1LogsStatsError = unknown;
  type GetV1LogsStreamResponse (line 1108) | type GetV1LogsStreamResponse = unknown;
  type GetV1LogsStreamError (line 1110) | type GetV1LogsStreamError = unknown;
  type PostV1LogsExportData (line 1112) | type PostV1LogsExportData = {
  type PostV1LogsExportResponse (line 1124) | type PostV1LogsExportResponse = unknown;
  type PostV1LogsExportError (line 1126) | type PostV1LogsExportError = unknown;
  type DeleteV1LogsResponse (line 1128) | type DeleteV1LogsResponse = unknown;
  type DeleteV1LogsError (line 1130) | type DeleteV1LogsError = unknown;
  type ScrapeResponseTransformer (line 1132) | type ScrapeResponseTransformer = (data: any) => Promise<ScrapeResponse>;
  type ScrapeResponseModelResponseTransformer (line 1134) | type ScrapeResponseModelResponseTransformer = (
  type LaunchBrowserSessionResponseTransformer (line 1153) | type LaunchBrowserSessionResponseTransformer = (
  type SessionDetailsModelResponseTransformer (line 1157) | type SessionDetailsModelResponseTransformer = (
  type GetSessionDetailsResponseTransformer (line 1175) | type GetSessionDetailsResponseTransformer = (
  type ReleaseBrowserSessionResponseTransformer (line 1185) | type ReleaseBrowserSessionResponseTransformer = (
  type ReleaseSessionModelResponseTransformer (line 1189) | type ReleaseSessionModelResponseTransformer = (
  type ReleaseBrowserSessionsResponseTransformer (line 1207) | type ReleaseBrowserSessionsResponseTransformer = (
  type ScrapeSessionResponseTransformer (line 1217) | type ScrapeSessionResponseTransformer = (
  type UploadFileResponseTransformer (line 1227) | type UploadFileResponseTransformer = (
  type FileDetailsModelResponseTransformer (line 1231) | type FileDetailsModelResponseTransformer = (data: any) => FileDetails;

FILE: ui/src/styles/theme.ts
  type ReplaceKeyPrefix (line 16) | type ReplaceKeyPrefix<
  function renameKeysWithPrefix (line 25) | function renameKeysWithPrefix<
  type Theme (line 64) | type Theme = typeof theme;

FILE: ui/src/types/cdp.ts
  type ProtocolCommands (line 1) | enum ProtocolCommands {
  type HostCommands (line 6) | enum HostCommands {
  type WorkerCommands (line 13) | enum WorkerCommands {
  type Message (line 21) | interface Message {

FILE: ui/src/types/props.ts
  type IconProps (line 1) | interface IconProps {

FILE: ui/src/utils/formatting.ts
  function formatDuration (line 1) | function formatDuration(durationInMs: number): string {
Condensed preview — 281 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,535K chars).
[
  {
    "path": ".dockerignore",
    "chars": 99,
    "preview": "**/node_modules/\nbuild/\n**/.env\n**/.env.local\nDockerfile\ndocker-compose.yml\napi/extensions/**/dist\n"
  },
  {
    "path": ".env.example",
    "chars": 56,
    "preview": "VITE_API_URL=http://HOST:3000\nVITE_WS_URL=ws://HOST:3000"
  },
  {
    "path": ".gitattributes",
    "chars": 48,
    "preview": "* text=auto\n*.sh text eol=lf\n*.conf text eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 985,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG] TITLE\"\nlabels: ''\nassignees: ''\n\n---\n\nIssue"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 766,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 💬 Discord Community\n    url: https://discord.gg/steel-dev\n    about"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 1038,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for Steel Browser\ntitle: \"[FEATURE] \"\nlabels: 'enhancement'\nassignees: "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "chars": 966,
    "preview": "---\nname: Question\nabout: Ask a question about using Steel Browser\ntitle: \"[QUESTION] \"\nlabels: 'question'\nassignees: ''"
  },
  {
    "path": ".github/auto-assign.yml",
    "chars": 503,
    "preview": "# Auto-assign configuration for Steel Browser\n# This file is used by the kentaro-m/auto-assign-action\n\n# Add reviewers t"
  },
  {
    "path": ".github/labeler.yml",
    "chars": 1493,
    "preview": "# GitHub Labeler Configuration for Steel Browser\n# This file is used by the actions/labeler@v5 action in pr-checks.yml\n\n"
  },
  {
    "path": ".github/labels.yml",
    "chars": 3394,
    "preview": "# GitHub Labels Configuration for Steel Browser\n# This file can be used with the github-labels CLI tool to sync labels\n\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 1841,
    "preview": "## Description\n\nBrief description of the changes in this PR.\n\n## Type of Change\n\n- [ ] Bug fix (non-breaking change whic"
  },
  {
    "path": ".github/workflows/auto-assign.yml",
    "chars": 314,
    "preview": "# .github/workflows/auto-assign.yml\nname: Auto Assign\n\non:\n  pull_request:\n    types: [opened, ready_for_review]\n\njobs:\n"
  },
  {
    "path": ".github/workflows/build-docker.yml",
    "chars": 1220,
    "preview": "name: Build and Push Latest Docker Image to GHCR\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  push_to_registry:\n    "
  },
  {
    "path": ".github/workflows/check-build.yml",
    "chars": 730,
    "preview": "name: Check Docker Build\n\non:\n  pull_request:\n    branches:\n      - main\n\njobs:\n  check-docker-build:\n    name: Check Do"
  },
  {
    "path": ".github/workflows/pr-checks.yml",
    "chars": 1419,
    "preview": "# .github/workflows/pr-checks.yml\nname: PR Quality Checks\n\non:\n  pull_request_target:\n    branches: [main]\n\npermissions:"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 3795,
    "preview": "name: Automatic Release\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permiss"
  },
  {
    "path": ".github/workflows/welcome.yml",
    "chars": 684,
    "preview": "# .github/workflows/welcome.yml\nname: Welcome\n\non:\n  issues:\n    types: [opened]\n  pull_request:\n    types: [opened]\n\njo"
  },
  {
    "path": ".gitignore",
    "chars": 298,
    "preview": "node_modules\n.pnp\n.pnp.js\ncoverage\n.DS_Store\n*.pem\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n.env\n"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 39,
    "preview": "npx --no-install commitlint --edit \"$1\""
  },
  {
    "path": ".husky/pre-commit",
    "chars": 346,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\n# Code formatting and linting\necho \"🎨 Running code formatting...\"\nn"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 14112,
    "preview": "# Contributing to Steel Browser\n\nWelcome to Steel Browser! 🎉 We're excited that you're interested in contributing to our"
  },
  {
    "path": "Dockerfile",
    "chars": 3140,
    "preview": "ARG NODE_VERSION=22.13.0\n\nFROM node:${NODE_VERSION} AS base\n\nWORKDIR /app\n\nENV NODE_ENV=\"production\" \\\n    PUPPETEER_CAC"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 13232,
    "preview": "<br />\n<p align=\"center\">\n<a href=\"https://steel.dev\">\n  <img src=\"images/steel_header_logo.png\" alt=\"Steel Logo\" width="
  },
  {
    "path": "api/.dockerignore",
    "chars": 96,
    "preview": "node_modules/\nbuild/\n**/.env\n**/.env.local\nDockerfile\ndocker-compose.yml\napi/extensions/**/dist\n"
  },
  {
    "path": "api/.env.example",
    "chars": 660,
    "preview": "# Server configuration\nNODE_ENV=development\nHOST=0.0.0.0\nPORT=3000\n# Use DOMAIN if you want to specify a full domain nam"
  },
  {
    "path": "api/.gitattributes",
    "chars": 63,
    "preview": "src/scripts/* linguist-vendored\nextensions/* linguist-vendored\n"
  },
  {
    "path": "api/.gitignore",
    "chars": 410,
    "preview": "node_modules\n.pnp\n.pnp.js\ncoverage\n.DS_Store\n*.pem\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n.env\n"
  },
  {
    "path": "api/.prettierignore",
    "chars": 35,
    "preview": "# Ignore artifacts:\nbuild\ncoverage\n"
  },
  {
    "path": "api/.prettierrc",
    "chars": 133,
    "preview": "{\n  \"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"bracketSpacing\": true,\n  \"arrowParens"
  },
  {
    "path": "api/.puppeteerrc.cjs",
    "chars": 196,
    "preview": "const { join } = require(\"path\");\n\n/**\n * @type {import(\"puppeteer\").Configuration}\n */\nmodule.exports = {\n  defaultProd"
  },
  {
    "path": "api/Dockerfile",
    "chars": 2661,
    "preview": "ARG NODE_VERSION=22.13.0\n\nFROM node:${NODE_VERSION}-slim AS base\n\nWORKDIR /app\n\nENV NODE_ENV=\"production\" \\\n    PUPPETEE"
  },
  {
    "path": "api/entrypoint.sh",
    "chars": 3526,
    "preview": "#!/bin/sh\nset -e  # Exit on error\n\n# Function to log with timestamp\nlog() {\n    if [ \"$DEBUG\" = \"true\" ]; then\n        e"
  },
  {
    "path": "api/extensions/recorder/.gitignore",
    "chars": 20,
    "preview": "node_modules/\ndist/\n"
  },
  {
    "path": "api/extensions/recorder/manifest.json",
    "chars": 433,
    "preview": "{\n  \"manifest_version\": 3,\n  \"name\": \"Steel Recording Extension\",\n  \"version\": \"1.0\",\n  \"permissions\": [\n    \"scripting\""
  },
  {
    "path": "api/extensions/recorder/package.json",
    "chars": 321,
    "preview": "{\n  \"name\": \"steel-recording-extension\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"dependencies\": "
  },
  {
    "path": "api/extensions/recorder/src/background.js",
    "chars": 1491,
    "preview": "const LOCAL_API_URL = \"http://localhost:3000/v1/events\";\nconst FALLBACK_API_URL = \"http://0.0.0.0:3000/v1/events\"; // Ne"
  },
  {
    "path": "api/extensions/recorder/src/inject.js",
    "chars": 1317,
    "preview": "import { record } from \"rrweb\";\nimport { pack } from \"@rrweb/packer\";\n\nrecord({\n  emit: (event) => {\n    chrome.runtime."
  },
  {
    "path": "api/extensions/recorder/webpack.config.mjs",
    "chars": 491,
    "preview": "import path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { dirname } from \"path\";\n\nconst __filename = fileUR"
  },
  {
    "path": "api/nginx.conf",
    "chars": 732,
    "preview": "events {\n    worker_connections 1024;\n}\n\nhttp {\n    map $http_upgrade $connection_upgrade {\n        default upgrade;\n   "
  },
  {
    "path": "api/openapi/generate.ts",
    "chars": 879,
    "preview": "import { writeFileSync } from \"fs\";\nimport { server } from \"../src\";\nimport { env } from \"../src/env.js\";\n\ninterface Ope"
  },
  {
    "path": "api/openapi/schemas.json",
    "chars": 74948,
    "preview": "{\n  \"openapi\": \"3.0.3\",\n  \"info\": {\n    \"title\": \"Steel Browser Instance API\",\n    \"description\": \"Documentation for con"
  },
  {
    "path": "api/package.json",
    "chars": 3748,
    "preview": "{\n  \"name\": \"@steel-browser/api\",\n  \"version\": \"0.5.1\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"type\": \"module\",\n "
  },
  {
    "path": "api/selenium/driver/LICENSE.chromedriver",
    "chars": 1536,
    "preview": "// Copyright 2015 The Chromium Authors\n//\n// Redistribution and use in source and binary forms, with or without\n// modif"
  },
  {
    "path": "api/selenium/driver/THIRD_PARTY_NOTICES.chromedriver",
    "chars": 460852,
    "preview": "--------------------\nLicense notice for Google Double Conversion\n--------------------\nCopyright 2006-2011, the V8 projec"
  },
  {
    "path": "api/src/config.ts",
    "chars": 1425,
    "preview": "import { FastifyServerOptions } from \"fastify\";\nimport { env } from \"./env.js\";\nimport stringify from \"json-stringify-sa"
  },
  {
    "path": "api/src/env.ts",
    "chars": 2558,
    "preview": "import { z } from \"zod\";\nimport { config } from \"dotenv\";\n\nconfig();\n\nconst envSchema = z.object({\n  NODE_ENV: z\n    .en"
  },
  {
    "path": "api/src/index.ts",
    "chars": 1349,
    "preview": "import fastify from \"fastify\";\nimport fastifyCors from \"@fastify/cors\";\nimport fastifySensible from \"@fastify/sensible\";"
  },
  {
    "path": "api/src/modules/actions/actions.controller.ts",
    "chars": 17803,
    "preview": "import { FastifyReply } from \"fastify\";\nimport { BrowserContext, Page, HTTPResponse } from \"puppeteer-core\";\nimport { CD"
  },
  {
    "path": "api/src/modules/actions/actions.routes.ts",
    "chars": 2325,
    "preview": "import { FastifyInstance, FastifyReply } from \"fastify\";\nimport { handlePDF, handleScrape, handleScreenshot, handleSearc"
  },
  {
    "path": "api/src/modules/actions/actions.schema.ts",
    "chars": 3681,
    "preview": "import { FastifyRequest } from \"fastify\";\nimport { z } from \"zod\";\nimport { ScrapeFormat } from \"../../types/enums.js\";\n"
  },
  {
    "path": "api/src/modules/cdp/cdp.routes.ts",
    "chars": 935,
    "preview": "import { FastifyInstance, FastifyRequest, FastifyReply } from \"fastify\";\nimport { z } from \"zod\";\nimport { $ref } from \""
  },
  {
    "path": "api/src/modules/cdp/cdp.schemas.ts",
    "chars": 155,
    "preview": "import { z } from \"zod\";\n\nexport const GetDevtoolsUrlSchema = z.object({\n  pageId: z.string().optional(),\n});\n\nexport de"
  },
  {
    "path": "api/src/modules/files/files.controller.ts",
    "chars": 10311,
    "preview": "import { MultipartFile } from \"@fastify/multipart\";\nimport archiver from \"archiver\";\nimport { randomUUID } from \"crypto\""
  },
  {
    "path": "api/src/modules/files/files.routes.ts",
    "chars": 4242,
    "preview": "import fastifyMultipart from \"@fastify/multipart\";\nimport { FastifyInstance, FastifyRequest } from \"fastify\";\nimport { $"
  },
  {
    "path": "api/src/modules/files/files.schema.ts",
    "chars": 909,
    "preview": "import { z } from \"zod\";\n\nconst FileUploadRequest = z.object({\n  file: z.any().describe(\"The file to upload (binary) or "
  },
  {
    "path": "api/src/modules/logs/logs.routes.ts",
    "chars": 4294,
    "preview": "import { FastifyPluginAsync, FastifyRequest } from \"fastify\";\nimport { LogQuerySchema, ExportLogsSchema, LogQueryInput }"
  },
  {
    "path": "api/src/modules/logs/logs.schema.ts",
    "chars": 1217,
    "preview": "import { z } from \"zod\";\n\nexport const LogQuerySchema = z.object({\n  startTime: z.string().datetime().optional(),\n  endT"
  },
  {
    "path": "api/src/modules/selenium/selenium.routes.ts",
    "chars": 2318,
    "preview": "import { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport fastifyReplyFrom from \"@fastify/reply-fr"
  },
  {
    "path": "api/src/modules/selenium/selenium.schema.ts",
    "chars": 1164,
    "preview": "import { z } from \"zod\";\n\nconst LaunchRequest = z.object({\n  options: z.object({\n    args: z.array(z.string()).optional("
  },
  {
    "path": "api/src/modules/sessions/sessions.controller.ts",
    "chars": 6992,
    "preview": "import { CDPService } from \"../../services/cdp/cdp.service.js\";\nimport { FastifyInstance, FastifyReply, FastifyRequest }"
  },
  {
    "path": "api/src/modules/sessions/sessions.routes.ts",
    "chars": 7251,
    "preview": "import { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport {\n  handleLaunchBrowserSession,\n  handle"
  },
  {
    "path": "api/src/modules/sessions/sessions.schema.ts",
    "chars": 7776,
    "preview": "import { FastifyRequest } from \"fastify\";\nimport { z } from \"zod\";\nimport {\n  ScrapeRequestBody,\n  ScreenshotRequestBody"
  },
  {
    "path": "api/src/plugins/browser-session.ts",
    "chars": 528,
    "preview": "import { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { SessionService } from \"../servic"
  },
  {
    "path": "api/src/plugins/browser-socket/browser-socket.ts",
    "chars": 2192,
    "preview": "import { type FastifyInstance, type FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { WebSo"
  },
  {
    "path": "api/src/plugins/browser-socket/casting.handler.ts",
    "chars": 14886,
    "preview": "import { IncomingMessage } from \"http\";\nimport puppeteer, { Browser, CDPSession, Page } from \"puppeteer-core\";\nimport { "
  },
  {
    "path": "api/src/plugins/browser-socket/handlers/cast.handler.ts",
    "chars": 651,
    "preview": "import { IncomingMessage } from \"http\";\nimport { Duplex } from \"stream\";\nimport { WebSocketHandler, WebSocketHandlerCont"
  },
  {
    "path": "api/src/plugins/browser-socket/handlers/index.ts",
    "chars": 609,
    "preview": "export { logsHandler } from \"./logs.handler.js\";\nexport { castHandler } from \"./cast.handler.js\";\nexport { pageIdHandler"
  },
  {
    "path": "api/src/plugins/browser-socket/handlers/logs.handler.ts",
    "chars": 1191,
    "preview": "import { IncomingMessage } from \"http\";\nimport { Duplex } from \"stream\";\nimport { WebSocket } from \"ws\";\nimport { EmitEv"
  },
  {
    "path": "api/src/plugins/browser-socket/handlers/pageId.handler.ts",
    "chars": 1140,
    "preview": "import { IncomingMessage } from \"http\";\nimport { Duplex } from \"stream\";\nimport { WebSocket } from \"ws\";\nimport { WebSoc"
  },
  {
    "path": "api/src/plugins/browser-socket/handlers/recording.handler.ts",
    "chars": 1357,
    "preview": "import { IncomingMessage } from \"http\";\nimport { Duplex } from \"stream\";\nimport { WebSocket } from \"ws\";\nimport { EmitEv"
  },
  {
    "path": "api/src/plugins/browser.ts",
    "chars": 2475,
    "preview": "import { FastifyPluginAsync } from \"fastify\";\nimport { CDPService } from \"../services/cdp/cdp.service.js\";\nimport fp fro"
  },
  {
    "path": "api/src/plugins/custom-body-parser.ts",
    "chars": 776,
    "preview": "import fp from \"fastify-plugin\";\nimport { type FastifyInstance, type FastifyPluginAsync } from \"fastify\";\n\nconst customB"
  },
  {
    "path": "api/src/plugins/file-storage.ts",
    "chars": 375,
    "preview": "import { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { FileService } from \"../services/"
  },
  {
    "path": "api/src/plugins/request-logger.ts",
    "chars": 1595,
    "preview": "import { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\n\ndeclare module \"fastify\" {\n  interface F"
  },
  {
    "path": "api/src/plugins/scalar-theme.ts",
    "chars": 5760,
    "preview": "export default `\n/* Basic theme */\n.light-mode {\n  --scalar-color-1: #21201c; /* Sand 12 */\n  --scalar-color-2: #63635e;"
  },
  {
    "path": "api/src/plugins/schemas.ts",
    "chars": 1971,
    "preview": "import { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport fastifySwagger from \"@fastify/swag"
  },
  {
    "path": "api/src/plugins/selenium.ts",
    "chars": 382,
    "preview": "import { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { SeleniumService } from \"../servi"
  },
  {
    "path": "api/src/plugins/ui-plugin.ts",
    "chars": 1565,
    "preview": "import fastifyStatic from \"@fastify/static\";\nimport { FastifyPluginAsync, FastifyRequest, FastifyReply } from \"fastify\";"
  },
  {
    "path": "api/src/routes.ts",
    "chars": 459,
    "preview": "export { default as actionsRoutes } from \"./modules/actions/actions.routes.js\";\nexport { default as sessionsRoutes } fro"
  },
  {
    "path": "api/src/scripts/fingerprint.js",
    "chars": 23940,
    "preview": "const _0x28f974 = _0x5610;\n(function (_0x3ccf48, _0x290ea1) {\n  const _0x17ecfd = _0x5610,\n    _0xa8e50e = _0x3ccf48();\n"
  },
  {
    "path": "api/src/scripts/index.ts",
    "chars": 2653,
    "preview": "import fs from \"fs\";\nimport path, { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst SCRIPTS_DIR = pat"
  },
  {
    "path": "api/src/services/cdp/cdp.service.ts",
    "chars": 52170,
    "preview": "import { EventEmitter } from \"events\";\nimport { FastifyBaseLogger } from \"fastify\";\nimport {\n  BrowserFingerprintWithHea"
  },
  {
    "path": "api/src/services/cdp/errors/launch-errors.ts",
    "chars": 9448,
    "preview": "/**\n * Custom error classes for categorizing CDPService launch failures\n * These allow for slightly more intelligent err"
  },
  {
    "path": "api/src/services/cdp/instrumentation/browser-logger.test.ts",
    "chars": 3717,
    "preview": "import { describe, it, expect, vi } from \"vitest\";\nimport { createBrowserLogger } from \"./browser-logger.js\";\nimport { B"
  },
  {
    "path": "api/src/services/cdp/instrumentation/browser-logger.ts",
    "chars": 3069,
    "preview": "import { BrowserEventUnion } from \"./types.js\";\nimport { LogStorage } from \"./storage/index.js\";\nimport { EventEmitter }"
  },
  {
    "path": "api/src/services/cdp/instrumentation/cdp-events.ts",
    "chars": 2296,
    "preview": "import type { CDPSession } from \"puppeteer-core\";\nimport { BrowserEventType } from \"../../../types/index.js\";\nimport { B"
  },
  {
    "path": "api/src/services/cdp/instrumentation/extension-events.ts",
    "chars": 2818,
    "preview": "import { Protocol, Target, TargetType } from \"puppeteer-core\";\nimport type { BrowserLogger } from \"./browser-logger.js\";"
  },
  {
    "path": "api/src/services/cdp/instrumentation/page-console.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "api/src/services/cdp/instrumentation/page-events.ts",
    "chars": 6415,
    "preview": "import type { Page, CDPSession, TargetType, Protocol } from \"puppeteer-core\";\nimport { BrowserEventType } from \"../../.."
  },
  {
    "path": "api/src/services/cdp/instrumentation/storage/duckdb-storage.ts",
    "chars": 13844,
    "preview": "import { Database } from \"duckdb-async\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { LogStorage, Log"
  },
  {
    "path": "api/src/services/cdp/instrumentation/storage/in-memory-storage.ts",
    "chars": 3341,
    "preview": "import { LogStorage, LogQuery, LogQueryResult } from \"./log-storage.interface.js\";\nimport { BrowserEventUnion } from \".."
  },
  {
    "path": "api/src/services/cdp/instrumentation/storage/index.ts",
    "chars": 121,
    "preview": "export * from \"./log-storage.interface.js\";\nexport * from \"./duckdb-storage.js\";\nexport * from \"./in-memory-storage.js\";"
  },
  {
    "path": "api/src/services/cdp/instrumentation/storage/log-storage.interface.ts",
    "chars": 1451,
    "preview": "import { BrowserEventUnion } from \"../types.js\";\n\nexport interface LogQuery {\n  startTime?: Date;\n  endTime?: Date;\n  ev"
  },
  {
    "path": "api/src/services/cdp/instrumentation/storage/safe-json.ts",
    "chars": 371,
    "preview": "export function safeStringify(value: unknown): string {\n  const seen = new WeakSet<object>();\n\n  return JSON.stringify(v"
  },
  {
    "path": "api/src/services/cdp/instrumentation/target-manager.ts",
    "chars": 5530,
    "preview": "import { type Target, type CDPSession, TargetType } from \"puppeteer-core\";\nimport type { FastifyBaseLogger } from \"fasti"
  },
  {
    "path": "api/src/services/cdp/instrumentation/types.ts",
    "chars": 2713,
    "preview": "import type { TargetType } from \"puppeteer-core\";\nimport type { BrowserEventType } from \"../../../types/enums.js\";\n\nexpo"
  },
  {
    "path": "api/src/services/cdp/instrumentation/utils.ts",
    "chars": 742,
    "preview": "import type { Target, Protocol } from \"puppeteer-core\";\nimport { safeStringify } from \"./storage/safe-json.js\";\n\nexport "
  },
  {
    "path": "api/src/services/cdp/instrumentation/worker-events.ts",
    "chars": 1260,
    "preview": "import type { Target, Protocol, TargetType, CDPSession } from \"puppeteer-core\";\nimport { BrowserEventType } from \"../../"
  },
  {
    "path": "api/src/services/cdp/plugins/core/base-plugin.ts",
    "chars": 1266,
    "preview": "import type { Browser, Page } from \"puppeteer-core\";\nimport type { CDPService } from \"../../cdp.service.js\";\nimport type"
  },
  {
    "path": "api/src/services/cdp/plugins/core/index.ts",
    "chars": 71,
    "preview": "export * from \"./base-plugin.js\";\nexport * from \"./plugin-manager.js\";\n"
  },
  {
    "path": "api/src/services/cdp/plugins/core/plugin-manager.ts",
    "chars": 5280,
    "preview": "import { Browser, Page } from \"puppeteer-core\";\nimport { CDPService } from \"../../cdp.service.js\";\nimport { BasePlugin }"
  },
  {
    "path": "api/src/services/cdp/plugins/pptr-extensions.d.ts",
    "chars": 152,
    "preview": "import { SessionManager } from \"./session/session-manager.js\";\n\ndeclare module \"puppeteer-core\" {\n  interface Page {\n   "
  },
  {
    "path": "api/src/services/cdp/utils/error-handlers.ts",
    "chars": 2750,
    "preview": "import { FastifyBaseLogger } from \"fastify\";\nimport { BaseLaunchError } from \"../errors/launch-errors.js\";\n\n/**\n * Execu"
  },
  {
    "path": "api/src/services/cdp/utils/validation.ts",
    "chars": 6264,
    "preview": "import { FastifyBaseLogger } from \"fastify\";\nimport { BrowserLauncherOptions } from \"../../../types/index.js\";\nimport { "
  },
  {
    "path": "api/src/services/context/chrome-context.service.ts",
    "chars": 3416,
    "preview": "import { EventEmitter } from \"events\";\nimport { FastifyBaseLogger } from \"fastify\";\nimport { getProfilePath } from \"../."
  },
  {
    "path": "api/src/services/context/types.ts",
    "chars": 5468,
    "preview": "import { z } from \"zod\";\n\nexport enum StorageProviderName {\n  Cookies = \"cookies\",\n  LocalStorage = \"localStorage\",\n  Se"
  },
  {
    "path": "api/src/services/file.service.ts",
    "chars": 15797,
    "preview": "import archiver from \"archiver\";\nimport chokidar, { FSWatcher } from \"chokidar\";\nimport fs from \"fs\";\nimport type { Debo"
  },
  {
    "path": "api/src/services/leveldb/localstorage.ts",
    "chars": 4270,
    "preview": "import fs from \"fs/promises\";\nimport path from \"path\";\nimport os from \"os\";\nimport { Level } from \"level\";\nimport iconv "
  },
  {
    "path": "api/src/services/leveldb/sessionstorage.ts",
    "chars": 6274,
    "preview": "import fs from \"fs/promises\";\nimport path from \"path\";\nimport os from \"os\";\nimport { Level } from \"level\";\nimport iconv "
  },
  {
    "path": "api/src/services/selenium.service.ts",
    "chars": 3426,
    "preview": "import { EventEmitter } from \"events\";\nimport { ChildProcess, spawn } from \"child_process\";\nimport { BrowserLauncherOpti"
  },
  {
    "path": "api/src/services/session.service.ts",
    "chars": 9328,
    "preview": "import { FastifyBaseLogger } from \"fastify\";\nimport { mkdir } from \"fs/promises\";\nimport os from \"os\";\nimport path, { di"
  },
  {
    "path": "api/src/services/timezone-fetcher.service.ts",
    "chars": 4250,
    "preview": "import { FastifyBaseLogger } from \"fastify\";\nimport axios, { AxiosError } from \"axios\";\nimport { HttpsProxyAgent } from "
  },
  {
    "path": "api/src/services/websocket-registry.service.ts",
    "chars": 779,
    "preview": "import { WebSocketHandler, WebSocketHandlerRegistry } from \"../types/websocket.js\";\n\nexport class WebSocketRegistryServi"
  },
  {
    "path": "api/src/steel-browser-plugin.ts",
    "chars": 3405,
    "preview": "import fastifyView from \"@fastify/view\";\nimport { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\n"
  },
  {
    "path": "api/src/telemetry/noop.ts",
    "chars": 555,
    "preview": "import type { Span } from \"@opentelemetry/api\";\n\nexport const noopSpan: Span = {\n  spanContext() {\n    return {\n      tr"
  },
  {
    "path": "api/src/telemetry/tracer.ts",
    "chars": 3502,
    "preview": "import type { Span, SpanOptions } from \"@opentelemetry/api\";\nimport { noopSpan } from \"./noop.js\";\n\nlet otel: typeof imp"
  },
  {
    "path": "api/src/templates/live-session-streamer.ejs",
    "chars": 67318,
    "preview": "<!DOCTYPE html>\n  <html data-theme=\"<%= theme %>\" <%= singlePageMode ? 'data-single-page-mode=\"true\"' : '' %>>\n<head>\n  "
  },
  {
    "path": "api/src/types/browser.ts",
    "chars": 1827,
    "preview": "import type { BrowserEventType } from \"./enums.js\";\nimport type {\n  CookieData,\n  IndexedDBDatabase,\n  LocalStorageData,"
  },
  {
    "path": "api/src/types/casting.ts",
    "chars": 1166,
    "preview": "export type MouseEvent = {\n  type: \"mouseEvent\";\n  pageId: string;\n  event: {\n    type: \"mousePressed\" | \"mouseReleased\""
  },
  {
    "path": "api/src/types/enums.ts",
    "chars": 721,
    "preview": "import { BrowserServerOptions } from \"./browser.js\";\n\nexport enum ScrapeFormat {\n  HTML = \"html\",\n  READABILITY = \"reada"
  },
  {
    "path": "api/src/types/fastify.d.ts",
    "chars": 531,
    "preview": "import { FastifyRequest } from \"fastify\";\nimport { CDPService } from \"../services/cdp/cdp.service.js\";\nimport { SessionS"
  },
  {
    "path": "api/src/types/index.ts",
    "chars": 90,
    "preview": "export * from \"./enums.js\";\nexport * from \"./browser.js\";\nexport * from \"./websocket.js\";\n"
  },
  {
    "path": "api/src/types/turndown.d.ts",
    "chars": 287,
    "preview": "declare module \"@joplin/turndown\" {\n  export { default } from \"turndown\";\n  export { Options } from \"turndown\";\n  export"
  },
  {
    "path": "api/src/types/websocket.ts",
    "chars": 760,
    "preview": "import { IncomingMessage } from \"http\";\nimport { Duplex } from \"stream\";\nimport { WebSocketServer } from \"ws\";\nimport { "
  },
  {
    "path": "api/src/utils/browser.ts",
    "chars": 5995,
    "preview": "import fs from \"fs\";\nimport path from \"path\";\nimport { Page } from \"puppeteer-core\";\nimport { env } from \"../env.js\";\n\ne"
  },
  {
    "path": "api/src/utils/casting.ts",
    "chars": 1385,
    "preview": "import { Page } from \"puppeteer-core\";\nimport { NavigationEvent } from \"../types/casting.js\";\nimport { normalizeUrl } fr"
  },
  {
    "path": "api/src/utils/context.ts",
    "chars": 14941,
    "preview": "import { Page } from \"puppeteer-core\";\nimport {\n  SessionData,\n  IndexedDBDatabase,\n  IndexedDBObjectStore,\n  IndexedDBR"
  },
  {
    "path": "api/src/utils/errors.ts",
    "chars": 228,
    "preview": "export function getErrors(e: unknown) {\n  let error: string;\n  if (typeof e === \"string\") {\n    error = e;\n  } else if ("
  },
  {
    "path": "api/src/utils/extensions.ts",
    "chars": 1106,
    "preview": "import fs from \"fs\";\nimport path, { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nexport async function ge"
  },
  {
    "path": "api/src/utils/leveldb.ts",
    "chars": 752,
    "preview": "import path from \"path\";\nimport fs from \"fs/promises\";\n\n/**\n * Utility to copy a LevelDB directory to a temporary path i"
  },
  {
    "path": "api/src/utils/logging.ts",
    "chars": 425,
    "preview": "export const updateLog = async (logUrl: string, log: any) => {\n  try {\n    const response = await fetch(logUrl, {\n      "
  },
  {
    "path": "api/src/utils/passthough-proxy.ts",
    "chars": 3751,
    "preview": "import http, { IncomingHttpHeaders } from \"node:http\";\nimport { PrepareRequestFunctionOpts, PrepareRequestFunctionResult"
  },
  {
    "path": "api/src/utils/proxy.ts",
    "chars": 1979,
    "preview": "import { env } from \"../env.js\";\nimport { SessionService } from \"../services/session.service.js\";\nimport { makePassthrou"
  },
  {
    "path": "api/src/utils/requests.ts",
    "chars": 2680,
    "preview": "const AD_HOSTS = [\n  // Ad Networks & Services\n  \"doubleclick.net\",\n  \"adservice.google.com\",\n  \"googlesyndication.com\","
  },
  {
    "path": "api/src/utils/retry.ts",
    "chars": 4840,
    "preview": "import { FastifyBaseLogger } from \"fastify\";\nimport {\n  BaseLaunchError,\n  ConfigurationError,\n  LaunchTimeoutError,\n  R"
  },
  {
    "path": "api/src/utils/schema.ts",
    "chars": 1772,
    "preview": "import { ZodType, z } from \"zod\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\n\nexport type Models<Key extends "
  },
  {
    "path": "api/src/utils/scrape/cleanHtml.ts",
    "chars": 1385,
    "preview": "import { JSDOM } from \"jsdom\";\n\nexport const cleanHtml = (html: string): string => {\n  const blacklistedElements = new S"
  },
  {
    "path": "api/src/utils/scrape/htmlToMarkdown.ts",
    "chars": 1678,
    "preview": "import { applyFixes } from \"markdownlint\";\nimport { lint } from \"markdownlint/promise\";\nimport Turndown from \"turndown\";"
  },
  {
    "path": "api/src/utils/scrape/index.ts",
    "chars": 205,
    "preview": "export { cleanHtml } from \"./cleanHtml.js\";\nexport { htmlToMarkdown } from \"./htmlToMarkdown.js\";\nexport { getDefuddleCo"
  },
  {
    "path": "api/src/utils/scrape/pdfToHtml.ts",
    "chars": 3744,
    "preview": "import { load as loadHtml } from \"cheerio\";\n\nfunction parsePdfDate(pdfDate?: string | null): string | null {\n  if (!pdfD"
  },
  {
    "path": "api/src/utils/scrape/plugins/highlightedCodeBlock.ts",
    "chars": 1030,
    "preview": "// Adapted from https://github.com/laurent22/joplin/blob/dev/packages/turndown-plugin-gfm/src/tables.js\n\nimport Turndown"
  },
  {
    "path": "api/src/utils/scrape/plugins/inlineLink.ts",
    "chars": 725,
    "preview": "// Adapted from https://github.com/laurent22/joplin/blob/dev/packages/turndown-plugin-gfm/src/tables.js\n\nimport Turndown"
  },
  {
    "path": "api/src/utils/scrape/plugins/strikethrough.ts",
    "chars": 902,
    "preview": "// Adapted from https://github.com/laurent22/joplin/blob/dev/packages/turndown-plugin-gfm/src/tables.js\n\nimport Turndown"
  },
  {
    "path": "api/src/utils/scrape/plugins/table.ts",
    "chars": 9070,
    "preview": "// Adapted from https://github.com/laurent22/joplin/blob/dev/packages/turndown-plugin-gfm/src/tables.js\n\nimport Turndown"
  },
  {
    "path": "api/src/utils/scrape/plugins/taskListItems.ts",
    "chars": 444,
    "preview": "// Adapted from https://github.com/laurent22/joplin/blob/dev/packages/turndown-plugin-gfm/src/tables.js\n\nimport Turndown"
  },
  {
    "path": "api/src/utils/scrape/plugins/utilities.ts",
    "chars": 1504,
    "preview": "// Adapted from https://github.com/laurent22/joplin/blob/dev/packages/turndown-plugin-gfm/src/tables.js\n\nimport css, { C"
  },
  {
    "path": "api/src/utils/scrape/readability.ts",
    "chars": 236,
    "preview": "import { Defuddle } from \"defuddle/node\";\n\nexport const getDefuddleContent = async (htmlString: string) => {\n  const def"
  },
  {
    "path": "api/src/utils/scrape/safeGoTo.ts",
    "chars": 1517,
    "preview": "import { Page, HTTPResponse } from \"puppeteer-core\";\n\n/**\n * Navigates to a URL and ignores net::ERR_ABORTED if the main"
  },
  {
    "path": "api/src/utils/scrape/transformHtml.ts",
    "chars": 2156,
    "preview": "import { JSDOM } from \"jsdom\";\n\nexport const transformHtml = (htmlContent: string, baseUrl?: string): string => {\n  cons"
  },
  {
    "path": "api/src/utils/size.ts",
    "chars": 85,
    "preview": "export const KB = 1000;\nexport const MB = 1000 * KB; // Most proxies use MB, not MiB\n"
  },
  {
    "path": "api/src/utils/text.ts",
    "chars": 174,
    "preview": "export function titleCase(input: string): string {\n  if (!input || typeof input !== \"string\") {\n    return \"\";\n  }\n  ret"
  },
  {
    "path": "api/src/utils/url.ts",
    "chars": 1998,
    "preview": "import { env } from \"../env.js\";\n\n/**\n * Returns the appropriate protocol based on the protocol type and HTTPS setting\n "
  },
  {
    "path": "api/tsconfig.json",
    "chars": 628,
    "preview": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"module\": \"Node16\",\n"
  },
  {
    "path": "api/tsconfig.test.json",
    "chars": 129,
    "preview": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"noEmit\": true\n  },\n  \"include\": [\"**/"
  },
  {
    "path": "commitlint.config.cjs",
    "chars": 70,
    "preview": "module.exports = {\n  extends: ['@commitlint/config-conventional'],\n};\n"
  },
  {
    "path": "docker-compose.dev.yml",
    "chars": 651,
    "preview": "services:\n  api:\n    build:\n      context: .\n      dockerfile: ./api/Dockerfile\n      args:\n        NODE_VERSION: 22.13."
  },
  {
    "path": "docker-compose.yml",
    "chars": 813,
    "preview": "services:\n  api:\n    image: ghcr.io/steel-dev/steel-browser-api:latest\n    ports:\n      - \"3000:3000\"\n      - \"9223:9223"
  },
  {
    "path": "docs/ARCHITECTURE.md",
    "chars": 11686,
    "preview": "# Steel Browser Architecture\n\nThis document provides a comprehensive overview of Steel Browser's architecture, design de"
  },
  {
    "path": "docs/DEVELOPMENT_SETUP.md",
    "chars": 11377,
    "preview": "# Development Setup Guide\n\nThis guide provides comprehensive instructions for setting up a Steel Browser development env"
  },
  {
    "path": "docs/PLUGIN_DEVELOPMENT.md",
    "chars": 12725,
    "preview": "# Plugin Development Guide\n\nThis guide walks you through developing custom plugins for Steel Browser's extensible archit"
  },
  {
    "path": "docs/README.md",
    "chars": 6623,
    "preview": "# Steel Browser Documentation\n\nWelcome to the Steel Browser documentation! This directory contains comprehensive guides "
  },
  {
    "path": "docs/TROUBLESHOOTING.md",
    "chars": 9835,
    "preview": "# Troubleshooting Guide\n\nThis guide helps you diagnose and resolve common issues with Steel Browser.\n\n## 🚀 Quick Diagnos"
  },
  {
    "path": "nginx.conf",
    "chars": 356,
    "preview": "events {\n    worker_connections 1024;\n}\n\nhttp {\n    server {\n        listen 9223;\n        \n        location / {\n        "
  },
  {
    "path": "package.json",
    "chars": 872,
    "preview": "{\n  \"name\": \"steel-browser\",\n  \"version\": \"0.5.1\",\n  \"private\": true,\n  \"license\": \"Apache-2.0\",\n  \"author\": \"\",\n  \"type"
  },
  {
    "path": "render.yaml",
    "chars": 381,
    "preview": "services:\n  - type: web\n    runtime: docker\n    name: steel-browser\n    dockerfilePath: ./Dockerfile\n    healthCheckPath"
  },
  {
    "path": "repl/README.md",
    "chars": 681,
    "preview": "# Steel REPL\n\nThis package provides a simple REPL to interact with the browser instance you've created using the API.\n\nT"
  },
  {
    "path": "repl/package.json",
    "chars": 342,
    "preview": "{\n  \"name\": \"@steel-browser/repl\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"st"
  },
  {
    "path": "repl/src/script.ts",
    "chars": 670,
    "preview": "import puppeteer from \"puppeteer-core\";\n\nasync function run() {\n  // WebSocket endpoint to connect Browser using Chrome "
  },
  {
    "path": "ui/.dockerignore",
    "chars": 30,
    "preview": "node_modules/\n.env.local\n.env\n"
  },
  {
    "path": "ui/.eslintrc.cjs",
    "chars": 528,
    "preview": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    \"eslint:recommended\",\n    \"plu"
  },
  {
    "path": "ui/.gitignore",
    "chars": 264,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "ui/Dockerfile",
    "chars": 779,
    "preview": "ARG NODE_VERSION=22.13.0\n\nFROM node:${NODE_VERSION} AS base\n\nWORKDIR /app\n\nLABEL org.opencontainers.image.source=\"https:"
  },
  {
    "path": "ui/README.md",
    "chars": 1293,
    "preview": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLin"
  },
  {
    "path": "ui/components.json",
    "chars": 413,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": "
  },
  {
    "path": "ui/entrypoint.sh",
    "chars": 375,
    "preview": "#!/bin/sh\nset -e\n\nlog() {\n    echo \"[$(date '+%Y-%m-%d %H:%M:%S')] $1\"\n}\n\nsubstitute_env_vars() {\n    log \"Substituting "
  },
  {
    "path": "ui/index.html",
    "chars": 957,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/png\" href=\"/icon"
  },
  {
    "path": "ui/nginx.conf.template",
    "chars": 700,
    "preview": "worker_processes 1;\nevents { worker_connections 1024; }\n\nhttp {\n  include       mime.types;\n  default_type  application/"
  },
  {
    "path": "ui/openapi-ts.config.ts",
    "chars": 375,
    "preview": "import { defineConfig } from \"@hey-api/openapi-ts\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({ path: \".env.local\" });"
  },
  {
    "path": "ui/package.json",
    "chars": 2424,
    "preview": "{\n  \"name\": \"@steel-browser/ui\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\""
  },
  {
    "path": "ui/postcss.config.js",
    "chars": 80,
    "preview": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "ui/src/App.tsx",
    "chars": 295,
    "preview": "import \"@fontsource/inter\";\nimport \"@radix-ui/themes/styles.css\";\nimport RootLayout from \"@/root-layout\";\nimport { clien"
  },
  {
    "path": "ui/src/components/badges/proxy-badge.tsx",
    "chars": 610,
    "preview": "import { CopyIcon } from \"@radix-ui/react-icons\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { copyText } fro"
  },
  {
    "path": "ui/src/components/badges/user-agent-badge.tsx",
    "chars": 1027,
    "preview": "import { DesktopIcon, CopyIcon } from \"@radix-ui/react-icons\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { C"
  },
  {
    "path": "ui/src/components/badges/websocket-url-badge.tsx",
    "chars": 594,
    "preview": "import { CopyIcon } from \"@radix-ui/react-icons\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { copyText } fro"
  },
  {
    "path": "ui/src/components/header/header.tsx",
    "chars": 2717,
    "preview": "import { ChevronRightIcon } from \"@radix-ui/react-icons\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Glowin"
  },
  {
    "path": "ui/src/components/header/index.tsx",
    "chars": 35,
    "preview": "export { Header } from \"./header\";\n"
  },
  {
    "path": "ui/src/components/icons/ChromeIcon.tsx",
    "chars": 1791,
    "preview": "import { IconProps } from \"@/types/props\";\n\nexport function ChromeIcon({\n  width = 12,\n  height = 12,\n  color = \"current"
  },
  {
    "path": "ui/src/components/icons/DeleteIcon.tsx",
    "chars": 1321,
    "preview": "import { IconProps } from \"@/types/props\";\n\nexport function DeleteIcon({ width = 28, height = 28 }: IconProps) {\n  retur"
  },
  {
    "path": "ui/src/components/icons/GlobeIcon.tsx",
    "chars": 780,
    "preview": "export const GlobeIcon = () => {\n  return (\n    <svg\n      width=\"17\"\n      height=\"18\"\n      viewBox=\"0 0 17 18\"\n      "
  },
  {
    "path": "ui/src/components/icons/GlowingGreenDot.tsx",
    "chars": 335,
    "preview": "export const GlowingGreenDot = () => {\n  return (\n    <span className=\"relative flex h-2 w-2\">\n      <span className=\"an"
  },
  {
    "path": "ui/src/components/icons/KeyIcon.tsx",
    "chars": 2029,
    "preview": "import { IconProps } from \"@/types/props\";\n\nexport function KeyIcon({\n  width = 12,\n  height = 12,\n  color = \"currentCol"
  },
  {
    "path": "ui/src/components/icons/LoadingSpinner.tsx",
    "chars": 538,
    "preview": "import { cn } from \"@/lib/utils\";\n\nexport interface LoadingSpinnerProps {\n  className?: string;\n}\n\nexport const LoadingS"
  },
  {
    "path": "ui/src/components/icons/NinjaIcon.tsx",
    "chars": 3453,
    "preview": "import { IconProps } from \"@/types/props\";\n\nexport function NinjaIcon({ color = \"#A1A1AA\" }: IconProps) {\n  return (\n   "
  },
  {
    "path": "ui/src/components/icons/SessionIcon.tsx",
    "chars": 1322,
    "preview": "export const SteelIcon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      hei"
  },
  {
    "path": "ui/src/components/icons/SettingsIcon.tsx",
    "chars": 1364,
    "preview": "export const SettingsIcon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"17\"\n      "
  },
  {
    "path": "ui/src/components/illustrations/command-line.tsx",
    "chars": 4418,
    "preview": "export function CommandLine() {\n  return (\n    <svg\n      width=\"272\"\n      height=\"271\"\n      viewBox=\"0 0 272 271\"\n   "
  },
  {
    "path": "ui/src/components/illustrations/globe.tsx",
    "chars": 14756,
    "preview": "export function Globe() {\n  return (\n    <svg\n      width=\"270\"\n      height=\"270\"\n      viewBox=\"0 0 324 324\"\n      fil"
  },
  {
    "path": "ui/src/components/loading/Loading.styles.tsx",
    "chars": 185,
    "preview": "import styled from \"styled-components\";\n\nexport const Container = styled.div`\n  display: flex;\n  flex-direction: column;"
  },
  {
    "path": "ui/src/components/loading/Loading.tsx",
    "chars": 133,
    "preview": "import * as Styled from \"./Loading.styles\";\n\nexport function Loading() {\n  return <Styled.Container>LOADING...</Styled.C"
  }
]

// ... and 81 more files (download for full content)

About this extraction

This page contains the full source code of the steel-dev/steel-browser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 281 files (54.9 MB), approximately 341.6k tokens, and a symbol index with 637 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!