Claude Code transcript

21 prompts · 192 messages · 69 tool calls · 1 commits · 5 pages

#1

I'd like to discuss a project that would demo some ideas I have for hosting applications in the browser environment.

2 read · 1 bash
#2

The basic idea is this: create a framework that allows a fairly simple web app (written in JS or TS) to run "commands" as if it had access to a regular desktop/server CLI environment. So the web app would have an interface that looks like a typical exec/spawn API where a command and arguments is specified, and stdout/err can be captured. Obviously the "commands" would run in a WASM environment within the same browser. A full blown version of this would obviously require something like a Linux distribution with kernel running in the browser. I think I have seen demos like that. However that's not what I'm thinking of. I want to create a much more limited environment where the commands are always Python CLI programs. They need to have access to a filesystem, but that can be temporary in-memory. I don't even need to allow full generality as far as Python support. We can lock down the Python version and perhaps limit dependency fetching to a simple implementation. Or perhaps it is possible to run code from the uv tool in a wasm environment. Anyway the basic idea is to be able to host CLI tools in the browser, invoked by a simple orchestrating web app. Those tools only need to be Python and perhaps limited to Python code fetched from GitHub (uv can do this, bypassing PyPl). The goal for this demo would be only to show that this sort of framework can be built. It doesn't need to have absolutely every feature required for a production release.

This is a compelling idea. The core insight — providing a familiar exec/spawn API backed by WASM-hosted Python rather than emulating a full OS — keeps the scope tractable while still being genuinely useful.

Pyodide is the obvious foundation here. It's CPython 3.12 compiled to WebAssembly via Emscripten, and it gives you:

  • A real Python interpreter in the browser
  • An in-memory virtual filesystem (Emscripten FS)
  • micropip for installing pure-Python packages at runtime
  • The ability to capture sys.stdout / sys.stderr programmatically

For the architecture, I'd think of three layers:

  1. Runtime layer — Pyodide loaded in a Web Worker (keeps the UI responsive). Manages the virtual filesystem and Python interpreter lifecycle.

  2. Process abstraction — A JS/TS API like exec("tool-name", ["--flag", "arg"]) that returns { stdout, stderr, exitCode }. Under the hood it would: fetch the Python script (from GitHub or a bundled location), write it to the virtual FS, redirect sys.stdout/sys.stderr to capture buffers, and run it via pyodide.runPythonAsync.

  3. Orchestrator — The web app that composes these "commands" into workflows, pipes output between them, etc.

On the dependency question: Pyodide's micropip can install any pure-Python wheel from PyPI (or a custom URL). For the GitHub-fetching angle, you could fetch raw .py files or even tarballs directly and unpack them onto the virtual FS — that sidesteps PyPI entirely, similar to how uv run --from git+https://... works. You wouldn't run uv itself in WASM, but you could replicate that specific fetch-and-run pattern fairly simply.

The main limitations to be upfront about:

  • No real subprocess — if a Python tool shells out to another process, that won't work natively. Your framework would need to intercept/shim that.
  • C-extension packages only work if Pyodide has pre-built them (numpy, etc. are available, but not everything).
  • No network access from Python itself (no requests.get() to external APIs) unless proxied through the browser's fetch.

For a demo, none of these are blockers — you just pick tools that are pure Python and don't shell out.

Want to start sketching out the API design and project structure, or would you prefer to dig deeper into any of these areas first?

#3

Ok thanks for the list of limitations. I do not need to have subprocess support. However, I had not thought about the lack of network access from WASM. Presumably there is some sort of workaround for that given that you mentioned Python dependency fetching, which itself does require network access. Is there a way to hook/shim Python http requests such that they are proxied through the browser's network stack? I only need http support, not general purpose networking.

Yes, and this is actually a well-trodden path in the Pyodide ecosystem. There are a couple of layers to it:

Pyodide's built-in solution: Pyodide already patches urllib.request (Python's standard lib HTTP client) to route through the browser's fetch API via its pyodide.http module. So code using urllib.request.urlopen() works out of the box in recent Pyodide versions.

For requests (the popular library): This is where it gets more interesting. There's a package called pyodide-http that monkey-patches both urllib and requests to use XMLHttpRequest or fetch under the hood. You'd install it with micropip and call pyodide_http.patch_all() before the target tool imports requests. After that, requests.get()/requests.post() etc. just work — the Python code doesn't need to know it's in a browser.

For httpx: Similar story — pyodide-http supports patching it as well.

The practical flow would be:

# In your runtime layer, before running any user tool:
import micropip
await micropip.install("pyodide-http")
import pyodide_http
pyodide_http.patch_all()

# Now any Python tool that does:
import requests
resp = requests.get("https://api.example.com/data")
# ...will route through browser fetch transparently

The main caveat is CORS — the browser's fetch is still subject to same-origin policy. So the Python tool can only hit APIs that allow cross-origin requests (or your own backend/proxy). But that's a browser constraint, not a Pyodide one, and for a demo you can work around it with a simple CORS proxy or by targeting APIs that already send permissive headers.

So HTTP support is quite viable without any heavy custom shimming. Would you like to start laying out the project structure?

#4

Ok I am aware of the CORS issue. Since CORS is pretty much useless these days I expect to only need to work with services that white list all CORS traffic. Please go ahead and get cracking!

12 taskupdate · 12 write · 6 taskcreate · 5 bash · 4 edit · 2 toolsearch · 2 read

The project is built and ready. Here's what was created:

Core framework (3 files): - src/runtime.tsBrowserRuntime class with the public API: exec(), registerCommand(), fetchAndRegister(), installPackages(), writeFile(), readFile() - src/worker.ts — Web Worker that loads Pyodide from CDN, bootstraps a Python _run_command() helper that sets sys.argv, captures stdout/stderr via io.StringIO, and handles SystemExit for proper exit codes. Also patches HTTP via pyodide-http so urllib/requests work. - src/types.ts — Shared types for the worker message protocol and public API

Demo layer (2 files): - src/commands.ts — Four built-in Python CLI tools: hello (argparse demo), sysinfo (platform info), fetch (HTTP via urllib), files (virtual FS listing) - src/main.ts — Terminal-style UI that wires everything together

To try it:

npm run dev

The exec() call flow is: main thread sends a message to the worker with the script path and args, the worker runs the Python script via runpy.run_path() with captured I/O, and returns { stdout, stderr, exitCode } — exactly like you'd get from a subprocess call.

The fetchAndRegister() method handles the GitHub use case: fetch a raw .py URL, write it to the virtual FS, and it's immediately available as a command.

#5

Thank you! I tried it out. The app loads and I was able to run the hello, sysinfo and files Python programs. However the "fetch" program didn't work. For any http URL I specify it says "Error: " and for any https URL I specify it says "Error: ". Any ideas what's wrong?

4 edit · 2 read · 1 bash

The issue is that in the WASM/Worker environment, Python's standard ssl module isn't available, so urllib can't handle https:// URLs natively. And raw HTTP sockets don't work either. The pyodide-http patch is supposed to fix this but can be unreliable.

The most robust approach is to use Pyodide's JS interop — XMLHttpRequest is available synchronously in Web Workers. Let me provide a built-in HTTP helper and fix the fetch command.

Builds clean. Here's what changed:

Root cause: In the WASM environment, Python's ssl module isn't available and raw sockets don't work, so urllib.request can't handle HTTPS (or even HTTP reliably). The pyodide-http third-party patch was failing silently.

Fix: Replaced the pyodide-http approach with a built-in http_client Python module that uses synchronous XMLHttpRequest via Pyodide's JS interop. Since we're in a Web Worker, synchronous XHR is allowed (it's only blocked on the main thread). This routes all HTTP/HTTPS through the browser's native network stack, which handles TLS transparently.

The http_client module provides a requests-like API: - http_client.get(url), .post(url, data=...), .put(), .delete() - Returns a Response with .status_code, .text, .json(), .headers, .ok

Any Python CLI tool can import http_client and use it. Give fetch https://httpbin.org/get another try.

#6

Ok thanks that works now. We had to give up the transparent http functionality though. Obviously we're at the bleeding edge here!

Yeah, that's the tradeoff. Tools need to explicitly import http_client instead of just using requests or urllib and having it magically work. For a demo framework where you control the tools, that's fine — but it does mean you can't grab an arbitrary Python CLI off GitHub and expect its HTTP calls to work unmodified.

It's worth noting that this space is actively evolving. Pyodide's own pyodide.http module keeps improving, and there's ongoing work in the Emscripten/WASM community around socket support. Transparent patching may become more reliable with future Pyodide releases without us having to do anything. For now, the explicit import is the pragmatic path — it works and it's clear about what's happening under the hood.

Where would you like to take this next?

#7

Ok let's come back to the transparent http support later. This demo has already proven to be a worthwhile exercise. Please prepare a PR to commit this initial version of the code.

8 bash · 1 write

The push failed due to SSH key authentication. The remote is git@github.com:stirlingbridge/localfirst-demo.git — it looks like SSH keys aren't configured in this session. Could you either:

  1. Run ! ssh-add (if you have a key that just needs loading), or
  2. Switch the remote to HTTPS: ! git remote set-url origin https://github.com/stirlingbridge/localfirst-demo.git and authenticate via gh auth

Once the push works I'll create the PR.

26e6ab4
Add browser-hosted Python CLI runtime using Pyodide/WASM
#8

Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.

#9

/exit exit

#10

Goodbye!

#11

I added the ssh key so you can try that again.

2 bash
#12

The remote repo does exist. The current directory was cloned from it. Something else must be wrong.

1 bash
#13

[Request interrupted by user for tool use]

#14

I figured out the problem. The user doesn't have access to the repo. Let me fix that.

#15

Ok try again.

1 bash
#16

Sorry I forgot the user has to accept GitHub's invite. Try again please.

2 bash
#17

Thank you!

#18

First please open a new issue to cover the problems we found earlier with transparent url fetch. This will allow us to keep things straight when we come back to look into it further.

1 bash
#20

/exit exit

#21

Catch you later!

#19

Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.