AgentSkillsCN

port-proxy

当用户希望通过Distiller平台内置的反向代理,将本地Web应用公开对外,或在修复由反向代理背后的绝对路径引发的路径问题(CSS/JS无法加载、API返回404)时,应使用此技能。当用户提及将应用公开、代理错误,或MIME类型问题时使用此功能。

SKILL.md
--- frontmatter
name: port-proxy
description: This skill should be used when users want to expose local web applications publicly through the Distiller platform's built-in reverse proxy, or when fixing path issues (CSS/JS not loading, API 404s) caused by absolute paths behind reverse proxies. Use when users mention making apps public, proxy errors, or MIME type issues.

Distiller Reverse Proxy

Overview

The Distiller platform provides a built-in reverse proxy capability that exposes local web applications to the internet via HTTPS without requiring tunnel services, port forwarding, or proxy configuration. This skill addresses the common issue of absolute paths breaking behind reverse proxies and provides tools to fix these issues automatically.

ℹ️ You might also see frpc/frps mentioned elsewhere. Those tunnels are optional: the Distiller proxy works entirely on-device. Use the FRP context in this doc only if your deployment already relies on /etc/frp/frpc.toml to advertise a hostname such as test.devices.pamir.ai.

Key Pattern:

code
Local App (port 5000) → Distiller Proxy → Public HTTPS URL
http://localhost:5000  → https://subdomain.devices.pamir.ai/distiller/proxy/5000/

When to Use This Skill

Use this skill when users:

  • Want to make a local web app publicly accessible
  • Report CSS or JavaScript not loading (MIME type errors)
  • Experience API calls returning 404 errors
  • Ask about exposing apps through the Distiller proxy
  • Mention reverse proxy path issues

Quick Start

Expose Any App in 3 Steps

Step 1: Run the app locally

bash
# Any web server on any port
python app.py              # Flask on 5000
npm run dev                # Vite on 3000
python -m http.server 8080 # HTTP server on 8080

Step 2: Access via proxy URL

code
https://{subdomain}.devices.pamir.ai/distiller/proxy/{PORT}/

Where do I get the subdomain?

  • If you’re using the Distiller proxy only, the Devices dashboard shows the assigned subdomain.
  • If you’re also tunneling with frpc, you can inspect /etc/frp/frpc.toml:
    bash
    cat /etc/frp/frpc.toml | grep subdomain
    # Example output: subdomain = "test"
    # Your URL: https://test.devices.pamir.ai/distiller/proxy/5000/
    

Step 3: Fix path issues if needed

bash
# Check for issues
./scripts/check-paths.sh /path/to/app

# Auto-fix common patterns
./scripts/fix-paths.sh /path/to/app

The Absolute vs Relative Path Problem

Root cause: Absolute paths (starting with /) resolve to domain root, breaking when behind reverse proxies.

Symptoms:

  • CSS not loading: "MIME type 'application/json' is not a supported stylesheet"
  • JavaScript 404 errors
  • API calls fail with 404
  • Images don't load

Examples:

Broken (absolute paths):

html
<link rel="stylesheet" href="/styles.css">
<script src="/main.js"></script>

Resolves to: https://domain.com/styles.css (wrong - not behind proxy path)

Working (relative paths):

html
<link rel="stylesheet" href="styles.css">
<script src="main.js"></script>

Resolves to: https://domain.com/distiller/proxy/5000/styles.css (correct)

Fixing Path Issues

Automatic Fix (Recommended)

Use the provided script to automatically fix common patterns:

bash
# Dry run (preview changes)
./scripts/fix-paths.sh /path/to/app --dry-run

# Apply fixes
./scripts/fix-paths.sh /path/to/app

The script fixes:

  • HTML: href="/..."href="..."
  • HTML: src="/..."src="..."
  • JavaScript: API_BASE = '/api'API_BASE = 'api'
  • JavaScript: fetch('/api/...')fetch('api/...')

⚠️ The fixer is intentionally conservative but still performs in-place edits. Run with --dry-run first, keep backups (e.g., via git), and review the diff—projects sometimes rely on intentional absolute URLs.

Manual Fix

HTML Files:

html
<!-- BEFORE -->
<link rel="stylesheet" href="/styles.css">
<script src="/main.js"></script>
<img src="/logo.png">

<!-- AFTER -->
<link rel="stylesheet" href="styles.css">
<script src="main.js"></script>
<img src="logo.png">

JavaScript Files:

javascript
// BEFORE
const API_BASE = '/api';
fetch('/api/data');

// AFTER
const API_BASE = 'api';
fetch('api/data');

Exception: Keep absolute paths for external resources:

html
<!-- These are fine (external URLs) -->
<script src="https://cdn.example.com/library.js"></script>

Serving Under a Base Path (e.g., /watchdog)

Sometimes you must keep the proxy path segment (such as /distiller/proxy/5000/watchdog) instead of stripping the /. The pattern below lets your app live under any prefix without code rewrites each time.

  1. Server-side base path variable

    bash
    # systemd / shell
    export BASE_PATH=/watchdog
    
    javascript
    // Express example
    const basePath = (process.env.BASE_PATH || '/').replace(/\/$/, '');
    app.use(`${basePath}/static`, express.static('public'));
    app.use(basePath, router);
    app.get(`${basePath}/health`, handler);
    
  2. Inject the base path into rendered HTML

    html
    <script>window.BASE_PATH = "{{ basePath }}";</script>
    <link rel="stylesheet" href="{{ basePath }}/static/dashboard.css">
    <script src="{{ basePath }}/static/app.js"></script>
    
  3. Teach the front-end to respect it

    javascript
    const BASE = window.BASE_PATH || '';
    await fetch(`${BASE}/api/status`);
    

With those three pieces, requests to https://subdomain.devices.pamir.ai/distiller/proxy/5000/watchdog/… stay scoped to /watchdog on both server and client, preventing 404s while still allowing the app to run at / locally.

Framework-Specific Guides

Flask (Python)

Works well by default. Only fix HTML templates.

python
# No changes needed to Flask code
app = Flask(__name__, static_folder='static')

@app.route('/api/data')
def data():
    return jsonify({'status': 'ok'})

Fix: Change paths in HTML templates from absolute to relative.

Vite (JavaScript)

Add base path configuration:

javascript
// vite.config.js
export default defineConfig({
  base: './', // Use relative base path
  server: {
    host: '0.0.0.0',
    port: 3000
  }
})

Create React App

Update package.json:

json
{
  "homepage": "."
}

Express (Node.js)

javascript
const express = require('express');
const app = express();

app.use(express.static('public'));

app.get('/api/data', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(5000, '0.0.0.0');

Fix: HTML files to use relative paths.

Common Issues

CSS loads but styles not applied

Cause: MIME type mismatch

Solution:

  1. Check browser console for error
  2. Verify path is relative: href="styles.css" not href="/styles.css"

API calls return HTML instead of JSON

Cause: Flask returning index.html for missing routes

Solution:

python
# Make sure API routes come BEFORE catch-all
@app.route('/api/data')
def data():
    return jsonify({'status': 'ok'})

# This should be last
@app.route('/<path:path>')
def serve_static(path):
    return send_from_directory(app.static_folder, path)

Page loads but refresh gives 404

Cause: Client-side routing needs server fallback

Solution:

python
@app.errorhandler(404)
def not_found(e):
    # For SPA with client-side routing
    if request.path.startswith('/api/'):
        return jsonify(error='Not found'), 404
    return send_from_directory(app.static_folder, 'index.html')

POST requests timeout or return 404

Cause: Long-running operations (>30s) don't work well through proxy

Symptoms:

  • POST requests hang/timeout after 30-60 seconds
  • Request completes on server but browser never gets response
  • Works locally but fails through proxy

Solution: Use background tasks with immediate response

python
import threading

def run_long_task():
    """Background task"""
    # Do expensive work here
    time.sleep(60)
    # Save results to file/database

@app.route('/api/process', methods=['POST'])
def process_data():
    # Start task in background
    thread = threading.Thread(target=run_long_task, daemon=True)
    thread.start()

    # Return immediately
    return jsonify({
        'success': True,
        'message': 'Processing started. Check back in 1-2 minutes.'
    })

# Frontend polls for updates
// JavaScript
async function startProcess() {
    const res = await fetch('./api/process', { method: 'POST' });
    const data = await res.json();

    // Poll every 10 seconds for updates
    const interval = setInterval(async () => {
        const status = await fetch('./api/status');
        const result = await status.json();
        if (result.complete) {
            clearInterval(interval);
            // Update UI with results
        }
    }, 10000);
}

Why: The proxy can't maintain connections for long-running synchronous requests. Background tasks + polling is the standard pattern for web apps.

Best Practices

  1. Use relative paths everywhere

    • href="style.css"
    • href="/style.css"
  2. Namespace API routes

    • /api/users, /api/data
    • /users (conflicts with static files)
  3. Support a base path

    • Use env vars/config to mount your router under /watchdog (or similar) when the proxy requires it.
    • Render BASE_PATH into templates and prepend it inside JavaScript fetch calls.
  4. Test locally first

    • Test on http://localhost:5000/
    • Then test through proxy
  5. Document the public URL

    markdown
    Public: https://subdomain.devices.pamir.ai/distiller/proxy/5000/
    Local: http://localhost:5000/
    

Port Management

Check what's running

bash
# See all listening ports
netstat -tuln | grep LISTEN

# Check specific port
lsof -i :5000

Common port assignments

PortTypical Use
5000Flask apps
3000Vite/React dev servers
8000Django apps
8080Generic web servers

Kill process on port

bash
lsof -i :5000
kill -9 {PID}

# Or one-liner
pkill -f "python app.py"

Resources

scripts/

check-paths.sh - Scans project files for absolute path issues and reports potential problems (uses ripgrep when available, falls back to grep). Accepts a project root and reports findings without modifying files.

fix-paths.sh - Automatically fixes common absolute path patterns in HTML and JavaScript files. Supports --dry-run to preview changes; always review the diff afterwards if you run it for real.

Usage examples are shown in the "Fixing Path Issues" section above.

Quick Reference

code
┌─────────────────────────────────────────────────┐
│ Distiller Proxy Quick Reference                │
├─────────────────────────────────────────────────┤
│ URL Pattern:                                    │
│ https://{subdomain}.devices.pamir.ai/          │
│        distiller/proxy/{PORT}/                  │
│                                                 │
│ Fix Checklist:                                  │
│ □ Remove leading / from href/src               │
│ □ Change API calls to relative paths           │
│ □ Test locally first                           │
│ □ Hard refresh browser (Ctrl+Shift+R)          │
│                                                 │
│ Common Fixes:                                   │
│ href="/style.css"  → href="style.css"          │
│ src="/main.js"     → src="main.js"             │
│ fetch('/api/...')  → fetch('api/...')          │
└─────────────────────────────────────────────────┘