Skip to content

Add optional self-hosted mode with SQLite-backed UUID artifact storage#18

Open
baanish wants to merge 2 commits intomainfrom
claude/add-self-hosted-app-pobSm
Open

Add optional self-hosted mode with SQLite-backed UUID artifact storage#18
baanish wants to merge 2 commits intomainfrom
claude/add-self-hosted-app-pobSm

Conversation

@baanish
Copy link
Owner

@baanish baanish commented Mar 20, 2026

Summary

This PR adds an optional self-hosted variant of agent-render that provides server-backed artifact storage as an alternative to the default fragment-based sharing. The self-hosted mode stores payloads in SQLite under UUID v4 keys and serves them via a simple Express API and viewer routes, enabling persistent short links and support for larger payloads without fragment-length constraints.

Key Changes

Self-hosted server implementation (selfhosted/)

  • Express server with SQLite storage for artifact CRUD operations
  • UUID v4-based artifact identification with 24-hour sliding TTL
  • API endpoints: POST /api/artifacts, GET /api/artifacts/:id, PUT /api/artifacts/:id, DELETE /api/artifacts/:id, POST /api/cleanup
  • Viewer route /:uuid that injects stored payloads into the static HTML template via window.__AGENT_RENDER_ENVELOPE__
  • Database module with prepared statements for safe queries and TTL management
  • Standalone cleanup script for removing expired artifacts
  • Docker Compose configuration for containerized deployment
  • Environment-based configuration (PORT, DB_PATH, STATIC_DIR, BASE_URL, TTL_HOURS)

Viewer integration

  • New src/lib/payload/injected.ts module that resolves server-injected envelopes from window.__AGENT_RENDER_ENVELOPE__
  • Updated src/components/viewer-shell.tsx to check for injected envelopes on mount before falling back to fragment decoding
  • Maintains full feature parity with fragment-based viewer (all artifact kinds, copy, download, print-to-PDF)

Documentation and configuration

  • Comprehensive skills/selfhosted-agent-render/SKILL.md with deployment guides (systemd, pm2, Docker Compose), API examples, and auth patterns
  • Updated docs/deployment.md, docs/architecture.md, docs/payload-format.md, and docs/testing.md with self-hosted information
  • Updated README.md and AGENTS.md to reference the optional self-hosted variant
  • .env.example for self-hosted configuration
  • Dockerfile and docker-compose.yml for containerized deployment

Testing

  • Unit tests for injected envelope resolution (tests/injected.test.ts) covering valid/invalid JSON, envelope validation, duplicate artifact IDs, and all artifact kinds

Notable Implementation Details

  • The self-hosted server is a completely optional add-on; the default static fragment-based product is unaffected
  • Payloads are stored as JSON strings in SQLite and injected into the HTML template via a script tag with proper XSS escaping
  • TTL uses SQLite's datetime() function with a configurable modifier (default 24 hours) for sliding-window expiry
  • Expired artifacts are filtered at query time; explicit cleanup is available via API or standalone script
  • Maximum payload size is 1 MB per artifact
  • The viewer shell gracefully falls back to fragment decoding if no injected envelope is present, maintaining backward compatibility
  • All artifact kinds (markdown, code, diff, CSV, JSON) are fully supported in self-hosted mode

https://claude.ai/code/session_01FWECp4ZWSgoxbPvzhZbp5n

…torage

Adds a separate self-hosted variant that stores agent-render payloads in
SQLite under UUID v4 keys and serves them at /{uuid} routes through the
existing viewer UI. This is an optional add-on for power users and agents
who need persistent short links or payloads exceeding the fragment budget.

- selfhosted/src/server.ts: Express server with CRUD API and viewer route
- selfhosted/src/db.ts: SQLite database with 24h sliding TTL
- selfhosted/Dockerfile + docker-compose.yml: container deployment
- src/lib/payload/injected.ts: viewer-shell integration for server-injected envelopes
- skills/selfhosted-agent-render/SKILL.md: agent workflow skill
- Updated docs across architecture, deployment, payload-format, testing, and dependency-notes
- 8 new unit tests for injected envelope resolution (all artifact kinds, validation, TTL)

The existing static fragment-based app is fully preserved and unmodified.

https://claude.ai/code/session_01FWECp4ZWSgoxbPvzhZbp5n
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

Warning

Rate limit exceeded

@baanish has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d06fb6c2-ec63-4892-9db9-ac14eb0f36e5

📥 Commits

Reviewing files that changed from the base of the PR and between 3d5ed45 and 56956c9.

⛔ Files ignored due to path filters (1)
  • selfhosted/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • AGENTS.md
  • README.md
  • docs/architecture.md
  • docs/dependency-notes.md
  • docs/deployment.md
  • docs/payload-format.md
  • docs/testing.md
  • selfhosted/.env.example
  • selfhosted/.gitignore
  • selfhosted/Dockerfile
  • selfhosted/docker-compose.yml
  • selfhosted/package.json
  • selfhosted/src/cleanup.ts
  • selfhosted/src/db.ts
  • selfhosted/src/server.ts
  • selfhosted/tsconfig.json
  • skills/agent-render-linking/SKILL.md
  • skills/selfhosted-agent-render/SKILL.md
  • src/components/viewer-shell.tsx
  • src/lib/payload/injected.ts
  • tests/injected.test.ts
  • tsconfig.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/add-self-hosted-app-pobSm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 20, 2026

Deploying agent-render with  Cloudflare Pages  Cloudflare Pages

Latest commit: 56956c9
Status: ✅  Deploy successful!
Preview URL: https://9157da24.agent-render.pages.dev
Branch Preview URL: https://claude-add-self-hosted-app-p.agent-render.pages.dev

View logs

@kilo-code-bot
Copy link

kilo-code-bot bot commented Mar 20, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (22 files)
  • AGENTS.md
  • README.md
  • docs/architecture.md
  • docs/dependency-notes.md
  • docs/deployment.md
  • docs/payload-format.md
  • docs/testing.md
  • selfhosted/.env.example
  • selfhosted/.gitignore
  • selfhosted/Dockerfile
  • selfhosted/docker-compose.yml
  • selfhosted/package.json
  • selfhosted/src/cleanup.ts
  • selfhosted/src/db.ts
  • selfhosted/src/server.ts
  • selfhosted/tsconfig.json
  • skills/selfhosted-agent-render/SKILL.md
  • src/components/viewer-shell.tsx
  • src/lib/payload/injected.ts
  • tests/injected.test.ts
  • tsconfig.json

Review Notes

The implementation is well-structured with proper security considerations:

  • XSS Protection: Server-side rendering uses JSON.stringify() with additional escaping for </ and <!-- to safely inject payloads into HTML script tags (server.ts:43-50)
  • SQL Injection: All database operations use parameterized queries (db.ts)
  • Input Validation: UUID format validation via regex on all API endpoints
  • Payload Validation: Envelope structure validated before storage
  • TTL Management: Proper sliding window implementation for artifact expiry
  • Tests: Comprehensive test coverage for the injected envelope resolver

The self-hosted variant is properly isolated from the default fragment-based mode, maintaining backward compatibility as documented.


Reviewed by minimax-m2.5-20260211 · 671,948 tokens

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

encodeEnvelopeAsync({ ...envelope, activeArtifactId: artifactId }, { codec: envelope.codec }).then((encoded) => {
setFragmentHash(`#${encoded}`);

P1 Badge Preserve UUID-backed viewers when switching artifacts

When the viewer is opened from /:uuid, this callback re-encodes the whole envelope into window.location.hash. That works only for fragment-sized bundles, but self-hosted artifacts are allowed up to 1 MB while decodeFragmentAsync() still rejects hashes over 8,000 characters in src/lib/payload/fragment.ts. For any multi-artifact payload that only fits in self-hosted mode, selecting another artifact replaces the working injected view with a too-large error state.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +20 to +21
COPY selfhosted/package.json selfhosted/package-lock.json* ./
RUN npm ci --omit=dev

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add a selfhosted lockfile or stop using npm ci

This image copies only selfhosted/package.json and then runs npm ci --omit=dev, but rg --files selfhosted shows there is no selfhosted/package-lock.json in the repo. npm ci exits with EUSAGE without a lockfile, so the documented Dockerfile / Compose deployment path fails before the image is even built.

Useful? React with 👍 / 👎.


# Install server dependencies
COPY selfhosted/package.json selfhosted/package-lock.json* ./
RUN npm ci --omit=dev

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Install the runtime loader used by npm start

CMD ["npm", "start"] resolves to node --import tsx src/server.ts in selfhosted/package.json, but this image installs with npm ci --omit=dev, which drops tsx because it is only listed under devDependencies. Even after fixing the lockfile problem, the container will still exit at startup because the TypeScript loader it relies on is missing from the runtime image.

Useful? React with 👍 / 👎.

Comment on lines +77 to +80
try {
const parsed = JSON.parse(payloadStr);
if (!parsed || typeof parsed !== "object" || parsed.v !== 1 || !Array.isArray(parsed.artifacts)) {
res.status(400).json({ error: "Payload must be a valid agent-render envelope (v: 1, artifacts array required)." });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject envelopes the injected viewer cannot open

The POST/PUT validation only checks for v === 1 and an artifacts array, so it still accepts envelopes that resolveInjectedEnvelope()/normalizeEnvelope() reject later—e.g. empty bundles, duplicate artifact IDs, or malformed diff artifacts. In those cases the API returns success and a UUID, but opening that UUID immediately lands on the viewer's invalid-envelope state instead of a renderable artifact.

Useful? React with 👍 / 👎.

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