Skip to content

Add generate command with provider-agnostic LLM support#8

Open
utsengar wants to merge 8 commits into
mainfrom
claude/pi-htmlbin-shipping-d1vht
Open

Add generate command with provider-agnostic LLM support#8
utsengar wants to merge 8 commits into
mainfrom
claude/pi-htmlbin-shipping-d1vht

Conversation

@utsengar

@utsengar utsengar commented May 25, 2026

Copy link
Copy Markdown
Owner

Summary

Adds htmlbin generate --prompt <text> — generates an HTML page via any OpenAI-compatible LLM and publishes it, returning the same URL output as htmlbin publish.

The command is built on top of HTMLBin's existing infrastructure rather than bypassing it:

  • Pattern-guided generation: the prompt is matched against installed pattern triggers (project > global > bundled precedence). The matching pattern's body — content checklist, layout directions, quality constraints — becomes the LLM system prompt. --pattern <name> pins a specific pattern explicitly.
  • Context travels with the drop: the generation prompt is passed as context to the cloud publish call so it's stored alongside the drop.
  • Provider-agnostic, zero SDK dependency: raw fetch against any OpenAI-compatible /chat/completions endpoint. No vendor SDK, nothing to go stale.
  • Cloud-only: generate is constrained to the cloud backend (same as update). gh-pages and cloudflare require PR context and don't carry the context field.

Configuration

Three env vars, fully user-controlled — no hardcoded URLs or model defaults:

HTMLBIN_LLM_BASE_URL   # required — any OpenAI-compatible endpoint
HTMLBIN_LLM_MODEL      # required — user picks, user maintains
HTMLBIN_LLM_API_KEY    # optional — omit for Ollama or keyless endpoints

Usage

# Auto-detects pr-explainer pattern from the prompt
htmlbin generate --prompt "explain this pr" --data changes.patch

# Explicit pattern
htmlbin generate --prompt "Q4 results" --data metrics.csv --pattern dashboard

# Local model, no key needed
HTMLBIN_LLM_BASE_URL=http://localhost:11434/v1 \
HTMLBIN_LLM_MODEL=llama3.2 \
htmlbin generate --prompt "a landing page for my project"

# All publish flags work
htmlbin generate --prompt "..." --slug my-report --metadata env=prod --upsert

New files

  • src/llm/provider.ts — resolves LLM config from env vars
  • src/llm/complete.ts — fetch call with timeout, error parsing, fence stripping, and pattern injection into system prompt
  • src/llm/pattern-resolve.ts — loads patterns from all three sources, matches triggers, throws not_found on unknown --pattern

Changes to existing files

  • src/backend.ts — adds context? to PublishOpts; removes pr? (was never used — slug is the right override)
  • src/backends/cloud.ts — forwards context to api.publish()
  • src/backends/gh-pages.ts / src/backends/cloudflare.ts — PR number now comes from CI env only; no explicit override
  • src/errors.ts — adds no_llm_provider (exit 9) and llm_error (exit 8)
  • src/bin.ts — registers generate with --prompt, --pattern, --data, and all standard publish flags via shared helpers; removes backend-specific flags (--pr, --repo, --branch, --project) from publish/list/delete/url — they belong on setup only
  • src/gh/repo.tsresolvePrNumber simplified: explicit param removed (dead code once --pr flag was dropped)
  • src/llm/pattern-resolve.tscwd/env params removed (never populated by any call site); source field removed from ResolvedPattern (only fed one debug log line)
  • src/llm/complete.tsextractHtml now lowercases before the <html check (was already lowercasing <!doctype)

Test plan

  • npm run build && npm test passes (183 tests, 0 failures)
  • generate --help shows the command and all flags
  • Prompt matching "explain this pr" auto-selects pr-explainer pattern (stderr shows pattern: pr-explainer)
  • --pattern pr-explainer pins the pattern explicitly
  • --pattern no-such-pattern exits 4 with not_found
  • Missing env vars exit 9 with an actionable hint
  • LLM provider error exits 8 with the provider's error message
  • --data file not found exits 4
  • JSON output returns { url, slug, backend }
  • Tmpfile is cleaned up even when publish fails
  • generate --to gh-pages exits with invalid_arg

claude added 8 commits May 25, 2026 02:26
Adds `htmlbin generate --prompt <text>` which calls any OpenAI-compatible
LLM endpoint, extracts the resulting HTML, and publishes it — returning
the same URL output as `htmlbin publish`. Users supply the endpoint and
model via env vars (HTMLBIN_LLM_BASE_URL, HTMLBIN_LLM_MODEL,
HTMLBIN_LLM_API_KEY), keeping the CLI free of any vendor SDK dependency.

- src/llm/provider.ts: resolves LLM config from env vars; no named
  providers, no hardcoded URLs or model defaults to go stale
- src/llm/complete.ts: raw fetch against /chat/completions with timeout,
  structured error parsing, and markdown fence stripping
- src/errors.ts: adds no_llm_provider (exit 9) and llm_error (exit 8)
- src/bin.ts: registers the generate command; writes to a tmpfile,
  delegates to the active backend's publish method, then cleans up
- test/llm.test.ts: unit tests for provider resolution and exit codes
- test/e2e/smoke.test.ts: adds generate to the --help surface check
The generate command now builds on HTMLBin's existing quality surface
instead of bypassing it:

- Pattern auto-detection: prompt is matched against installed pattern
  triggers (project > global > bundled precedence). The matching
  pattern's body becomes the LLM system prompt, giving the model the
  same content checklist, layout directions, and quality constraints
  that the patterns system was designed to enforce.
- Explicit override: --pattern <name> pins a specific pattern.
- context field: the generation prompt is passed as context to the
  cloud publish call so it travels with the drop.
- backend.ts: adds context? to PublishOpts; cloud backend forwards it
  to api.publish().
- src/llm/pattern-resolve.ts: loads and merges patterns from all three
  sources, matches triggers, throws not_found on unknown --pattern.
- Tests updated to cover pattern auto-detection and explicit lookup.
Extracts three shared helpers so the option list, opts mapping, and
result emission live in one place:

- addPublishOptions(cmd): registers all publish-related flags on any
  command — publish and generate both call this
- buildPublishOpts(file, cmdOpts, extra): converts cmd opts into
  PublishOpts; accepts extra fields (context) for generate
- emitDropResult(r, backend): shared emit for both commands

GenerateCmdOpts now extends PublishCmdOpts with only prompt/pattern/data.
Adding a new publish flag in future requires changing one line.
addPublishOptions now only contains backend-agnostic flags: title,
description, slug, metadata, upsert. The --pr, --repo, --branch,
--project flags are backend configuration and stay directly on the
publish command where they were before. generate does not inherit them.
--pr, --repo, --branch, --project were leaking backend implementation
details into the public CLI surface across publish, list, delete, and
url commands. Backend config belongs in the config file and env vars,
not as per-command flags.

- PublishOpts: drops pr field; backends auto-detect from $GITHUB_REF
- makeBackend: now only accepts SetupCmdOpts overrides; all other
  commands use config values directly
- publish, list, delete, url: no backend-specific flags
- setup: keeps --repo, --branch, --project (one-time config, correct)
- resolvePrNumber hint updated: directs to --slug or $GITHUB_REF
- Drop `explicit` param from resolvePrNumber; all call sites already passed `{}`
- Drop `cwd` and `env` params from resolvePattern/allPatterns; they were never populated
- Drop `source` field from ResolvedPattern; only existed for a single debug log line
- Fix extractHtml to lowercase `<html` check (was already lowercasing `<!doctype`)
- Guard generate command to cloud-only backend (matches how update is constrained)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants