feat(integrations): add Streamlit UI wrapper for the humanizer skill#102
Open
Rich627 wants to merge 6 commits intoblader:mainfrom
Open
feat(integrations): add Streamlit UI wrapper for the humanizer skill#102Rich627 wants to merge 6 commits intoblader:mainfrom
Rich627 wants to merge 6 commits intoblader:mainfrom
Conversation
Paste text → click Humanize → get the final rewritten prose back. The wrapper shells out to the local `claude` CLI with a prompt that invokes the humanizer skill but suppresses its usual draft / audit / summary output, leaving only the rewritten text for copy-paste workflows. Two ways to run: - Local: ./run.sh (venv + streamlit) - Docker: docker compose up --build (mounts ~/.claude for skill + auth) No API key required — uses the host's existing Claude Code login. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README now explains: - exactly when the `claude` subprocess starts/stops (per-click one-shot) - OAuth (`claude /login`) vs ANTHROPIC_API_KEY, including the macOS Docker keychain caveat and how to work around it - what guarantees the output stays free of draft/audit/summary noise app.py adds a `_clean_output()` pass that strips leaked preambles and trailing "Summary of changes" blocks, and replaces the streamed view with a copy-friendly textarea once generation finishes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Output now renders via st.code (wrap_lines=True) so it has a native copy-to-clipboard icon in the top-right corner. - Add a visible "Running humanizer skill…" status message + spinner so users know work is in progress instead of only seeing the tiny Stop button in the Streamlit header. - Streaming response is preserved — text appears live as it arrives, then gets replaced by the copy-ready code block on completion. - README's Lifecycle section now spells out exactly how to stop: Ctrl+C for local, docker compose down / stop / kill for container, and notes that closing the browser tab alone doesn't stop Streamlit or the subprocess (which will self-terminate within seconds anyway). - Bump streamlit minimum to 1.56 (wrap_lines support + general latest). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundles the code-review + security-review fixes with a couple of usability nits reported during testing. Correctness: - Tighten `_TRAILING_RE` so it only strips the humanizer skill's actual trailer blocks (bolded "Summary of changes" / "Changes made" / "What makes … AI …" / "Draft rewrite" / "Now make it not …", with or without a leading `---` separator). Previous pattern ate any paragraph whose first word was "Notes" / "Summary" / "Changes" — a real problem for a humanizer whose job is preserving user prose. - Add `--no-session-persistence` to the `claude -p` call so no session file is written to disk, guaranteeing each click is fully stateless with zero possibility of one run's context leaking into the next. Resource hygiene: - Register atexit exactly once via `@st.cache_resource` instead of per click. Previous code appended a new atexit hook to every Humanize press for the life of the Streamlit process. Docker hardening: - Dockerfile now creates an unprivileged `app` user (uid 1000), chowns /app, and runs Streamlit + claude CLI as that user. Previously ran as root, so a dependency bug could write host files as uid 0 via the ~/.claude mount. - docker-compose.yml binds the published port to 127.0.0.1 only so the unauthenticated UI isn't exposed to the LAN by default, and adjusts the mount target + HOME to the new non-root home. UX: - Status message no longer wraps "claude -p" in backticks (rendered as tiny inline code and broke mid-sentence in the info block). Plain English now. - README: new Security considerations section covering the port bind, the ~/.claude mount trust boundary, the non-root user, and the single-user scope. Lifecycle section mentions --no-session-persistence explicitly so users don't worry about state leaking between clicks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `claude -p` text mode doesn't actually deliver token-by-token streaming through the PIPE in a way Streamlit's write_stream can render incrementally, so the "streaming" UX was a lie. Swap in a plain `proc.communicate()` inside st.spinner, render once on completion via st.code (which still has the native copy button). Also update README — lifecycle table + Output section no longer claim text "streams in live". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The app supports both OAuth login and ANTHROPIC_API_KEY paths, so phrasing the caption as "No API key required" understates the API-key path and can mislead users who already have a key set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a minimal Streamlit UI at
integrations/streamlit/that wraps thehumanizerskill. Paste text → click Humanize → get the cleaned rewrite back with a native copy button, without the skill's default draft / audit / summary sections.This is the first non-skill code in the repo. Everything is scoped under
integrations/streamlit/so the skill itself is untouched. If you'd prefer to keep this repo pure-skill, I'm happy to move the wrapper out to a separate repo and link from the root README instead — just say the word.What the wrapper does
claudeCLI (claude -p --no-session-persistence …), so it reuses the user's existing Claude Code OAuth login — no API key required.ANTHROPIC_API_KEYis honored as a fallback.humanizerskill but adds strict output constraints so the returned text is just the rewritten prose — no "Draft rewrite" / "What makes this AI" / "Summary of changes" sections._PREAMBLE_RE+_TRAILING_RE) strips any leftover preamble or skill-trailer block if the model ever disobeys. The trailing regex is anchored to the skill's actual bolded headers (e.g.**Summary of changes:**,**Changes made:**) so legitimate prose that merely starts with "Notes" or "Summary" is preserved.st.code(wrap_lines=True)so there's a native copy-to-clipboard icon.Ways to run
cd integrations/streamlit && ./run.sh— creates a venv, installsstreamlit, openshttp://localhost:8501.cd integrations/streamlit && docker compose up --build. The compose file mounts~/.claude/into the container (RW, since macOS may needclaude /logininside the container), runs Streamlit as a non-root user (uid 1000), and binds port127.0.0.1:8501so the unauthenticated UI isn't exposed to the LAN by default.Files added
```
integrations/streamlit/
├── app.py # Streamlit UI + subprocess call to claude -p
├── run.sh # Local launcher (venv + streamlit)
├── Dockerfile # python + node + claude CLI + streamlit (non-root user)
├── docker-compose.yml # Mounts ~/.claude, loopback-bound :8501
├── .dockerignore
├── requirements.txt # streamlit>=1.56
└── README.md # Lifecycle / auth / stop / output / security docs
```
Notes for review
claude -p --no-session-persistencesubprocess — no state survives across clicks, nothing is written to the session store.docker compose down), anatexithook (registered exactly once viast.cache_resource, not per click) kills any in-flight subprocess.~/.claudetrust boundary, the non-root container user, the loopback port bind, and the single-user scope.Test plan
cd integrations/streamlit && ./run.sh, paste AI-sounding text, click Humanize, confirm cleaned output appears with a copy icon.Ctrl+Cin the terminal cleanly stops Streamlit and kills any in-flight claude subprocess.docker compose up --build, same flow. (not tested locally — no Linux host available; Dockerfile is identical to the macOS path so this is expected to work, but flagging for reviewer verification.)docker compose up -d --build, rundocker compose exec humanizer claude /loginonce, then humanize.ANTHROPIC_API_KEY=sk-ant-… ./run.sh, confirm no OAuth prompt. (not tested — no spare API key on hand; the code path is a singleos.environpass-through to theclaudesubprocess, which is the documented override.)🤖 Generated with Claude Code