Full Code of winfunc/opcode for AI

main 70c16d8a4910 cached
179 files
1.8 MB
430.8k tokens
843 symbols
1 requests
Download .txt
Showing preview only (1,937K chars total). Download the full file or copy to clipboard to get everything.
Repository: winfunc/opcode
Branch: main
Commit: 70c16d8a4910
Files: 179
Total size: 1.8 MB

Directory structure:
gitextract_3l4qo4o3/

├── .cargo/
│   └── config.toml
├── .github/
│   └── workflows/
│       ├── build-linux.yml
│       ├── build-macos.yml
│       ├── build-test.yml
│       ├── claude-code-review.yml
│       ├── claude.yml
│       ├── pr-check.yml
│       └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bun.lockb
├── cc_agents/
│   ├── README.md
│   ├── git-commit-bot.opcode.json
│   ├── security-scanner.opcode.json
│   └── unit-tests-bot.opcode.json
├── index.html
├── justfile
├── package.json
├── scripts/
│   └── bump-version.sh
├── shell.nix
├── src/
│   ├── App.tsx
│   ├── assets/
│   │   ├── nfo/
│   │   │   └── opcode-nfo.ogg
│   │   └── shimmer.css
│   ├── components/
│   │   ├── AgentExecution.tsx
│   │   ├── AgentExecutionDemo.tsx
│   │   ├── AgentRunOutputViewer.tsx
│   │   ├── AgentRunView.tsx
│   │   ├── AgentRunsList.tsx
│   │   ├── Agents.tsx
│   │   ├── AgentsModal.tsx
│   │   ├── AnalyticsConsent.tsx
│   │   ├── AnalyticsErrorBoundary.tsx
│   │   ├── App.cleaned.tsx
│   │   ├── CCAgents.tsx
│   │   ├── CheckpointSettings.tsx
│   │   ├── ClaudeBinaryDialog.tsx
│   │   ├── ClaudeCodeSession.refactored.tsx
│   │   ├── ClaudeCodeSession.tsx
│   │   ├── ClaudeFileEditor.tsx
│   │   ├── ClaudeMemoriesDropdown.tsx
│   │   ├── ClaudeVersionSelector.tsx
│   │   ├── CreateAgent.tsx
│   │   ├── CustomTitlebar.tsx
│   │   ├── ErrorBoundary.tsx
│   │   ├── ExecutionControlBar.tsx
│   │   ├── FilePicker.optimized.tsx
│   │   ├── FilePicker.tsx
│   │   ├── FloatingPromptInput.tsx
│   │   ├── GitHubAgentBrowser.tsx
│   │   ├── HooksEditor.tsx
│   │   ├── IconPicker.tsx
│   │   ├── ImagePreview.tsx
│   │   ├── MCPAddServer.tsx
│   │   ├── MCPImportExport.tsx
│   │   ├── MCPManager.tsx
│   │   ├── MCPServerList.tsx
│   │   ├── MarkdownEditor.tsx
│   │   ├── NFOCredits.tsx
│   │   ├── PreviewPromptDialog.tsx
│   │   ├── ProjectList.tsx
│   │   ├── ProjectSettings.tsx
│   │   ├── ProxySettings.tsx
│   │   ├── RunningClaudeSessions.tsx
│   │   ├── SessionList.optimized.tsx
│   │   ├── SessionList.tsx
│   │   ├── SessionOutputViewer.tsx
│   │   ├── Settings.tsx
│   │   ├── SlashCommandPicker.tsx
│   │   ├── SlashCommandsManager.tsx
│   │   ├── StartupIntro.tsx
│   │   ├── StorageTab.tsx
│   │   ├── StreamMessage.tsx
│   │   ├── TabContent.tsx
│   │   ├── TabManager.tsx
│   │   ├── TimelineNavigator.tsx
│   │   ├── TokenCounter.tsx
│   │   ├── ToolWidgets.new.tsx
│   │   ├── ToolWidgets.tsx
│   │   ├── Topbar.tsx
│   │   ├── UsageDashboard.original.tsx
│   │   ├── UsageDashboard.tsx
│   │   ├── WebviewPreview.tsx
│   │   ├── claude-code-session/
│   │   │   ├── MessageList.tsx
│   │   │   ├── PromptQueue.tsx
│   │   │   ├── SessionHeader.tsx
│   │   │   ├── useCheckpoints.ts
│   │   │   └── useClaudeMessages.ts
│   │   ├── index.ts
│   │   ├── ui/
│   │   │   ├── badge.tsx
│   │   │   ├── button.tsx
│   │   │   ├── card.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── pagination.tsx
│   │   │   ├── popover.tsx
│   │   │   ├── radio-group.tsx
│   │   │   ├── scroll-area.tsx
│   │   │   ├── select.tsx
│   │   │   ├── split-pane.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── tabs.tsx
│   │   │   ├── textarea.tsx
│   │   │   ├── toast.tsx
│   │   │   ├── tooltip-modern.tsx
│   │   │   └── tooltip.tsx
│   │   └── widgets/
│   │       ├── BashWidget.tsx
│   │       ├── LSWidget.tsx
│   │       ├── TodoWidget.tsx
│   │       └── index.ts
│   ├── contexts/
│   │   ├── TabContext.tsx
│   │   └── ThemeContext.tsx
│   ├── hooks/
│   │   ├── index.ts
│   │   ├── useAnalytics.ts
│   │   ├── useApiCall.ts
│   │   ├── useDebounce.ts
│   │   ├── useLoadingState.ts
│   │   ├── usePagination.ts
│   │   ├── usePerformanceMonitor.ts
│   │   ├── useTabState.ts
│   │   └── useTheme.ts
│   ├── lib/
│   │   ├── analytics/
│   │   │   ├── consent.ts
│   │   │   ├── events.ts
│   │   │   ├── index.ts
│   │   │   ├── resourceMonitor.ts
│   │   │   └── types.ts
│   │   ├── api-tracker.ts
│   │   ├── api.ts
│   │   ├── apiAdapter.ts
│   │   ├── claudeSyntaxTheme.ts
│   │   ├── date-utils.ts
│   │   ├── hooksManager.ts
│   │   ├── linkDetector.tsx
│   │   ├── outputCache.tsx
│   │   └── utils.ts
│   ├── main.tsx
│   ├── services/
│   │   ├── sessionPersistence.ts
│   │   └── tabPersistence.ts
│   ├── stores/
│   │   ├── README.md
│   │   ├── agentStore.ts
│   │   └── sessionStore.ts
│   ├── styles.css
│   ├── types/
│   │   └── hooks.ts
│   └── vite-env.d.ts
├── src-tauri/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── Info.plist
│   ├── build.rs
│   ├── capabilities/
│   │   └── default.json
│   ├── entitlements.plist
│   ├── icons/
│   │   └── icon.icns
│   ├── src/
│   │   ├── checkpoint/
│   │   │   ├── manager.rs
│   │   │   ├── mod.rs
│   │   │   ├── state.rs
│   │   │   └── storage.rs
│   │   ├── claude_binary.rs
│   │   ├── commands/
│   │   │   ├── agents.rs
│   │   │   ├── claude.rs
│   │   │   ├── mcp.rs
│   │   │   ├── mod.rs
│   │   │   ├── proxy.rs
│   │   │   ├── slash_commands.rs
│   │   │   ├── storage.rs
│   │   │   └── usage.rs
│   │   ├── lib.rs
│   │   ├── main.rs
│   │   ├── process/
│   │   │   ├── mod.rs
│   │   │   └── registry.rs
│   │   ├── web_main.rs
│   │   └── web_server.rs
│   ├── tauri.conf.json
│   └── tests/
│       └── TESTS_COMPLETE.md
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── web_server.design.md

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

================================================
FILE: .cargo/config.toml
================================================
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

[env]
PKG_CONFIG_ALLOW_CROSS = "1"


================================================
FILE: .github/workflows/build-linux.yml
================================================
name: Build Linux

on:
  workflow_call:
  workflow_dispatch:
  push:
    branches: [main]

jobs:
  build:
    name: Build Linux x86_64
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            pkg-config \
            libwebkit2gtk-4.1-dev \
            libgtk-3-dev \
            libssl-dev \
            libayatana-appindicator3-dev \
            librsvg2-dev
      
      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-unknown-linux-gnu
      
      - name: Setup Rust cache
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: src-tauri
      
      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
      
      - name: Install dependencies
        run: bun install
      
      - name: Build Tauri app
        run: bun run tauri build --target x86_64-unknown-linux-gnu
      
      - name: Create artifacts directory
        run: |
          mkdir -p dist/linux-x86_64
          cp src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb dist/linux-x86_64/ || true
          cp src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage dist/linux-x86_64/ || true
          
          # Generate checksums
          cd dist/linux-x86_64
          sha256sum * > checksums.txt
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: linux-x86_64
          path: dist/linux-x86_64/*


================================================
FILE: .github/workflows/build-macos.yml
================================================
name: Build macOS

on:
  workflow_call:
    secrets:
      APPLE_CERTIFICATE:
        required: true
      APPLE_CERTIFICATE_PASSWORD:
        required: true
      KEYCHAIN_PASSWORD:
        required: true
      APPLE_SIGNING_IDENTITY:
        required: true
      APPLE_ID:
        required: true
      APPLE_TEAM_ID:
        required: true
      APPLE_PASSWORD:
        required: true
  workflow_dispatch:
    inputs:
      skip_build:
        description: 'Skip build and use artifacts from a previous run'
        required: false
        default: false
        type: boolean
      run_id:
        description: 'Run ID to download artifacts from (leave empty for latest)'
        required: false
        type: string
  push:
    branches: [main]

jobs:
  build:
    name: Build macOS ${{ matrix.target }}
    if: ${{ !inputs.skip_build }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: macos-13  # Intel
            target: x86_64-apple-darwin
            arch: x86_64
          - os: macos-14  # Apple Silicon
            target: aarch64-apple-darwin
            arch: aarch64
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
      
      - name: Setup Rust cache
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: src-tauri
      
      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
      
      - name: Install dependencies
        run: bun install
      
      - name: Import Apple certificates
        env:
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          # Create variables
          CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          
          # Import certificate from secrets
          echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
          
          # Create temporary keychain
          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          
          # Import certificate to keychain
          security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH
      
      - name: Build native
        env:
          CI: true
        run: bun run tauri build
      
      - name: Upload architecture-specific artifacts
        uses: actions/upload-artifact@v4
        with:
          name: macos-${{ matrix.arch }}
          path: |
            src-tauri/target/release/bundle/macos/opcode.app
            src-tauri/target/release/bundle/dmg/*.dmg
          retention-days: 1
  
  universal:
    name: Create Universal Binary
    needs: [build]
    if: ${{ !cancelled() && (needs.build.result == 'success' || needs.build.result == 'skipped') }}
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Download artifacts from current workflow
        if: ${{ !inputs.skip_build }}
        uses: actions/download-artifact@v4
        with:
          pattern: macos-*
          path: artifacts
      
      - name: Download artifacts from specific run
        if: ${{ inputs.skip_build && inputs.run_id != '' }}
        uses: dawidd6/action-download-artifact@v3
        with:
          workflow: build-macos.yml
          run_id: ${{ inputs.run_id }}
          name: macos-*
          path: artifacts
      
      - name: Download artifacts from latest run
        if: ${{ inputs.skip_build && inputs.run_id == '' }}
        uses: dawidd6/action-download-artifact@v3
        with:
          workflow: build-macos.yml
          workflow_conclusion: success
          name: macos-*
          path: artifacts
      
      - name: List downloaded artifacts
        run: |
          echo "📁 Artifact structure:"
          find artifacts -type f -name "*.app" -o -name "*.dmg" | head -20
          echo ""
          echo "📁 Full directory structure:"
          ls -la artifacts/
          ls -la artifacts/macos-aarch64/ || echo "macos-aarch64 directory not found"
          ls -la artifacts/macos-x86_64/ || echo "macos-x86_64 directory not found"
      
      - name: Import Apple certificates
        env:
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          # Create variables
          CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          
          # Import certificate from secrets
          echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
          
          # Create temporary keychain
          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          
          # Import certificate to keychain
          security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH
      
      - name: Create universal app
        run: |
          # Create temp directory
          mkdir -p dmg_temp
          
          # Extract zip files if they exist
          if [ -f "artifacts/macos-aarch64.zip" ]; then
            echo "📦 Extracting macos-aarch64.zip..."
            unzip -q artifacts/macos-aarch64.zip -d artifacts/macos-aarch64/
          fi
          
          if [ -f "artifacts/macos-x86_64.zip" ]; then
            echo "📦 Extracting macos-x86_64.zip..."
            unzip -q artifacts/macos-x86_64.zip -d artifacts/macos-x86_64/
          fi
          
          # Find the actual app paths
          AARCH64_APP=$(find artifacts/macos-aarch64 -name "opcode.app" -type d | head -1)
          X86_64_APP=$(find artifacts/macos-x86_64 -name "opcode.app" -type d | head -1)
          
          if [ -z "$AARCH64_APP" ] || [ -z "$X86_64_APP" ]; then
            echo "❌ Could not find app bundles"
            echo "AARCH64_APP: $AARCH64_APP"
            echo "X86_64_APP: $X86_64_APP"
            exit 1
          fi
          
          echo "✅ Found app bundles:"
          echo "  ARM64: $AARCH64_APP"
          echo "  x86_64: $X86_64_APP"
          
          # Copy ARM64 app as base
          cp -R "$AARCH64_APP" dmg_temp/
          
          # Create universal binary using lipo
          lipo -create -output dmg_temp/opcode.app/Contents/MacOS/opcode \
            "$AARCH64_APP/Contents/MacOS/opcode" \
            "$X86_64_APP/Contents/MacOS/opcode"
          
          # Ensure executable permissions are set
          chmod +x dmg_temp/opcode.app/Contents/MacOS/opcode
          
          echo "✅ Universal binary created"
          lipo -info dmg_temp/opcode.app/Contents/MacOS/opcode
      
      - name: Sign app bundle
        env:
          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
        run: |
          codesign --sign "$APPLE_SIGNING_IDENTITY" \
            --timestamp \
            --options runtime \
            --force \
            --deep \
            --entitlements src-tauri/entitlements.plist \
            dmg_temp/opcode.app
      
      - name: Create DMG
        run: |
          hdiutil create -volname "opcode Installer" \
            -srcfolder dmg_temp \
            -ov -format UDZO opcode.dmg
      
      - name: Sign DMG
        env:
          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
        run: |
          codesign --sign "$APPLE_SIGNING_IDENTITY" \
            --timestamp \
            --force opcode.dmg
      
      - name: Notarize DMG
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
        run: |
          # Store notarization credentials
          xcrun notarytool store-credentials "notarytool-profile" \
            --apple-id "$APPLE_ID" \
            --team-id "$APPLE_TEAM_ID" \
            --password "$APPLE_PASSWORD"
          
          # Submit for notarization
          xcrun notarytool submit opcode.dmg \
            --keychain-profile "notarytool-profile" \
            --wait
      
      - name: Staple notarization
        run: xcrun stapler staple opcode.dmg
      
      - name: Verify DMG
        run: |
          spctl -a -t open -vvv --context context:primary-signature opcode.dmg
          echo "✅ DMG verification complete"
      
      - name: Create artifacts directory
        run: |
          mkdir -p dist/macos-universal
          cp opcode.dmg dist/macos-universal/
          
          # Also save the app bundle using ditto to preserve permissions and signatures
          ditto -c -k --sequesterRsrc --keepParent \
            dmg_temp/opcode.app dist/macos-universal/opcode.app.zip
          
          # Generate checksum
          shasum -a 256 dist/macos-universal/* > dist/macos-universal/checksums.txt
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: macos-universal
          path: dist/macos-universal/*
      
      - name: Cleanup
        if: always()
        run: |
          echo "🧹 Cleaning up temporary directories..."
          rm -rf dmg_temp temp_x86 artifacts
          
          # Clean up keychain
          if [ -n "$RUNNER_TEMP" ] && [ -f "$RUNNER_TEMP/app-signing.keychain-db" ]; then
            security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" || true
          fi
          
          echo "✅ Cleanup complete"


================================================
FILE: .github/workflows/build-test.yml
================================================
name: Build Test

# Trigger on every push and pull request
on:
  push:
    branches: [ main, develop, 'release/**', 'feature/**' ]
  pull_request:
    branches: [ main, develop ]
    types: [opened, synchronize, reopened]

# Cancel in-progress workflows when a new commit is pushed
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1

jobs:
  build-test:
    name: Build Test (${{ matrix.platform.name }})
    
    strategy:
      fail-fast: false
      matrix:
        platform:
          - name: Linux
            os: ubuntu-latest
            rust-target: x86_64-unknown-linux-gnu
          - name: Linux ARM64
            os: ubuntu-24.04-arm64
            rust-target: aarch64-unknown-linux-gnu
          - name: Windows
            os: windows-latest
            rust-target: x86_64-pc-windows-msvc
          - name: macOS
            os: macos-latest
            rust-target: x86_64-apple-darwin
    
    runs-on: ${{ matrix.platform.os }}
    
    steps:
      # Checkout the repository
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Install system dependencies for Linux
      - name: Install Linux dependencies
        if: matrix.platform.os == 'ubuntu-latest'
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libwebkit2gtk-4.1-dev \
            libgtk-3-dev \
            libayatana-appindicator3-dev \
            librsvg2-dev \
            libssl-dev \
            libglib2.0-dev \
            libjavascriptcoregtk-4.1-dev \
            libsoup-3.0-dev \
            libxdo-dev \
            libxcb-shape0-dev \
            libxcb-xfixes0-dev

      # Setup Rust with caching
      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.platform.rust-target }}

      # Cache Rust dependencies
      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: './src-tauri -> target'
          key: ${{ matrix.platform.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
          
      # Setup Bun
      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      # Cache Bun dependencies
      - name: Cache Bun dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.bun
            node_modules
          key: ${{ matrix.platform.os }}-bun-${{ hashFiles('bun.lockb', 'package.json') }}
          restore-keys: |
            ${{ matrix.platform.os }}-bun-
      
      # Install frontend dependencies
      - name: Install frontend dependencies
        run: bun install --frozen-lockfile

      # Build frontend
      - name: Build frontend
        run: bun run build

      # Build Tauri application
      - name: Build Tauri application
        run: bun run tauri build --no-bundle -d
        env:
          TAURI_SIGNING_PRIVATE_KEY: ""
          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""

      # Upload build artifacts for debugging (optional)
      - name: Upload build logs on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: build-logs-${{ matrix.platform.name }}
          path: |
            src-tauri/target/release/build/*/output
            src-tauri/target/debug/build/*/output
          retention-days: 3

  # Summary job to ensure all builds pass
  build-test-summary:
    name: Build Test Summary
    runs-on: ubuntu-latest
    needs: [build-test]
    if: always()
    
    steps:
      - name: Check build results
        run: |
          if [[ "${{ needs.build-test.result }}" == "failure" ]]; then
            echo "❌ One or more build tests failed"
            exit 1
          elif [[ "${{ needs.build-test.result }}" == "cancelled" ]]; then
            echo "⚠️ Build tests were cancelled"
            exit 1
          else
            echo "✅ All build tests passed successfully"
          fi

      - name: Create status comment (PR only)
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const result = '${{ needs.build-test.result }}';
            const emoji = result === 'success' ? '✅' : '❌';
            const status = result === 'success' ? 'All build tests passed!' : 'Build tests failed';
            
            // Create a comment summarizing the build status
            const comment = `## ${emoji} Build Test Results
            
            **Status**: ${status}
            **Commit**: ${{ github.event.pull_request.head.sha || github.sha }}
            
            | Platform | Status |
            |----------|--------|
            | Linux    | ${{ contains(needs.build-test.result, 'success') && '✅' || '❌' }} |
            | Windows  | ${{ contains(needs.build-test.result, 'success') && '✅' || '❌' }} |
            | macOS    | ${{ contains(needs.build-test.result, 'success') && '✅' || '❌' }} |
            
            [View full workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
            
            // Only post comment if it's a PR
            if (context.eventName === 'pull_request') {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: comment
              });
            } 


================================================
FILE: .github/workflows/claude-code-review.yml
================================================
name: Claude Code Review

on:
  pull_request:
    types: [opened, synchronize]
    # Optional: Only run on specific file changes
    # paths:
    #   - "src/**/*.ts"
    #   - "src/**/*.tsx"
    #   - "src/**/*.js"
    #   - "src/**/*.jsx"

jobs:
  claude-review:
    # Optional: Filter by PR author
    # if: |
    #   github.event.pull_request.user.login == 'external-contributor' ||
    #   github.event.pull_request.user.login == 'new-developer' ||
    #   github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
      issues: read
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Run Claude Code Review
        id: claude-review
        uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            REPO: ${{ github.repository }}
            PR NUMBER: ${{ github.event.pull_request.number }}

            Please review this pull request and provide feedback on:
            - Code quality and best practices
            - Potential bugs or issues
            - Performance considerations
            - Security concerns
            - Test coverage

            Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.

            Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.

          # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
          # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
          claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'



================================================
FILE: .github/workflows/claude.yml
================================================
name: Claude Code

on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
  issues:
    types: [opened, assigned]
  pull_request_review:
    types: [submitted]

jobs:
  claude:
    if: |
      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
      (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
      issues: read
      id-token: write
      actions: read # Required for Claude to read CI results on PRs
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Run Claude Code
        id: claude
        uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

          # This is an optional setting that allows Claude to read CI results on PRs
          additional_permissions: |
            actions: read

          # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
          # prompt: 'Update the pull request description to include a summary of changes.'

          # Optional: Add claude_args to customize behavior and configuration
          # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
          # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
          # claude_args: '--allowed-tools Bash(gh pr:*)'



================================================
FILE: .github/workflows/pr-check.yml
================================================
name: PR Checks (bun run check)

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

permissions:
  contents: read
  pull-requests: read

concurrency:
  group: pr-check-${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.sha }}
  cancel-in-progress: true

jobs:
  check:
    name: bun run check
    runs-on: ubuntu-latest

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

      - name: Set up Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Cache Bun dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.bun/install/cache
            node_modules
          key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
          restore-keys: |
            ${{ runner.os }}-bun-

      - name: Install JS/TS dependencies
        run: bun install --frozen-lockfile

      - name: Set up Rust (stable)
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: |
            src-tauri -> src-tauri/target
          cache-on-failure: true

      - name: Run checks
        run: bun run check


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

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (e.g., v1.0.0)'
        required: true
        type: string

permissions:
  contents: write

jobs:
  # Build jobs for each platform
  build-linux:
    uses: ./.github/workflows/build-linux.yml
    secrets: inherit
  
  build-macos:
    uses: ./.github/workflows/build-macos.yml
    secrets: inherit
  

  # Create release after all builds complete
  create-release:
    name: Create Release
    needs: [build-linux, build-macos]
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Determine version
        id: version
        run: |
          if [ "${{ github.event_name }}" = "push" ]; then
            VERSION="${GITHUB_REF#refs/tags/}"
          else
            VERSION="${{ inputs.version }}"
          fi
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Version: $VERSION"
      
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
      
      - name: Prepare release assets
        run: |
          mkdir -p release-assets
          
          # Linux artifacts
          if [ -d "artifacts/linux-x86_64" ]; then
            cp artifacts/linux-x86_64/*.deb release-assets/opcode_${{ steps.version.outputs.version }}_linux_x86_64.deb || true
            cp artifacts/linux-x86_64/*.AppImage release-assets/opcode_${{ steps.version.outputs.version }}_linux_x86_64.AppImage || true
          fi
          
          # macOS artifacts
          if [ -d "artifacts/macos-universal" ]; then
            cp artifacts/macos-universal/opcode.dmg release-assets/opcode_${{ steps.version.outputs.version }}_macos_universal.dmg || true
            cp artifacts/macos-universal/opcode.app.zip release-assets/opcode_${{ steps.version.outputs.version }}_macos_universal.app.tar.gz || true
          fi
          
          # Create source code archives
          # Clean version without 'v' prefix for archive names
          CLEAN_VERSION="${{ steps.version.outputs.version }}"
          CLEAN_VERSION="${CLEAN_VERSION#v}"
          
          # Create source code archives (excluding .git and other unnecessary files)
          echo "Creating source code archives..."
          
          # Create a clean export of the repository
          git archive --format=tar.gz --prefix=opcode-${CLEAN_VERSION}/ -o release-assets/opcode-${CLEAN_VERSION}.tar.gz HEAD
          git archive --format=zip --prefix=opcode-${CLEAN_VERSION}/ -o release-assets/opcode-${CLEAN_VERSION}.zip HEAD

          # Generate signatures for all files
          cd release-assets
          for file in *; do
            if [ -f "$file" ]; then
              sha256sum "$file" > "$file.sha256"
            fi
          done
          cd ..
      
      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: ${{ steps.version.outputs.version }}
          name: opcode ${{ steps.version.outputs.version }}
          draft: true
          prerelease: false
          generate_release_notes: true
          files: release-assets/*
          body: |
            <div align="center">
              <img src="https://raw.githubusercontent.com/${{ github.repository }}/${{ steps.version.outputs.version }}/src-tauri/icons/icon.png" alt="opcode Logo" width="128" height="128">
            </div>

            ## opcode ${{ steps.version.outputs.version }}

            This release was built and signed by CI. Artifacts for macOS and Linux are attached below.

            - Auto-generated release notes are included below (commits, PRs, and contributors).
            - Checksums (`.sha256`) are provided for all assets.

            ### Downloads

            - macOS: `.dmg`, `.app.tar.gz` (Universal: Apple Silicon + Intel)
            - Linux: `.AppImage`, `.deb`

            ### Installation

            - macOS: Open the `.dmg` and drag opcode to Applications.
            - Linux: `chmod +x` the `.AppImage` and run it, or install the `.deb` on Debian/Ubuntu.
            


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
*.bun-build

# Tauri binaries (built executables)
src-tauri/binaries/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
temp_lib/

.cursor/
AGENTS.md
CLAUDE.md
*_TASK.md

# Claude project-specific files
.claude/

.env

================================================
FILE: CONTRIBUTING.md
================================================
# Welcome Contributors

We welcome contributions to enhance opcode's capabilities and improve its performance. To report bugs, create a [GitHub issue](https://github.com/getAsterisk/opcode/issues).

> Before contributing, read through the existing issues and pull requests to see if someone else is already working on something similar. That way you can avoid duplicating efforts.

To contribute, please follow these steps:

1. Fork the opcode repository on GitHub.
2. Create a new branch for your feature or bug fix.
3. Make your changes and ensure that the code passes all tests.
4. Submit a pull request describing your changes and their benefits.

## Pull Request Guidelines

When submitting a pull request, please follow these guidelines:

1. **Title**: Please include following prefixes:
   - `Feature:` for new features
   - `Fix:` for bug fixes
   - `Docs:` for documentation changes
   - `Refactor:` for code refactoring
   - `Improve:` for performance improvements
   - `Other:` for other changes

   For example:
   - `Feature: added custom agent timeout configuration`
   - `Fix: resolved session list scrolling issue`

2. **Description**: Provide a clear and detailed description of your changes in the pull request. Explain the problem you are solving, the approach you took, and any potential side effects or limitations of your changes.

3. **Documentation**: Update the relevant documentation to reflect your changes. This includes the README file, code comments, and any other relevant documentation.

4. **Dependencies**: If your changes require new dependencies, ensure that they are properly documented and added to the `package.json` or `Cargo.toml` files.

5. If the pull request does not meet the above guidelines, it may be closed without merging.

**Note**: Please ensure that you have the latest version of the code before creating a pull request. If you have an existing fork, just sync your fork with the latest version of the opcode repository.

## Coding Standards

### Frontend (React/TypeScript)
- Use TypeScript for all new code
- Follow functional components with hooks
- Use Tailwind CSS for styling
- Add JSDoc comments for exported functions and components

### Backend (Rust)
- Follow Rust standard conventions
- Use `cargo fmt` for formatting
- Use `cargo clippy` for linting
- Handle all `Result` types explicitly
- Add comprehensive documentation with `///` comments

### Security Requirements
- Validate all inputs from the frontend
- Use prepared statements for database operations
- Never log sensitive data (tokens, passwords, etc.)
- Use secure defaults for all configurations

## Testing
- Add tests for new functionality
- Ensure all existing tests pass
- Run `cargo test` for Rust code
- Test the application manually before submitting

Please adhere to the coding conventions, maintain clear documentation, and provide thorough testing for your contributions. 


================================================
FILE: LICENSE
================================================
                    GNU AFFERO GENERAL PUBLIC LICENSE
                       Version 3, 19 November 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.

  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
them 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.

  Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.

  A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate.  Many developers of free software are heartened and
encouraged by the resulting cooperation.  However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.

  The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community.  It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server.  Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.

  An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals.  This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU Affero General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Remote Network Interaction; Use with the GNU General Public License.

  Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software.  This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time.  Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source.  For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code.  There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.


================================================
FILE: README.md
================================================

<div align="center">
  <img src="src-tauri/icons/icon.png" alt="opcode Logo" width="120" height="120">

  <h1>opcode</h1>
  
  <p>
    <strong>A powerful GUI app and Toolkit for Claude Code</strong>
  </p>
  <p>
    <strong>Create custom agents, manage interactive Claude Code sessions, run secure background agents, and more.</strong>
  </p>
  
  <p>
    <a href="#features"><img src="https://img.shields.io/badge/Features-✨-blue?style=for-the-badge" alt="Features"></a>
    <a href="#installation"><img src="https://img.shields.io/badge/Install-🚀-green?style=for-the-badge" alt="Installation"></a>
    <a href="#usage"><img src="https://img.shields.io/badge/Usage-📖-purple?style=for-the-badge" alt="Usage"></a>
    <a href="#development"><img src="https://img.shields.io/badge/Develop-🛠️-orange?style=for-the-badge" alt="Development"></a>
    <a href="https://discord.com/invite/KYwhHVzUsY"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"></a>
  </p>
</div>

![457013521-6133a738-d0cb-4d3e-8746-c6768c82672c](https://github.com/user-attachments/assets/a028de9e-d881-44d8-bae5-7326ab3558b9)



https://github.com/user-attachments/assets/6bceea0f-60b6-4c3e-a745-b891de00b8d0



> [!TIP]
> **⭐ Star the repo and follow [@getAsterisk](https://x.com/getAsterisk) on X for early access to `asteria-swe-v0`**.

> [!NOTE]
> This project is not affiliated with, endorsed by, or sponsored by Anthropic. Claude is a trademark of Anthropic, PBC. This is an independent developer project using Claude.

## 🌟 Overview

**opcode** is a powerful desktop application that transforms how you interact with Claude Code. Built with Tauri 2, it provides a beautiful GUI for managing your Claude Code sessions, creating custom agents, tracking usage, and much more.

Think of opcode as your command center for Claude Code - bridging the gap between the command-line tool and a visual experience that makes AI-assisted development more intuitive and productive.

## 📋 Table of Contents

- [🌟 Overview](#-overview)
- [✨ Features](#-features)
  - [🗂️ Project & Session Management](#️-project--session-management)
  - [🤖 CC Agents](#-cc-agents)
  
  - [📊 Usage Analytics Dashboard](#-usage-analytics-dashboard)
  - [🔌 MCP Server Management](#-mcp-server-management)
  - [⏰ Timeline & Checkpoints](#-timeline--checkpoints)
  - [📝 CLAUDE.md Management](#-claudemd-management)
- [📖 Usage](#-usage)
  - [Getting Started](#getting-started)
  - [Managing Projects](#managing-projects)
  - [Creating Agents](#creating-agents)
  - [Tracking Usage](#tracking-usage)
  - [Working with MCP Servers](#working-with-mcp-servers)
- [🚀 Installation](#-installation)
- [🔨 Build from Source](#-build-from-source)
- [🛠️ Development](#️-development)
- [🔒 Security](#-security)
- [🤝 Contributing](#-contributing)
- [📄 License](#-license)
- [🙏 Acknowledgments](#-acknowledgments)

## ✨ Features

### 🗂️ **Project & Session Management**
- **Visual Project Browser**: Navigate through all your Claude Code projects in `~/.claude/projects/`
- **Session History**: View and resume past coding sessions with full context
- **Smart Search**: Find projects and sessions quickly with built-in search
- **Session Insights**: See first messages, timestamps, and session metadata at a glance

### 🤖 **CC Agents**
- **Custom AI Agents**: Create specialized agents with custom system prompts and behaviors
- **Agent Library**: Build a collection of purpose-built agents for different tasks
- **Background Execution**: Run agents in separate processes for non-blocking operations
- **Execution History**: Track all agent runs with detailed logs and performance metrics



### 📊 **Usage Analytics Dashboard**
- **Cost Tracking**: Monitor your Claude API usage and costs in real-time
- **Token Analytics**: Detailed breakdown by model, project, and time period
- **Visual Charts**: Beautiful charts showing usage trends and patterns
- **Export Data**: Export usage data for accounting and analysis

### 🔌 **MCP Server Management**
- **Server Registry**: Manage Model Context Protocol servers from a central UI
- **Easy Configuration**: Add servers via UI or import from existing configs
- **Connection Testing**: Verify server connectivity before use
- **Claude Desktop Import**: Import server configurations from Claude Desktop

### ⏰ **Timeline & Checkpoints**
- **Session Versioning**: Create checkpoints at any point in your coding session
- **Visual Timeline**: Navigate through your session history with a branching timeline
- **Instant Restore**: Jump back to any checkpoint with one click
- **Fork Sessions**: Create new branches from existing checkpoints
- **Diff Viewer**: See exactly what changed between checkpoints

### 📝 **CLAUDE.md Management**
- **Built-in Editor**: Edit CLAUDE.md files directly within the app
- **Live Preview**: See your markdown rendered in real-time
- **Project Scanner**: Find all CLAUDE.md files in your projects
- **Syntax Highlighting**: Full markdown support with syntax highlighting

## 📖 Usage

### Getting Started

1. **Launch opcode**: Open the application after installation
2. **Welcome Screen**: Choose between CC Agents or Projects
3. **First Time Setup**: opcode will automatically detect your `~/.claude` directory

### Managing Projects

```
Projects → Select Project → View Sessions → Resume or Start New
```

- Click on any project to view its sessions
- Each session shows the first message and timestamp
- Resume sessions directly or start new ones

### Creating Agents

```
CC Agents → Create Agent → Configure → Execute
```

1. **Design Your Agent**: Set name, icon, and system prompt
2. **Configure Model**: Choose between available Claude models
3. **Set Permissions**: Configure file read/write and network access
4. **Execute Tasks**: Run your agent on any project

### Tracking Usage

```
Menu → Usage Dashboard → View Analytics
```

- Monitor costs by model, project, and date
- Export data for reports
- Set up usage alerts (coming soon)

### Working with MCP Servers

```
Menu → MCP Manager → Add Server → Configure
```

- Add servers manually or via JSON
- Import from Claude Desktop configuration
- Test connections before using

## 🚀 Installation

### Prerequisites

- **Claude Code CLI**: Install from [Claude's official site](https://claude.ai/code)

### Release Executables Will Be Published Soon

## 🔨 Build from Source

### Prerequisites

Before building opcode from source, ensure you have the following installed:

#### System Requirements

- **Operating System**: Windows 10/11, macOS 11+, or Linux (Ubuntu 20.04+)
- **RAM**: Minimum 4GB (8GB recommended)
- **Storage**: At least 1GB free space

#### Required Tools

1. **Rust** (1.70.0 or later)
   ```bash
   # Install via rustup
   curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
   ```

2. **Bun** (latest version)
   ```bash
   # Install bun
   curl -fsSL https://bun.sh/install | bash
   ```

3. **Git**
   ```bash
   # Usually pre-installed, but if not:
   # Ubuntu/Debian: sudo apt install git
   # macOS: brew install git
   # Windows: Download from https://git-scm.com
   ```

4. **Claude Code CLI**
   - Download and install from [Claude's official site](https://claude.ai/code)
   - Ensure `claude` is available in your PATH

#### Platform-Specific Dependencies

**Linux (Ubuntu/Debian)**
```bash
# Install system dependencies
sudo apt update
sudo apt install -y \
  libwebkit2gtk-4.1-dev \
  libgtk-3-dev \
  libayatana-appindicator3-dev \
  librsvg2-dev \
  patchelf \
  build-essential \
  curl \
  wget \
  file \
  libssl-dev \
  libxdo-dev \
  libsoup-3.0-dev \
  libjavascriptcoregtk-4.1-dev
```

**macOS**
```bash
# Install Xcode Command Line Tools
xcode-select --install

# Install additional dependencies via Homebrew (optional)
brew install pkg-config
```

**Windows**
- Install [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
- Install [WebView2](https://developer.microsoft.com/microsoft-edge/webview2/) (usually pre-installed on Windows 11)

### Build Steps

1. **Clone the Repository**
   ```bash
   git clone https://github.com/getAsterisk/opcode.git
   cd opcode
   ```

2. **Install Frontend Dependencies**
   ```bash
   bun install
   ```

3. **Build the Application**
   
   **For Development (with hot reload)**
   ```bash
   bun run tauri dev
   ```
   
   **For Production Build**
   ```bash
   # Build the application
   bun run tauri build
   
   # The built executable will be in:
   # - Linux: src-tauri/target/release/
   # - macOS: src-tauri/target/release/
   # - Windows: src-tauri/target/release/
   ```

4. **Platform-Specific Build Options**
   
   **Debug Build (faster compilation, larger binary)**
   ```bash
   bun run tauri build --debug
   ```
   
   **Universal Binary for macOS (Intel + Apple Silicon)**
   ```bash
   bun run tauri build --target universal-apple-darwin
   ```

### Troubleshooting

#### Common Issues

1. **"cargo not found" error**
   - Ensure Rust is installed and `~/.cargo/bin` is in your PATH
   - Run `source ~/.cargo/env` or restart your terminal

2. **Linux: "webkit2gtk not found" error**
   - Install the webkit2gtk development packages listed above
   - On newer Ubuntu versions, you might need `libwebkit2gtk-4.0-dev`

3. **Windows: "MSVC not found" error**
   - Install Visual Studio Build Tools with C++ support
   - Restart your terminal after installation

4. **"claude command not found" error**
   - Ensure Claude Code CLI is installed and in your PATH
   - Test with `claude --version`

5. **Build fails with "out of memory"**
   - Try building with fewer parallel jobs: `cargo build -j 2`
   - Close other applications to free up RAM

#### Verify Your Build

After building, you can verify the application works:

```bash
# Run the built executable directly
# Linux/macOS
./src-tauri/target/release/opcode

# Windows
./src-tauri/target/release/opcode.exe
```

### Build Artifacts

The build process creates several artifacts:

- **Executable**: The main opcode application
- **Installers** (when using `tauri build`):
  - `.deb` package (Linux)
  - `.AppImage` (Linux)
  - `.dmg` installer (macOS)
  - `.msi` installer (Windows)
  - `.exe` installer (Windows)

All artifacts are located in `src-tauri/target/release/`.

## 🛠️ Development

### Tech Stack

- **Frontend**: React 18 + TypeScript + Vite 6
- **Backend**: Rust with Tauri 2
- **UI Framework**: Tailwind CSS v4 + shadcn/ui
- **Database**: SQLite (via rusqlite)
- **Package Manager**: Bun

### Project Structure

```
opcode/
├── src/                   # React frontend
│   ├── components/        # UI components
│   ├── lib/               # API client & utilities
│   └── assets/            # Static assets
├── src-tauri/             # Rust backend
│   ├── src/
│   │   ├── commands/      # Tauri command handlers
│   │   ├── checkpoint/    # Timeline management
│   │   └── process/       # Process management
│   └── tests/             # Rust test suite
└── public/                # Public assets
```

### Development Commands

```bash
# Start development server
bun run tauri dev

# Run frontend only
bun run dev

# Type checking
bunx tsc --noEmit

# Run Rust tests
cd src-tauri && cargo test

# Format code
cd src-tauri && cargo fmt
```

## 🔒 Security

opcode prioritizes your privacy and security:

1. **Process Isolation**: Agents run in separate processes
2. **Permission Control**: Configure file and network access per agent
3. **Local Storage**: All data stays on your machine
4. **No Telemetry**: No data collection or tracking
5. **Open Source**: Full transparency through open source code

## 🤝 Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

### Areas for Contribution

- 🐛 Bug fixes and improvements
- ✨ New features and enhancements
- 📚 Documentation improvements
- 🎨 UI/UX enhancements
- 🧪 Test coverage
- 🌐 Internationalization

## 📄 License

This project is licensed under the AGPL License - see the [LICENSE](LICENSE) file for details.

## 🙏 Acknowledgments

- Built with [Tauri](https://tauri.app/) - The secure framework for building desktop apps
- [Claude](https://claude.ai) by Anthropic

---

<div align="center">
  <p>
    <strong>Made with ❤️ by the <a href="https://asterisk.so/">Asterisk</a></strong>
  </p>
  <p>
    <a href="https://github.com/getAsterisk/opcode/issues">Report Bug</a>
    ·
    <a href="https://github.com/getAsterisk/opcode/issues">Request Feature</a>
  </p>
</div>


## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=getAsterisk/opcode&type=Date)](https://www.star-history.com/#getAsterisk/opcode&Date)


================================================
FILE: cc_agents/README.md
================================================
# 🤖 opcode CC Agents

<div align="center">
  <p>
    <strong>Pre-built AI agents for opcode powered by Claude Code</strong>
  </p>
  <p>
    <a href="#available-agents">Browse Agents</a> •
    <a href="#importing-agents">Import Guide</a> •
    <a href="#exporting-agents">Export Guide</a> •
    <a href="#contributing">Contribute</a>
  </p>
</div>

---

## 📦 Available Agents

| Agent | Model | Description | Default Task |
|-------|-------|-------------|--------------|
| **🎯 Git Commit Bot**<br/>🤖 `bot` | <img src="https://img.shields.io/badge/Sonnet-blue?style=flat-square" alt="Sonnet"> | **Automate your Git workflow with intelligent commit messages**<br/><br/>Analyzes Git repository changes, generates detailed commit messages following Conventional Commits specification, and pushes changes to remote repository. | "Push all changes." |
| **🛡️ Security Scanner**<br/>🛡️ `shield` | <img src="https://img.shields.io/badge/Opus-purple?style=flat-square" alt="Opus"> | **Advanced AI-powered Static Application Security Testing (SAST)**<br/><br/>Performs comprehensive security audits by spawning specialized sub-agents for: codebase intelligence gathering, threat modeling (STRIDE), vulnerability scanning (OWASP Top 10, CWE), exploit validation, remediation design, and professional report generation. | "Review the codebase for security issues." |
| **🧪 Unit Tests Bot**<br/>💻 `code` | <img src="https://img.shields.io/badge/Opus-purple?style=flat-square" alt="Opus"> | **Automated comprehensive unit test generation for any codebase**<br/><br/>Analyzes codebase and generates comprehensive unit tests by: analyzing code structure, creating test plans, writing tests matching your style, verifying execution, optimizing coverage (>80% overall, 100% critical paths), and generating documentation. | "Generate unit tests for this codebase." |

### Available Icons

Choose from these icon options when creating agents:
- `bot` - 🤖 General purpose
- `shield` - 🛡️ Security related
- `code` - 💻 Development
- `terminal` - 🖥️ System/CLI
- `database` - 🗄️ Data operations
- `globe` - 🌐 Network/Web
- `file-text` - 📄 Documentation
- `git-branch` - 🌿 Version control

---

## 📥 Importing Agents

### Method 1: Import from GitHub (Recommended)

1. In opcode, navigate to **CC Agents**
2. Click the **Import** dropdown button
3. Select **From GitHub**
4. Browse available agents from the official repository
5. Preview agent details and click **Import Agent**

### Method 2: Import from Local File

1. Download a `.opcode.json` file from this repository
2. In opcode, navigate to **CC Agents**
3. Click the **Import** dropdown button
4. Select **From File**
5. Choose the downloaded `.opcode.json` file

## 📤 Exporting Agents

### Export Your Custom Agents

1. In opcode, navigate to **CC Agents**
2. Find your agent in the grid
3. Click the **Export** button
4. Choose where to save the `.opcode.json` file

### Agent File Format

All agents are stored in `.opcode.json` format with the following structure:

```json
{
  "version": 1,
  "exported_at": "2025-01-23T14:29:58.156063+00:00",
  "agent": {
    "name": "Your Agent Name",
    "icon": "bot",
    "model": "opus|sonnet|haiku",
    "system_prompt": "Your agent's instructions...",
    "default_task": "Default task description"
  }
}
```

## 🔧 Technical Implementation

### How Import/Export Works

The agent import/export system is built on a robust architecture:

#### Backend (Rust/Tauri)
- **Storage**: SQLite database stores agent configurations
- **Export**: Serializes agent data to JSON with version control
- **Import**: Validates and deduplicates agents on import
- **GitHub Integration**: Fetches agents via GitHub API

#### Frontend (React/TypeScript)
- **UI Components**: 
  - `CCAgents.tsx` - Main agent management interface
  - `GitHubAgentBrowser.tsx` - GitHub repository browser
  - `CreateAgent.tsx` - Agent creation/editing form
- **File Operations**: Native file dialogs for import/export
- **Real-time Updates**: Live agent status and execution monitoring

### Key Features

1. **Version Control**: Each agent export includes version metadata
2. **Duplicate Prevention**: Automatic naming conflict resolution
3. **Model Selection**: Choose between Opus, Sonnet, and Haiku models
4. **GitHub Integration**: Direct import from the official repository

## 🤝 Contributing

We welcome agent contributions! Here's how to add your agent:

### 1. Create Your Agent
Design and test your agent in opcode with a clear, focused purpose.

### 2. Export Your Agent
Export your agent to a `.opcode.json` file with a descriptive name.

### 3. Submit a Pull Request
1. Fork this repository
2. Add your `.opcode.json` file to the `cc_agents` directory
3. Update this README with your agent's details
4. Submit a PR with a description of what your agent does

### Agent Guidelines

- **Single Purpose**: Each agent should excel at one specific task
- **Clear Documentation**: Write comprehensive system prompts
- **Model Choice**: Use Haiku for simple tasks, Sonnet for general purpose, Opus for complex reasoning
- **Naming**: Use descriptive names that clearly indicate the agent's function

## 📜 License

These agents are provided under the same license as the opcode project. See the main LICENSE file for details.

---

<div align="center">
  <strong>Built with ❤️ by the opcode community</strong>
</div> 


================================================
FILE: cc_agents/git-commit-bot.opcode.json
================================================
{
  "agent": {
    "default_task": "Push all changes.",
    "icon": "bot",
    "model": "sonnet",
    "name": "Git Commit Bot",
    "system_prompt": "<task>\nYou are a Git Commit Push bot. Your task is to analyze changes in a git repository, write a detailed commit message following the Conventional Commits specification, and push the changes to git.\n</task>\n\n# Instructions\n\n<instructions>\nFirst, check if there are commits in the remote repository that have not been synced locally:\n1. Run `git fetch` to update remote tracking branches\n2. Check if the local branch is behind the remote using `git status` or `git log`\n3. If there are unsynced commits from the remote:\n   - Perform a `git pull` to merge remote changes\n   - If merge conflicts occur:\n     a. Carefully analyze the conflicting changes\n     b. Resolve conflicts by keeping the appropriate changes from both versions\n     c. Mark conflicts as resolved using `git add`\n     d. Complete the merge\n4. Only proceed with the following steps after ensuring the local repository is up-to-date\n\nAnalyze the changes shown in the git diff and status outputs. Pay attention to:\n1. Which files were modified, added, or deleted\n2. The nature of the changes (e.g., bug fixes, new features, refactoring)\n3. The scope of the changes (which part of the project was affected)\n\nBased on your analysis, write a commit message following the Conventional Commits specification:\n1. Use one of the following types: feat, fix, docs, style, refactor, perf, test, or chore\n2. Include a scope in parentheses if applicable\n3. Write a concise description in the present tense\n4. If necessary, add a longer description after a blank line\n5. Include any breaking changes or issues closed\n\nThen finally push the changes to git.\n</instructions>\n\n# Notes\n\n<notes>\n- Replace [branch_name] with the appropriate branch name based on the information in the git log. If you cannot determine the branch name, use \"main\" as the default.\n- Remember to think carefully about the changes and their impact on the project when crafting your commit message. Your goal is to provide a clear and informative record of the changes made to the repository.\n- When resolving merge conflicts, prioritize maintaining functionality and avoiding breaking changes. If unsure about a conflict resolution, prefer a conservative approach that preserves existing behavior.\n</notes>"
  },
  "exported_at": "2025-06-23T14:29:58.156063+00:00",
  "version": 1
}


================================================
FILE: cc_agents/security-scanner.opcode.json
================================================
{
  "agent": {
    "default_task": "Review the codebase for security issues.",
    "icon": "shield",
    "model": "opus",
    "name": "Security Scanner",
    "system_prompt": "# AI SAST Agent - System Prompt\n\n<role>\nYou are an advanced AI-powered Static Application Security Testing (SAST) agent specialized in performing deep, comprehensive security audits of codebases. You identify vulnerabilities with high precision, analyze attack vectors, and produce professional security reports following industry standards. You operate by orchestrating specialized sub-agents for each phase of the security assessment.\n</role>\n\n<primary_objectives>\n1. Perform thorough static analysis to identify security vulnerabilities\n2. Minimize false positives through contextual analysis and validation\n3. Provide actionable remediation guidance with code examples\n4. Generate professional security reports suitable for development and security teams\n5. Prioritize findings based on exploitability and business impact\n</primary_objectives>\n\n<methodology>\nApply a systematic approach combining:\n- **OWASP Top 10** vulnerability patterns\n- **CWE (Common Weakness Enumeration)** classification\n- **STRIDE** threat modeling\n- **Data Flow Analysis** for taint tracking\n- **Control Flow Analysis** for logic vulnerabilities\n</methodology>\n\n<workflow>\n\n## Phase 1: Codebase Intelligence Gathering\n<task_spawn>\nSpawn a **Codebase Intelligence Analyzer** sub-agent using the `Task` tool with the following instruction:\n\n```\nPerform deep codebase analysis to extract:\n\n<analysis_targets>\n- Language(s), frameworks, and libraries with versions\n- Architecture patterns (MVC, microservices, serverless, etc.)\n- Authentication and authorization mechanisms\n- Data storage systems and ORM usage\n- External integrations and API endpoints\n- Input validation and sanitization practices\n- Cryptographic implementations\n- Session management approach\n- File and resource handling\n- Third-party dependencies and known CVEs\n</analysis_targets>\n```\n</task_spawn>\n\n## Phase 2: Threat Modeling\n<task_spawn>\nSpawn a **Threat Modeling Specialist** sub-agent using the `Task` tool with the following instruction:\n\n```\nCreate a comprehensive threat model based on the codebase intelligence:\n\n<threat_model_components>\n1. Asset Identification:\n   - Sensitive data (PII, credentials, financial)\n   - Critical business logic\n   - Infrastructure components\n   \n2. Trust Boundaries:\n   - User-to-application boundaries\n   - Service-to-service boundaries\n   - Network segmentation points\n   \n3. Entry Points:\n   - API endpoints\n   - User interfaces\n   - File upload mechanisms\n   - Background job processors\n   - WebSocket connections\n   \n4. STRIDE Analysis per component:\n   - Spoofing threats\n   - Tampering threats\n   - Repudiation threats\n   - Information disclosure threats\n   - Denial of service threats\n   - Elevation of privilege threats\n</threat_model_components>\n```\n</task_spawn>\n\n## Phase 3: Vulnerability Scanning\n<task_spawn>\nFor each identified entry point and component, spawn a **Vulnerability Scanner** sub-agent using the `Task` tool:\n\n```\nScan for vulnerabilities in component: [COMPONENT_NAME]\n\n<scanning_checklist>\nINJECTION VULNERABILITIES:\n- SQL Injection (including blind, time-based, union-based)\n- NoSQL Injection\n- LDAP Injection\n- OS Command Injection\n- Code Injection (eval, dynamic execution)\n- XML/XXE Injection\n- Template Injection\n- Header Injection\n\nAUTHENTICATION & SESSION:\n- Broken authentication flows\n- Weak password policies\n- Session fixation\n- Insufficient session expiration\n- Predictable tokens\n- Missing MFA enforcement\n\nACCESS CONTROL:\n- Horizontal privilege escalation\n- Vertical privilege escalation\n- IDOR (Insecure Direct Object References)\n- Missing function-level access control\n- Path traversal\n- Forced browsing\n\nDATA EXPOSURE:\n- Sensitive data in logs\n- Unencrypted sensitive data\n- Information leakage in errors\n- Directory listing\n- Source code disclosure\n- API information disclosure\n\nCRYPTOGRAPHIC ISSUES:\n- Weak algorithms\n- Hardcoded keys/secrets\n- Insufficient key length\n- Improper IV usage\n- Insecure random number generation\n\nBUSINESS LOGIC:\n- Race conditions\n- Time-of-check time-of-use (TOCTOU)\n- Workflow bypass\n- Price manipulation\n- Insufficient rate limiting\n\nCONFIGURATION:\n- Security misconfiguration\n- Default credentials\n- Unnecessary services\n- Verbose error messages\n- Missing security headers\n</scanning_checklist>\n\n<analysis_requirements>\nFor each potential vulnerability:\n1. Trace complete data flow from source to sink\n2. Identify all transformations applied\n3. Check for existing mitigations\n4. Verify exploitability conditions\n5. Map to CWE identifier\n</analysis_requirements>\n\nReturn findings in structured format with full context.\n```\n</task_spawn>\n\n## Phase 4: Exploit Development & Validation\n<task_spawn>\nSpawn an **Exploit Developer** sub-agent using the `Task` tool with the following instruction:\n\n```\nFor each identified vulnerability, develop proof-of-concept exploits:\n\n<exploit_requirements>\n1. Create minimal, working PoC code\n2. Document exact preconditions\n3. Show full attack chain\n4. Demonstrate impact clearly\n5. Avoid destructive payloads\n6. Include both manual and automated versions\n</exploit_requirements>\n\n<poc_template>\nFor each vulnerability provide:\n- Setup requirements\n- Step-by-step exploitation\n- Expected vs actual behavior\n- Screenshot/output evidence\n- Automation script (curl/python/etc)\n</poc_template>\n\nValidate each finding to ensure:\n- Reproducibility\n- Real-world exploitability\n- No false positives\n```\n</task_spawn>\n\n## Phase 5: Remediation Design\n<task_spawn>\nSpawn a **Security Architect** sub-agent using the `Task` tool with the following instruction:\n\n```\nDesign comprehensive remediation strategies:\n\n<remediation_components>\n1. Immediate Fixes:\n   - Code patches with examples\n   - Configuration changes\n   - Quick mitigations\n\n2. Long-term Solutions:\n   - Architectural improvements\n   - Security control implementations\n   - Process enhancements\n\n3. Defense in Depth:\n   - Primary fix\n   - Compensating controls\n   - Detection mechanisms\n   - Incident response procedures\n</remediation_components>\n\nInclude:\n- Specific code examples in the target language\n- Library recommendations with versions\n- Testing strategies for fixes\n- Regression prevention measures\n```\n</task_spawn>\n\n## Phase 6: Report Generation\n<task_spawn>\nSpawn a **Security Report Writer** sub-agent using the `Task` tool with the following instruction:\n\n```\nGenerate a professional security assessment report:\n\n<report_sections>\n1. Executive Summary\n   - Key findings overview\n   - Risk summary\n   - Business impact analysis\n   - Prioritized recommendations\n\n2. Technical Summary\n   - Vulnerability statistics\n   - Severity distribution\n   - Attack vector analysis\n   - Affected components\n\n3. Detailed Findings\n   [Use HackerOne format for each]\n\n4. Remediation Roadmap\n   - Quick wins (< 1 day)\n   - Short-term (1-7 days)\n   - Long-term (> 7 days)\n\n5. Appendices\n   - Methodology\n   - Tools used\n   - References\n</report_sections>\n```\n</task_spawn>\n\n</workflow>\n\n<vulnerability_report_format>\n## [CWE-XXX] Vulnerability Title\n\n### Summary\n**Severity**: Critical | High | Medium | Low | Informational\n**CVSS Score**: X.X (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)\n**CWE**: CWE-XXX\n**OWASP**: A0X:2021 – Category Name\n\n### Description\n[Concise explanation of the vulnerability and its potential impact]\n\n### Technical Details\n<details>\n<summary>Affected Component</summary>\n\n```\nFile: /path/to/vulnerable/file.ext\nFunction: vulnerableFunction()\nLines: 42-58\n```\n</details>\n\n<details>\n<summary>Data Flow Analysis</summary>\n\n```\n1. User input received at: controller.getUserInput() [line 42]\n   ↓ (no sanitization)\n2. Passed to: service.processData(input) [line 45]\n   ↓ (string concatenation)\n3. Used in: database.query(sql + input) [line 58]\n   ↓ (direct execution)\n4. SINK: SQL query execution with untrusted data\n```\n</details>\n\n### Proof of Concept\n\n```bash\n# Manual exploitation\ncurl -X POST https://target.com/api/users \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"admin\\\"; DROP TABLE users; --\"}'\n\n# Automated PoC\npython3 exploit_sqli.py --target https://target.com --payload \"' OR '1'='1\"\n```\n\n**Expected Result**: Error or filtered input\n**Actual Result**: SQL query executed, data exposed\n\n### Impact\n- **Confidentiality**: High - Full database access possible\n- **Integrity**: High - Data manipulation possible\n- **Availability**: Medium - DoS via resource exhaustion\n\n### Remediation\n\n#### Immediate Fix\n```[language]\n// Vulnerable code\nconst query = `SELECT * FROM users WHERE id = ${userId}`;\n\n// Secure code\nconst query = 'SELECT * FROM users WHERE id = ?';\ndb.query(query, [userId]);\n```\n\n#### Long-term Solution\n1. Implement parameterized queries throughout\n2. Add input validation layer\n3. Deploy WAF rules for SQL injection patterns\n4. Enable database query logging and monitoring\n\n### References\n- [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html)\n- [OWASP SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)\n\n---\n</vulnerability_report_format>\n\n<severity_classification>\n**Critical**: \n- Remote code execution\n- Authentication bypass\n- Full data breach potential\n- Complete system compromise\n\n**High**:\n- SQL/NoSQL injection\n- Privilege escalation\n- Sensitive data exposure\n- Critical business logic flaws\n\n**Medium**:\n- XSS (stored/reflected)\n- CSRF on sensitive actions\n- Session management issues\n- Information disclosure\n\n**Low**:\n- Missing security headers\n- Weak configurations\n- Information leakage\n- Minor logic flaws\n\n**Informational**:\n- Best practice violations\n- Defense-in-depth opportunities\n- Future-proofing recommendations\n</severity_classification>\n\n<quality_assurance>\nBefore finalizing any finding:\n1. ✓ Verified exploitability (not just theoretical)\n2. ✓ Confirmed source-to-sink flow\n3. ✓ Tested proposed fix\n4. ✓ No false positives\n5. ✓ Business context considered\n6. ✓ CWE/OWASP mapping accurate\n</quality_assurance>\n\n<communication_guidelines>\n- Use clear, non-technical language in summaries\n- Provide technical depth in detailed sections\n- Include visual diagrams where helpful\n- Reference industry standards\n- Maintain professional, constructive tone\n- Focus on solutions, not just problems\n</communication_guidelines>\n\n<continuous_improvement>\nAfter each phase:\n- Log any false positives encountered\n- Document new vulnerability patterns discovered\n- Update scanning rules based on findings\n- Refine severity ratings based on context\n- Enhance PoC templates for efficiency\n</continuous_improvement>"
  },
  "exported_at": "2025-06-23T14:29:55.510402+00:00",
  "version": 1
}


================================================
FILE: cc_agents/unit-tests-bot.opcode.json
================================================
{
  "agent": {
    "default_task": "Generate unit tests for this codebase.",
    "icon": "code",
    "model": "opus",
    "name": "Unit Tests Bot",
    "system_prompt": "# Unit Tests Generation Agent\n\n<role>\nYou are an autonomous Unit Test Generation Agent specialized in analyzing codebases, writing comprehensive unit tests, verifying test coverage, and documenting the testing process. You work by spawning specialized sub-agents for each phase of the testing workflow.\n</role>\n\n<primary_objectives>\n1. Analyze the existing codebase structure and coding patterns\n2. Generate comprehensive unit tests that match the codebase style\n3. Execute and verify all generated tests\n4. Create detailed documentation of the testing process and coverage\n5. Ensure 100% critical path coverage and >80% overall code coverage\n</primary_objectives>\n\n<workflow>\n\n## Phase 1: Codebase Analysis\n<task_spawn>\nSpawn a **Codebase Analyzer** sub-agent using the `Task` tool with the following instruction:\n\n```\nAnalyze the codebase structure and extract:\n- Programming language(s) and frameworks\n- Existing test framework and patterns\n- Code style conventions (naming, formatting, structure)\n- Directory structure and test file locations\n- Dependencies and testing utilities\n- Coverage requirements and existing coverage reports\n```\n</task_spawn>\n\n## Phase 2: Test Planning\n<task_spawn>\nSpawn a **Test Planner** sub-agent using the `Task` tool with the following instruction:\n\n```\nBased on the codebase analysis, create a comprehensive test plan:\n- Identify all testable modules/classes/functions\n- Categorize by priority (critical, high, medium, low)\n- Define test scenarios for each component\n- Specify edge cases and error conditions\n- Plan integration test requirements\n- Estimate coverage targets per module\n```\n</task_spawn>\n\n## Phase 3: Test Generation\n<task_spawn>\nFor each module identified in the test plan, spawn a **Test Writer** sub-agent using the `Task` tool:\n\n```\nGenerate unit tests for module: [MODULE_NAME]\nRequirements:\n- Follow existing test patterns and conventions\n- Use the same testing framework as the codebase\n- Include positive, negative, and edge case scenarios\n- Add descriptive test names and comments\n- Mock external dependencies appropriately\n- Ensure tests are isolated and repeatable\nReturn the complete test file(s) with proper imports and setup.\n```\n</task_spawn>\n\n## Phase 4: Test Verification\n<task_spawn>\nSpawn a **Test Verifier** sub-agent using the `Task` tool with the following instruction:\n```\nExecute and verify all generated tests:\n- Run the test suite and capture results\n- Identify any failing tests\n- Check for flaky or non-deterministic tests\n- Measure code coverage metrics\n- Validate test isolation and independence\n- Ensure no test pollution or side effects\nReturn a verification report with any necessary fixes.\n```\n</task_spawn>\n\n## Phase 5: Coverage Optimization\n<task_spawn>\nIf coverage targets are not met, spawn a **Coverage Optimizer** sub-agent using the `Task` tool:\n\n```\nAnalyze coverage gaps and generate additional tests:\n- Identify uncovered code paths\n- Generate tests for missed branches\n- Add tests for error handling paths\n- Cover edge cases in complex logic\n- Ensure mutation testing resistance\nReturn additional tests to meet coverage targets.\n```\n</task_spawn>\n\n## Phase 6: Documentation Generation\n<task_spawn>\nSpawn a **Documentation Writer** sub-agent using the `Task` tool with the following instruction:\n\n```\nCreate comprehensive testing documentation:\n- Overview of test suite structure\n- Test coverage summary and metrics\n- Guide for running and maintaining tests\n- Description of key test scenarios\n- Known limitations and future improvements\n- CI/CD integration instructions\nReturn documentation in Markdown format.\n```\n</task_spawn>\n\n</workflow>\n\n<style_consistency_rules>\n- **Naming Conventions**: Match the existing codebase patterns (camelCase, snake_case, PascalCase)\n- **Test Structure**: Follow the Arrange-Act-Assert or Given-When-Then pattern consistently\n- **File Organization**: Place tests in the same structure as source files\n- **Import Style**: Use the same import conventions as the main codebase\n- **Assertion Style**: Use the project's preferred assertion library and patterns\n- **Comment Style**: Match the documentation style (JSDoc, docstrings, etc.)\n</style_consistency_rules>\n\n<test_quality_criteria>\n- Each test should have a single, clear purpose\n- Test names must describe what is being tested and expected outcome\n- Tests must be independent and can run in any order\n- Use appropriate mocking for external dependencies\n- Include both happy path and error scenarios\n- Ensure tests fail meaningfully when code is broken\n- Avoid testing implementation details, focus on behavior\n</test_quality_criteria>\n\n<error_handling>\nIf any phase encounters errors:\n1. Log the error with context\n2. Attempt automatic resolution\n3. If resolution fails, document the issue\n4. Continue with remaining modules\n5. Report unresolvable issues in final documentation\n</error_handling>\n\n<verification_steps>\n1. **Syntax Verification**: Ensure all tests compile/parse correctly\n2. **Execution Verification**: Run each test in isolation and as a suite\n3. **Coverage Verification**: Confirm coverage meets targets\n4. **Performance Verification**: Ensure tests complete in reasonable time\n5. **Determinism Verification**: Run tests multiple times to check consistency\n</verification_steps>\n\n<best_practices>\n- **DRY Principle**: Extract common test utilities and helpers\n- **Clear Assertions**: Use descriptive matchers and error messages\n- **Test Data**: Use factories or builders for complex test data\n- **Async Testing**: Properly handle promises and async operations\n- **Resource Cleanup**: Always clean up after tests (files, connections, etc.)\n- **Meaningful Variables**: Use descriptive names for test data and results\n</best_practices>\n\n<communication_protocol>\n- Report progress after each major phase\n- Log detailed information for debugging\n- Summarize results at each stage\n- Provide actionable feedback for failures\n- Include time estimates for long-running operations\n</communication_protocol>\n\n<final_checklist>\nBefore completing the task, verify:\n- [ ] All source files have corresponding test files\n- [ ] Coverage targets are met (>80% overall, 100% critical)\n- [ ] All tests pass consistently\n- [ ] No hardcoded values or environment dependencies\n- [ ] Tests follow codebase conventions\n- [ ] Documentation is complete and accurate\n- [ ] CI/CD integration is configured\n</final_checklist>"
  },
  "exported_at": "2025-06-23T14:29:51.009370+00:00",
  "version": 1
}


================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en" class="dark">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="color-scheme" content="dark" />
    <title>opcode - Claude Code Session Browser</title>
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: justfile
================================================
# Opcode - NixOS Build & Development Commands

# Show available commands
default:
    @just --list

# Enter the Nix development environment
shell:
    nix-shell

# Install frontend dependencies
install:
    npm install

# Build the React frontend
build-frontend:
    npm run build

# Build the Tauri backend (debug)
build-backend:
    cd src-tauri && cargo build

# Build the Tauri backend (release)
build-backend-release:
    cd src-tauri && cargo build --release

# Build everything (frontend + backend)
build: install build-frontend build-backend

# Run the application in development mode
run: build-frontend
    cd src-tauri && cargo run

# Run the application (release mode)
run-release: build-frontend build-backend-release
    cd src-tauri && cargo run --release

# Clean all build artifacts
clean:
    rm -rf node_modules dist
    cd src-tauri && cargo clean

# Development server (requires frontend build first)
dev: build-frontend
    cd src-tauri && cargo run

# Run tests
test:
    cd src-tauri && cargo test

# Format Rust code
fmt:
    cd src-tauri && cargo fmt

# Check Rust code
check:
    cd src-tauri && cargo check

# Quick development cycle: build frontend and run
quick: build-frontend
    cd src-tauri && cargo run

# Full rebuild from scratch
rebuild: clean build run

# Run web server mode for phone access
web: build-frontend
    cd src-tauri && cargo run --bin opcode-web

# Run web server on custom port
web-port PORT: build-frontend
    cd src-tauri && cargo run --bin opcode-web -- --port {{PORT}}

# Get local IP for phone access
ip:
    @echo "🌐 Your PC's IP addresses:"
    @ip route get 1.1.1.1 | grep -oP 'src \K\S+' || echo "Could not detect IP"
    @echo ""
    @echo "📱 Use this IP on your phone: http://YOUR_IP:8080"

# Show build information
info:
    @echo "🚀 Opcode - Claude Code GUI Application"
    @echo "Built for NixOS without Docker"
    @echo ""
    @echo "📦 Frontend: React + TypeScript + Vite"
    @echo "🦀 Backend: Rust + Tauri"
    @echo "🏗️  Build System: Nix + Just"
    @echo ""
    @echo "💡 Common commands:"
    @echo "  just run      - Build and run (desktop)"
    @echo "  just web      - Run web server for phone access"
    @echo "  just quick    - Quick build and run"
    @echo "  just rebuild  - Full clean rebuild"
    @echo "  just shell    - Enter Nix environment"
    @echo "  just ip       - Show IP for phone access"

================================================
FILE: package.json
================================================
{
  "name": "opcode",
  "private": true,
  "version": "0.2.1",
  "license": "AGPL-3.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "prebuild": "",
    "build:executables": "bun run scripts/fetch-and-build.js --version=1.0.41",
    "build:executables:current": "bun run scripts/fetch-and-build.js current --version=1.0.41",
    "build:executables:linux": "bun run scripts/fetch-and-build.js linux --version=1.0.41",
    "build:executables:macos": "bun run scripts/fetch-and-build.js macos --version=1.0.41",
    "build:executables:windows": "bun run scripts/fetch-and-build.js windows --version=1.0.41",
    "preview": "vite preview",
    "tauri": "tauri",
    "build:dmg": "tauri build --bundles dmg",
    "check": "tsc --noEmit && cd src-tauri && cargo check"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-dialog": "^1.1.4",
    "@radix-ui/react-dropdown-menu": "^2.1.15",
    "@radix-ui/react-label": "^2.1.1",
    "@radix-ui/react-popover": "^1.1.4",
    "@radix-ui/react-radio-group": "^1.3.7",
    "@radix-ui/react-select": "^2.1.3",
    "@radix-ui/react-switch": "^1.1.3",
    "@radix-ui/react-tabs": "^1.1.3",
    "@radix-ui/react-toast": "^1.2.3",
    "@radix-ui/react-tooltip": "^1.1.5",
    "@tailwindcss/cli": "^4.1.8",
    "@tailwindcss/vite": "^4.1.8",
    "@tanstack/react-virtual": "^3.13.10",
    "@tauri-apps/api": "^2.1.1",
    "@tauri-apps/plugin-dialog": "^2.0.2",
    "@tauri-apps/plugin-global-shortcut": "^2.0.0",
    "@tauri-apps/plugin-opener": "^2",
    "@tauri-apps/plugin-shell": "^2.0.1",
    "@types/diff": "^8.0.0",
    "@types/react-syntax-highlighter": "^15.5.13",
    "@uiw/react-md-editor": "^4.0.7",
    "ansi-to-html": "^0.7.2",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "date-fns": "^3.6.0",
    "diff": "^8.0.2",
    "framer-motion": "^12.0.0-alpha.1",
    "html2canvas": "^1.4.1",
    "lucide-react": "^0.468.0",
    "posthog-js": "^1.258.3",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.54.2",
    "react-markdown": "^9.0.3",
    "react-syntax-highlighter": "^15.6.1",
    "recharts": "^2.14.1",
    "remark-gfm": "^4.0.0",
    "tailwind-merge": "^2.6.0",
    "tailwindcss": "^4.1.8",
    "zod": "^3.24.1",
    "zustand": "^5.0.6"
  },
  "devDependencies": {
    "@tauri-apps/cli": "^2.7.1",
    "@types/node": "^22.15.30",
    "@types/react": "^18.3.1",
    "@types/react-dom": "^18.3.1",
    "@types/sharp": "^0.32.0",
    "@vitejs/plugin-react": "^4.3.4",
    "sharp": "^0.34.2",
    "typescript": "~5.6.2",
    "vite": "^6.0.3"
  },
  "trustedDependencies": [
    "@parcel/watcher",
    "@tailwindcss/oxide"
  ],
  "optionalDependencies": {
    "@esbuild/linux-x64": "^0.25.6",
    "@rollup/rollup-linux-x64-gnu": "^4.45.1"
  }
}


================================================
FILE: scripts/bump-version.sh
================================================
#!/bin/bash

# Script to bump version across all files
# Usage: ./scripts/bump-version.sh 1.0.0

set -e

if [ -z "$1" ]; then
    echo "Usage: $0 <version>"
    echo "Example: $0 1.0.0"
    exit 1
fi

VERSION=$1

echo "Bumping version to $VERSION..."

# Update package.json
sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" package.json && rm package.json.bak

# Update Cargo.toml
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" src-tauri/Cargo.toml && rm src-tauri/Cargo.toml.bak

# Update tauri.conf.json
sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" src-tauri/tauri.conf.json && rm src-tauri/tauri.conf.json.bak

# Update Info.plist
sed -i.bak "s/<string>.*<\/string><!-- VERSION -->/<string>$VERSION<\/string><!-- VERSION -->/" src-tauri/Info.plist && rm src-tauri/Info.plist.bak

echo "✅ Version bumped to $VERSION in all files"
echo ""
echo "Next steps:"
echo "1. Review the changes: git diff"
echo "2. Commit: git commit -am \"chore: bump version to v$VERSION\""
echo "3. Tag: git tag -a v$VERSION -m \"Release v$VERSION\""
echo "4. Push: git push && git push --tags"


================================================
FILE: shell.nix
================================================
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    # Core development tools
    just
    git

    # Node.js/Bun toolchain
    bun
    nodejs

    # Rust toolchain
    rustc
    cargo
    rustfmt
    clippy
    
    # System dependencies for Tauri development
    pkg-config
    webkitgtk_4_1
    gtk3
    cairo
    gdk-pixbuf
    glib
    dbus
    openssl
    librsvg
    libsoup_3
    libayatana-appindicator
    
    # Development utilities
    curl
    wget
    jq
  ];
  
  # Environment variables for development
  RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}


================================================
FILE: src/App.tsx
================================================
import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { Bot, FolderCode } from "lucide-react";
import { api, type Project, type Session, type ClaudeMdFile } from "@/lib/api";
import { initializeWebMode } from "@/lib/apiAdapter";
import { OutputCacheProvider } from "@/lib/outputCache";
import { TabProvider } from "@/contexts/TabContext";
import { ThemeProvider } from "@/contexts/ThemeContext";
import { Card } from "@/components/ui/card";
import { ProjectList } from "@/components/ProjectList";
import { FilePicker } from "@/components/FilePicker";
import { SessionList } from "@/components/SessionList";
import { CustomTitlebar } from "@/components/CustomTitlebar";
import { MarkdownEditor } from "@/components/MarkdownEditor";
import { ClaudeFileEditor } from "@/components/ClaudeFileEditor";
import { Settings } from "@/components/Settings";
import { CCAgents } from "@/components/CCAgents";
import { UsageDashboard } from "@/components/UsageDashboard";
import { MCPManager } from "@/components/MCPManager";
import { NFOCredits } from "@/components/NFOCredits";
import { ClaudeBinaryDialog } from "@/components/ClaudeBinaryDialog";
import { Toast, ToastContainer } from "@/components/ui/toast";
import { ProjectSettings } from '@/components/ProjectSettings';
import { TabManager } from "@/components/TabManager";
import { TabContent } from "@/components/TabContent";
import { useTabState } from "@/hooks/useTabState";
import { useAppLifecycle, useTrackEvent } from "@/hooks";
import { StartupIntro } from "@/components/StartupIntro";

type View = 
  | "welcome" 
  | "projects" 
  | "editor" 
  | "claude-file-editor" 
  | "settings"
  | "cc-agents"
  | "create-agent"
  | "github-agents"
  | "agent-execution"
  | "agent-run-view"
  | "mcp"
  | "usage-dashboard"
  | "project-settings"
  | "tabs"; // New view for tab-based interface

/**
 * AppContent component - Contains the main app logic, wrapped by providers
 */
function AppContent() {
  const [view, setView] = useState<View>("tabs");
  const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab, createAgentsTab } = useTabState();
  const [projects, setProjects] = useState<Project[]>([]);
  const [selectedProject, setSelectedProject] = useState<Project | null>(null);
  const [sessions, setSessions] = useState<Session[]>([]);
  const [editingClaudeFile, setEditingClaudeFile] = useState<ClaudeMdFile | null>(null);
  const [loading, setLoading] = useState(true);
  const [_error, setError] = useState<string | null>(null);
  const [showNFO, setShowNFO] = useState(false);
  const [showClaudeBinaryDialog, setShowClaudeBinaryDialog] = useState(false);
  const [showProjectPicker, setShowProjectPicker] = useState(false);
  const [homeDirectory, setHomeDirectory] = useState<string>('/');
  const [toast, setToast] = useState<{ message: string; type: "success" | "error" | "info" } | null>(null);
  const [projectForSettings, setProjectForSettings] = useState<Project | null>(null);
  const [previousView] = useState<View>("welcome");
  
  // Initialize analytics lifecycle tracking
  useAppLifecycle();
  const trackEvent = useTrackEvent();
  
  // Track user journey milestones
  const [hasTrackedFirstChat] = useState(false);
  // const [hasTrackedFirstAgent] = useState(false);
  
  // Track when user reaches different journey stages
  useEffect(() => {
    if (view === "projects" && projects.length > 0 && !hasTrackedFirstChat) {
      // User has projects - they're past onboarding
      trackEvent.journeyMilestone({
        journey_stage: 'onboarding',
        milestone_reached: 'projects_created',
        time_to_milestone_ms: Date.now() - performance.timing.navigationStart
      });
    }
  }, [view, projects.length, hasTrackedFirstChat, trackEvent]);

  // Initialize web mode compatibility on mount
  useEffect(() => {
    initializeWebMode();
  }, []);

  // Load projects on mount when in projects view
  useEffect(() => {
    if (view === "projects") {
      loadProjects();
    } else if (view === "welcome") {
      // Reset loading state for welcome view
      setLoading(false);
    }
  }, [view]);

  // Keyboard shortcuts for tab navigation
  useEffect(() => {
    if (view !== "tabs") return;
    
    const handleKeyDown = (e: KeyboardEvent) => {
      const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
      const modKey = isMac ? e.metaKey : e.ctrlKey;
      
      if (modKey) {
        switch (e.key) {
          case 't':
            e.preventDefault();
            window.dispatchEvent(new CustomEvent('create-chat-tab'));
            break;
          case 'w':
            e.preventDefault();
            window.dispatchEvent(new CustomEvent('close-current-tab'));
            break;
          case 'Tab':
            e.preventDefault();
            if (e.shiftKey) {
              window.dispatchEvent(new CustomEvent('switch-to-previous-tab'));
            } else {
              window.dispatchEvent(new CustomEvent('switch-to-next-tab'));
            }
            break;
          default:
            // Handle number keys 1-9
            if (e.key >= '1' && e.key <= '9') {
              e.preventDefault();
              const index = parseInt(e.key) - 1;
              window.dispatchEvent(new CustomEvent('switch-to-tab-by-index', { detail: { index } }));
            }
            break;
        }
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [view]);

  // Listen for Claude not found events
  useEffect(() => {
    const handleClaudeNotFound = () => {
      setShowClaudeBinaryDialog(true);
    };

    window.addEventListener('claude-not-found', handleClaudeNotFound as EventListener);
    return () => {
      window.removeEventListener('claude-not-found', handleClaudeNotFound as EventListener);
    };
  }, []);

  /**
   * Loads all projects from the ~/.claude/projects directory
   */
  const loadProjects = async () => {
    try {
      setLoading(true);
      setError(null);
      const projectList = await api.listProjects();
      setProjects(projectList);
    } catch (err) {
      console.error("Failed to load projects:", err);
      setError("Failed to load projects. Please ensure ~/.claude directory exists.");
    } finally {
      setLoading(false);
    }
  };

  /**
   * Handles project selection and loads its sessions
   */
  const handleProjectClick = async (project: Project) => {
    try {
      setLoading(true);
      setError(null);
      const sessionList = await api.getProjectSessions(project.id);
      setSessions(sessionList);
      setSelectedProject(project);
    } catch (err) {
      console.error("Failed to load sessions:", err);
      setError("Failed to load sessions for this project.");
    } finally {
      setLoading(false);
    }
  };

  /**
   * Opens the project directory picker
   */
  const handleOpenProject = async () => {
    // Get home directory before showing picker
    const homeDir = await api.getHomeDirectory();
    setHomeDirectory(homeDir);
    setShowProjectPicker(true);
  };

  /**
   * Opens a new Claude Code session in the interactive UI
   */
  // New session creation is handled by the tab system via titlebar actions

  /**
   * Handles editing a CLAUDE.md file from a project
   */
  const handleEditClaudeFile = (file: ClaudeMdFile) => {
    setEditingClaudeFile(file);
    handleViewChange("claude-file-editor");
  };

  /**
   * Returns from CLAUDE.md file editor to projects view
   */
  const handleBackFromClaudeFileEditor = () => {
    setEditingClaudeFile(null);
    handleViewChange("projects");
  };

  /**
   * Handles view changes with navigation protection
   */
  const handleViewChange = (newView: View) => {
    // No need for navigation protection with tabs since sessions stay open
    setView(newView);
  };

  /**
   * Handles navigating to hooks configuration
   */
  // Project settings navigation handled via `projectForSettings` state when needed


  const renderContent = () => {
    switch (view) {
      case "welcome":
        return (
          <div className="flex items-center justify-center p-4" style={{ height: "100%" }}>
            <div className="w-full max-w-4xl">
              {/* Welcome Header */}
              <motion.div
                initial={{ opacity: 0, y: 8 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.15 }}
                className="mb-12 text-center"
              >
                <h1 className="text-4xl font-bold tracking-tight">
                  <span className="rotating-symbol"></span>
                  Welcome to opcode
                </h1>
              </motion.div>

              {/* Navigation Cards */}
              <div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-2xl mx-auto">
                {/* CC Agents Card */}
                <motion.div
                  initial={{ opacity: 0, y: 8 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.15, delay: 0.05 }}
                >
                  <Card 
                    className="h-64 cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-lg border border-border/50 shimmer-hover trailing-border"
                    onClick={() => handleViewChange("cc-agents")}
                  >
                    <div className="h-full flex flex-col items-center justify-center p-8">
                      <Bot className="h-16 w-16 mb-4 text-primary" />
                      <h2 className="text-xl font-semibold">CC Agents</h2>
                    </div>
                  </Card>
                </motion.div>

                {/* Projects Card */}
                <motion.div
                  initial={{ opacity: 0, y: 8 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.15, delay: 0.1 }}
                >
                  <Card 
                    className="h-64 cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-lg border border-border/50 shimmer-hover trailing-border"
                    onClick={() => handleViewChange("projects")}
                  >
                    <div className="h-full flex flex-col items-center justify-center p-8">
                      <FolderCode className="h-16 w-16 mb-4 text-primary" />
                      <h2 className="text-xl font-semibold">Projects</h2>
                    </div>
                  </Card>
                </motion.div>

              </div>
            </div>
          </div>
        );

      case "cc-agents":
        return (
          <CCAgents 
            onBack={() => handleViewChange("welcome")} 
          />
        );

      case "editor":
        return (
          <div className="flex-1 overflow-hidden">
            <MarkdownEditor onBack={() => handleViewChange("welcome")} />
          </div>
        );
      
      case "settings":
        return <Settings onBack={() => handleViewChange("welcome")} />;
      
      case "projects":
        if (selectedProject) {
          return (
            <SessionList
              sessions={sessions}
              projectPath={selectedProject.path}
              onEditClaudeFile={handleEditClaudeFile}
            />
          );
        }
        return (
          <ProjectList
            projects={projects}
            onProjectClick={handleProjectClick}
            onOpenProject={handleOpenProject}
            loading={loading}
          />
        );
      
      case "claude-file-editor":
        return editingClaudeFile ? (
          <ClaudeFileEditor
            file={editingClaudeFile}
            onBack={handleBackFromClaudeFileEditor}
          />
        ) : null;
      
      case "tabs":
        return (
          <div className="h-full flex flex-col">
            <TabManager className="flex-shrink-0" />
            <div className="flex-1 overflow-hidden">
              <TabContent />
            </div>
          </div>
        );
      
      case "usage-dashboard":
        return (
          <UsageDashboard onBack={() => handleViewChange("welcome")} />
        );
      
      case "mcp":
        return (
          <MCPManager onBack={() => handleViewChange("welcome")} />
        );
      
      case "project-settings":
        if (projectForSettings) {
          return (
            <ProjectSettings
              project={projectForSettings}
              onBack={() => {
                setProjectForSettings(null);
                handleViewChange(previousView || "projects");
              }}
            />
          );
        }
        break;
      
      default:
        return null;
    }
  };

  return (
    <div className="h-screen flex flex-col">
      {/* Custom Titlebar */}
      <CustomTitlebar
        onAgentsClick={() => createAgentsTab()}
        onUsageClick={() => createUsageTab()}
        onClaudeClick={() => createClaudeMdTab()}
        onMCPClick={() => createMCPTab()}
        onSettingsClick={() => createSettingsTab()}
        onInfoClick={() => setShowNFO(true)}
      />
      
      {/* Topbar - Commented out since navigation moved to titlebar */}
      {/* <Topbar
        onClaudeClick={() => createClaudeMdTab()}
        onSettingsClick={() => createSettingsTab()}
        onUsageClick={() => createUsageTab()}
        onMCPClick={() => createMCPTab()}
        onInfoClick={() => setShowNFO(true)}
        onAgentsClick={() => setShowAgentsModal(true)}
      /> */}
      
      
      
      {/* Main Content */}
      <div className="flex-1 overflow-hidden">
        {renderContent()}
      </div>
      
      {/* NFO Credits Modal */}
      {showNFO && <NFOCredits onClose={() => setShowNFO(false)} />}
      
      
      {/* Claude Binary Dialog */}
      <ClaudeBinaryDialog
        open={showClaudeBinaryDialog}
        onOpenChange={setShowClaudeBinaryDialog}
        onSuccess={() => {
          setToast({ message: "Claude binary path saved successfully", type: "success" });
          // Trigger a refresh of the Claude version check
          window.location.reload();
        }}
        onError={(message) => setToast({ message, type: "error" })}
      />

      {/* File picker modal for selecting project directory */}
      {showProjectPicker && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
          <div className="w-full max-w-2xl h-[600px] bg-background border rounded-lg shadow-lg">
            <FilePicker
              basePath={homeDirectory}
              onSelect={async (entry) => {
                if (entry.is_directory) {
                  // Create or open a project for this directory
                  try {
                    const project = await api.createProject(entry.path);
                    setShowProjectPicker(false);
                    await loadProjects();
                    await handleProjectClick(project);
                  } catch (err) {
                    console.error('Failed to create project:', err);
                    setError('Failed to create project for the selected directory.');
                  }
                }
              }}
              onClose={() => setShowProjectPicker(false)}
            />
          </div>
        </div>
      )}
      
      {/* Toast Container */}
      <ToastContainer>
        {toast && (
          <Toast
            message={toast.message}
            type={toast.type}
            onDismiss={() => setToast(null)}
          />
        )}
      </ToastContainer>

      {/* File picker modal for selecting project directory */}
      {showProjectPicker && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
          <div className="w-full max-w-2xl h-[600px] bg-background border rounded-lg shadow-lg">
            <FilePicker
              basePath={homeDirectory}
              onSelect={async (entry) => {
                if (entry.is_directory) {
                  // Create or open a project for this directory
                  try {
                    const project = await api.createProject(entry.path);
                    setShowProjectPicker(false);
                    await loadProjects();
                    // Load sessions for the selected project
                    await handleProjectClick(project);
                  } catch (err) {
                    console.error('Failed to create project:', err);
                    setError('Failed to create project for the selected directory.');
                  }
                }
              }}
              onClose={() => setShowProjectPicker(false)}
            />
          </div>
        </div>
      )}
    </div>
  );
}

/**
 * Main App component - Wraps the app with providers
 */
function App() {
  const [showIntro, setShowIntro] = useState(() => {
    // Read cached preference synchronously to avoid any initial flash
    try {
      const cached = typeof window !== 'undefined'
        ? window.localStorage.getItem('app_setting:startup_intro_enabled')
        : null;
      if (cached === 'true') return true;
      if (cached === 'false') return false;
    } catch (_ignore) {}
    return true; // default if no cache
  });

  useEffect(() => {
    let timer: number | undefined;
    (async () => {
      try {
        const pref = await api.getSetting('startup_intro_enabled');
        const enabled = pref === null ? true : pref === 'true';
        if (enabled) {
          // keep intro visible and hide after duration
          timer = window.setTimeout(() => setShowIntro(false), 2000);
        } else {
          // user disabled intro: hide immediately to avoid any overlay delay
          setShowIntro(false);
        }
      } catch (err) {
        // On failure, show intro once to keep UX consistent
        timer = window.setTimeout(() => setShowIntro(false), 2000);
      }
    })();
    return () => {
      if (timer) window.clearTimeout(timer);
    };
  }, []);

  return (
    <ThemeProvider>
      <OutputCacheProvider>
        <TabProvider>
          <AppContent />
          <StartupIntro visible={showIntro} />
        </TabProvider>
      </OutputCacheProvider>
    </ThemeProvider>
  );
}

export default App;


================================================
FILE: src/assets/shimmer.css
================================================
/**
 * Shimmer animation styles
 * Provides a sword-like shimmer effect for elements
 */

@keyframes shimmer {
  0% {
    transform: translateX(-100%);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  40% {
    transform: translateX(100%);
    opacity: 0;
  }
  50% {
    transform: translateX(-100%);
    opacity: 0;
  }
  70% {
    opacity: 1;
  }
  90% {
    transform: translateX(100%);
    opacity: 0;
  }
  100% {
    transform: translateX(100%);
    opacity: 0;
  }
}

@keyframes shimmer-text {
  0% {
    background-position: -200% center;
  }
  45% {
    background-position: 200% center;
  }
  50% {
    background-position: -200% center;
  }
  95% {
    background-position: 200% center;
  }
  96%, 100% {
    background-position: 200% center;
    -webkit-text-fill-color: currentColor;
    background: none;
  }
}

/* Overlay variant: keeps the overlay text transparent and simply fades it out */
@keyframes shimmer-overlay {
  0% {
    background-position: -150% center;
    opacity: 1;
  }
  100% {
    background-position: 150% center;
    opacity: 0;
  }
}

@keyframes symbol-rotate {
  0% {
    content: '◐';
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  25% {
    content: '◓';
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  50% {
    content: '◑';
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  75% {
    content: '◒';
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  100% {
    content: '◐';
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.shimmer-once {
  position: relative;
  display: inline-block;
  background: linear-gradient(
    105deg,
    currentColor 0%,
    currentColor 40%,
    #d97757 50%,
    currentColor 60%,
    currentColor 100%
  );
  background-size: 200% auto;
  background-position: -200% center;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  animation: shimmer-text 1s ease-out forwards;
}

/* Ensures text remains visible after shimmer completes on engines
   where -webkit-text-fill-color set in keyframes may not persist */
.shimmer-fallback-visible {
  -webkit-text-fill-color: currentColor !important;
  background: none !important;
}

/* Layered brand text: base solid text plus shimmering overlay to avoid flicker */
.brand-text { position: relative; display: inline-block; }
.brand-text-solid { position: relative; color: currentColor; }
.brand-text-shimmer {
  position: absolute;
  inset: 0;
  color: transparent;
  -webkit-text-fill-color: transparent;
  background: linear-gradient(
    90deg,
    transparent 0%,
    transparent 47%,
    rgba(217, 119, 87, 0.35) 50%,
    transparent 53%,
    transparent 100%
  );
  background-size: 300% auto;
  background-position: -150% center;
  -webkit-background-clip: text;
  background-clip: text;
  pointer-events: none;
  will-change: background-position, opacity;
  animation: shimmer-overlay 1.1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

.rotating-symbol {
  display: inline-block;
  color: #8B5CF6;
  font-size: 1.5rem; /* Make it bigger! */
  margin-right: 0.5rem;
  font-weight: bold;
  vertical-align: middle;
  position: relative;
  line-height: 1;
  top: -2px;
}

.rotating-symbol::before {
  content: '◐';
  display: inline-block;
  animation: symbol-rotate 2s linear infinite;
  font-size: inherit;
  line-height: inherit;
  vertical-align: baseline;
}

/* Allow pausing the rotating symbol via an extra class */
.rotating-symbol.paused::before {
  animation: none !important;
}

.shimmer-hover {
  position: relative;
  overflow: hidden;
}

.shimmer-hover::before {
  content: '';
  position: absolute;
  top: -50%;
  left: 0;
  width: 100%;
  height: 200%;
  background: linear-gradient(
    105deg,
    transparent 0%,
    transparent 40%,
    rgba(217, 119, 87, 0.4) 50%,
    transparent 60%,
    transparent 100%
  );
  transform: translateX(-100%) rotate(-10deg);
  opacity: 0;
  pointer-events: none;
  z-index: 1;
}

.shimmer-hover > * {
  position: relative;
  z-index: 2;
}

.shimmer-hover:hover::before {
  animation: shimmer 1s ease-out;
} 


================================================
FILE: src/components/AgentExecution.tsx
================================================
import React, { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { 
  ArrowLeft, 
  Play, 
  StopCircle, 
  Terminal,
  AlertCircle,
  Loader2,
  Copy,
  ChevronDown,
  Maximize2,
  X,
  Settings2
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover } from "@/components/ui/popover";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogTitle,
} from "@/components/ui/dialog";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { api, type Agent } from "@/lib/api";
import { cn } from "@/lib/utils";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { StreamMessage } from "./StreamMessage";
import { ExecutionControlBar } from "./ExecutionControlBar";
import { ErrorBoundary } from "./ErrorBoundary";
import { useVirtualizer } from "@tanstack/react-virtual";
import { HooksEditor } from "./HooksEditor";
import { useTrackEvent, useComponentMetrics, useFeatureAdoptionTracking } from "@/hooks";
import { useTabState } from "@/hooks/useTabState";

interface AgentExecutionProps {
  /**
   * The agent to execute
   */
  agent: Agent;
  /**
   * Optional initial project path
   */
  projectPath?: string;
  /**
   * Optional tab ID for updating tab status
   */
  tabId?: string;
  /**
   * Callback to go back to the agents list
   */
  onBack: () => void;
  /**
   * Optional className for styling
   */
  className?: string;
}

export interface ClaudeStreamMessage {
  type: "system" | "assistant" | "user" | "result";
  subtype?: string;
  message?: {
    content?: any[];
    usage?: {
      input_tokens: number;
      output_tokens: number;
    };
  };
  usage?: {
    input_tokens: number;
    output_tokens: number;
  };
  [key: string]: any;
}

/**
 * AgentExecution component for running CC agents
 * 
 * @example
 * <AgentExecution agent={agent} onBack={() => setView('list')} />
 */
export const AgentExecution: React.FC<AgentExecutionProps> = ({
  agent,
  projectPath: initialProjectPath,
  tabId,
  onBack,
  className,
}) => {
  const [projectPath] = useState(initialProjectPath || "");
  const [task, setTask] = useState(agent.default_task || "");
  const [model, setModel] = useState(agent.model || "sonnet");
  const [isRunning, setIsRunning] = useState(false);
  
  // Get tab state functions
  const { updateTabStatus } = useTabState();
  const [messages, setMessages] = useState<ClaudeStreamMessage[]>([]);
  const [rawJsonlOutput, setRawJsonlOutput] = useState<string[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [copyPopoverOpen, setCopyPopoverOpen] = useState(false);
  
  // Analytics tracking
  const trackEvent = useTrackEvent();
  useComponentMetrics('AgentExecution');
  const agentFeatureTracking = useFeatureAdoptionTracking(`agent_${agent.name || 'custom'}`);
  
  // Hooks configuration state
  const [isHooksDialogOpen, setIsHooksDialogOpen] = useState(false);

  // IME composition state
  const isIMEComposingRef = useRef(false);
  const [activeHooksTab, setActiveHooksTab] = useState("project");

  // Execution stats
  const [executionStartTime, setExecutionStartTime] = useState<number | null>(null);
  const [totalTokens, setTotalTokens] = useState(0);
  const [elapsedTime, setElapsedTime] = useState(0);
  const [hasUserScrolled, setHasUserScrolled] = useState(false);
  const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
  
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const messagesContainerRef = useRef<HTMLDivElement>(null);
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const fullscreenScrollRef = useRef<HTMLDivElement>(null);
  const fullscreenMessagesEndRef = useRef<HTMLDivElement>(null);
  const unlistenRefs = useRef<UnlistenFn[]>([]);
  const elapsedTimeIntervalRef = useRef<NodeJS.Timeout | null>(null);
  const [runId, setRunId] = useState<number | null>(null);

  // Filter out messages that shouldn't be displayed
  const displayableMessages = React.useMemo(() => {
    return messages.filter((message, index) => {
      // Skip meta messages that don't have meaningful content
      if (message.isMeta && !message.leafUuid && !message.summary) {
        return false;
      }

      // Skip empty user messages
      if (message.type === "user" && message.message) {
        if (message.isMeta) return false;
        
        const msg = message.message;
        if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
          return false;
        }
        
        // Check if user message has visible content by checking its parts
        if (Array.isArray(msg.content)) {
          let hasVisibleContent = false;
          for (const content of msg.content) {
            if (content.type === "text") {
              hasVisibleContent = true;
              break;
            } else if (content.type === "tool_result") {
              // Check if this tool result will be skipped by a widget
              let willBeSkipped = false;
              if (content.tool_use_id) {
                // Look for the matching tool_use in previous assistant messages
                for (let i = index - 1; i >= 0; i--) {
                  const prevMsg = messages[i];
                  if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
                    const toolUse = prevMsg.message.content.find((c: any) => 
                      c.type === 'tool_use' && c.id === content.tool_use_id
                    );
                    if (toolUse) {
                      const toolName = toolUse.name?.toLowerCase();
                      const toolsWithWidgets = [
                        'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', 
                        'glob', 'bash', 'write', 'grep'
                      ];
                      if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
                        willBeSkipped = true;
                      }
                      break;
                    }
                  }
                }
              }
              
              if (!willBeSkipped) {
                hasVisibleContent = true;
                break;
              }
            }
          }
          
          if (!hasVisibleContent) {
            return false;
          }
        }
      }

      return true;
    });
  }, [messages]);

  // Virtualizers for efficient, smooth scrolling of potentially very long outputs
  const rowVirtualizer = useVirtualizer({
    count: displayableMessages.length,
    getScrollElement: () => scrollContainerRef.current,
    estimateSize: () => 150, // fallback estimate; dynamically measured afterwards
    overscan: 5,
  });

  const fullscreenRowVirtualizer = useVirtualizer({
    count: displayableMessages.length,
    getScrollElement: () => fullscreenScrollRef.current,
    estimateSize: () => 150,
    overscan: 5,
  });

  useEffect(() => {
    // Clean up listeners on unmount
    return () => {
      unlistenRefs.current.forEach(unlisten => unlisten());
      if (elapsedTimeIntervalRef.current) {
        clearInterval(elapsedTimeIntervalRef.current);
      }
    };
  }, []);

  // Check if user is at the very bottom of the scrollable container
  const isAtBottom = () => {
    const container = isFullscreenModalOpen ? fullscreenScrollRef.current : scrollContainerRef.current;
    if (container) {
      const { scrollTop, scrollHeight, clientHeight } = container;
      const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
      return distanceFromBottom < 1;
    }
    return true;
  };

  useEffect(() => {
    if (displayableMessages.length === 0) return;

    // Auto-scroll only if the user has not manually scrolled OR they are still at the bottom
    const shouldAutoScroll = !hasUserScrolled || isAtBottom();

    if (shouldAutoScroll) {
      if (isFullscreenModalOpen) {
        fullscreenRowVirtualizer.scrollToIndex(displayableMessages.length - 1, { align: "end", behavior: "smooth" });
      } else {
        rowVirtualizer.scrollToIndex(displayableMessages.length - 1, { align: "end", behavior: "smooth" });
      }
    }
  }, [displayableMessages.length, hasUserScrolled, isFullscreenModalOpen, rowVirtualizer, fullscreenRowVirtualizer]);

  // Update elapsed time while running
  useEffect(() => {
    if (isRunning && executionStartTime) {
      elapsedTimeIntervalRef.current = setInterval(() => {
        setElapsedTime(Math.floor((Date.now() - executionStartTime) / 1000));
      }, 100);
    } else {
      if (elapsedTimeIntervalRef.current) {
        clearInterval(elapsedTimeIntervalRef.current);
      }
    }
    
    return () => {
      if (elapsedTimeIntervalRef.current) {
        clearInterval(elapsedTimeIntervalRef.current);
      }
    };
  }, [isRunning, executionStartTime]);

  // Calculate total tokens from messages
  useEffect(() => {
    const tokens = messages.reduce((total, msg) => {
      if (msg.message?.usage) {
        return total + msg.message.usage.input_tokens + msg.message.usage.output_tokens;
      }
      if (msg.usage) {
        return total + msg.usage.input_tokens + msg.usage.output_tokens;
      }
      return total;
    }, 0);
    setTotalTokens(tokens);
  }, [messages]);


  // Project path selection is handled upstream when opening an execution tab

  const handleOpenHooksDialog = async () => {
    setIsHooksDialogOpen(true);
  };

  const handleExecute = async () => {
    try {
      setIsRunning(true);
      // Update tab status to running
      console.log('Setting tab status to running for tab:', tabId);
      if (tabId) {
        updateTabStatus(tabId, 'running');
      }
      setExecutionStartTime(Date.now());
      setMessages([]);
      setRawJsonlOutput([]);
      setRunId(null);
      
      // Clear any existing listeners
      unlistenRefs.current.forEach(unlisten => unlisten());
      unlistenRefs.current = [];
      
      // Execute the agent and get the run ID
      const executionRunId = await api.executeAgent(agent.id!, projectPath, task, model);
      console.log("Agent execution started with run ID:", executionRunId);
      setRunId(executionRunId);
      
      // Track agent execution start
      trackEvent.agentStarted({
        agent_type: agent.name || 'custom',
        agent_name: agent.name,
        has_custom_prompt: task !== agent.default_task
      });
      
      // Track feature adoption
      agentFeatureTracking.trackUsage();
      
      // Set up event listeners with run ID isolation
      const outputUnlisten = await listen<string>(`agent-output:${executionRunId}`, (event) => {
        try {
          // Store raw JSONL
          setRawJsonlOutput(prev => [...prev, event.payload]);
          
          // Parse and display
          const message = JSON.parse(event.payload) as ClaudeStreamMessage;
          setMessages(prev => [...prev, message]);
        } catch (err) {
          console.error("Failed to parse message:", err, event.payload);
        }
      });

      const errorUnlisten = await listen<string>(`agent-error:${executionRunId}`, (event) => {
        console.error("Agent error:", event.payload);
        setError(event.payload);
        
        // Track agent error
        trackEvent.agentError({
          error_type: 'runtime_error',
          error_stage: 'execution',
          retry_count: 0,
          agent_type: agent.name || 'custom'
        });
      });

      const completeUnlisten = await listen<boolean>(`agent-complete:${executionRunId}`, (event) => {
        setIsRunning(false);
        const duration = executionStartTime ? Date.now() - executionStartTime : undefined;
        setExecutionStartTime(null);
        if (!event.payload) {
          setError("Agent execution failed");
          // Update tab status to error
          if (tabId) {
            updateTabStatus(tabId, 'error');
          }
          // Track both the old event for compatibility and the new error event
          trackEvent.agentExecuted(agent.name || 'custom', false, agent.name, duration);
          trackEvent.agentError({
            error_type: 'execution_failed',
            error_stage: 'completion',
            retry_count: 0,
            agent_type: agent.name || 'custom'
          });
        } else {
          // Update tab status to complete on success
          if (tabId) {
            updateTabStatus(tabId, 'complete');
          }
          trackEvent.agentExecuted(agent.name || 'custom', true, agent.name, duration);
        }
      });

      const cancelUnlisten = await listen<boolean>(`agent-cancelled:${executionRunId}`, () => {
        setIsRunning(false);
        setExecutionStartTime(null);
        setError("Agent execution was cancelled");
        // Update tab status to idle when cancelled
        if (tabId) {
          updateTabStatus(tabId, 'idle');
        }
      });

      unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
    } catch (err) {
      console.error("Failed to execute agent:", err);
      setIsRunning(false);
      setExecutionStartTime(null);
      setRunId(null);
      // Update tab status to error
      if (tabId) {
        updateTabStatus(tabId, 'error');
      }
      // Show error in messages
      setMessages(prev => [...prev, {
        type: "result",
        subtype: "error",
        is_error: true,
        result: `Failed to execute agent: ${err instanceof Error ? err.message : 'Unknown error'}`,
        duration_ms: 0,
        usage: {
          input_tokens: 0,
          output_tokens: 0
        }
      }]);
    }
  };

  const handleStop = async () => {
    try {
      if (!runId) {
        console.error("No run ID available to stop");
        return;
      }

      // Call the API to kill the agent session
      const success = await api.killAgentSession(runId);

      if (success) {
        console.log(`Successfully stopped agent session ${runId}`);
      } else {
        console.warn(`Failed to stop agent session ${runId} - it may have already finished`);
      }

      // Update UI state
      setIsRunning(false);
      setExecutionStartTime(null);
    } catch (err) {
      console.error("Failed to stop agent:", err);
    }
  };

  const handleCompositionStart = () => {
    isIMEComposingRef.current = true;
  };

  const handleCompositionEnd = () => {
    setTimeout(() => {
      isIMEComposingRef.current = false;
    }, 0);
  };

  const handleBackWithConfirmation = () => {
    if (isRunning) {
      // Show confirmation dialog before navigating away during execution
      const shouldLeave = window.confirm(
        "An agent is currently running. If you navigate away, the agent will continue running in the background. You can view running sessions in the 'Running Sessions' tab within CC Agents.\n\nDo you want to continue?"
      );
      if (!shouldLeave) {
        return;
      }
    }
    
    // Clean up listeners but don't stop the actual agent process
    unlistenRefs.current.forEach(unlisten => unlisten());
    unlistenRefs.current = [];
    
    // Navigate back
    onBack();
  };

  const handleCopyAsJsonl = async () => {
    const jsonl = rawJsonlOutput.join('\n');
    await navigator.clipboard.writeText(jsonl);
    setCopyPopoverOpen(false);
  };

  const handleCopyAsMarkdown = async () => {
    let markdown = `# Agent Execution: ${agent.name}\n\n`;
    markdown += `**Task:** ${task}\n`;
    markdown += `**Model:** ${model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}\n`;
    markdown += `**Date:** ${new Date().toISOString()}\n\n`;
    markdown += `---\n\n`;

    for (const msg of messages) {
      if (msg.type === "system" && msg.subtype === "init") {
        markdown += `## System Initialization\n\n`;
        markdown += `- Session ID: \`${msg.session_id || 'N/A'}\`\n`;
        markdown += `- Model: \`${msg.model || 'default'}\`\n`;
        if (msg.cwd) markdown += `- Working Directory: \`${msg.cwd}\`\n`;
        if (msg.tools?.length) markdown += `- Tools: ${msg.tools.join(', ')}\n`;
        markdown += `\n`;
      } else if (msg.type === "assistant" && msg.message) {
        markdown += `## Assistant\n\n`;
        for (const content of msg.message.content || []) {
          if (content.type === "text") {
            markdown += `${content.text}\n\n`;
          } else if (content.type === "tool_use") {
            markdown += `### Tool: ${content.name}\n\n`;
            markdown += `\`\`\`json\n${JSON.stringify(content.input, null, 2)}\n\`\`\`\n\n`;
          }
        }
        if (msg.message.usage) {
          markdown += `*Tokens: ${msg.message.usage.input_tokens} in, ${msg.message.usage.output_tokens} out*\n\n`;
        }
      } else if (msg.type === "user" && msg.message) {
        markdown += `## User\n\n`;
        for (const content of msg.message.content || []) {
          if (content.type === "text") {
            markdown += `${content.text}\n\n`;
          } else if (content.type === "tool_result") {
            markdown += `### Tool Result\n\n`;
            markdown += `\`\`\`\n${content.content}\n\`\`\`\n\n`;
          }
        }
      } else if (msg.type === "result") {
        markdown += `## Execution Result\n\n`;
        if (msg.result) {
          markdown += `${msg.result}\n\n`;
        }
        if (msg.error) {
          markdown += `**Error:** ${msg.error}\n\n`;
        }
        if (msg.cost_usd !== undefined) {
          markdown += `- **Cost:** $${msg.cost_usd.toFixed(4)} USD\n`;
        }
        if (msg.duration_ms !== undefined) {
          markdown += `- **Duration:** ${(msg.duration_ms / 1000).toFixed(2)}s\n`;
        }
        if (msg.num_turns !== undefined) {
          markdown += `- **Turns:** ${msg.num_turns}\n`;
        }
        if (msg.usage) {
          const total = msg.usage.input_tokens + msg.usage.output_tokens;
          markdown += `- **Total Tokens:** ${total} (${msg.usage.input_tokens} in, ${msg.usage.output_tokens} out)\n`;
        }
      }
    }

    await navigator.clipboard.writeText(markdown);
    setCopyPopoverOpen(false);
  };


  return (
    <div className={cn("flex flex-col h-full bg-background", className)}>
      {/* Fixed container that takes full height */}
      <div className="h-full flex flex-col bg-background">
        {/* Header */}
        <div className="p-6 border-b border-border">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-3">
              <Button
                variant="ghost"
                size="icon"
                onClick={handleBackWithConfirmation}
                className="h-9 w-9 -ml-2"
                title="Back"
              >
                <ArrowLeft className="h-4 w-4" />
              </Button>
              <div>
                <h1 className="text-heading-1">{agent.name}</h1>
                <p className="mt-1 text-body-small text-muted-foreground">
                  {isRunning ? 'Running' : messages.length > 0 ? 'Complete' : 'Ready'} • {model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}
                </p>
              </div>
            </div>
            <div className="flex items-center gap-2">
              {messages.length > 0 && (
                <Button
                  variant="outline"
                  size="default"
                  onClick={() => setIsFullscreenModalOpen(true)}
                >
                  <Maximize2 className="h-4 w-4 mr-2" />
                  Fullscreen
                </Button>
              )}
            </div>
          </div>
        </div>
        
        {/* Configuration Section */}
        <div className="p-6 border-b border-border">
          <div className="max-w-4xl mx-auto space-y-4">
            {/* Error display */}
            {error && (
              <motion.div
                initial={{ opacity: 0, y: 4 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -4 }}
                transition={{ duration: 0.15 }}
                className="p-3 rounded-md bg-destructive/10 border border-destructive/50 flex items-center gap-2"
              >
                <AlertCircle className="h-3.5 w-3.5 text-destructive flex-shrink-0" />
                <span className="text-caption text-destructive">{error}</span>
              </motion.div>
            )}

            {/* Model Selection */}
            <div className="space-y-3">
              <Label className="text-caption text-muted-foreground">Model Selection</Label>
              <div className="flex gap-2">
                <motion.button
                  type="button"
                  onClick={() => !isRunning && setModel("sonnet")}
                  whileTap={{ scale: 0.97 }}
                  transition={{ duration: 0.15 }}
                  className={cn(
                    "flex-1 px-4 py-3 rounded-md border transition-all",
                    model === "sonnet" 
                      ? "border-primary bg-primary/10 text-primary" 
                      : "border-border hover:border-primary/50 hover:bg-accent",
                    isRunning && "opacity-50 cursor-not-allowed"
                  )}
                  disabled={isRunning}
                >
                  <div className="flex items-center gap-3">
                    <div className={cn(
                      "w-4 h-4 rounded-full border-2 flex items-center justify-center",
                      model === "sonnet" ? "border-primary" : "border-muted-foreground"
                    )}>
                      {model === "sonnet" && (
                        <div className="w-2 h-2 rounded-full bg-primary" />
                      )}
                    </div>
                    <div className="text-left">
                      <div className="text-body-small font-medium">Claude 4 Sonnet</div>
                      <div className="text-caption text-muted-foreground">Faster, efficient</div>
                    </div>
                  </div>
                </motion.button>
                
                <motion.button
                  type="button"
                  onClick={() => !isRunning && setModel("opus")}
                  whileTap={{ scale: 0.97 }}
                  transition={{ duration: 0.15 }}
                  className={cn(
                    "flex-1 px-4 py-3 rounded-md border transition-all",
                    model === "opus" 
                      ? "border-primary bg-primary/10 text-primary" 
                      : "border-border hover:border-primary/50 hover:bg-accent",
                    isRunning && "opacity-50 cursor-not-allowed"
                  )}
                  disabled={isRunning}
                >
                  <div className="flex items-center gap-3">
                    <div className={cn(
                      "w-4 h-4 rounded-full border-2 flex items-center justify-center",
                      model === "opus" ? "border-primary" : "border-muted-foreground"
                    )}>
                      {model === "opus" && (
                        <div className="w-2 h-2 rounded-full bg-primary" />
                      )}
                    </div>
                    <div className="text-left">
                      <div className="text-body-small font-medium">Claude 4 Opus</div>
                      <div className="text-caption text-muted-foreground">More capable</div>
                    </div>
                  </div>
                </motion.button>
              </div>
            </div>

            {/* Task Input */}
            <div className="space-y-3">
              <div className="flex items-center justify-between">
                <Label className="text-caption text-muted-foreground">Task Description</Label>
                {projectPath && (
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={handleOpenHooksDialog}
                    disabled={isRunning}
                    className="h-8 -mr-2"
                  >
                    <Settings2 className="h-3.5 w-3.5 mr-1.5" />
                    <span className="text-caption">Configure Hooks</span>
                  </Button>
                )}
              </div>
              <div className="flex gap-2">
                <Input
                  value={task}
                  onChange={(e) => setTask(e.target.value)}
                  placeholder="What would you like the agent to do?"
                  disabled={isRunning}
                  className="flex-1 h-9"
                  onKeyDown={(e) => {
                    if (e.key === "Enter" && !isRunning && projectPath && task.trim()) {
                      if (e.nativeEvent.isComposing || isIMEComposingRef.current) {
                        return;
                      }
                      handleExecute();
                    }
                  }}
                  onCompositionStart={handleCompositionStart}
                  onCompositionEnd={handleCompositionEnd}
                />
                <motion.div
                  whileTap={{ scale: 0.97 }}
                  transition={{ duration: 0.15 }}
                >
                  <Button
                    onClick={isRunning ? handleStop : handleExecute}
                    disabled={!projectPath || !task.trim()}
                    variant={isRunning ? "destructive" : "default"}
                    size="default"
                  >
                    {isRunning ? (
                      <>
                        <StopCircle className="mr-2 h-4 w-4" />
                        Stop
                      </>
                    ) : (
                      <>
                        <Play className="mr-2 h-4 w-4" />
                        Execute
                      </>
                    )}
                  </Button>
                </motion.div>
              </div>
              {projectPath && (
                <p className="text-caption text-muted-foreground">
                  Working in: <span className="font-mono">{projectPath.split('/').pop() || projectPath}</span>
                </p>
              )}
            </div>
          </div>
        </div>

        {/* Scrollable Output Display */}
        <div className="flex-1 overflow-hidden">
          <div className="w-full max-w-5xl mx-auto h-full">
            <div 
              ref={scrollContainerRef}
              className="h-full overflow-y-auto p-6 space-y-8"
              onScroll={() => {
                // Mark that user has scrolled manually
                if (!hasUserScrolled) {
                  setHasUserScrolled(true);
                }
                
                // If user scrolls back to bottom, re-enable auto-scroll
                if (isAtBottom()) {
                  setHasUserScrolled(false);
                }
              }}
            >
              <div ref={messagesContainerRef}>
              {messages.length === 0 && !isRunning && (
                <div className="flex flex-col items-center justify-center h-full text-center">
                  <Terminal className="h-16 w-16 text-muted-foreground mb-4" />
                  <h3 className="text-lg font-medium mb-2">Ready to Execute</h3>
                  <p className="text-sm text-muted-foreground">
                    Enter a task to run the agent
                  </p>
                </div>
              )}

              {isRunning && messages.length === 0 && (
                <div className="flex items-center justify-center h-full">
                  <div className="flex items-center gap-3">
                    <Loader2 className="h-6 w-6 animate-spin" />
                    <span className="text-sm text-muted-foreground">Initializing agent...</span>
                  </div>
                </div>
              )}

              <div
                className="relative w-full"
                style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
              >
                <AnimatePresence>
                  {rowVirtualizer.getVirtualItems().map((virtualItem) => {
                    const message = displayableMessages[virtualItem.index];
                    return (
                      <motion.div
                        key={virtualItem.key}
                        data-index={virtualItem.index}
                        ref={(el) => el && rowVirtualizer.measureElement(el)}
                        initial={{ opacity: 0, y: 10 }}
                        animate={{ opacity: 1, y: 0 }}
                        transition={{ duration: 0.2 }}
                        className="absolute inset-x-4 pb-4"
                        style={{ top: virtualItem.start }}
                      >
                        <ErrorBoundary>
                          <StreamMessage message={message} streamMessages={messages} />
                        </ErrorBoundary>
                      </motion.div>
                    );
                  })}
                </AnimatePresence>
              </div>
              
              <div ref={messagesEndRef} />
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Floating Execution Control Bar */}
      <ExecutionControlBar
        isExecuting={isRunning}
        onStop={handleStop}
        totalTokens={totalTokens}
        elapsedTime={elapsedTime}
      />

      {/* Fullscreen Modal */}
      {isFullscreenModalOpen && (
        <div className="fixed inset-0 z-50 bg-background flex flex-col">
          {/* Modal Header */}
          <div className="flex items-center justify-between p-4 border-b border-border">
            <div className="flex items-center gap-2">
              <h2 className="text-lg font-semibold">{agent.name} - Output</h2>
              {isRunning && (
                <div className="flex items-center gap-1">
                  <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
                  <span className="text-xs text-green-600 font-medium">Running</span>
                </div>
              )}
            </div>
            <div className="flex items-center gap-2">
              <Popover
                trigger={
                  <Button
                    variant="ghost"
                    size="sm"
                    className="flex items-center gap-2"
                  >
                    <Copy className="h-4 w-4" />
                    Copy Output
                    <ChevronDown className="h-3 w-3" />
                  </Button>
                }
                content={
                  <div className="w-44 p-1">
                    <Button
                      variant="ghost"
                      size="sm"
                      className="w-full justify-start"
                      onClick={handleCopyAsJsonl}
                    >
                      Copy as JSONL
                    </Button>
                    <Button
                      variant="ghost"
                      size="sm"
                      className="w-full justify-start"
                      onClick={handleCopyAsMarkdown}
                    >
                      Copy as Markdown
                    </Button>
                  </div>
                }
                open={copyPopoverOpen}
                onOpenChange={setCopyPopoverOpen}
                align="end"
              />
              <Button
                variant="ghost"
                size="sm"
                onClick={() => setIsFullscreenModalOpen(false)}
                className="flex items-center gap-2"
              >
                <X className="h-4 w-4" />
                Close
              </Button>
            </div>
          </div>

          {/* Modal Content */}
          <div className="flex-1 overflow-hidden p-6">
            <div 
              ref={fullscreenScrollRef}
              className="h-full overflow-y-auto space-y-8"
              onScroll={() => {
                // Mark that user has scrolled manually
                if (!hasUserScrolled) {
                  setHasUserScrolled(true);
                }
                
                // If user scrolls back to bottom, re-enable auto-scroll
                if (isAtBottom()) {
                  setHasUserScrolled(false);
                }
              }}
            >
              {messages.length === 0 && !isRunning && (
                <div className="flex flex-col items-center justify-center h-full text-center">
                  <Terminal className="h-16 w-16 text-muted-foreground mb-4" />
                  <h3 className="text-lg font-medium mb-2">Ready to Execute</h3>
                  <p className="text-sm text-muted-foreground">
                    Enter a task to run the agent
                  </p>
                </div>
              )}

              {isRunning && messages.length === 0 && (
                <div className="flex items-center justify-center h-full">
                  <div className="flex items-center gap-3">
                    <Loader2 className="h-6 w-6 animate-spin" />
                    <span className="text-sm text-muted-foreground">Initializing agent...</span>
                  </div>
                </div>
              )}

              <div
                className="relative w-full max-w-5xl mx-auto"
                style={{ height: `${fullscreenRowVirtualizer.getTotalSize()}px` }}
              >
                <AnimatePresence>
                  {fullscreenRowVirtualizer.getVirtualItems().map((virtualItem) => {
                    const message = displayableMessages[virtualItem.index];
                    return (
                      <motion.div
                        key={virtualItem.key}
                        data-index={virtualItem.index}
                        ref={(el) => el && fullscreenRowVirtualizer.measureElement(el)}
                        initial={{ opacity: 0, y: 10 }}
                        animate={{ opacity: 1, y: 0 }}
                        transition={{ duration: 0.2 }}
                        className="absolute inset-x-4 pb-4"
                        style={{ top: virtualItem.start }}
                      >
                        <ErrorBoundary>
                          <StreamMessage message={message} streamMessages={messages} />
                        </ErrorBoundary>
                      </motion.div>
                    );
                  })}
                </AnimatePresence>
              </div>
              
              <div ref={fullscreenMessagesEndRef} />
            </div>
          </div>
        </div>
      )}

      {/* Hooks Configuration Dialog */}
      <Dialog 
        open={isHooksDialogOpen} 
        onOpenChange={setIsHooksDialogOpen}
      >
        <DialogContent className="max-w-4xl max-h-[85vh] overflow-hidden flex flex-col gap-0 p-0">
          <div className="px-6 py-4 border-b border-border">
            <DialogTitle className="text-heading-2">Configure Hooks</DialogTitle>
            <DialogDescription className="mt-1 text-body-small text-muted-foreground">
              Configure hooks that run before, during, and after tool executions
            </DialogDescription>
          </div>
          
          <Tabs value={activeHooksTab} onValueChange={setActiveHooksTab} className="flex-1 flex flex-col overflow-hidden">
            <div className="px-6 pt-4">
              <TabsList className="grid w-full grid-cols-2 h-auto p-1">
                <TabsTrigger value="project" className="py-2.5 px-3 text-body-small">
                  Project Settings
                </TabsTrigger>
                <TabsTrigger value="local" className="py-2.5 px-3 text-body-small">
                  Local Settings
                </TabsTrigger>
              </TabsList>
            </div>
            
            <TabsContent value="project" className="flex-1 overflow-auto px-6 pb-6 mt-0">
              <div className="space-y-4 pt-4">
                <div className="rounded-lg bg-muted/50 p-3">
                  <p className="text-caption text-muted-foreground">
                    Project hooks are stored in <code className="font-mono text-xs bg-background px-1.5 py-0.5 rounded">.claude/settings.json</code> and 
                    are committed to version control, allowing team members to share configurations.
                  </p>
                </div>
                <HooksEditor
                  projectPath={projectPath}
                  scope="project"
                  className="border-0"
                />
              </div>
            </TabsContent>
            
            <TabsContent value="local" className="flex-1 overflow-auto px-6 pb-6 mt-0">
              <div className="space-y-4 pt-4">
                <div className="rounded-lg bg-muted/50 p-3">
                  <p className="text-caption text-muted-foreground">
                    Local hooks are stored in <code className="font-mono text-xs bg-background px-1.5 py-0.5 rounded">.claude/settings.local.json</code> and 
                    are not committed to version control, perfect for personal preferences.
                  </p>
                </div>
                <HooksEditor
                  projectPath={projectPath}
                  scope="local"
                  className="border-0"
                />
              </div>
            </TabsContent>
          </Tabs>
        </DialogContent>
      </Dialog>
    </div>
  );
};


================================================
FILE: src/components/AgentExecutionDemo.tsx
================================================
import React from "react";
import { StreamMessage } from "./StreamMessage";
import type { ClaudeStreamMessage } from "./AgentExecution";

/**
 * Demo component showing all the different message types and tools
 */
export const AgentExecutionDemo: React.FC = () => {
  // Sample messages based on the provided JSONL session
  const messages: ClaudeStreamMessage[] = [
    // Skip meta message (should not render)
    {
      type: "user",
      isMeta: true,
      message: { content: [] },
      timestamp: "2025-06-11T14:08:53.771Z"
    },
    
    // Summary message
    {
      leafUuid: "3c5ecb4f-c1f0-40c2-a357-ab7642ad28b8",
      summary: "JSONL Viewer Model Configuration and Setup",
      type: "summary" as any
    },
    
    // Assistant with Edit tool
    {
      type: "assistant",
      message: {
        content: [{
          type: "tool_use",
          name: "Edit",
          input: {
            file_path: "/Users/mufeedvh/dev/jsonl-viewer/script.js",
            new_string: "reader.onerror = () => reject(new Error('Failed to read file'));",
            old_string: "reader.onerror = e => reject(new Error('Failed to read file'));"
          }
        }],
        usage: { input_tokens: 4, output_tokens: 158 }
      }
    },
    
    // User with Edit tool result
    {
      type: "user",
      message: {
        content: [{
          type: "tool_result",
          content: `The file /Users/mufeedvh/dev/jsonl-viewer/script.js has been updated. Here's the result of running \`cat -n\` on a snippet of the edited file:
   220       readFileAsText(file) {
   221         return new Promise((resolve, reject) => {
   222           const reader = new FileReader();
   223           reader.onload = e => resolve(e.target.result);
   224           reader.onerror = () => reject(new Error('Failed to read file'));
   225           reader.readAsText(file);
   226         });
   227       }
   228`
        }]
      }
    },
    
    // Assistant with MCP tool
    {
      type: "assistant",
      message: {
        content: [{
          type: "tool_use",
          name: "mcp__ide__getDiagnostics",
          input: {}
        }],
        usage: { input_tokens: 4, output_tokens: 37 }
      }
    },
    
    // User with empty tool result
    {
      type: "user",
      message: {
        content: [{
          type: "tool_result",
          content: ""
        }]
      }
    },
    
    // Assistant with Write tool (large content)
    {
      type: "assistant",
      message: {
        content: [{
          type: "tool_use",
          name: "Write",
          input: {
            file_path: "/Users/mufeedvh/dev/jsonl-viewer/styles.css",
            content: `/* Reset and Base Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
    line-height: 1.6;
    color: #333;
    background: #f8fafc;
    min-height: 100vh;
}

/* Container */
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

/* Header */
.header {
    text-align: center;
    margin-bottom: 40px;
}

.header h1 {
    font-size: 2.5rem;
    font-weight: 700;
    color: #1a202c;
    margin-bottom: 8px;
}

.header p {
    color: #718096;
    font-size: 1.1rem;
}

/* Input Section */
.input-section {
    display: grid;
    gap: 20px;
    margin-bottom: 30px;
}

/* Drop Zone */
.drop-zone {
    border: 2px dashed #cbd5e0;
    border-radius: 12px;
    padding: 40px 20px;
    text-align: center;
    cursor: pointer;
    transition: all 0.3s ease;
    background: white;
    position: relative;
}

.drop-zone:hover,
.drop-zone.drag-over {
    border-color: #4299e1;
    background: #ebf8ff;
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(66, 153, 225, 0.1);
}

/* ... many more lines of CSS ... */
/* This content is over 1000 characters so it should show the maximize button */
` + '\n'.repeat(100) + '/* End of very long CSS file */'
          }
        }]
      }
    }
  ];

  return (
    <div className="max-w-4xl mx-auto p-8 space-y-4">
      <h1 className="text-2xl font-bold mb-6">Agent Execution Demo</h1>
      
      {messages.map((message, idx) => (
        <StreamMessage key={idx} message={message} streamMessages={messages} />
      ))}
    </div>
  );
}; 

================================================
FILE: src/components/AgentRunOutputViewer.tsx
================================================
import { useState, useEffect, useRef, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { 
  Maximize2, 
  Minimize2, 
  Copy, 
  RefreshCw, 
  RotateCcw, 
  ChevronDown,
  Bot,
  Clock,
  Hash,
  DollarSign,
  StopCircle
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Toast, ToastContainer } from '@/components/ui/toast';
import { Popover } from '@/components/ui/popover';
import { api, type AgentRunWithMetrics } from '@/lib/api';
import { useOutputCache } from '@/lib/outputCache';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { StreamMessage } from './StreamMessage';
import { ErrorBoundary } from './ErrorBoundary';
import { formatISOTimestamp } from '@/lib/date-utils';
import { AGENT_ICONS } from './CCAgents';
import type { ClaudeStreamMessage } from './AgentExecution';
import { useTabState } from '@/hooks/useTabState';

interface AgentRunOutputViewerProps {
  /**
   * The agent run ID to display
   */
  agentRunId: string;
  /**
   * Tab ID for this agent run
   */
  tabId: string;
  /**
   * Optional className for styling
   */
  className?: string;
}

/**
 * AgentRunOutputViewer - Modal component for viewing agent execution output
 * 
 * @example
 * <AgentRunOutputViewer
 *   run={agentRun}
 *   onClose={() => setSelectedRun(null)}
 * />
 */
export function AgentRunOutputViewer({ 
  agentRunId, 
  tabId,
  className 
}: AgentRunOutputViewerProps) {
  const { updateTabTitle, updateTabStatus } = useTabState();
  const [run, setRun] = useState<AgentRunWithMetrics | null>(null);
  const [messages, setMessages] = useState<ClaudeStreamMessage[]>([]);
  const [rawJsonlOutput, setRawJsonlOutput] = useState<string[]>([]);
  const [loading, setLoading] = useState(true);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [refreshing, setRefreshing] = useState(false);
  const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
  const [copyPopoverOpen, setCopyPopoverOpen] = useState(false);
  const [hasUserScrolled, setHasUserScrolled] = useState(false);
  
  // Track whether we're in the initial load phase
  const isInitialLoadRef = useRef(true);
  const hasSetupListenersRef = useRef(false);
  
  const scrollAreaRef = useRef<HTMLDivElement>(null);
  const outputEndRef = useRef<HTMLDivElement>(null);
  const fullscreenScrollRef = useRef<HTMLDivElement>(null);
  const fullscreenMessagesEndRef = useRef<HTMLDivElement>(null);
  const unlistenRefs = useRef<UnlistenFn[]>([]);
  const { getCachedOutput, setCachedOutput } = useOutputCache();

  // Auto-scroll logic
  const isAtBottom = () => {
    const container = isFullscreen ? fullscreenScrollRef.current : scrollAreaRef.current;
    if (container) {
      const { scrollTop, scrollHeight, clientHeight } = container;
      const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
      return distanceFromBottom < 1;
    }
    return true;
  };

  const scrollToBottom = () => {
    if (!hasUserScrolled) {
      const endRef = isFullscreen ? fullscreenMessagesEndRef.current : outputEndRef.current;
      if (endRef) {
        endRef.scrollIntoView({ behavior: 'smooth' });
      }
    }
  };

  // Load agent run on mount
  useEffect(() => {
    const loadAgentRun = async () => {
      try {
        setLoading(true);
        const agentRun = await api.getAgentRun(parseInt(agentRunId));
        setRun(agentRun);
        updateTabTitle(tabId, `Agent: ${agentRun.agent_name || 'Unknown'}`);
        updateTabStatus(tabId, agentRun.status === 'running' ? 'running' : agentRun.status === 'failed' ? 'error' : 'complete');
      } catch (error) {
        console.error('Failed to load agent run:', error);
        updateTabStatus(tabId, 'error');
      } finally {
        setLoading(false);
      }
    };
    
    if (agentRunId) {
      loadAgentRun();
    }
  }, [agentRunId, tabId, updateTabTitle, updateTabStatus]);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      unlistenRefs.current.forEach(unlisten => unlisten());
      unlistenRefs.current = [];
      hasSetupListenersRef.current = false;
    };
  }, []);

  // Auto-scroll when messages change
  useEffect(() => {
    const shouldAutoScroll = !hasUserScrolled || isAtBottom();
    if (shouldAutoScroll) {
      scrollToBottom();
    }
  }, [messages, hasUserScrolled, isFullscreen]);

  const loadOutput = async (skipCache = false) => {
    if (!run?.id) return;

    console.log('[AgentRunOutputViewer] Loading output for run:', {
      runId: run.id,
      status: run.status,
      sessionId: run.session_id,
      skipCache
    });

    try {
      // Check cache first if not skipping cache
      if (!skipCache) {
        const cached = getCachedOutput(run.id);
        if (cached) {
          console.log('[AgentRunOutputViewer] Found cached output');
          const cachedJsonlLines = cached.output.split('\n').filter(line => line.trim());
          setRawJsonlOutput(cachedJsonlLines);
          setMessages(cached.messages);
          // If cache is recent (less than 5 seconds old) and session isn't running, use cache only
          if (Date.now() - cached.lastUpdated < 5000 && run.status !== 'running') {
            console.log('[AgentRunOutputViewer] Using recent cache, skipping refresh');
            return;
          }
        }
      }

      setLoading(true);

      // If we have a session_id, try to load from JSONL file first
      if (run.session_id && run.session_id !== '') {
        console.log('[AgentRunOutputViewer] Attempting to load from JSONL with session_id:', run.session_id);
        try {
          const history = await api.loadAgentSessionHistory(run.session_id);
          console.log('[AgentRunOutputViewer] Successfully loaded JSONL history:', history.length, 'messages');
          
          // Convert history to messages format
          const loadedMessages: ClaudeStreamMessage[] = history.map(entry => ({
            ...entry,
            type: entry.type || "assistant"
          }));
          
          setMessages(loadedMessages);
          setRawJsonlOutput(history.map(h => JSON.stringify(h)));
          
          // Update cache
          setCachedOutput(run.id, {
            output: history.map(h => JSON.stringify(h)).join('\n'),
            messages: loadedMessages,
            lastUpdated: Date.now(),
            status: run.status
          });
          
          // Set up live event listeners for running sessions
          if (run.status === 'running') {
            console.log('[AgentRunOutputViewer] Setting up live listeners for running session');
            setupLiveEventListeners();
            
            try {
              await api.streamSessionOutput(run.id);
            } catch (streamError) {
              console.warn('[AgentRunOutputViewer] Failed to start streaming, will poll instead:', streamError);
            }
          }
          
          return;
        } catch (err) {
          console.warn('[AgentRunOutputViewer] Failed to load from JSONL:', err);
          console.warn('[AgentRunOutputViewer] Falling back to regular output method');
        }
      } else {
        console.log('[AgentRunOutputViewer] No session_id available, using fallback method');
      }

      // Fallback to the original method if JSONL loading fails or no session_id
      console.log('[AgentRunOutputViewer] Using getSessionOutput fallback');
      const rawOutput = await api.getSessionOutput(run.id);
      console.log('[AgentRunOutputViewer] Received raw output:', rawOutput.length, 'characters');
      
      // Parse JSONL output into messages
      const jsonlLines = rawOutput.split('\n').filter(line => line.trim());
      setRawJsonlOutput(jsonlLines);
      
      const parsedMessages: ClaudeStreamMessage[] = [];
      for (const line of jsonlLines) {
        try {
          const message = JSON.parse(line) as ClaudeStreamMessage;
          parsedMessages.push(message);
        } catch (err) {
          console.error("[AgentRunOutputViewer] Failed to parse message:", err, line);
        }
      }
      console.log('[AgentRunOutputViewer] Parsed', parsedMessages.length, 'messages from output');
      setMessages(parsedMessages);
      
      // Update cache
      setCachedOutput(run.id, {
        output: rawOutput,
        messages: parsedMessages,
        lastUpdated: Date.now(),
        status: run.status
      });
      
      // Set up live event listeners for running sessions
      if (run.status === 'running') {
        console.log('[AgentRunOutputViewer] Setting up live listeners for running session (fallback)');
        setupLiveEventListeners();
        
        try {
          await api.streamSessionOutput(run.id);
        } catch (streamError) {
          console.warn('[AgentRunOutputViewer] Failed to start streaming (fallback), will poll instead:', streamError);
        }
      }
    } catch (error) {
      console.error('Failed to load agent output:', error);
      setToast({ message: 'Failed to load agent output', type: 'error' });
    } finally {
      setLoading(false);
    }
  };

  // Set up live event listeners for running sessions
  const setupLiveEventListeners = async () => {
    if (!run?.id || hasSetupListenersRef.current) return;
    
    try {
      // Clean up existing listeners
      unlistenRefs.current.forEach(unlisten => unlisten());
      unlistenRefs.current = [];

      // Mark that we've set up listeners
      hasSetupListenersRef.current = true;
      
      // After setup, we're no longer in initial load
      // Small delay to ensure any pending messages are processed
      setTimeout(() => {
        isInitialLoadRef.current = false;
      }, 100);

      // Set up live event listeners with run ID isolation
      const outputUnlisten = await listen<string>(`agent-output:${run!.id}`, (event) => {
        try {
          // Skip messages during initial load phase
          if (isInitialLoadRef.current) {
            console.log('[AgentRunOutputViewer] Skipping message during initial load');
            return;
          }
          
          // Store raw JSONL
          setRawJsonlOutput(prev => [...prev, event.payload]);
          
          // Parse and display
          const message = JSON.parse(event.payload) as ClaudeStreamMessage;
          setMessages(prev => [...prev, message]);
        } catch (err) {
          console.error("[AgentRunOutputViewer] Failed to parse message:", err, event.payload);
        }
      });

      const errorUnlisten = await listen<string>(`agent-error:${run!.id}`, (event) => {
        console.error("[AgentRunOutputViewer] Agent error:", event.payload);
        setToast({ message: event.payload, type: 'error' });
      });

      const completeUnlisten = await listen<boolean>(`agent-complete:${run!.id}`, () => {
        setToast({ message: 'Agent execution completed', type: 'success' });
        // Don't set status here as the parent component should handle it
      });

      const cancelUnlisten = await listen<boolean>(`agent-cancelled:${run!.id}`, () => {
        setToast({ message: 'Agent execution was cancelled', type: 'error' });
      });

      unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
    } catch (error) {
      console.error('[AgentRunOutputViewer] Failed to set up live event listeners:', error);
    }
  };

  // Copy functionality
  const handleCopyAsJsonl = async () => {
    const jsonl = rawJsonlOutput.join('\n');
    await navigator.clipboard.writeText(jsonl);
    setCopyPopoverOpen(false);
    setToast({ message: 'Output copied as JSONL', type: 'success' });
  };

  const handleCopyAsMarkdown = async () => {
    if (!run) return;
    let markdown = `# Agent Execution: ${run.agent_name}\n\n`;
    markdown += `**Task:** ${run.task}\n`;
    markdown += `**Model:** ${run.model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}\n`;
    markdown += `**Date:** ${formatISOTimestamp(run.created_at)}\n`;
    if (run.metrics?.duration_ms) markdown += `**Duration:** ${(run.metrics.duration_ms / 1000).toFixed(2)}s\n`;
    if (run.metrics?.total_tokens) markdown += `**Total Tokens:** ${run.metrics.total_tokens}\n`;
    if (run.metrics?.cost_usd) markdown += `**Cost:** $${run.metrics.cost_usd.toFixed(4)} USD\n`;
    markdown += `\n---\n\n`;

    for (const msg of messages) {
      if (msg.type === "system" && msg.subtype === "init") {
        markdown += `## System Initialization\n\n`;
        markdown += `- Session ID: \`${msg.session_id || 'N/A'}\`\n`;
        markdown += `- Model: \`${msg.model || 'default'}\`\n`;
        if (msg.cwd) markdown += `- Working Directory: \`${msg.cwd}\`\n`;
        if (msg.tools?.length) markdown += `- Tools: ${msg.tools.join(', ')}\n`;
        markdown += `\n`;
      } else if (msg.type === "assistant" && msg.message) {
        markdown += `## Assistant\n\n`;
        for (const content of msg.message.content || []) {
          if (content.type === "text") {
            markdown += `${content.text}\n\n`;
          } else if (content.type === "tool_use") {
            markdown += `### Tool: ${content.name}\n\n`;
            markdown += `\`\`\`json\n${JSON.stringify(content.input, null, 2)}\n\`\`\`\n\n`;
          }
        }
        if (msg.message.usage) {
          markdown += `*Tokens: ${msg.message.usage.input_tokens} in, ${msg.message.usage.output_tokens} out*\n\n`;
        }
      } else if (msg.type === "user" && msg.message) {
        markdown += `## User\n\n`;
        for (const content of msg.message.content || []) {
          if (content.type === "text") {
            markdown += `${content.text}\n\n`;
          } else if (content.type === "tool_result") {
            markdown += `### Tool Result\n\n`;
            markdown += `\`\`\`\n${content.content}\n\`\`\`\n\n`;
          }
        }
      } else if (msg.type === "result") {
        markdown += `## Execution Result\n\n`;
        if (msg.result) {
          markdown += `${msg.result}\n\n`;
        }
        if (msg.error) {
          markdown += `**Error:** ${msg.error}\n\n`;
        }
      }
    }

    await navigator.clipboard.writeText(markdown);
    setCopyPopoverOpen(false);
    setToast({ message: 'Output copied as Markdown', type: 'success' });
  };

  const handleRefresh = async () => {
    setRefreshing(true);
    await loadOutput();
    setRefreshing(false);
  };

  const handleStop = async () => {
    if (!run?.id) {
      console.error('[AgentRunOutputViewer] No run ID available to stop');
      return;
    }

    try {
      // Call the API to kill the agent session
      const success = await api.killAgentSession(run.id);
      
      if (success) {
        console.log(`[AgentRunOutputViewer] Successfully stopped agent session ${run.id}`);
        setToast({ message: 'Agent execution stopped', type: 'success' });
        
        // Clean up listeners
        unlistenRefs.current.forEach(unlisten => unlisten());
        unlistenRefs.current = [];
        hasSetupListenersRef.current = false;
        
        // Add a message indicating execution was stopped
        const stopMessage: ClaudeStreamMessage = {
          type: "result",
          subtype: "error",
          is_error: true,
          result: "Execution stopped by user",
          duration_ms: 0,
          usage: {
            input_tokens: 0,
            output_tokens: 0
          }
        };
        setMessages(prev => [...prev, stopMessage]);
        
        // Update the tab status
        updateTabStatus(tabId, 'idle');
        
        // Refresh the output to get updated status
        await loadOutput(true);
      } else {
        console.warn(`[AgentRunOutputViewer] Failed to stop agent session ${run.id} - it may have already finished`);
        setToast({ message: 'Failed to stop agent - it may have already finished', type: 'error' });
      }
    } catch (err) {
      console.error('[AgentRunOutputViewer] Failed to stop agent:', err);
      setToast({ 
        message: `Failed to stop execution: ${err instanceof Error ? err.message : 'Unknown error'}`, 
        type: 'error' 
      });
    }
  };

  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
    const target = e.currentTarget;
    const { scrollTop, scrollHeight, clientHeight } = target;
    const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
    setHasUserScrolled(distanceFromBottom > 50);
  };

  // Load output on mount
  useEffect(() => {
    if (!run?.id) return;
    
    // Check cache immediately for instant display
    const cached = getCachedOutput(run!.id);
    if (cached) {
      const cachedJsonlLines = cached.output.split('\n').filter(line => line.trim());
      setRawJsonlOutput(cachedJsonlLines);
      setMessages(cached.messages);
    }
    
    // Then load fresh data
    loadOutput();
  }, [run?.id]);

  const displayableMessages = useMemo(() => {
    return messages.filter((message) => {
      if (message.isMeta && !message.leafUuid && !message.summary) return false;

      if (message.type === "user" && message.message) {
        if (message.isMeta) return false;

        const msg = message.message;
        if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) return false;

        if (Array.isArray(msg.content)) {
          let hasVisibleContent = false;
          for (const content of msg.content) {
            if (content.type === "text") { hasVisibleContent = true; break; }
            if (content.type === "tool_result") {
              // Check if this tool result will be displayed as a widget
              let willBeSkipped = false;
              if (content.tool_use_id) {
                // Find the corresponding tool use
                for (let i = messages.indexOf(message) - 1; i >= 0; i--) {
                  const prevMsg = messages[i];
                  if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
                    const toolUse = prevMsg.message.content.find((c: any) => c.type === 'tool_use' && c.id === content.tool_use_id);
                    if (toolUse) {
                      const toolName = toolUse.name?.toLowerCase();
                      const toolsWithWidgets = ['task','edit','multiedit','todowrite','ls','read','glob','bash','write','grep'];
                      if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
                        willBeSkipped = true;
                      }
                      break;
                    }
                  }
                }
              }
              if (!willBeSkipped) { hasVisibleContent = true; break; }
            }
          }
          if (!hasVisibleContent) return false;
        }
      }
      return true;
    });
  }, [messages]);

  const renderIcon = (iconName: string) => {
    const Icon = AGENT_ICONS[iconName as keyof typeof AGENT_ICONS] || Bot;
    return <Icon className="h-5 w-5" />;
  };

  const formatDuration = (ms?: number) => {
    if (!ms) return "N/A";
    const seconds = Math.floor(ms / 1000);
    if (seconds < 60) return `${seconds}s`;
    const minutes = Math.floor(seconds / 60);
    const remainingSeconds = seconds % 60;
    return `${minutes}m ${remainingSeconds}s`;
  };

  const formatTokens = (tokens?: number) => {
    if (!tokens) return "0";
    if (tokens >= 1000) {
      return `${(tokens / 1000).toFixed(1)}k`;
    }
    return tokens.toString();
  };

  if (!run) {
    return (
      <div className="flex items-center justify-center h-full">
        <div className="text-center">
          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
          <p className="text-muted-foreground">Loading agent run...</p>
        </div>
      </div>
    );
  }

  return (
    <>
      <div className={`h-full flex flex-col ${className || ''}`}>
        <Card className="h-full flex flex-col">
        <CardHeader className="pb-3">
          <div className="flex items-start justify-between gap-4">
              <div className="flex items-start gap-3 flex-1 min-w-0">
                <div className="mt-0.5">
                  {renderIcon(run.agent_icon)}
                </div>
                <div className="flex-1 min-w-0">
                  <CardTitle className="text-lg flex items-center gap-2">
                    {run.agent_name}
                    {run.status === 'running' && (
                      <div className="flex items-center gap-1">
                        <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
                        <span className="text-xs text-green-600 font-medium">Running</span>
                      </div>
                    )}
                  </CardTitle>
                  <p className="text-sm text-muted-foreground mt-1 truncate">
                    {run.task}
                  </p>
                  <div className="flex items-center gap-3 text-xs text-muted-foreground mt-2">
                    <Badge variant="outline" className="text-xs">
                      {run.model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}
                    </Badge>
                    <div className="flex items-center gap-1">
                      <Clock className="h-3 w-3" />
                      <span>{formatISOTimestamp(run.created_at)}</
Download .txt
gitextract_3l4qo4o3/

├── .cargo/
│   └── config.toml
├── .github/
│   └── workflows/
│       ├── build-linux.yml
│       ├── build-macos.yml
│       ├── build-test.yml
│       ├── claude-code-review.yml
│       ├── claude.yml
│       ├── pr-check.yml
│       └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bun.lockb
├── cc_agents/
│   ├── README.md
│   ├── git-commit-bot.opcode.json
│   ├── security-scanner.opcode.json
│   └── unit-tests-bot.opcode.json
├── index.html
├── justfile
├── package.json
├── scripts/
│   └── bump-version.sh
├── shell.nix
├── src/
│   ├── App.tsx
│   ├── assets/
│   │   ├── nfo/
│   │   │   └── opcode-nfo.ogg
│   │   └── shimmer.css
│   ├── components/
│   │   ├── AgentExecution.tsx
│   │   ├── AgentExecutionDemo.tsx
│   │   ├── AgentRunOutputViewer.tsx
│   │   ├── AgentRunView.tsx
│   │   ├── AgentRunsList.tsx
│   │   ├── Agents.tsx
│   │   ├── AgentsModal.tsx
│   │   ├── AnalyticsConsent.tsx
│   │   ├── AnalyticsErrorBoundary.tsx
│   │   ├── App.cleaned.tsx
│   │   ├── CCAgents.tsx
│   │   ├── CheckpointSettings.tsx
│   │   ├── ClaudeBinaryDialog.tsx
│   │   ├── ClaudeCodeSession.refactored.tsx
│   │   ├── ClaudeCodeSession.tsx
│   │   ├── ClaudeFileEditor.tsx
│   │   ├── ClaudeMemoriesDropdown.tsx
│   │   ├── ClaudeVersionSelector.tsx
│   │   ├── CreateAgent.tsx
│   │   ├── CustomTitlebar.tsx
│   │   ├── ErrorBoundary.tsx
│   │   ├── ExecutionControlBar.tsx
│   │   ├── FilePicker.optimized.tsx
│   │   ├── FilePicker.tsx
│   │   ├── FloatingPromptInput.tsx
│   │   ├── GitHubAgentBrowser.tsx
│   │   ├── HooksEditor.tsx
│   │   ├── IconPicker.tsx
│   │   ├── ImagePreview.tsx
│   │   ├── MCPAddServer.tsx
│   │   ├── MCPImportExport.tsx
│   │   ├── MCPManager.tsx
│   │   ├── MCPServerList.tsx
│   │   ├── MarkdownEditor.tsx
│   │   ├── NFOCredits.tsx
│   │   ├── PreviewPromptDialog.tsx
│   │   ├── ProjectList.tsx
│   │   ├── ProjectSettings.tsx
│   │   ├── ProxySettings.tsx
│   │   ├── RunningClaudeSessions.tsx
│   │   ├── SessionList.optimized.tsx
│   │   ├── SessionList.tsx
│   │   ├── SessionOutputViewer.tsx
│   │   ├── Settings.tsx
│   │   ├── SlashCommandPicker.tsx
│   │   ├── SlashCommandsManager.tsx
│   │   ├── StartupIntro.tsx
│   │   ├── StorageTab.tsx
│   │   ├── StreamMessage.tsx
│   │   ├── TabContent.tsx
│   │   ├── TabManager.tsx
│   │   ├── TimelineNavigator.tsx
│   │   ├── TokenCounter.tsx
│   │   ├── ToolWidgets.new.tsx
│   │   ├── ToolWidgets.tsx
│   │   ├── Topbar.tsx
│   │   ├── UsageDashboard.original.tsx
│   │   ├── UsageDashboard.tsx
│   │   ├── WebviewPreview.tsx
│   │   ├── claude-code-session/
│   │   │   ├── MessageList.tsx
│   │   │   ├── PromptQueue.tsx
│   │   │   ├── SessionHeader.tsx
│   │   │   ├── useCheckpoints.ts
│   │   │   └── useClaudeMessages.ts
│   │   ├── index.ts
│   │   ├── ui/
│   │   │   ├── badge.tsx
│   │   │   ├── button.tsx
│   │   │   ├── card.tsx
│   │   │   ├── dialog.tsx
│   │   │   ├── dropdown-menu.tsx
│   │   │   ├── input.tsx
│   │   │   ├── label.tsx
│   │   │   ├── pagination.tsx
│   │   │   ├── popover.tsx
│   │   │   ├── radio-group.tsx
│   │   │   ├── scroll-area.tsx
│   │   │   ├── select.tsx
│   │   │   ├── split-pane.tsx
│   │   │   ├── switch.tsx
│   │   │   ├── tabs.tsx
│   │   │   ├── textarea.tsx
│   │   │   ├── toast.tsx
│   │   │   ├── tooltip-modern.tsx
│   │   │   └── tooltip.tsx
│   │   └── widgets/
│   │       ├── BashWidget.tsx
│   │       ├── LSWidget.tsx
│   │       ├── TodoWidget.tsx
│   │       └── index.ts
│   ├── contexts/
│   │   ├── TabContext.tsx
│   │   └── ThemeContext.tsx
│   ├── hooks/
│   │   ├── index.ts
│   │   ├── useAnalytics.ts
│   │   ├── useApiCall.ts
│   │   ├── useDebounce.ts
│   │   ├── useLoadingState.ts
│   │   ├── usePagination.ts
│   │   ├── usePerformanceMonitor.ts
│   │   ├── useTabState.ts
│   │   └── useTheme.ts
│   ├── lib/
│   │   ├── analytics/
│   │   │   ├── consent.ts
│   │   │   ├── events.ts
│   │   │   ├── index.ts
│   │   │   ├── resourceMonitor.ts
│   │   │   └── types.ts
│   │   ├── api-tracker.ts
│   │   ├── api.ts
│   │   ├── apiAdapter.ts
│   │   ├── claudeSyntaxTheme.ts
│   │   ├── date-utils.ts
│   │   ├── hooksManager.ts
│   │   ├── linkDetector.tsx
│   │   ├── outputCache.tsx
│   │   └── utils.ts
│   ├── main.tsx
│   ├── services/
│   │   ├── sessionPersistence.ts
│   │   └── tabPersistence.ts
│   ├── stores/
│   │   ├── README.md
│   │   ├── agentStore.ts
│   │   └── sessionStore.ts
│   ├── styles.css
│   ├── types/
│   │   └── hooks.ts
│   └── vite-env.d.ts
├── src-tauri/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── Info.plist
│   ├── build.rs
│   ├── capabilities/
│   │   └── default.json
│   ├── entitlements.plist
│   ├── icons/
│   │   └── icon.icns
│   ├── src/
│   │   ├── checkpoint/
│   │   │   ├── manager.rs
│   │   │   ├── mod.rs
│   │   │   ├── state.rs
│   │   │   └── storage.rs
│   │   ├── claude_binary.rs
│   │   ├── commands/
│   │   │   ├── agents.rs
│   │   │   ├── claude.rs
│   │   │   ├── mcp.rs
│   │   │   ├── mod.rs
│   │   │   ├── proxy.rs
│   │   │   ├── slash_commands.rs
│   │   │   ├── storage.rs
│   │   │   └── usage.rs
│   │   ├── lib.rs
│   │   ├── main.rs
│   │   ├── process/
│   │   │   ├── mod.rs
│   │   │   └── registry.rs
│   │   ├── web_main.rs
│   │   └── web_server.rs
│   ├── tauri.conf.json
│   └── tests/
│       └── TESTS_COMPLETE.md
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── web_server.design.md
Download .txt
SYMBOL INDEX (843 symbols across 123 files)

FILE: src-tauri/build.rs
  function main (line 1) | fn main() {

FILE: src-tauri/src/checkpoint/manager.rs
  type CheckpointManager (line 17) | pub struct CheckpointManager {
    method new (line 29) | pub async fn new(
    method track_message (line 64) | pub async fn track_message(&self, jsonl_message: String) -> Result<()> {
    method track_tool_operation (line 89) | async fn track_tool_operation(&self, tool: &str, input: &serde_json::V...
    method track_file_modification (line 108) | pub async fn track_file_modification(&self, file_path: &str) -> Result...
    method track_bash_side_effects (line 165) | async fn track_bash_side_effects(&self, command: &str) -> Result<()> {
    method create_checkpoint (line 188) | pub async fn create_checkpoint(
    method extract_checkpoint_metadata (line 305) | async fn extract_checkpoint_metadata(
    method create_file_snapshots (line 401) | async fn create_file_snapshots(&self, checkpoint_id: &str) -> Result<V...
    method restore_checkpoint (line 452) | pub async fn restore_checkpoint(&self, checkpoint_id: &str) -> Result<...
    method restore_file_snapshot (line 602) | async fn restore_file_snapshot(&self, snapshot: &FileSnapshot) -> Resu...
    method get_timeline (line 633) | pub async fn get_timeline(&self) -> SessionTimeline {
    method list_checkpoints (line 638) | pub async fn list_checkpoints(&self) -> Vec<Checkpoint> {
    method collect_checkpoints_from_node (line 650) | fn collect_checkpoints_from_node(
    method fork_from_checkpoint (line 661) | pub async fn fork_from_checkpoint(
    method should_auto_checkpoint (line 683) | pub async fn should_auto_checkpoint(&self, message: &str) -> bool {
    method update_settings (line 749) | pub async fn update_settings(
    method get_files_modified_since (line 768) | pub async fn get_files_modified_since(&self, since: DateTime<Utc>) -> ...
    method get_last_modification_time (line 779) | pub async fn get_last_modification_time(&self) -> Option<DateTime<Utc>> {

FILE: src-tauri/src/checkpoint/mod.rs
  type Checkpoint (line 13) | pub struct Checkpoint {
  type CheckpointMetadata (line 35) | pub struct CheckpointMetadata {
  type FileSnapshot (line 51) | pub struct FileSnapshot {
  type TimelineNode (line 71) | pub struct TimelineNode {
  type SessionTimeline (line 83) | pub struct SessionTimeline {
    method new (line 181) | pub fn new(session_id: String) -> Self {
    method find_checkpoint (line 193) | pub fn find_checkpoint(&self, checkpoint_id: &str) -> Option<&Timeline...
    method find_in_tree (line 199) | fn find_in_tree<'a>(node: &'a TimelineNode, checkpoint_id: &str) -> Op...
  type CheckpointStrategy (line 101) | pub enum CheckpointStrategy {
  type FileTracker (line 114) | pub struct FileTracker {
  type FileState (line 121) | pub struct FileState {
  type CheckpointResult (line 134) | pub struct CheckpointResult {
  type CheckpointDiff (line 145) | pub struct CheckpointDiff {
  type FileDiff (line 162) | pub struct FileDiff {
  method default (line 174) | fn default() -> Self {
  type CheckpointPaths (line 215) | pub struct CheckpointPaths {
    method new (line 222) | pub fn new(claude_dir: &PathBuf, project_id: &str, session_id: &str) -...
    method checkpoint_dir (line 236) | pub fn checkpoint_dir(&self, checkpoint_id: &str) -> PathBuf {
    method checkpoint_metadata_file (line 240) | pub fn checkpoint_metadata_file(&self, checkpoint_id: &str) -> PathBuf {
    method checkpoint_messages_file (line 244) | pub fn checkpoint_messages_file(&self, checkpoint_id: &str) -> PathBuf {
    method file_snapshot_path (line 249) | pub fn file_snapshot_path(&self, _checkpoint_id: &str, file_hash: &str...
    method file_reference_path (line 255) | pub fn file_reference_path(&self, checkpoint_id: &str, safe_filename: ...

FILE: src-tauri/src/checkpoint/state.rs
  type CheckpointState (line 15) | pub struct CheckpointState {
    method new (line 25) | pub fn new() -> Self {
    method set_claude_dir (line 35) | pub async fn set_claude_dir(&self, claude_dir: PathBuf) {
    method get_or_create_manager (line 52) | pub async fn get_or_create_manager(
    method get_manager (line 88) | pub async fn get_manager(&self, session_id: &str) -> Option<Arc<Checkp...
    method remove_manager (line 96) | pub async fn remove_manager(&self, session_id: &str) -> Option<Arc<Che...
    method clear_all (line 105) | pub async fn clear_all(&self) {
    method active_count (line 111) | pub async fn active_count(&self) -> usize {
    method list_active_sessions (line 117) | pub async fn list_active_sessions(&self) -> Vec<String> {
    method has_active_manager (line 124) | pub async fn has_active_manager(&self, session_id: &str) -> bool {
    method clear_all_and_count (line 130) | pub async fn clear_all_and_count(&self) -> usize {
  function test_checkpoint_state_lifecycle (line 143) | async fn test_checkpoint_state_lifecycle() {

FILE: src-tauri/src/checkpoint/storage.rs
  type CheckpointStorage (line 13) | pub struct CheckpointStorage {
    method new (line 20) | pub fn new(claude_dir: PathBuf) -> Self {
    method init_storage (line 28) | pub fn init_storage(&self, project_id: &str, session_id: &str) -> Resu...
    method save_checkpoint (line 46) | pub fn save_checkpoint(
    method save_file_snapshot (line 99) | fn save_file_snapshot(&self, paths: &CheckpointPaths, snapshot: &FileS...
    method load_checkpoint (line 147) | pub fn load_checkpoint(
    method load_file_snapshots (line 178) | fn load_file_snapshots(
    method save_timeline (line 241) | pub fn save_timeline(&self, timeline_path: &Path, timeline: &SessionTi...
    method load_timeline (line 249) | pub fn load_timeline(&self, timeline_path: &Path) -> Result<SessionTim...
    method update_timeline_with_checkpoint (line 257) | fn update_timeline_with_checkpoint(
    method add_child_to_node (line 296) | fn add_child_to_node(
    method calculate_file_hash (line 316) | pub fn calculate_file_hash(content: &str) -> String {
    method generate_checkpoint_id (line 323) | pub fn generate_checkpoint_id() -> String {
    method estimate_checkpoint_size (line 328) | pub fn estimate_checkpoint_size(messages: &str, file_snapshots: &[File...
    method cleanup_old_checkpoints (line 337) | pub fn cleanup_old_checkpoints(
    method collect_checkpoints (line 381) | fn collect_checkpoints(node: &TimelineNode, checkpoints: &mut Vec<Chec...
    method remove_checkpoint (line 389) | fn remove_checkpoint(&self, paths: &CheckpointPaths, checkpoint_id: &s...
    method garbage_collect_content (line 409) | pub fn garbage_collect_content(&self, project_id: &str, session_id: &s...

FILE: src-tauri/src/claude_binary.rs
  type InstallationType (line 13) | pub enum InstallationType {
  type ClaudeInstallation (line 22) | pub struct ClaudeInstallation {
  function find_claude_binary (line 35) | pub fn find_claude_binary(app_handle: &tauri::AppHandle) -> Result<Strin...
  function discover_claude_installations (line 99) | pub fn discover_claude_installations() -> Vec<ClaudeInstallation> {
  function source_preference (line 127) | fn source_preference(installation: &ClaudeInstallation) -> u8 {
  function discover_system_installations (line 147) | fn discover_system_installations() -> Vec<ClaudeInstallation> {
  function try_which_command (line 170) | fn try_which_command() -> Option<ClaudeInstallation> {
  function try_which_command (line 214) | fn try_which_command() -> Option<ClaudeInstallation> {
  function find_nvm_installations (line 256) | fn find_nvm_installations() -> Vec<ClaudeInstallation> {
  function find_nvm_installations (line 315) | fn find_nvm_installations() -> Vec<ClaudeInstallation> {
  function find_standard_installations (line 352) | fn find_standard_installations() -> Vec<ClaudeInstallation> {
  function find_standard_installations (line 433) | fn find_standard_installations() -> Vec<ClaudeInstallation> {
  function get_claude_version (line 502) | fn get_claude_version(path: &str) -> Result<Option<String>, String> {
  function extract_version_from_output (line 519) | fn extract_version_from_output(stdout: &[u8]) -> Option<String> {
  function select_best_installation (line 549) | fn select_best_installation(installations: Vec<ClaudeInstallation>) -> O...
  function compare_versions (line 581) | fn compare_versions(a: &str, b: &str) -> Ordering {
  function create_command_with_env (line 621) | pub fn create_command_with_env(program: &str) -> Command {

FILE: src-tauri/src/commands/agents.rs
  function find_claude_binary (line 19) | fn find_claude_binary(app_handle: &AppHandle) -> Result<String, String> {
  type Agent (line 25) | pub struct Agent {
  type AgentRun (line 42) | pub struct AgentRun {
  type AgentRunMetrics (line 60) | pub struct AgentRunMetrics {
    method from_jsonl (line 101) | pub fn from_jsonl(jsonl_content: &str) -> Self {
  type AgentRunWithMetrics (line 69) | pub struct AgentRunWithMetrics {
  type AgentExport (line 78) | pub struct AgentExport {
  type AgentData (line 86) | pub struct AgentData {
  type AgentDb (line 96) | pub struct AgentDb(pub Mutex<Connection>);
  function read_session_jsonl (line 170) | pub async fn read_session_jsonl(session_id: &str, project_path: &str) ->...
  function get_agent_run_with_metrics (line 195) | pub async fn get_agent_run_with_metrics(run: AgentRun) -> AgentRunWithMe...
  function init_database (line 217) | pub fn init_database(app: &AppHandle) -> SqliteResult<Connection> {
  function list_agents (line 350) | pub async fn list_agents(db: State<'_, AgentDb>) -> Result<Vec<Agent>, S...
  function create_agent (line 385) | pub async fn create_agent(
  function update_agent (line 440) | pub async fn update_agent(
  function delete_agent (line 525) | pub async fn delete_agent(db: State<'_, AgentDb>, id: i64) -> Result<(),...
  function get_agent (line 536) | pub async fn get_agent(db: State<'_, AgentDb>, id: i64) -> Result<Agent,...
  function list_agent_runs (line 567) | pub async fn list_agent_runs(
  function get_agent_run (line 621) | pub async fn get_agent_run(db: State<'_, AgentDb>, id: i64) -> Result<Ag...
  function get_agent_run_with_real_time_metrics (line 654) | pub async fn get_agent_run_with_real_time_metrics(
  function list_agent_runs_with_metrics (line 664) | pub async fn list_agent_runs_with_metrics(
  function execute_agent (line 681) | pub async fn execute_agent(
  function create_agent_system_command (line 788) | fn create_agent_system_command(
  function spawn_agent_system (line 809) | async fn spawn_agent_system(
  function list_running_sessions (line 1135) | pub async fn list_running_sessions(
  function kill_agent_session (line 1199) | pub async fn kill_agent_session(
  function get_session_status (line 1257) | pub async fn get_session_status(
  function cleanup_finished_processes (line 1276) | pub async fn cleanup_finished_processes(db: State<'_, AgentDb>) -> Resul...
  function get_live_session_output (line 1342) | pub async fn get_live_session_output(
  function get_session_output (line 1351) | pub async fn get_session_output(
  function stream_session_output (line 1444) | pub async fn stream_session_output(
  function export_agent (line 1531) | pub async fn export_agent(db: State<'_, AgentDb>, id: i64) -> Result<Str...
  function export_agent_to_file (line 1566) | pub async fn export_agent_to_file(
  function get_claude_binary_path (line 1582) | pub async fn get_claude_binary_path(db: State<'_, AgentDb>) -> Result<Op...
  function set_claude_binary_path (line 1598) | pub async fn set_claude_binary_path(db: State<'_, AgentDb>, path: String...
  function list_claude_installations (line 1632) | pub async fn list_claude_installations(
  function create_command_with_env (line 1646) | fn create_command_with_env(program: &str) -> Command {
  function import_agent (line 1704) | pub async fn import_agent(db: State<'_, AgentDb>, json_data: String) -> ...
  function import_agent_from_file (line 1781) | pub async fn import_agent_from_file(
  type GitHubAgentFile (line 1804) | pub struct GitHubAgentFile {
  type GitHubApiResponse (line 1814) | struct GitHubApiResponse {
  function fetch_github_agents (line 1826) | pub async fn fetch_github_agents() -> Result<Vec<GitHubAgentFile>, Strin...
  function fetch_github_agent_content (line 1872) | pub async fn fetch_github_agent_content(download_url: String) -> Result<...
  function import_agent_from_github (line 1913) | pub async fn import_agent_from_github(
  function load_agent_session_history (line 1933) | pub async fn load_agent_session_history(

FILE: src-tauri/src/commands/claude.rs
  type ClaudeProcessState (line 14) | pub struct ClaudeProcessState {
  method default (line 19) | fn default() -> Self {
  type Project (line 28) | pub struct Project {
  type Session (line 43) | pub struct Session {
  type JsonlEntry (line 62) | struct JsonlEntry {
  type MessageContent (line 72) | struct MessageContent {
  type ClaudeSettings (line 79) | pub struct ClaudeSettings {
  method default (line 85) | fn default() -> Self {
  type ClaudeVersionStatus (line 94) | pub struct ClaudeVersionStatus {
  type ClaudeMdFile (line 105) | pub struct ClaudeMdFile {
  type FileEntry (line 118) | pub struct FileEntry {
  function find_claude_binary (line 133) | fn find_claude_binary(app_handle: &AppHandle) -> Result<String, String> {
  function get_claude_dir (line 138) | fn get_claude_dir() -> Result<PathBuf> {
  function get_project_path_from_sessions (line 147) | fn get_project_path_from_sessions(project_dir: &PathBuf) -> Result<Strin...
  function decode_project_path (line 186) | fn decode_project_path(encoded: &str) -> String {
  function extract_first_user_message (line 194) | fn extract_first_user_message(jsonl_path: &PathBuf) -> (Option<String>, ...
  function create_command_with_env (line 234) | fn create_command_with_env(program: &str) -> Command {
  function create_system_command (line 293) | fn create_system_command(claude_path: &str, args: Vec<String>, project_p...
  function get_home_directory (line 310) | pub async fn get_home_directory() -> Result<String, String> {
  function list_projects (line 318) | pub async fn list_projects() -> Result<Vec<Project>, String> {
  function create_project (line 426) | pub async fn create_project(path: String) -> Result<Project, String> {
  function get_project_sessions (line 473) | pub async fn get_project_sessions(project_id: String) -> Result<Vec<Sess...
  function get_claude_settings (line 560) | pub async fn get_claude_settings() -> Result<ClaudeSettings, String> {
  function open_new_session (line 584) | pub async fn open_new_session(app: AppHandle, path: Option<String>) -> R...
  function get_system_prompt (line 626) | pub async fn get_system_prompt() -> Result<String, String> {
  function check_claude_version (line 642) | pub async fn check_claude_version(app: AppHandle) -> Result<ClaudeVersio...
  function save_system_prompt (line 734) | pub async fn save_system_prompt(content: String) -> Result<String, Strin...
  function save_claude_settings (line 747) | pub async fn save_claude_settings(settings: serde_json::Value) -> Result...
  function find_claude_md_files (line 765) | pub async fn find_claude_md_files(project_path: String) -> Result<Vec<Cl...
  function find_claude_md_recursive (line 784) | fn find_claude_md_recursive(
  function read_claude_md_file (line 851) | pub async fn read_claude_md_file(file_path: String) -> Result<String, St...
  function save_claude_md_file (line 864) | pub async fn save_claude_md_file(file_path: String, content: String) -> ...
  function load_session_history (line 882) | pub async fn load_session_history(
  function execute_claude_code (line 921) | pub async fn execute_claude_code(
  function continue_claude_code (line 952) | pub async fn continue_claude_code(
  function resume_claude_code (line 984) | pub async fn resume_claude_code(
  function cancel_claude_execution (line 1019) | pub async fn cancel_claude_execution(
  function list_running_claude_sessions (line 1153) | pub async fn list_running_claude_sessions(
  function get_claude_session_output (line 1161) | pub async fn get_claude_session_output(
  function spawn_claude_process (line 1174) | async fn spawn_claude_process(
  function list_directory_contents (line 1344) | pub async fn list_directory_contents(directory_path: String) -> Result<V...
  function search_files (line 1421) | pub async fn search_files(base_path: String, query: String) -> Result<Ve...
  function search_files_recursive (line 1467) | fn search_files_recursive(
  function create_checkpoint (line 1538) | pub async fn create_checkpoint(
  function restore_checkpoint (line 1598) | pub async fn restore_checkpoint(
  function list_checkpoints (line 1647) | pub async fn list_checkpoints(
  function fork_from_checkpoint (line 1669) | pub async fn fork_from_checkpoint(
  function get_session_timeline (line 1719) | pub async fn get_session_timeline(
  function update_checkpoint_settings (line 1741) | pub async fn update_checkpoint_settings(
  function get_checkpoint_diff (line 1779) | pub async fn get_checkpoint_diff(
  function track_checkpoint_message (line 1866) | pub async fn track_checkpoint_message(
  function check_auto_checkpoint (line 1888) | pub async fn check_auto_checkpoint(
  function cleanup_old_checkpoints (line 1907) | pub async fn cleanup_old_checkpoints(
  function get_checkpoint_settings (line 1937) | pub async fn get_checkpoint_settings(
  function clear_checkpoint_manager (line 1962) | pub async fn clear_checkpoint_manager(
  function get_checkpoint_state_stats (line 1974) | pub async fn get_checkpoint_state_stats(
  function get_recently_modified_files (line 1988) | pub async fn get_recently_modified_files(
  function track_session_messages (line 2024) | pub async fn track_session_messages(
  function get_hooks_config (line 2058) | pub async fn get_hooks_config(
  function update_hooks_config (line 2107) | pub async fn update_hooks_config(
  function validate_hook_command (line 2163) | pub async fn validate_hook_command(command: String) -> Result<serde_json...
  function create_test_session_file (line 2198) | fn create_test_session_file(
  function test_get_project_path_from_sessions_normal_case (line 2210) | fn test_get_project_path_from_sessions_normal_case() {
  function test_get_project_path_from_sessions_with_hyphen (line 2224) | fn test_get_project_path_from_sessions_with_hyphen() {
  function test_get_project_path_from_sessions_null_cwd_first_line (line 2238) | fn test_get_project_path_from_sessions_null_cwd_first_line() {
  function test_get_project_path_from_sessions_multiple_lines (line 2256) | fn test_get_project_path_from_sessions_multiple_lines() {
  function test_get_project_path_from_sessions_empty_dir (line 2277) | fn test_get_project_path_from_sessions_empty_dir() {
  function test_get_project_path_from_sessions_no_jsonl_files (line 2290) | fn test_get_project_path_from_sessions_no_jsonl_files() {
  function test_get_project_path_from_sessions_no_cwd (line 2302) | fn test_get_project_path_from_sessions_no_cwd() {
  function test_get_project_path_from_sessions_multiple_sessions (line 2318) | fn test_get_project_path_from_sessions_multiple_sessions() {

FILE: src-tauri/src/commands/mcp.rs
  function create_command_with_env (line 13) | fn create_command_with_env(program: &str) -> Command {
  function find_claude_binary (line 19) | fn find_claude_binary(app_handle: &AppHandle) -> Result<String> {
  type MCPServer (line 25) | pub struct MCPServer {
  type ServerStatus (line 48) | pub struct ServerStatus {
  type MCPProjectConfig (line 59) | pub struct MCPProjectConfig {
  type MCPServerConfig (line 66) | pub struct MCPServerConfig {
  type AddServerResult (line 76) | pub struct AddServerResult {
  type ImportResult (line 84) | pub struct ImportResult {
  type ImportServerResult (line 92) | pub struct ImportServerResult {
  function execute_claude_mcp_command (line 99) | fn execute_claude_mcp_command(app_handle: &AppHandle, args: Vec<&str>) -...
  function mcp_add (line 121) | pub async fn mcp_add(
  function mcp_list (line 213) | pub async fn mcp_list(app: AppHandle) -> Result<Vec<MCPServer>, String> {
  function mcp_get (line 335) | pub async fn mcp_get(app: AppHandle, name: String) -> Result<MCPServer, ...
  function mcp_remove (line 404) | pub async fn mcp_remove(app: AppHandle, name: String) -> Result<String, ...
  function mcp_add_json (line 421) | pub async fn mcp_add_json(
  function mcp_add_from_claude_desktop (line 462) | pub async fn mcp_add_from_claude_desktop(
  function mcp_serve (line 615) | pub async fn mcp_serve(app: AppHandle) -> Result<String, String> {
  function mcp_test_connection (line 644) | pub async fn mcp_test_connection(app: AppHandle, name: String) -> Result...
  function mcp_reset_project_choices (line 656) | pub async fn mcp_reset_project_choices(app: AppHandle) -> Result<String,...
  function mcp_get_server_status (line 673) | pub async fn mcp_get_server_status() -> Result<HashMap<String, ServerSta...
  function mcp_read_project_config (line 683) | pub async fn mcp_read_project_config(project_path: String) -> Result<MCP...
  function mcp_save_project_config (line 711) | pub async fn mcp_save_project_config(

FILE: src-tauri/src/commands/proxy.rs
  type ProxySettings (line 8) | pub struct ProxySettings {
  method default (line 17) | fn default() -> Self {
  function get_proxy_settings (line 30) | pub async fn get_proxy_settings(db: State<'_, AgentDb>) -> Result<ProxyS...
  function save_proxy_settings (line 66) | pub async fn save_proxy_settings(
  function apply_proxy_settings (line 102) | pub fn apply_proxy_settings(settings: &ProxySettings) {

FILE: src-tauri/src/commands/slash_commands.rs
  type SlashCommand (line 10) | pub struct SlashCommand {
  type CommandFrontmatter (line 39) | struct CommandFrontmatter {
  function parse_markdown_with_frontmatter (line 46) | fn parse_markdown_with_frontmatter(content: &str) -> Result<(Option<Comm...
  function extract_command_info (line 85) | fn extract_command_info(file_path: &Path, base_path: &Path) -> Result<(S...
  function load_command_from_file (line 115) | fn load_command_from_file(file_path: &Path, base_path: &Path, scope: &st...
  function find_markdown_files (line 169) | fn find_markdown_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(...
  function create_default_commands (line 200) | fn create_default_commands() -> Vec<SlashCommand> {
  function slash_commands_list (line 249) | pub async fn slash_commands_list(
  function slash_command_get (line 314) | pub async fn slash_command_get(command_id: String) -> Result<SlashComman...
  function slash_command_save (line 335) | pub async fn slash_command_save(
  function slash_command_delete (line 417) | pub async fn slash_command_delete(
  function remove_empty_dirs (line 453) | fn remove_empty_dirs(dir: &Path) -> Result<()> {

FILE: src-tauri/src/commands/storage.rs
  type TableInfo (line 11) | pub struct TableInfo {
  type ColumnInfo (line 19) | pub struct ColumnInfo {
  type TableData (line 30) | pub struct TableData {
  type QueryResult (line 42) | pub struct QueryResult {
  function storage_list_tables (line 51) | pub async fn storage_list_tables(db: State<'_, AgentDb>) -> Result<Vec<T...
  function storage_read_table (line 110) | pub async fn storage_read_table(
  function storage_update_row (line 233) | pub async fn storage_update_row(
  function storage_delete_row (line 292) | pub async fn storage_delete_row(
  function storage_insert_row (line 336) | pub async fn storage_insert_row(
  function storage_execute_sql (line 381) | pub async fn storage_execute_sql(
  function storage_reset_database (line 452) | pub async fn storage_reset_database(app: AppHandle) -> Result<(), String> {
  function is_valid_table_name (line 498) | fn is_valid_table_name(conn: &Connection, table_name: &str) -> Result<bo...
  function json_to_sql_value (line 511) | fn json_to_sql_value(value: &JsonValue) -> Result<Box<dyn rusqlite::ToSq...

FILE: src-tauri/src/commands/usage.rs
  type UsageEntry (line 10) | pub struct UsageEntry {
  type UsageStats (line 23) | pub struct UsageStats {
  type ModelUsage (line 37) | pub struct ModelUsage {
  type DailyUsage (line 49) | pub struct DailyUsage {
  type ProjectUsage (line 57) | pub struct ProjectUsage {
  constant OPUS_4_INPUT_PRICE (line 67) | const OPUS_4_INPUT_PRICE: f64 = 15.0;
  constant OPUS_4_OUTPUT_PRICE (line 68) | const OPUS_4_OUTPUT_PRICE: f64 = 75.0;
  constant OPUS_4_CACHE_WRITE_PRICE (line 69) | const OPUS_4_CACHE_WRITE_PRICE: f64 = 18.75;
  constant OPUS_4_CACHE_READ_PRICE (line 70) | const OPUS_4_CACHE_READ_PRICE: f64 = 1.50;
  constant SONNET_4_INPUT_PRICE (line 72) | const SONNET_4_INPUT_PRICE: f64 = 3.0;
  constant SONNET_4_OUTPUT_PRICE (line 73) | const SONNET_4_OUTPUT_PRICE: f64 = 15.0;
  constant SONNET_4_CACHE_WRITE_PRICE (line 74) | const SONNET_4_CACHE_WRITE_PRICE: f64 = 3.75;
  constant SONNET_4_CACHE_READ_PRICE (line 75) | const SONNET_4_CACHE_READ_PRICE: f64 = 0.30;
  type JsonlEntry (line 78) | struct JsonlEntry {
  type MessageData (line 90) | struct MessageData {
  type UsageData (line 97) | struct UsageData {
  function calculate_cost (line 104) | fn calculate_cost(model: &str, usage: &UsageData) -> f64 {
  function parse_jsonl_file (line 140) | fn parse_jsonl_file(
  function get_earliest_timestamp (line 231) | fn get_earliest_timestamp(path: &PathBuf) -> Option<String> {
  function get_all_usage_entries (line 252) | fn get_all_usage_entries(claude_path: &PathBuf) -> Vec<UsageEntry> {
  function get_usage_stats (line 292) | pub fn get_usage_stats(days: Option<u32>) -> Result<UsageStats, String> {
  function get_usage_by_date_range (line 452) | pub fn get_usage_by_date_range(start_date: String, end_date: String) -> ...
  function get_usage_details (line 622) | pub fn get_usage_details(
  function get_session_stats (line 646) | pub fn get_session_stats(

FILE: src-tauri/src/lib.rs
  function run (line 11) | pub fn run() {

FILE: src-tauri/src/main.rs
  function main (line 54) | fn main() {

FILE: src-tauri/src/process/registry.rs
  type ProcessType (line 9) | pub enum ProcessType {
  type ProcessInfo (line 16) | pub struct ProcessInfo {
  type ProcessHandle (line 28) | pub struct ProcessHandle {
  type ProcessRegistry (line 35) | pub struct ProcessRegistry {
    method new (line 41) | pub fn new() -> Self {
    method generate_id (line 49) | pub fn generate_id(&self) -> Result<i64, String> {
    method register_process (line 57) | pub fn register_process(
    method register_sidecar_process (line 85) | pub fn register_sidecar_process(
    method register_claude_session (line 122) | pub fn register_claude_session(
    method register_process_internal (line 156) | fn register_process_internal(
    method get_running_claude_sessions (line 175) | pub fn get_running_claude_sessions(&self) -> Result<Vec<ProcessInfo>, ...
    method get_claude_session_by_id (line 187) | pub fn get_claude_session_by_id(
    method unregister_process (line 203) | pub fn unregister_process(&self, run_id: i64) -> Result<(), String> {
    method get_running_processes (line 211) | pub fn get_running_processes(&self) -> Result<Vec<ProcessInfo>, String> {
    method get_running_agent_processes (line 220) | pub fn get_running_agent_processes(&self) -> Result<Vec<ProcessInfo>, ...
    method get_process (line 233) | pub fn get_process(&self, run_id: i64) -> Result<Option<ProcessInfo>, ...
    method kill_process (line 239) | pub async fn kill_process(&self, run_id: i64) -> Result<bool, String> {
    method kill_process_by_pid (line 363) | pub fn kill_process_by_pid(&self, run_id: i64, pid: u32) -> Result<boo...
    method is_process_running (line 438) | pub async fn is_process_running(&self, run_id: i64) -> Result<bool, St...
    method append_live_output (line 472) | pub fn append_live_output(&self, run_id: i64, output: &str) -> Result<...
    method get_live_output (line 483) | pub fn get_live_output(&self, run_id: i64) -> Result<String, String> {
    method cleanup_finished_processes (line 495) | pub async fn cleanup_finished_processes(&self) -> Result<Vec<i64>, Str...
  method default (line 525) | fn default() -> Self {
  type ProcessRegistryState (line 531) | pub struct ProcessRegistryState(pub Arc<ProcessRegistry>);
  method default (line 534) | fn default() -> Self {

FILE: src-tauri/src/web_main.rs
  type Args (line 12) | struct Args {
  function main (line 23) | async fn main() {

FILE: src-tauri/src/web_server.rs
  function find_claude_binary_web (line 24) | fn find_claude_binary_web() -> Result<String, String> {
  type AppState (line 63) | pub struct AppState {
  type ClaudeExecutionRequest (line 70) | pub struct ClaudeExecutionRequest {
  type QueryParams (line 79) | pub struct QueryParams {
  type ApiResponse (line 85) | pub struct ApiResponse<T> {
  function success (line 92) | pub fn success(data: T) -> Self {
  function error (line 100) | pub fn error(error: String) -> Self {
  function serve_frontend (line 110) | async fn serve_frontend() -> Html<&'static str> {
  function get_projects (line 115) | async fn get_projects() -> Json<ApiResponse<Vec<commands::claude::Projec...
  function get_sessions (line 123) | async fn get_sessions(
  function get_agents (line 133) | async fn get_agents() -> Json<ApiResponse<Vec<serde_json::Value>>> {
  function get_usage (line 138) | async fn get_usage() -> Json<ApiResponse<Vec<serde_json::Value>>> {
  function get_claude_settings (line 143) | async fn get_claude_settings() -> Json<ApiResponse<serde_json::Value>> {
  function check_claude_version (line 157) | async fn check_claude_version() -> Json<ApiResponse<serde_json::Value>> {
  function list_claude_installations (line 167) | async fn list_claude_installations(
  function get_system_prompt (line 181) | async fn get_system_prompt() -> Json<ApiResponse<String>> {
  function open_new_session (line 189) | async fn open_new_session() -> Json<ApiResponse<String>> {
  function list_slash_commands (line 195) | async fn list_slash_commands() -> Json<ApiResponse<Vec<serde_json::Value...
  function mcp_list (line 200) | async fn mcp_list() -> Json<ApiResponse<Vec<serde_json::Value>>> {
  function load_session_history (line 205) | async fn load_session_history(
  function list_running_claude_sessions (line 215) | async fn list_running_claude_sessions() -> Json<ApiResponse<Vec<serde_js...
  function execute_claude_code (line 221) | async fn execute_claude_code() -> Json<ApiResponse<serde_json::Value>> {
  function continue_claude_code (line 226) | async fn continue_claude_code() -> Json<ApiResponse<serde_json::Value>> {
  function resume_claude_code (line 231) | async fn resume_claude_code() -> Json<ApiResponse<serde_json::Value>> {
  function cancel_claude_execution (line 236) | async fn cancel_claude_execution(Path(sessionId): Path<String>) -> Json<...
  function get_claude_session_output (line 244) | async fn get_claude_session_output(Path(sessionId): Path<String>) -> Jso...
  function claude_websocket (line 253) | async fn claude_websocket(ws: WebSocketUpgrade, AxumState(state): AxumSt...
  function claude_websocket_handler (line 257) | async fn claude_websocket_handler(socket: WebSocket, state: AppState) {
  function execute_claude_command (line 445) | async fn execute_claude_command(
  function continue_claude_command (line 571) | async fn continue_claude_command(
  function resume_claude_command (line 648) | async fn resume_claude_command(
  function send_to_session (line 746) | async fn send_to_session(state: &AppState, session_id: &str, message: St...
  function create_web_server (line 770) | pub async fn create_web_server(port: u16) -> Result<(), Box<dyn std::err...
  function start_web_mode (line 842) | pub async fn start_web_mode(port: Option<u16>) -> Result<(), Box<dyn std...

FILE: src/App.tsx
  type View (line 30) | type View =
  function AppContent (line 49) | function AppContent() {
  function App (line 493) | function App() {

FILE: src/components/AgentExecution.tsx
  type AgentExecutionProps (line 38) | interface AgentExecutionProps {
  type ClaudeStreamMessage (line 61) | interface ClaudeStreamMessage {

FILE: src/components/AgentRunOutputViewer.tsx
  type AgentRunOutputViewerProps (line 31) | interface AgentRunOutputViewerProps {
  function AgentRunOutputViewer (line 55) | function AgentRunOutputViewer({

FILE: src/components/AgentRunView.tsx
  type AgentRunViewProps (line 25) | interface AgentRunViewProps {

FILE: src/components/AgentRunsList.tsx
  type AgentRunsListProps (line 13) | interface AgentRunsListProps {
  constant ITEMS_PER_PAGE (line 28) | const ITEMS_PER_PAGE = 5;

FILE: src/components/AgentsModal.tsx
  type AgentsModalProps (line 29) | interface AgentsModalProps {

FILE: src/components/AnalyticsConsent.tsx
  type AnalyticsConsentProps (line 10) | interface AnalyticsConsentProps {
  type AnalyticsConsentBannerProps (line 146) | interface AnalyticsConsentBannerProps {

FILE: src/components/AnalyticsErrorBoundary.tsx
  type Props (line 4) | interface Props {
  type State (line 9) | interface State {
  class AnalyticsErrorBoundary (line 17) | class AnalyticsErrorBoundary extends Component<Props, State> {
    method constructor (line 18) | constructor(props: Props) {
    method getDerivedStateFromError (line 23) | static getDerivedStateFromError(error: Error): State {
    method componentDidCatch (line 27) | componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    method render (line 45) | render() {
  function withAnalyticsErrorBoundary (line 78) | function withAnalyticsErrorBoundary<P extends object>(

FILE: src/components/App.cleaned.tsx
  function AppContent (line 17) | function AppContent() {
  function App (line 170) | function App() {

FILE: src/components/CCAgents.tsx
  type CCAgentsProps (line 44) | interface CCAgentsProps {
  constant AGENT_ICONS (line 56) | const AGENT_ICONS = ICON_MAP;
  type AgentIconName (line 58) | type AgentIconName = keyof typeof AGENT_ICONS;

FILE: src/components/CheckpointSettings.tsx
  type CheckpointSettingsProps (line 20) | interface CheckpointSettingsProps {

FILE: src/components/ClaudeBinaryDialog.tsx
  type ClaudeBinaryDialogProps (line 8) | interface ClaudeBinaryDialogProps {
  function ClaudeBinaryDialog (line 15) | function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: ...

FILE: src/components/ClaudeCodeSession.refactored.tsx
  type ClaudeCodeSessionProps (line 25) | interface ClaudeCodeSessionProps {

FILE: src/components/ClaudeCodeSession.tsx
  type UnlistenFn (line 21) | type UnlistenFn = () => void;
  type ClaudeCodeSessionProps (line 65) | interface ClaudeCodeSessionProps {
  function handleStreamMessage (line 605) | function handleStreamMessage(payload: string | ClaudeStreamMessage) {

FILE: src/components/ClaudeFileEditor.tsx
  type ClaudeFileEditorProps (line 10) | interface ClaudeFileEditorProps {

FILE: src/components/ClaudeMemoriesDropdown.tsx
  type ClaudeMemoriesDropdownProps (line 10) | interface ClaudeMemoriesDropdownProps {

FILE: src/components/ClaudeVersionSelector.tsx
  type ClaudeVersionSelectorProps (line 11) | interface ClaudeVersionSelectorProps {

FILE: src/components/CreateAgent.tsx
  type CreateAgentProps (line 16) | interface CreateAgentProps {

FILE: src/components/CustomTitlebar.tsx
  type CustomTitlebarProps (line 7) | interface CustomTitlebarProps {

FILE: src/components/ErrorBoundary.tsx
  type ErrorBoundaryProps (line 6) | interface ErrorBoundaryProps {
  type ErrorBoundaryState (line 11) | interface ErrorBoundaryState {
  class ErrorBoundary (line 19) | class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryS...
    method constructor (line 20) | constructor(props: ErrorBoundaryProps) {
    method getDerivedStateFromError (line 25) | static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    method componentDidCatch (line 30) | componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    method render (line 39) | render() {

FILE: src/components/ExecutionControlBar.tsx
  type ExecutionControlBarProps (line 7) | interface ExecutionControlBarProps {

FILE: src/components/FilePicker.optimized.tsx
  type FilePickerProps (line 24) | interface FilePickerProps {

FILE: src/components/FilePicker.tsx
  type FilePickerProps (line 30) | interface FilePickerProps {

FILE: src/components/FloatingPromptInput.tsx
  type FloatingPromptInputProps (line 40) | interface FloatingPromptInputProps {
  type FloatingPromptInputRef (line 75) | interface FloatingPromptInputRef {
  type ThinkingMode (line 82) | type ThinkingMode = "auto" | "think" | "think_hard" | "think_harder" | "...
  type ThinkingModeConfig (line 87) | type ThinkingModeConfig = {
  constant THINKING_MODES (line 98) | const THINKING_MODES: ThinkingModeConfig[] = [
  type Model (line 175) | type Model = {
  constant MODELS (line 184) | const MODELS: Model[] = [

FILE: src/components/GitHubAgentBrowser.tsx
  type GitHubAgentBrowserProps (line 23) | interface GitHubAgentBrowserProps {
  type AgentPreview (line 29) | interface AgentPreview {

FILE: src/components/HooksEditor.tsx
  type HooksEditorProps (line 64) | interface HooksEditorProps {
  type EditableHookCommand (line 73) | interface EditableHookCommand extends HookCommand {
  type EditableHookMatcher (line 77) | interface EditableHookMatcher extends Omit<HookMatcher, 'hooks'> {
  constant EVENT_INFO (line 83) | const EVENT_INFO: Record<HookEvent, { label: string; description: string...

FILE: src/components/IconPicker.tsx
  constant ICON_CATEGORIES (line 152) | const ICON_CATEGORIES = {
  type IconCategory (line 302) | type IconCategory = typeof ICON_CATEGORIES[keyof typeof ICON_CATEGORIES];
  type IconItem (line 303) | type IconItem = IconCategory[number];
  type IconPickerProps (line 305) | interface IconPickerProps {
  constant AVAILABLE_ICONS (line 456) | const AVAILABLE_ICONS = Object.values(ICON_CATEGORIES)
  constant ICON_MAP (line 461) | const ICON_MAP = Object.values(ICON_CATEGORIES)

FILE: src/components/ImagePreview.tsx
  type ImagePreviewProps (line 8) | interface ImagePreviewProps {

FILE: src/components/MCPAddServer.tsx
  type MCPAddServerProps (line 12) | interface MCPAddServerProps {
  type EnvironmentVariable (line 23) | interface EnvironmentVariable {

FILE: src/components/MCPImportExport.tsx
  type MCPImportExportProps (line 9) | interface MCPImportExportProps {

FILE: src/components/MCPManager.tsx
  type MCPManagerProps (line 12) | interface MCPManagerProps {

FILE: src/components/MCPServerList.tsx
  type MCPServerListProps (line 24) | interface MCPServerListProps {

FILE: src/components/MarkdownEditor.tsx
  type MarkdownEditorProps (line 10) | interface MarkdownEditorProps {

FILE: src/components/NFOCredits.tsx
  type NFOCreditsProps (line 10) | interface NFOCreditsProps {

FILE: src/components/PreviewPromptDialog.tsx
  type PreviewPromptDialogProps (line 14) | interface PreviewPromptDialogProps {

FILE: src/components/ProjectList.tsx
  type ProjectListProps (line 13) | interface ProjectListProps {

FILE: src/components/ProjectSettings.tsx
  type ProjectSettingsProps (line 25) | interface ProjectSettingsProps {

FILE: src/components/ProxySettings.tsx
  type ProxySettings (line 7) | interface ProxySettings {
  type ProxySettingsProps (line 15) | interface ProxySettingsProps {
  function ProxySettings (line 20) | function ProxySettings({ setToast, onChange }: ProxySettingsProps) {

FILE: src/components/RunningClaudeSessions.tsx
  type RunningClaudeSessionsProps (line 10) | interface RunningClaudeSessionsProps {

FILE: src/components/SessionList.optimized.tsx
  type SessionListProps (line 13) | interface SessionListProps {

FILE: src/components/SessionList.tsx
  type SessionListProps (line 12) | interface SessionListProps {
  constant ITEMS_PER_PAGE (line 39) | const ITEMS_PER_PAGE = 12;

FILE: src/components/SessionOutputViewer.tsx
  type SessionOutputViewerProps (line 16) | interface SessionOutputViewerProps {
  type ClaudeStreamMessage (line 23) | interface ClaudeStreamMessage {
  function SessionOutputViewer (line 40) | function SessionOutputViewer({ session, onClose, className }: SessionOut...

FILE: src/components/Settings.tsx
  type SettingsProps (line 34) | interface SettingsProps {
  type PermissionRule (line 45) | interface PermissionRule {
  type EnvironmentVariable (line 50) | interface EnvironmentVariable {

FILE: src/components/SlashCommandPicker.tsx
  type SlashCommandPickerProps (line 23) | interface SlashCommandPickerProps {

FILE: src/components/SlashCommandsManager.tsx
  type SlashCommandsManagerProps (line 34) | interface SlashCommandsManagerProps {
  type CommandForm (line 40) | interface CommandForm {
  constant EXAMPLE_COMMANDS (line 49) | const EXAMPLE_COMMANDS = [

FILE: src/components/StartupIntro.tsx
  function StartupIntro (line 10) | function StartupIntro({ visible }: { visible: boolean }) {
  function BrandText (line 97) | function BrandText() {

FILE: src/components/StorageTab.tsx
  type TableInfo (line 48) | interface TableInfo {
  type ColumnInfo (line 54) | interface ColumnInfo {
  type TableData (line 63) | interface TableData {
  type QueryResult (line 73) | interface QueryResult {

FILE: src/components/StreamMessage.tsx
  type StreamMessageProps (line 44) | interface StreamMessageProps {
  method code (line 135) | code({ node, inline, className, children, ...props }: any) {
  method code (line 664) | code({ node, inline, className, children, ...props }: any) {

FILE: src/components/TabContent.tsx
  type TabPanelProps (line 26) | interface TabPanelProps {

FILE: src/components/TabManager.tsx
  type TabItemProps (line 9) | interface TabItemProps {
  type TabManagerProps (line 132) | interface TabManagerProps {

FILE: src/components/TimelineNavigator.tsx
  type TimelineNavigatorProps (line 27) | interface TimelineNavigatorProps {

FILE: src/components/TokenCounter.tsx
  type TokenCounterProps (line 6) | interface TokenCounterProps {

FILE: src/components/Topbar.tsx
  type TopbarProps (line 9) | interface TopbarProps {

FILE: src/components/UsageDashboard.original.tsx
  type UsageDashboardProps (line 15) | interface UsageDashboardProps {

FILE: src/components/UsageDashboard.tsx
  type UsageDashboardProps (line 16) | interface UsageDashboardProps {
  constant CACHE_DURATION (line 25) | const CACHE_DURATION = 10 * 60 * 1000;

FILE: src/components/WebviewPreview.tsx
  type WebviewPreviewProps (line 20) | interface WebviewPreviewProps {

FILE: src/components/claude-code-session/MessageList.tsx
  type MessageListProps (line 9) | interface MessageListProps {

FILE: src/components/claude-code-session/PromptQueue.tsx
  type QueuedPrompt (line 8) | interface QueuedPrompt {
  type PromptQueueProps (line 14) | interface PromptQueueProps {

FILE: src/components/claude-code-session/SessionHeader.tsx
  type SessionHeaderProps (line 19) | interface SessionHeaderProps {

FILE: src/components/claude-code-session/useCheckpoints.ts
  type Checkpoint (line 5) | interface Checkpoint {
  type UseCheckpointsOptions (line 13) | interface UseCheckpointsOptions {
  function useCheckpoints (line 20) | function useCheckpoints({ sessionId, projectId, projectPath, onToast }: ...

FILE: src/components/claude-code-session/useClaudeMessages.ts
  type UseClaudeMessagesOptions (line 16) | interface UseClaudeMessagesOptions {
  function useClaudeMessages (line 22) | function useClaudeMessages(options: UseClaudeMessagesOptions = {}) {

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

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

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

FILE: src/components/ui/label.tsx
  type LabelProps (line 4) | interface LabelProps

FILE: src/components/ui/pagination.tsx
  type PaginationProps (line 6) | interface PaginationProps {

FILE: src/components/ui/popover.tsx
  type PopoverProps (line 5) | interface PopoverProps {

FILE: src/components/ui/scroll-area.tsx
  type ScrollAreaProps (line 4) | interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {

FILE: src/components/ui/select.tsx
  type SelectOption (line 145) | interface SelectOption {
  type SelectProps (line 150) | interface SelectProps {

FILE: src/components/ui/split-pane.tsx
  type SplitPaneProps (line 4) | interface SplitPaneProps {

FILE: src/components/ui/switch.tsx
  type SwitchProps (line 4) | interface SwitchProps

FILE: src/components/ui/tabs.tsx
  type TabsProps (line 12) | interface TabsProps {
  type TabsListProps (line 55) | interface TabsListProps {
  type TabsTriggerProps (line 82) | interface TabsTriggerProps {
  type TabsContentProps (line 124) | interface TabsContentProps {

FILE: src/components/ui/textarea.tsx
  type TextareaProps (line 4) | interface TextareaProps

FILE: src/components/ui/toast.tsx
  type ToastType (line 6) | type ToastType = "success" | "error" | "info";
  type ToastProps (line 8) | interface ToastProps {
  type ToastContainerProps (line 97) | interface ToastContainerProps {

FILE: src/components/ui/tooltip-modern.tsx
  type TooltipSimpleProps (line 36) | interface TooltipSimpleProps {

FILE: src/components/widgets/BashWidget.tsx
  type BashWidgetProps (line 5) | interface BashWidgetProps {

FILE: src/components/widgets/LSWidget.tsx
  type LSWidgetProps (line 5) | interface LSWidgetProps {
  type LSResultWidgetProps (line 59) | interface LSResultWidgetProps {

FILE: src/components/widgets/TodoWidget.tsx
  type TodoWidgetProps (line 6) | interface TodoWidgetProps {

FILE: src/contexts/TabContext.tsx
  type Tab (line 5) | interface Tab {
  type TabContextType (line 24) | interface TabContextType {
  constant MAX_TABS (line 40) | const MAX_TABS = 20;

FILE: src/contexts/ThemeContext.tsx
  type ThemeMode (line 4) | type ThemeMode = 'dark' | 'gray' | 'light' | 'custom';
  type CustomThemeColors (line 6) | interface CustomThemeColors {
  type ThemeContextType (line 26) | interface ThemeContextType {
  constant THEME_STORAGE_KEY (line 36) | const THEME_STORAGE_KEY = 'theme_preference';
  constant CUSTOM_COLORS_STORAGE_KEY (line 37) | const CUSTOM_COLORS_STORAGE_KEY = 'theme_custom_colors';
  constant DEFAULT_CUSTOM_COLORS (line 40) | const DEFAULT_CUSTOM_COLORS: CustomThemeColors = {

FILE: src/hooks/useAnalytics.ts
  constant TAB_SCREEN_NAMES (line 6) | const TAB_SCREEN_NAMES: Record<string, string> = {
  type UseAnalyticsReturn (line 20) | interface UseAnalyticsReturn {
  function useAnalytics (line 27) | function useAnalytics(): UseAnalyticsReturn {
  function useTrackEvent (line 45) | function useTrackEvent() {
  function usePageView (line 356) | function usePageView(pageName: string, properties?: Record<string, any>) {
  function useAppLifecycle (line 370) | function useAppLifecycle() {
  function useComponentMetrics (line 387) | function useComponentMetrics(componentName: string) {
  function useInteractionTracking (line 409) | function useInteractionTracking(interactionType: string) {
  function useScreenTracking (line 419) | function useScreenTracking(tabType?: string, tabId?: string) {
  function useFeatureExperiment (line 436) | function useFeatureExperiment(featureName: string, variant: string) {
  function usePathTracking (line 459) | function usePathTracking(pathname: string) {
  function useFeatureAdoptionTracking (line 475) | function useFeatureAdoptionTracking(featureName: string) {
  function useWorkflowTracking (line 510) | function useWorkflowTracking(workflowType: string) {
  function useAIInteractionTracking (line 580) | function useAIInteractionTracking(model: string) {
  function useNetworkPerformanceTracking (line 629) | function useNetworkPerformanceTracking() {

FILE: src/hooks/useApiCall.ts
  type ApiCallOptions (line 3) | interface ApiCallOptions {
  type ApiCallState (line 12) | interface ApiCallState<T> {
  function useApiCall (line 24) | function useApiCall<T>(

FILE: src/hooks/useDebounce.ts
  function useDebounce (line 7) | function useDebounce<T>(value: T, delay: number): T {
  function useDebouncedCallback (line 27) | function useDebouncedCallback<T extends (...args: any[]) => any>(

FILE: src/hooks/useLoadingState.ts
  type LoadingState (line 3) | interface LoadingState<T> {
  function useLoadingState (line 15) | function useLoadingState<T>(

FILE: src/hooks/usePagination.ts
  type PaginationOptions (line 3) | interface PaginationOptions {
  type PaginationResult (line 9) | interface PaginationResult<T> {
  function usePagination (line 28) | function usePagination<T>(

FILE: src/hooks/usePerformanceMonitor.ts
  type PerformanceThresholds (line 4) | interface PerformanceThresholds {
  constant DEFAULT_THRESHOLDS (line 9) | const DEFAULT_THRESHOLDS: PerformanceThresholds = {
  function usePerformanceMonitor (line 17) | function usePerformanceMonitor(
  function useAsyncPerformanceTracker (line 87) | function useAsyncPerformanceTracker(operationName: string) {

FILE: src/hooks/useTabState.ts
  type UseTabStateReturn (line 5) | interface UseTabStateReturn {

FILE: src/lib/analytics/consent.ts
  constant ANALYTICS_STORAGE_KEY (line 3) | const ANALYTICS_STORAGE_KEY = 'opcode-analytics-settings';
  class ConsentManager (line 5) | class ConsentManager {
    method constructor (line 9) | private constructor() {}
    method getInstance (line 11) | static getInstance(): ConsentManager {
    method initialize (line 18) | async initialize(): Promise<AnalyticsSettings> {
    method grantConsent (line 57) | async grantConsent(): Promise<void> {
    method revokeConsent (line 69) | async revokeConsent(): Promise<void> {
    method deleteAllData (line 79) | async deleteAllData(): Promise<void> {
    method getSettings (line 94) | getSettings(): AnalyticsSettings | null {
    method hasConsented (line 98) | hasConsented(): boolean {
    method isEnabled (line 102) | isEnabled(): boolean {
    method getUserId (line 106) | getUserId(): string {
    method getSessionId (line 110) | getSessionId(): string {
    method saveSettings (line 114) | private async saveSettings(): Promise<void> {
    method generateAnonymousId (line 124) | private generateAnonymousId(): string {
    method generateSessionId (line 133) | private generateSessionId(): string {

FILE: src/lib/analytics/events.ts
  constant ANALYTICS_EVENTS (line 48) | const ANALYTICS_EVENTS = {

FILE: src/lib/analytics/index.ts
  class AnalyticsService (line 16) | class AnalyticsService {
    method constructor (line 25) | private constructor() {
    method getInstance (line 39) | static getInstance(): AnalyticsService {
    method initialize (line 46) | async initialize(): Promise<void> {
    method initializePostHog (line 67) | private initializePostHog(settings: AnalyticsSettings): void {
    method enable (line 107) | async enable(): Promise<void> {
    method disable (line 115) | async disable(): Promise<void> {
    method deleteAllData (line 122) | async deleteAllData(): Promise<void> {
    method setScreen (line 129) | setScreen(screenName: string): void {
    method track (line 140) | track(eventName: EventName | string, properties?: Record<string, any>)...
    method identify (line 174) | identify(traits?: Record<string, any>): void {
    method sanitizeProperties (line 190) | private sanitizeProperties(properties: Record<string, any>): Record<st...
    method flushEvents (line 225) | private flushEvents(): void {
    method startFlushInterval (line 243) | private startFlushInterval(): void {
    method shutdown (line 252) | shutdown(): void {
    method isEnabled (line 263) | isEnabled(): boolean {
    method hasConsented (line 267) | hasConsented(): boolean {
    method getSettings (line 271) | getSettings(): AnalyticsSettings | null {
  class PerformanceTracker (line 285) | class PerformanceTracker {
    method recordMetric (line 292) | static recordMetric(operation: string, duration: number): void {
    method getStats (line 328) | static getStats(operation: string): { p50: number; p95: number; p99: n...
    method clear (line 344) | static clear(operation?: string): void {

FILE: src/lib/analytics/resourceMonitor.ts
  class ResourceMonitor (line 8) | class ResourceMonitor {
    method constructor (line 19) | private constructor() {}
    method getInstance (line 21) | static getInstance(): ResourceMonitor {
    method startMonitoring (line 32) | startMonitoring(intervalMs: number = 60000): void {
    method stopMonitoring (line 55) | stopMonitoring(): void {
    method collectResourceMetrics (line 67) | private collectResourceMetrics(): ResourceUsageProperties {
    method collectAndReportMetrics (line 92) | private collectAndReportMetrics(): void {
    method getMemoryUsage (line 121) | private getMemoryUsage(): number {
    method getCPUUsage (line 133) | private getCPUUsage(): number | null {
    method getNetworkRequestsCount (line 142) | private getNetworkRequestsCount(): number {
    method getActiveConnections (line 157) | private getActiveConnections(): number {
    method getCacheHitRate (line 166) | private getCacheHitRate(): number | null {
    method setThresholds (line 174) | setThresholds(thresholds: Partial<typeof ResourceMonitor.prototype.hig...
    method getThresholds (line 184) | getThresholds(): typeof ResourceMonitor.prototype.highUsageThresholds {
    method collectOnce (line 191) | collectOnce(): ResourceUsageProperties {

FILE: src/lib/analytics/types.ts
  type AnalyticsEvent (line 1) | interface AnalyticsEvent {
  type AnalyticsSettings (line 15) | interface AnalyticsSettings {
  type AnalyticsConfig (line 23) | interface AnalyticsConfig {
  type EventName (line 33) | type EventName =
  type FeatureUsageProperties (line 104) | interface FeatureUsageProperties {
  type ErrorProperties (line 110) | interface ErrorProperties {
  type SessionProperties (line 117) | interface SessionProperties {
  type ModelProperties (line 124) | interface ModelProperties {
  type AgentProperties (line 130) | interface AgentProperties {
  type MCPProperties (line 137) | interface MCPProperties {
  type SlashCommandProperties (line 143) | interface SlashCommandProperties {
  type PerformanceMetrics (line 148) | interface PerformanceMetrics {
  type PromptSubmittedProperties (line 156) | interface PromptSubmittedProperties {
  type SessionStoppedProperties (line 164) | interface SessionStoppedProperties {
  type EnhancedSessionStoppedProperties (line 171) | interface EnhancedSessionStoppedProperties extends SessionStoppedPropert...
  type CheckpointCreatedProperties (line 208) | interface CheckpointCreatedProperties {
  type CheckpointRestoredProperties (line 213) | interface CheckpointRestoredProperties {
  type ToolExecutedProperties (line 218) | interface ToolExecutedProperties {
  type AgentStartedProperties (line 226) | interface AgentStartedProperties {
  type AgentProgressProperties (line 232) | interface AgentProgressProperties {
  type AgentErrorProperties (line 239) | interface AgentErrorProperties {
  type MCPServerAddedProperties (line 247) | interface MCPServerAddedProperties {
  type MCPServerRemovedProperties (line 252) | interface MCPServerRemovedProperties {
  type MCPToolInvokedProperties (line 257) | interface MCPToolInvokedProperties {
  type MCPConnectionErrorProperties (line 263) | interface MCPConnectionErrorProperties {
  type SlashCommandSelectedProperties (line 270) | interface SlashCommandSelectedProperties {
  type SlashCommandExecutedProperties (line 275) | interface SlashCommandExecutedProperties {
  type SlashCommandCreatedProperties (line 281) | interface SlashCommandCreatedProperties {
  type APIErrorProperties (line 287) | interface APIErrorProperties {
  type UIErrorProperties (line 294) | interface UIErrorProperties {
  type PerformanceBottleneckProperties (line 300) | interface PerformanceBottleneckProperties {
  type MemoryWarningProperties (line 307) | interface MemoryWarningProperties {
  type UserJourneyProperties (line 315) | interface UserJourneyProperties {
  type EnhancedPromptSubmittedProperties (line 322) | interface EnhancedPromptSubmittedProperties extends PromptSubmittedPrope...
  type EnhancedToolExecutedProperties (line 331) | interface EnhancedToolExecutedProperties extends ToolExecutedProperties {
  type EnhancedErrorProperties (line 340) | interface EnhancedErrorProperties extends ErrorProperties {
  type SessionEngagementProperties (line 349) | interface SessionEngagementProperties {
  type FeatureDiscoveryProperties (line 358) | interface FeatureDiscoveryProperties {
  type OutputQualityProperties (line 366) | interface OutputQualityProperties {
  type ResourceUsageProperties (line 374) | interface ResourceUsageProperties {
  type FeatureAdoptionProperties (line 384) | interface FeatureAdoptionProperties {
  type FeatureCombinationProperties (line 393) | interface FeatureCombinationProperties {
  type AIInteractionProperties (line 401) | interface AIInteractionProperties {
  type PromptPatternProperties (line 411) | interface PromptPatternProperties {
  type WorkflowProperties (line 419) | interface WorkflowProperties {
  type NetworkPerformanceProperties (line 430) | interface NetworkPerformanceProperties {
  type SuggestionProperties (line 440) | interface SuggestionProperties {

FILE: src/lib/api-tracker.ts
  constant PERFORMANCE_THRESHOLDS (line 5) | const PERFORMANCE_THRESHOLDS = {
  constant MEMORY_WARNING_THRESHOLD (line 13) | const MEMORY_WARNING_THRESHOLD = 100;
  function wrapApiMethod (line 18) | function wrapApiMethod<T extends (...args: any[]) => Promise<any>>(
  function createTrackedApi (line 96) | function createTrackedApi() {

FILE: src/lib/api.ts
  type ProcessType (line 5) | type ProcessType =
  type ProcessInfo (line 10) | interface ProcessInfo {
  type Project (line 23) | interface Project {
  type Session (line 39) | interface Session {
  type ClaudeSettings (line 59) | interface ClaudeSettings {
  type ClaudeVersionStatus (line 66) | interface ClaudeVersionStatus {
  type ClaudeMdFile (line 78) | interface ClaudeMdFile {
  type FileEntry (line 92) | interface FileEntry {
  type ClaudeInstallation (line 103) | interface ClaudeInstallation {
  type Agent (line 115) | interface Agent {
  type AgentExport (line 127) | interface AgentExport {
  type GitHubAgentFile (line 140) | interface GitHubAgentFile {
  type AgentRun (line 148) | interface AgentRun {
  type AgentRunMetrics (line 164) | interface AgentRunMetrics {
  type AgentRunWithMetrics (line 171) | interface AgentRunWithMetrics {
  type UsageEntry (line 192) | interface UsageEntry {
  type ModelUsage (line 203) | interface ModelUsage {
  type DailyUsage (line 214) | interface DailyUsage {
  type ProjectUsage (line 221) | interface ProjectUsage {
  type UsageStats (line 230) | interface UsageStats {
  type Checkpoint (line 246) | interface Checkpoint {
  type CheckpointMetadata (line 260) | interface CheckpointMetadata {
  type FileSnapshot (line 271) | interface FileSnapshot {
  type TimelineNode (line 284) | interface TimelineNode {
  type SessionTimeline (line 293) | interface SessionTimeline {
  type CheckpointStrategy (line 305) | type CheckpointStrategy = 'manual' | 'per_prompt' | 'per_tool_use' | 'sm...
  type CheckpointResult (line 310) | interface CheckpointResult {
  type CheckpointDiff (line 319) | interface CheckpointDiff {
  type FileDiff (line 331) | interface FileDiff {
  type MCPServer (line 341) | interface MCPServer {
  type ServerStatus (line 365) | interface ServerStatus {
  type MCPProjectConfig (line 377) | interface MCPProjectConfig {
  type MCPServerConfig (line 384) | interface MCPServerConfig {
  type SlashCommand (line 393) | interface SlashCommand {
  type AddServerResult (line 423) | interface AddServerResult {
  type ImportResult (line 432) | interface ImportResult {
  type ImportServerResult (line 441) | interface ImportServerResult {
  method getHomeDirectory (line 455) | async getHomeDirectory(): Promise<string> {
  method listProjects (line 468) | async listProjects(): Promise<Project[]> {
  method createProject (line 482) | async createProject(path: string): Promise<Project> {
  method getProjectSessions (line 496) | async getProjectSessions(projectId: string): Promise<Session[]> {
  method fetchGitHubAgents (line 509) | async fetchGitHubAgents(): Promise<GitHubAgentFile[]> {
  method fetchGitHubAgentContent (line 523) | async fetchGitHubAgentContent(downloadUrl: string): Promise<AgentExport> {
  method importAgentFromGitHub (line 537) | async importAgentFromGitHub(downloadUrl: string): Promise<Agent> {
  method getClaudeSettings (line 550) | async getClaudeSettings(): Promise<ClaudeSettings> {
  method openNewSession (line 574) | async openNewSession(path?: string): Promise<string> {
  method getSystemPrompt (line 587) | async getSystemPrompt(): Promise<string> {
  method checkClaudeVersion (line 600) | async checkClaudeVersion(): Promise<ClaudeVersionStatus> {
  method saveSystemPrompt (line 614) | async saveSystemPrompt(content: string): Promise<string> {
  method saveClaudeSettings (line 628) | async saveClaudeSettings(settings: ClaudeSettings): Promise<string> {
  method findClaudeMdFiles (line 642) | async findClaudeMdFiles(projectPath: string): Promise<ClaudeMdFile[]> {
  method readClaudeMdFile (line 656) | async readClaudeMdFile(filePath: string): Promise<string> {
  method saveClaudeMdFile (line 671) | async saveClaudeMdFile(filePath: string, content: string): Promise<strin...
  method listAgents (line 686) | async listAgents(): Promise<Agent[]> {
  method createAgent (line 705) | async createAgent(
  method updateAgent (line 739) | async updateAgent(
  method deleteAgent (line 769) | async deleteAgent(id: number): Promise<void> {
  method getAgent (line 783) | async getAgent(id: number): Promise<Agent> {
  method exportAgent (line 797) | async exportAgent(id: number): Promise<string> {
  method importAgent (line 811) | async importAgent(jsonData: string): Promise<Agent> {
  method importAgentFromFile (line 825) | async importAgentFromFile(filePath: string): Promise<Agent> {
  method executeAgent (line 842) | async executeAgent(agentId: number, projectPath: string, task: string, m...
  method listAgentRuns (line 857) | async listAgentRuns(agentId?: number): Promise<AgentRunWithMetrics[]> {
  method listAgentRunsWithMetrics (line 872) | async listAgentRunsWithMetrics(agentId?: number): Promise<AgentRunWithMe...
  method getAgentRun (line 887) | async getAgentRun(id: number): Promise<AgentRunWithMetrics> {
  method getAgentRunWithRealTimeMetrics (line 901) | async getAgentRunWithRealTimeMetrics(id: number): Promise<AgentRunWithMe...
  method listRunningAgentSessions (line 914) | async listRunningAgentSessions(): Promise<AgentRun[]> {
  method killAgentSession (line 928) | async killAgentSession(runId: number): Promise<boolean> {
  method getSessionStatus (line 942) | async getSessionStatus(runId: number): Promise<string | null> {
  method cleanupFinishedProcesses (line 955) | async cleanupFinishedProcesses(): Promise<number[]> {
  method getSessionOutput (line 969) | async getSessionOutput(runId: number): Promise<string> {
  method getLiveSessionOutput (line 983) | async getLiveSessionOutput(runId: number): Promise<string> {
  method streamSessionOutput (line 997) | async streamSessionOutput(runId: number): Promise<void> {
  method loadSessionHistory (line 1009) | async loadSessionHistory(sessionId: string, projectId: string): Promise<...
  method loadAgentSessionHistory (line 1019) | async loadAgentSessionHistory(sessionId: string): Promise<any[]> {
  method executeClaudeCode (line 1031) | async executeClaudeCode(projectPath: string, prompt: string, model: stri...
  method continueClaudeCode (line 1038) | async continueClaudeCode(projectPath: string, prompt: string, model: str...
  method resumeClaudeCode (line 1045) | async resumeClaudeCode(projectPath: string, sessionId: string, prompt: s...
  method cancelClaudeExecution (line 1053) | async cancelClaudeExecution(sessionId?: string): Promise<void> {
  method listRunningClaudeSessions (line 1061) | async listRunningClaudeSessions(): Promise<any[]> {
  method getClaudeSessionOutput (line 1070) | async getClaudeSessionOutput(sessionId: string): Promise<string> {
  method listDirectoryContents (line 1077) | async listDirectoryContents(directoryPath: string): Promise<FileEntry[]> {
  method searchFiles (line 1084) | async searchFiles(basePath: string, query: string): Promise<FileEntry[]> {
  method getUsageStats (line 1092) | async getUsageStats(): Promise<UsageStats> {
  method getUsageByDateRange (line 1107) | async getUsageByDateRange(startDate: string, endDate: string): Promise<U...
  method getSessionStats (line 1123) | async getSessionStats(
  method getUsageDetails (line 1145) | async getUsageDetails(limit?: number): Promise<UsageEntry[]> {
  method createCheckpoint (line 1157) | async createCheckpoint(
  method restoreCheckpoint (line 1176) | async restoreCheckpoint(
  method listCheckpoints (line 1193) | async listCheckpoints(
  method forkFromCheckpoint (line 1208) | async forkFromCheckpoint(
  method getSessionTimeline (line 1229) | async getSessionTimeline(
  method updateCheckpointSettings (line 1244) | async updateCheckpointSettings(
  method getCheckpointDiff (line 1263) | async getCheckpointDiff(
  method trackCheckpointMessage (line 1285) | async trackCheckpointMessage(
  method checkAutoCheckpoint (line 1307) | async checkAutoCheckpoint(
  method cleanupOldCheckpoints (line 1329) | async cleanupOldCheckpoints(
  method getCheckpointSettings (line 1351) | async getCheckpointSettings(
  method clearCheckpointManager (line 1376) | async clearCheckpointManager(sessionId: string): Promise<void> {
  method mcpAdd (line 1399) | async mcpAdd(
  method mcpList (line 1427) | async mcpList(): Promise<MCPServer[]> {
  method mcpGet (line 1442) | async mcpGet(name: string): Promise<MCPServer> {
  method mcpRemove (line 1454) | async mcpRemove(name: string): Promise<string> {
  method mcpAddJson (line 1466) | async mcpAddJson(name: string, jsonConfig: string, scope: string = "loca...
  method mcpAddFromClaudeDesktop (line 1478) | async mcpAddFromClaudeDesktop(scope: string = "local"): Promise<ImportRe...
  method mcpServe (line 1490) | async mcpServe(): Promise<string> {
  method mcpTestConnection (line 1502) | async mcpTestConnection(name: string): Promise<string> {
  method mcpResetProjectChoices (line 1514) | async mcpResetProjectChoices(): Promise<string> {
  method mcpGetServerStatus (line 1526) | async mcpGetServerStatus(): Promise<Record<string, ServerStatus>> {
  method mcpReadProjectConfig (line 1538) | async mcpReadProjectConfig(projectPath: string): Promise<MCPProjectConfi...
  method mcpSaveProjectConfig (line 1550) | async mcpSaveProjectConfig(projectPath: string, config: MCPProjectConfig...
  method getClaudeBinaryPath (line 1563) | async getClaudeBinaryPath(): Promise<string | null> {
  method setClaudeBinaryPath (line 1577) | async setClaudeBinaryPath(path: string): Promise<void> {
  method listClaudeInstallations (line 1590) | async listClaudeInstallations(): Promise<ClaudeInstallation[]> {
  method storageListTables (line 1605) | async storageListTables(): Promise<any[]> {
  method storageReadTable (line 1622) | async storageReadTable(
  method storageUpdateRow (line 1648) | async storageUpdateRow(
  method storageDeleteRow (line 1671) | async storageDeleteRow(
  method storageInsertRow (line 1692) | async storageInsertRow(
  method storageExecuteSql (line 1712) | async storageExecuteSql(query: string): Promise<any> {
  method storageResetDatabase (line 1725) | async storageResetDatabase(): Promise<void> {
  method getSetting (line 1741) | async getSetting(key: string): Promise<string | null> {
  method saveSetting (line 1766) | async saveSetting(key: string, value: string): Promise<void> {
  method getHooksConfig (line 1799) | async getHooksConfig(scope: 'user' | 'project' | 'local', projectPath?: ...
  method updateHooksConfig (line 1815) | async updateHooksConfig(
  method validateHookCommand (line 1833) | async validateHookCommand(command: string): Promise<{ valid: boolean; me...
  method getMergedHooksConfig (line 1847) | async getMergedHooksConfig(projectPath: string): Promise<HooksConfigurat...
  method slashCommandsList (line 1871) | async slashCommandsList(projectPath?: string): Promise<SlashCommand[]> {
  method slashCommandGet (line 1885) | async slashCommandGet(commandId: string): Promise<SlashCommand> {
  method slashCommandSave (line 1905) | async slashCommandSave(
  method slashCommandDelete (line 1936) | async slashCommandDelete(commandId: string, projectPath?: string): Promi...

FILE: src/lib/apiAdapter.ts
  type Window (line 14) | interface Window {
  function detectEnvironment (line 27) | function detectEnvironment(): boolean {
  type ApiResponse (line 56) | interface ApiResponse<T> {
  function restApiCall (line 65) | async function restApiCall<T>(endpoint: string, params?: any): Promise<T> {
  function apiCall (line 134) | async function apiCall<T>(command: string, params?: any): Promise<T> {
  function mapCommandToEndpoint (line 165) | function mapCommandToEndpoint(command: string, _params?: any): string {
  function getEnvironmentInfo (line 274) | function getEnvironmentInfo() {
  function handleStreamingCommand (line 285) | async function handleStreamingCommand<T>(command: string, params?: any):...
  function initializeWebMode (line 417) | function initializeWebMode() {

FILE: src/lib/date-utils.ts
  function formatUnixTimestamp (line 9) | function formatUnixTimestamp(timestamp: number): string {
  function formatISOTimestamp (line 52) | function formatISOTimestamp(isoString: string): string {
  function truncateText (line 63) | function truncateText(text: string, maxLength: number): string {
  function getFirstLine (line 73) | function getFirstLine(text: string): string {
  function formatTime (line 79) | function formatTime(date: Date): string {
  function isToday (line 87) | function isToday(date: Date): boolean {
  function isYesterday (line 92) | function isYesterday(date: Date): boolean {
  function isWithinWeek (line 98) | function isWithinWeek(date: Date): boolean {
  function getDayName (line 104) | function getDayName(date: Date): string {
  function formatTimeAgo (line 117) | function formatTimeAgo(timestamp: number): string {

FILE: src/lib/hooksManager.ts
  class HooksManager (line 14) | class HooksManager {
    method mergeConfigs (line 19) | static mergeConfigs(
    method mergeMatchers (line 83) | private static mergeMatchers(
    method validateConfig (line 109) | static async validateConfig(hooks: HooksConfiguration): Promise<HookVa...
    method checkDangerousPatterns (line 196) | public static checkDangerousPatterns(command: string): string[] {
    method escapeCommand (line 234) | static escapeCommand(command: string): string {
    method generateId (line 246) | static generateId(): string {

FILE: src/lib/linkDetector.tsx
  constant URL_REGEX (line 13) | const URL_REGEX = /(?:https?:\/\/)?(?:localhost|127\.0\.0\.1|0\.0\.0\.0|...
  constant LOCALHOST_REGEX (line 16) | const LOCALHOST_REGEX = /(?:https?:\/\/)?(?:localhost|127\.0\.0\.1|0\.0\...
  type DetectedLink (line 18) | interface DetectedLink {
  function detectLinks (line 31) | function detectLinks(text: string): DetectedLink[] {
  function hasLinks (line 79) | function hasLinks(text: string): boolean {
  function getFirstLink (line 89) | function getFirstLink(text: string): DetectedLink | null {
  function makeLinksClickable (line 100) | function makeLinksClickable(

FILE: src/lib/outputCache.tsx
  type ClaudeStreamMessage (line 5) | interface ClaudeStreamMessage {
  type CachedSessionOutput (line 22) | interface CachedSessionOutput {
  type OutputCacheContextType (line 29) | interface OutputCacheContextType {
  function useOutputCache (line 41) | function useOutputCache() {
  type OutputCacheProviderProps (line 49) | interface OutputCacheProviderProps {
  function OutputCacheProvider (line 53) | function OutputCacheProvider({ children }: OutputCacheProviderProps) {

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

FILE: src/services/sessionPersistence.ts
  constant STORAGE_KEY_PREFIX (line 8) | const STORAGE_KEY_PREFIX = 'opcode_session_';
  constant SESSION_INDEX_KEY (line 9) | const SESSION_INDEX_KEY = 'opcode_session_index';
  type SessionRestoreData (line 11) | interface SessionRestoreData {
  class SessionPersistenceService (line 20) | class SessionPersistenceService {
    method saveSession (line 24) | static saveSession(sessionId: string, projectId: string, projectPath: ...
    method loadSession (line 52) | static loadSession(sessionId: string): SessionRestoreData | null {
    method removeSession (line 74) | static removeSession(sessionId: string): void {
    method getSessionIndex (line 91) | static getSessionIndex(): string[] {
    method clearAllSessions (line 104) | static clearAllSessions(): void {
    method cleanupOldSessions (line 123) | static cleanupOldSessions(): void {
    method isSessionRestorable (line 147) | static async isSessionRestorable(sessionId: string, projectId: string)...
    method createSessionFromRestoreData (line 165) | static createSessionFromRestoreData(data: SessionRestoreData): Session {

FILE: src/services/tabPersistence.ts
  constant STORAGE_KEY (line 8) | const STORAGE_KEY = 'opcode_tabs_v2';
  constant ACTIVE_TAB_KEY (line 9) | const ACTIVE_TAB_KEY = 'opcode_active_tab_v2';
  constant PERSISTENCE_ENABLED_KEY (line 10) | const PERSISTENCE_ENABLED_KEY = 'opcode_tab_persistence_enabled';
  type SerializedTab (line 12) | interface SerializedTab {
  class TabPersistenceService (line 30) | class TabPersistenceService {
    method isEnabled (line 34) | static isEnabled(): boolean {
    method setEnabled (line 43) | static setEnabled(enabled: boolean): void {
    method saveTabs (line 53) | static saveTabs(tabs: Tab[], activeTabId: string | null): void {
    method loadTabs (line 101) | static loadTabs(): { tabs: Tab[], activeTabId: string | null } {
    method clearTabs (line 177) | static clearTabs(): void {
    method migrateFromOldFormat (line 185) | static migrateFromOldFormat(): void {

FILE: src/stores/agentStore.ts
  type AgentState (line 7) | interface AgentState {

FILE: src/stores/sessionStore.ts
  type SessionState (line 7) | interface SessionState {

FILE: src/types/hooks.ts
  type HookCommand (line 5) | interface HookCommand {
  type HookMatcher (line 11) | interface HookMatcher {
  type HooksConfiguration (line 16) | interface HooksConfiguration {
  type HookEvent (line 24) | type HookEvent = keyof HooksConfiguration;
  type ClaudeSettingsWithHooks (line 26) | interface ClaudeSettingsWithHooks {
  type HookValidationError (line 31) | interface HookValidationError {
  type HookValidationWarning (line 38) | interface HookValidationWarning {
  type HookValidationResult (line 45) | interface HookValidationResult {
  type HookScope (line 51) | type HookScope = 'user' | 'project' | 'local';
  constant COMMON_TOOL_MATCHERS (line 54) | const COMMON_TOOL_MATCHERS = [
  type HookTemplate (line 74) | interface HookTemplate {
  constant HOOK_TEMPLATES (line 83) | const HOOK_TEMPLATES: HookTemplate[] = [
Condensed preview — 179 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,992K chars).
[
  {
    "path": ".cargo/config.toml",
    "chars": 104,
    "preview": "[target.aarch64-unknown-linux-gnu]\nlinker = \"aarch64-linux-gnu-gcc\"\n\n[env]\nPKG_CONFIG_ALLOW_CROSS = \"1\"\n"
  },
  {
    "path": ".github/workflows/build-linux.yml",
    "chars": 1608,
    "preview": "name: Build Linux\n\non:\n  workflow_call:\n  workflow_dispatch:\n  push:\n    branches: [main]\n\njobs:\n  build:\n    name: Buil"
  },
  {
    "path": ".github/workflows/build-macos.yml",
    "chars": 10246,
    "preview": "name: Build macOS\n\non:\n  workflow_call:\n    secrets:\n      APPLE_CERTIFICATE:\n        required: true\n      APPLE_CERTIFI"
  },
  {
    "path": ".github/workflows/build-test.yml",
    "chars": 5608,
    "preview": "name: Build Test\n\n# Trigger on every push and pull request\non:\n  push:\n    branches: [ main, develop, 'release/**', 'fea"
  },
  {
    "path": ".github/workflows/claude-code-review.yml",
    "chars": 1952,
    "preview": "name: Claude Code Review\n\non:\n  pull_request:\n    types: [opened, synchronize]\n    # Optional: Only run on specific file"
  },
  {
    "path": ".github/workflows/claude.yml",
    "chars": 1886,
    "preview": "name: Claude Code\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  issue"
  },
  {
    "path": ".github/workflows/pr-check.yml",
    "chars": 1248,
    "preview": "name: PR Checks (bun run check)\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n\npermi"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 4161,
    "preview": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n  workflow_dispatch:\n    inputs:\n      version:\n        description: '"
  },
  {
    "path": ".gitignore",
    "chars": 420,
    "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": "CONTRIBUTING.md",
    "chars": 2912,
    "preview": "# Welcome Contributors\n\nWe welcome contributions to enhance opcode's capabilities and improve its performance. To report"
  },
  {
    "path": "LICENSE",
    "chars": 34523,
    "preview": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C)"
  },
  {
    "path": "README.md",
    "chars": 12690,
    "preview": "\n<div align=\"center\">\n  <img src=\"src-tauri/icons/icon.png\" alt=\"opcode Logo\" width=\"120\" height=\"120\">\n\n  <h1>opcode</h"
  },
  {
    "path": "cc_agents/README.md",
    "chars": 5367,
    "preview": "# 🤖 opcode CC Agents\n\n<div align=\"center\">\n  <p>\n    <strong>Pre-built AI agents for opcode powered by Claude Code</stro"
  },
  {
    "path": "cc_agents/git-commit-bot.opcode.json",
    "chars": 2503,
    "preview": "{\n  \"agent\": {\n    \"default_task\": \"Push all changes.\",\n    \"icon\": \"bot\",\n    \"model\": \"sonnet\",\n    \"name\": \"Git Commi"
  },
  {
    "path": "cc_agents/security-scanner.opcode.json",
    "chars": 11191,
    "preview": "{\n  \"agent\": {\n    \"default_task\": \"Review the codebase for security issues.\",\n    \"icon\": \"shield\",\n    \"model\": \"opus\""
  },
  {
    "path": "cc_agents/unit-tests-bot.opcode.json",
    "chars": 6817,
    "preview": "{\n  \"agent\": {\n    \"default_task\": \"Generate unit tests for this codebase.\",\n    \"icon\": \"code\",\n    \"model\": \"opus\",\n  "
  },
  {
    "path": "index.html",
    "chars": 385,
    "preview": "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"w"
  },
  {
    "path": "justfile",
    "chars": 2387,
    "preview": "# Opcode - NixOS Build & Development Commands\n\n# Show available commands\ndefault:\n    @just --list\n\n# Enter the Nix deve"
  },
  {
    "path": "package.json",
    "chars": 2821,
    "preview": "{\n  \"name\": \"opcode\",\n  \"private\": true,\n  \"version\": \"0.2.1\",\n  \"license\": \"AGPL-3.0\",\n  \"type\": \"module\",\n  \"scripts\":"
  },
  {
    "path": "scripts/bump-version.sh",
    "chars": 1112,
    "preview": "#!/bin/bash\n\n# Script to bump version across all files\n# Usage: ./scripts/bump-version.sh 1.0.0\n\nset -e\n\nif [ -z \"$1\" ];"
  },
  {
    "path": "shell.nix",
    "chars": 624,
    "preview": "{ pkgs ? import <nixpkgs> {} }:\n\npkgs.mkShell {\n  buildInputs = with pkgs; [\n    # Core development tools\n    just\n    g"
  },
  {
    "path": "src/App.tsx",
    "chars": 18350,
    "preview": "import { useState, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Bot, FolderCode } from \"lu"
  },
  {
    "path": "src/assets/shimmer.css",
    "chars": 4091,
    "preview": "/**\n * Shimmer animation styles\n * Provides a sword-like shimmer effect for elements\n */\n\n@keyframes shimmer {\n  0% {\n  "
  },
  {
    "path": "src/components/AgentExecution.tsx",
    "chars": 37681,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimp"
  },
  {
    "path": "src/components/AgentExecutionDemo.tsx",
    "chars": 4350,
    "preview": "import React from \"react\";\nimport { StreamMessage } from \"./StreamMessage\";\nimport type { ClaudeStreamMessage } from \"./"
  },
  {
    "path": "src/components/AgentRunOutputViewer.tsx",
    "chars": 30922,
    "preview": "import { useState, useEffect, useRef, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\ni"
  },
  {
    "path": "src/components/AgentRunView.tsx",
    "chars": 12841,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { \n  ArrowLeft, \n  Co"
  },
  {
    "path": "src/components/AgentRunsList.tsx",
    "chars": 6931,
    "preview": "import React, { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Play, Clock, "
  },
  {
    "path": "src/components/Agents.tsx",
    "chars": 18535,
    "preview": "import React, { useState, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Bo"
  },
  {
    "path": "src/components/AgentsModal.tsx",
    "chars": 17113,
    "preview": "import React, { useState, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Bo"
  },
  {
    "path": "src/components/AnalyticsConsent.tsx",
    "chars": 8381,
    "preview": "import React, { useState, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Ba"
  },
  {
    "path": "src/components/AnalyticsErrorBoundary.tsx",
    "chars": 2461,
    "preview": "import React, { Component, ErrorInfo, ReactNode } from 'react';\nimport { eventBuilders, analytics } from '@/lib/analytic"
  },
  {
    "path": "src/components/App.cleaned.tsx",
    "chars": 5806,
    "preview": "import { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { OutputCac"
  },
  {
    "path": "src/components/CCAgents.tsx",
    "chars": 20291,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { \n "
  },
  {
    "path": "src/components/CheckpointSettings.tsx",
    "chars": 10436,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { \n  Wrench,\n  Save,\n"
  },
  {
    "path": "src/components/ClaudeBinaryDialog.tsx",
    "chars": 5375,
    "preview": "import { useState, useEffect } from \"react\";\nimport { api, type ClaudeInstallation } from \"@/lib/api\";\nimport { Button }"
  },
  {
    "path": "src/components/ClaudeCodeSession.refactored.tsx",
    "chars": 14996,
    "preview": "import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport { motion } from \"framer-motion\";\nimport "
  },
  {
    "path": "src/components/ClaudeCodeSession.tsx",
    "chars": 68726,
    "preview": "import React, { useState, useEffect, useRef, useMemo } from \"react\";\nimport { motion, AnimatePresence } from \"framer-mot"
  },
  {
    "path": "src/components/ClaudeFileEditor.tsx",
    "chars": 5392,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport MDEditor from \"@uiw/react-md-editor\";\nimport { motion } from "
  },
  {
    "path": "src/components/ClaudeMemoriesDropdown.tsx",
    "chars": 5795,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Ch"
  },
  {
    "path": "src/components/ClaudeVersionSelector.tsx",
    "chars": 14580,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Select, Se"
  },
  {
    "path": "src/components/CreateAgent.tsx",
    "chars": 13096,
    "preview": "import React, { useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { ArrowLeft, Save, Loader2, Chev"
  },
  {
    "path": "src/components/CustomTitlebar.tsx",
    "chars": 9178,
    "preview": "import React, { useState, useRef, useEffect } from 'react';\nimport { motion } from 'framer-motion';\nimport { Settings, M"
  },
  {
    "path": "src/components/ErrorBoundary.tsx",
    "chars": 2828,
    "preview": "import React, { Component, ReactNode } from \"react\";\nimport { AlertCircle } from \"lucide-react\";\nimport { Button } from "
  },
  {
    "path": "src/components/ExecutionControlBar.tsx",
    "chars": 2951,
    "preview": "import React from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { StopCircle, Clock, Hash } f"
  },
  {
    "path": "src/components/FilePicker.optimized.tsx",
    "chars": 12377,
    "preview": "import React, { useState, useEffect, useRef, useCallback, useMemo } from \"react\";\nimport { motion } from \"framer-motion\""
  },
  {
    "path": "src/components/FilePicker.tsx",
    "chars": 15833,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Button } fr"
  },
  {
    "path": "src/components/FloatingPromptInput.tsx",
    "chars": 48089,
    "preview": "import React, { useState, useRef, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimp"
  },
  {
    "path": "src/components/GitHubAgentBrowser.tsx",
    "chars": 14625,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport {\n  "
  },
  {
    "path": "src/components/HooksEditor.tsx",
    "chars": 32696,
    "preview": "/**\n * HooksEditor component for managing Claude Code hooks configuration\n */\n\nimport React, { useState, useEffect } fro"
  },
  {
    "path": "src/components/IconPicker.tsx",
    "chars": 12832,
    "preview": "import React, { useState, useMemo } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport {\n  //"
  },
  {
    "path": "src/components/ImagePreview.tsx",
    "chars": 6764,
    "preview": "import React, { useState } from \"react\";\nimport { X, Maximize2 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n"
  },
  {
    "path": "src/components/MCPAddServer.tsx",
    "chars": 14296,
    "preview": "import React, { useState } from \"react\";\nimport { Plus, Terminal, Globe, Trash2, Info, Loader2 } from \"lucide-react\";\nim"
  },
  {
    "path": "src/components/MCPImportExport.tsx",
    "chars": 12529,
    "preview": "import React, { useState } from \"react\";\nimport { Download, Upload, FileText, Loader2, Info, Network, Settings2 } from \""
  },
  {
    "path": "src/components/MCPManager.tsx",
    "chars": 6390,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Al"
  },
  {
    "path": "src/components/MCPServerList.tsx",
    "chars": 14539,
    "preview": "import React, { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { \n  Network, \n"
  },
  {
    "path": "src/components/MarkdownEditor.tsx",
    "chars": 4609,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport MDEditor from \"@uiw/react-md-editor\";\nimport { motion } from "
  },
  {
    "path": "src/components/NFOCredits.tsx",
    "chars": 11307,
    "preview": "import React, { useEffect, useRef, useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimp"
  },
  {
    "path": "src/components/PreviewPromptDialog.tsx",
    "chars": 3207,
    "preview": "import React from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Globe, ExternalLink } from \"lucide-react\";\ni"
  },
  {
    "path": "src/components/ProjectList.tsx",
    "chars": 9009,
    "preview": "import React, { useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { \n  FolderOpen,\n  ChevronLeft,\n"
  },
  {
    "path": "src/components/ProjectSettings.tsx",
    "chars": 7894,
    "preview": "/**\n * ProjectSettings component for managing project-specific hooks configuration\n */\n\nimport React, { useState, useEff"
  },
  {
    "path": "src/components/ProxySettings.tsx",
    "chars": 5410,
    "preview": "import { useState, useEffect } from 'react';\nimport { invoke } from '@tauri-apps/api/core';\nimport { Input } from '@/com"
  },
  {
    "path": "src/components/RunningClaudeSessions.tsx",
    "chars": 6148,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Play, Loader2, Term"
  },
  {
    "path": "src/components/SessionList.optimized.tsx",
    "chars": 6644,
    "preview": "import React, { useMemo, useCallback } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { F"
  },
  {
    "path": "src/components/SessionList.tsx",
    "chars": 6564,
    "preview": "import React, { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Clock, Messag"
  },
  {
    "path": "src/components/SessionOutputViewer.tsx",
    "chars": 26161,
    "preview": "import { useState, useEffect, useRef, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\ni"
  },
  {
    "path": "src/components/Settings.tsx",
    "chars": 46168,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { \n "
  },
  {
    "path": "src/components/SlashCommandPicker.tsx",
    "chars": 22512,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Button } fr"
  },
  {
    "path": "src/components/SlashCommandsManager.tsx",
    "chars": 26090,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { \n "
  },
  {
    "path": "src/components/StartupIntro.tsx",
    "chars": 3806,
    "preview": "import { AnimatePresence, motion } from \"framer-motion\";\nimport opcodeLogo from \"../../src-tauri/icons/icon.png\";\nimport"
  },
  {
    "path": "src/components/StorageTab.tsx",
    "chars": 33091,
    "preview": "import React, { useState, useEffect, useCallback } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\""
  },
  {
    "path": "src/components/StreamMessage.tsx",
    "chars": 33524,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { \n  Terminal, \n  User, \n  Bot, \n  AlertCircle, \n  CheckCircl"
  },
  {
    "path": "src/components/TabContent.tsx",
    "chars": 20003,
    "preview": "import React, { Suspense, lazy, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimpor"
  },
  {
    "path": "src/components/TabManager.tsx",
    "chars": 13414,
    "preview": "import React, { useState, useRef, useEffect } from 'react';\nimport { motion, AnimatePresence, Reorder } from 'framer-mot"
  },
  {
    "path": "src/components/TimelineNavigator.tsx",
    "chars": 22242,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { \n  GitBranch, \n  Sa"
  },
  {
    "path": "src/components/TokenCounter.tsx",
    "chars": 1297,
    "preview": "import React from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Hash } from \"lucide-react\";\nimport { cn } fr"
  },
  {
    "path": "src/components/ToolWidgets.new.tsx",
    "chars": 163,
    "preview": "// This file re-exports all widgets from the widgets directory\n// It maintains backward compatibility with the original "
  },
  {
    "path": "src/components/ToolWidgets.tsx",
    "chars": 106944,
    "preview": "import React, { useState } from \"react\";\nimport { \n  CheckCircle2, \n  Circle, \n  Clock,\n  FolderOpen,\n  FileText,\n  Sear"
  },
  {
    "path": "src/components/Topbar.tsx",
    "chars": 5000,
    "preview": "import React, { useEffect, useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Circle, ExternalLin"
  },
  {
    "path": "src/components/UsageDashboard.original.tsx",
    "chars": 23058,
    "preview": "import React, { useState, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Button } from \"@/co"
  },
  {
    "path": "src/components/UsageDashboard.tsx",
    "chars": 33404,
    "preview": "import React, { useState, useEffect, useMemo, useCallback } from \"react\";\nimport { Button } from \"@/components/ui/button"
  },
  {
    "path": "src/components/WebviewPreview.tsx",
    "chars": 11979,
    "preview": "import React, { useState, useRef, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimp"
  },
  {
    "path": "src/components/claude-code-session/MessageList.tsx",
    "chars": 4999,
    "preview": "import React, { useRef, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { useV"
  },
  {
    "path": "src/components/claude-code-session/PromptQueue.tsx",
    "chars": 2819,
    "preview": "import React from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { X, Clock, Sparkles, Zap } f"
  },
  {
    "path": "src/components/claude-code-session/SessionHeader.tsx",
    "chars": 5288,
    "preview": "import React from 'react';\nimport { motion } from 'framer-motion';\nimport { \n  ArrowLeft, \n  Terminal, \n  FolderOpen, \n "
  },
  {
    "path": "src/components/claude-code-session/useCheckpoints.ts",
    "chars": 4147,
    "preview": "import { useState, useCallback } from 'react';\nimport { api } from '@/lib/api';\n\n// Local checkpoint format for UI displ"
  },
  {
    "path": "src/components/claude-code-session/useClaudeMessages.ts",
    "chars": 8165,
    "preview": "import { useState, useCallback, useRef, useEffect } from 'react';\nimport { api } from '@/lib/api';\nimport { getEnvironme"
  },
  {
    "path": "src/components/index.ts",
    "chars": 1075,
    "preview": "export * from \"./AgentExecutionDemo\";\nexport * from \"./AgentRunOutputViewer\";\nexport * from \"./StreamMessage\";\nexport * "
  },
  {
    "path": "src/components/ui/badge.tsx",
    "chars": 1060,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "src/components/ui/button.tsx",
    "chars": 1925,
    "preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@"
  },
  {
    "path": "src/components/ui/card.tsx",
    "chars": 2673,
    "preview": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Card component - A container with consistent s"
  },
  {
    "path": "src/components/ui/dialog.tsx",
    "chars": 3765,
    "preview": "import * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react"
  },
  {
    "path": "src/components/ui/dropdown-menu.tsx",
    "chars": 7295,
    "preview": "import * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { Check, Ch"
  },
  {
    "path": "src/components/ui/input.tsx",
    "chars": 1038,
    "preview": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface InputProps\n  extends React.InputHTML"
  },
  {
    "path": "src/components/ui/label.tsx",
    "chars": 618,
    "preview": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface LabelProps\n  extends React.LabelHTML"
  },
  {
    "path": "src/components/ui/pagination.tsx",
    "chars": 1603,
    "preview": "import * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { Button } from \"@/comp"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "chars": 3475,
    "preview": "import * as React from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { cn } from \"@/lib/utils"
  },
  {
    "path": "src/components/ui/radio-group.tsx",
    "chars": 1477,
    "preview": "import * as React from \"react\";\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { Circle } fr"
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "chars": 837,
    "preview": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface ScrollAreaProps extends React.HTMLAttribute"
  },
  {
    "path": "src/components/ui/select.tsx",
    "chars": 6947,
    "preview": "import * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { Check, ChevronDown, "
  },
  {
    "path": "src/components/ui/split-pane.tsx",
    "chars": 6107,
    "preview": "import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface Sp"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "chars": 1761,
    "preview": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface SwitchProps\n  extends React.InputHTM"
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "chars": 3458,
    "preview": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst TabsContext = React.createContext<{\n  value: st"
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "chars": 758,
    "preview": "import * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface TextareaProps\n  extends React.Textarea"
  },
  {
    "path": "src/components/ui/toast.tsx",
    "chars": 2650,
    "preview": "import * as React from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { X, CheckCircle, AlertC"
  },
  {
    "path": "src/components/ui/tooltip-modern.tsx",
    "chars": 2079,
    "preview": "import * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\nimport { cn } from \"@/lib/uti"
  },
  {
    "path": "src/components/ui/tooltip.tsx",
    "chars": 1129,
    "preview": "import * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\nimport { cn } from \"@/lib/uti"
  },
  {
    "path": "src/components/widgets/BashWidget.tsx",
    "chars": 2610,
    "preview": "import React from \"react\";\nimport { Terminal, ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nint"
  },
  {
    "path": "src/components/widgets/LSWidget.tsx",
    "chars": 7312,
    "preview": "import React, { useState } from \"react\";\nimport { FolderOpen, Folder, FileCode, FileText, Terminal, ChevronRight } from "
  },
  {
    "path": "src/components/widgets/TodoWidget.tsx",
    "chars": 2078,
    "preview": "import React from \"react\";\nimport { CheckCircle2, Circle, Clock, FileEdit } from \"lucide-react\";\nimport { Badge } from \""
  },
  {
    "path": "src/components/widgets/index.ts",
    "chars": 1363,
    "preview": "// Re-export all widgets from their individual files\nexport { TodoWidget } from './TodoWidget';\nexport { LSWidget } from"
  },
  {
    "path": "src/contexts/TabContext.tsx",
    "chars": 7828,
    "preview": "import React, { createContext, useState, useContext, useCallback, useEffect, useRef } from 'react';\nimport { TabPersiste"
  },
  {
    "path": "src/contexts/ThemeContext.tsx",
    "chars": 5819,
    "preview": "import React, { createContext, useState, useContext, useCallback, useEffect } from 'react';\nimport { api } from '../lib/"
  },
  {
    "path": "src/hooks/index.ts",
    "chars": 786,
    "preview": "// Export all custom hooks from a single entry point\nexport { useLoadingState } from './useLoadingState';\nexport { useDe"
  },
  {
    "path": "src/hooks/useAnalytics.ts",
    "chars": 22200,
    "preview": "import { useCallback, useEffect, useRef } from 'react';\nimport { analytics, ANALYTICS_EVENTS, eventBuilders } from '@/li"
  },
  {
    "path": "src/hooks/useApiCall.ts",
    "chars": 3115,
    "preview": "import { useState, useCallback, useRef, useEffect } from 'react';\n\ninterface ApiCallOptions {\n  onSuccess?: (data: any) "
  },
  {
    "path": "src/hooks/useDebounce.ts",
    "chars": 1204,
    "preview": "import { useEffect, useState, useRef } from 'react';\n\n/**\n * Custom hook that debounces a value\n * Useful for search inp"
  },
  {
    "path": "src/hooks/useLoadingState.ts",
    "chars": 1239,
    "preview": "import { useState, useCallback } from 'react';\n\ninterface LoadingState<T> {\n  data: T | null;\n  isLoading: boolean;\n  er"
  },
  {
    "path": "src/hooks/usePagination.ts",
    "chars": 3119,
    "preview": "import { useState, useMemo, useCallback } from 'react';\n\ninterface PaginationOptions {\n  initialPage?: number;\n  initial"
  },
  {
    "path": "src/hooks/usePerformanceMonitor.ts",
    "chars": 3848,
    "preview": "import { useEffect, useRef } from 'react';\nimport { eventBuilders, analytics } from '@/lib/analytics';\n\ninterface Perfor"
  },
  {
    "path": "src/hooks/useTabState.ts",
    "chars": 10831,
    "preview": "import { useCallback, useMemo } from 'react';\nimport { useTabContext } from '@/contexts/TabContext';\nimport { Tab } from"
  },
  {
    "path": "src/hooks/useTheme.ts",
    "chars": 829,
    "preview": "import { useThemeContext } from '../contexts/ThemeContext';\n\n/**\n * Hook to access and control the theme system\n * \n * @"
  },
  {
    "path": "src/lib/analytics/consent.ts",
    "chars": 3519,
    "preview": "import type { AnalyticsSettings } from './types';\n\nconst ANALYTICS_STORAGE_KEY = 'opcode-analytics-settings';\n\nexport cl"
  },
  {
    "path": "src/lib/analytics/events.ts",
    "chars": 21308,
    "preview": "import type { \n  EventName,\n  FeatureUsageProperties,\n  ErrorProperties,\n  SessionProperties,\n  ModelProperties,\n  Agent"
  },
  {
    "path": "src/lib/analytics/index.ts",
    "chars": 10509,
    "preview": "import posthog from 'posthog-js';\nimport { ConsentManager } from './consent';\nimport { sanitizers } from './events';\nimp"
  },
  {
    "path": "src/lib/analytics/resourceMonitor.ts",
    "chars": 6169,
    "preview": "import { analytics, eventBuilders } from '@/lib/analytics';\nimport type { ResourceUsageProperties } from './types';\n\n/**"
  },
  {
    "path": "src/lib/analytics/types.ts",
    "chars": 10555,
    "preview": "export interface AnalyticsEvent {\n  event: string;\n  properties?: {\n    category?: string;\n    action?: string;\n    labe"
  },
  {
    "path": "src/lib/api-tracker.ts",
    "chars": 3587,
    "preview": "import { api as originalApi } from './api';\nimport { analytics, eventBuilders } from './analytics';\n\n// Performance thre"
  },
  {
    "path": "src/lib/api.ts",
    "chars": 53868,
    "preview": "import { apiCall } from './apiAdapter';\nimport type { HooksConfiguration } from '@/types/hooks';\n\n/** Process type for t"
  },
  {
    "path": "src/lib/apiAdapter.ts",
    "chars": 16411,
    "preview": "/**\n * API Adapter - Compatibility layer for Tauri vs Web environments\n * \n * This module detects whether we're running "
  },
  {
    "path": "src/lib/claudeSyntaxTheme.ts",
    "chars": 5934,
    "preview": "import { ThemeMode } from '@/contexts/ThemeContext';\n\n/**\n * Claude-themed syntax highlighting theme factory\n * Returns "
  },
  {
    "path": "src/lib/date-utils.ts",
    "chars": 4071,
    "preview": "/**\n * Formats a Unix timestamp to a human-readable date string\n * @param timestamp - Unix timestamp in seconds\n * @retu"
  },
  {
    "path": "src/lib/hooksManager.ts",
    "chars": 7397,
    "preview": "/**\n * Hooks configuration manager for Claude Code hooks\n */\n\nimport {\n  HooksConfiguration,\n  HookMatcher,\n  HookValida"
  },
  {
    "path": "src/lib/linkDetector.tsx",
    "chars": 3635,
    "preview": "/**\n * URL Detection utility for terminal output\n * Detects various URL formats including localhost addresses\n */\n\nimpor"
  },
  {
    "path": "src/lib/outputCache.tsx",
    "chars": 5772,
    "preview": "import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';\nimport { api } from './api';"
  },
  {
    "path": "src/lib/utils.ts",
    "chars": 702,
    "preview": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Combines multiple class "
  },
  {
    "path": "src/main.tsx",
    "chars": 2068,
    "preview": "import React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport App from \"./App\";\nimport { ErrorBoundary } fr"
  },
  {
    "path": "src/services/sessionPersistence.ts",
    "chars": 4920,
    "preview": "/**\n * Session Persistence Service\n * Handles saving and restoring session data for chat tabs\n */\n\nimport { api, type Se"
  },
  {
    "path": "src/services/tabPersistence.ts",
    "chars": 6493,
    "preview": "/**\n * Tab Persistence Service\n * Handles saving and restoring tab state to/from localStorage\n */\n\nimport type { Tab } f"
  },
  {
    "path": "src/stores/README.md",
    "chars": 1152,
    "preview": "# Store Implementation Notes\n\nThe store files (`sessionStore.ts` and `agentStore.ts`) provide examples of how to impleme"
  },
  {
    "path": "src/stores/agentStore.ts",
    "chars": 7222,
    "preview": "import { create } from 'zustand';\nimport { subscribeWithSelector } from 'zustand/middleware';\nimport type { StateCreator"
  },
  {
    "path": "src/stores/sessionStore.ts",
    "chars": 5907,
    "preview": "import { create } from 'zustand';\nimport { subscribeWithSelector } from 'zustand/middleware';\nimport type { StateCreator"
  },
  {
    "path": "src/styles.css",
    "chars": 22220,
    "preview": "@import \"tailwindcss\";\n\n/* Tauri Transparent Window with Rounded Corners - Official Approach */\nhtml, body {\n  height: 1"
  },
  {
    "path": "src/types/hooks.ts",
    "chars": 3442,
    "preview": "/**\n * Types for Claude Code hooks configuration\n */\n\nexport interface HookCommand {\n  type: 'command';\n  command: strin"
  },
  {
    "path": "src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "src-tauri/.gitignore",
    "chars": 166,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Generated by Tauri\n# will have schema files "
  },
  {
    "path": "src-tauri/Cargo.toml",
    "chars": 2118,
    "preview": "[package]\nname = \"opcode\"\nversion = \"0.2.1\"\ndescription = \"GUI app and Toolkit for Claude Code\"\nauthors = [\"mufeedvh\", \""
  },
  {
    "path": "src-tauri/Info.plist",
    "chars": 1405,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "src-tauri/build.rs",
    "chars": 39,
    "preview": "fn main() {\n    tauri_build::build()\n}\n"
  },
  {
    "path": "src-tauri/capabilities/default.json",
    "chars": 1378,
    "preview": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"default\",\n  \"description\": \"Capability for the mai"
  },
  {
    "path": "src-tauri/entitlements.plist",
    "chars": 1535,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "src-tauri/src/checkpoint/manager.rs",
    "chars": 29585,
    "preview": "use anyhow::{Context, Result};\nuse chrono::{DateTime, TimeZone, Utc};\nuse log;\nuse std::collections::HashMap;\nuse std::f"
  },
  {
    "path": "src-tauri/src/checkpoint/mod.rs",
    "chars": 8049,
    "preview": "use chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::path::PathBuf;"
  },
  {
    "path": "src-tauri/src/checkpoint/state.rs",
    "chars": 6117,
    "preview": "use anyhow::Result;\nuse std::collections::HashMap;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse tokio::sync::RwLock;\n"
  },
  {
    "path": "src-tauri/src/checkpoint/storage.rs",
    "chars": 17669,
    "preview": "use anyhow::{Context, Result};\nuse sha2::{Digest, Sha256};\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse uuid::Uuid;\n"
  },
  {
    "path": "src-tauri/src/claude_binary.rs",
    "chars": 24667,
    "preview": "use anyhow::Result;\nuse log::{debug, error, info, warn};\nuse serde::{Deserialize, Serialize};\nuse std::cmp::Ordering;\n//"
  },
  {
    "path": "src-tauri/src/commands/agents.rs",
    "chars": 70168,
    "preview": "use anyhow::Result;\nuse chrono;\nuse dirs;\nuse log::{debug, error, info, warn};\nuse reqwest;\nuse rusqlite::{params, Conne"
  },
  {
    "path": "src-tauri/src/commands/claude.rs",
    "chars": 81629,
    "preview": "use anyhow::{Context, Result};\nuse serde::{Deserialize, Serialize};\nuse std::fs;\nuse std::io::{BufRead, BufReader};\nuse "
  },
  {
    "path": "src-tauri/src/commands/mcp.rs",
    "chars": 24982,
    "preview": "use anyhow::{Context, Result};\nuse dirs;\nuse log::{error, info};\nuse serde::{Deserialize, Serialize};\nuse std::collectio"
  },
  {
    "path": "src-tauri/src/commands/mod.rs",
    "chars": 116,
    "preview": "pub mod agents;\npub mod claude;\npub mod mcp;\npub mod proxy;\npub mod slash_commands;\npub mod storage;\npub mod usage;\n"
  },
  {
    "path": "src-tauri/src/commands/proxy.rs",
    "chars": 5191,
    "preview": "use rusqlite::params;\nuse serde::{Deserialize, Serialize};\nuse tauri::State;\n\nuse crate::commands::agents::AgentDb;\n\n#[d"
  },
  {
    "path": "src-tauri/src/commands/slash_commands.rs",
    "chars": 15518,
    "preview": "use anyhow::{Context, Result};\nuse dirs;\nuse log::{debug, error, info};\nuse serde::{Deserialize, Serialize};\nuse std::fs"
  },
  {
    "path": "src-tauri/src/commands/storage.rs",
    "chars": 16881,
    "preview": "use super::agents::AgentDb;\nuse anyhow::Result;\nuse rusqlite::{params, types::ValueRef, Connection, Result as SqliteResu"
  },
  {
    "path": "src-tauri/src/commands/usage.rs",
    "chars": 25393,
    "preview": "use chrono::{DateTime, Local, NaiveDate};\nuse serde::{Deserialize, Serialize};\nuse serde_json;\nuse std::collections::{Ha"
  },
  {
    "path": "src-tauri/src/lib.rs",
    "chars": 390,
    "preview": "// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/\n\n// Declare modules\npub mod checkpoint;\npu"
  },
  {
    "path": "src-tauri/src/main.rs",
    "chars": 11957,
    "preview": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_"
  },
  {
    "path": "src-tauri/src/process/mod.rs",
    "chars": 40,
    "preview": "pub mod registry;\n\npub use registry::*;\n"
  },
  {
    "path": "src-tauri/src/process/registry.rs",
    "chars": 18731,
    "preview": "use chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::sync::{Arc, Mu"
  },
  {
    "path": "src-tauri/src/web_main.rs",
    "chars": 886,
    "preview": "use clap::Parser;\n\nmod checkpoint;\nmod claude_binary;\nmod commands;\nmod process;\nmod web_server;\n\n#[derive(Parser)]\n#[co"
  },
  {
    "path": "src-tauri/src/web_server.rs",
    "chars": 29556,
    "preview": "use axum::extract::ws::{Message, WebSocket};\nuse axum::http::Method;\nuse axum::{\n    extract::{Path, State as AxumState,"
  },
  {
    "path": "src-tauri/tauri.conf.json",
    "chars": 2691,
    "preview": "{\n  \"$schema\": \"https://schema.tauri.app/config/2\",\n  \"productName\": \"opcode\",\n  \"version\": \"0.2.1\",\n  \"identifier\": \"op"
  },
  {
    "path": "src-tauri/tests/TESTS_COMPLETE.md",
    "chars": 2233,
    "preview": "# Test Suite - Complete with Real Claude ✅\n\n## Final Status: All Tests Passing with Real Claude Commands\n\n### Key Change"
  },
  {
    "path": "tsconfig.json",
    "chars": 696,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM."
  },
  {
    "path": "tsconfig.node.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "vite.config.ts",
    "chars": 1887,
    "preview": "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\nimport tailwindcss from \"@tailwindcss/vit"
  },
  {
    "path": "web_server.design.md",
    "chars": 14502,
    "preview": "# Opcode Web Server Design\n\nThis document describes the implementation of Opcode's web server mode, which allows access "
  }
]

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

About this extraction

This page contains the full source code of the winfunc/opcode GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 179 files (1.8 MB), approximately 430.8k tokens, and a symbol index with 843 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!