Cloudsteading
Product image still needed. This listing has source documentation, but no reviewed screenshot yet.

Glance

Publish agent-created pages and review them through shareable links and comments.

Glance is a self-hosted Claude Artifacts alternative built on Cloudflare (D1, Durable Objects, KV, R2, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.

Source & license

Upstream license: MIT

License TL;DR

You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.

Explain MIT in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@plivo-labs

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Free tier eligible within limits

The documented Glance deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs.

Hosting requirements
  • Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits.
  • Keep aggregate D1 use below 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; a request can touch many rows.
  • Keep KV below 100,000 reads/day, 1,000 writes, deletes and list operations/day each, and 1 GB; cache refreshes and backups consume writes.
  • Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account.
  • Use the configured SQLite Durable Object classes within 100,000 requests/day, 13,000 GB-s duration/day, 5 million SQL rows read/day, 100,000 written/day and 5 GB storage; active sockets consume duration.
  • Only ordinary Free-eligible Workers AI models are covered, within 10,000 neurons/day; optional external providers and paid-only models are excluded.
  • Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: ff602b1. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • claude-artifacts ↗

    Works with Claude Code, Cursor, Codex, Cline, Aider, or anything else that can run a shell command. Runs on Cloudflare's free tier.

  • workers ↗

    { "$schema": "node_modules/wrangler/config-schema.json", "name": "glance", "main": "src/index.ts", "account_id": "YOUR_ACCOUNT_ID", // EDIT: replace with your own (see README) "compatibility_date": "2026-06-01", "compatibility_flags": ["nodejs_compat"], "workers_dev": true, "observability": { "enabled": true }, // Non-secret config. Secrets (GOOGLE_CLIENT_ID/SECRET, SESSION_SECRET, and the optional // DATA_TOKEN_

  • d1 ↗

    ": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*"] }, "d1_databases": [ { "binding": "GLANCE_DB", "database_name": "glance-db", "database_id": "YOUR_D1_DATABASE_ID", // EDIT: replace with your own (see README) "migrations_dir": "drizzle" } ], // One hibernating Durable Object per site, fanning out glance.db change events to subscribed // pages (src/realtime/site-room.ts, re-exported from src/index.ts so class_name

  • kv ↗

    w_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice-comment transcription (Whisper). Optional at runtime (AI?: Ai) — // a deploy/dev without it degrades to a transcript placeholder, never an error. Workers AI has NO // local simulator, so `remote: true` proxies inference to the real

  • r2 ↗

    : "SITE_ROOM", "class_name": "SiteRoom" }] }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice-comment transcription (Whisper). Optional at runtime (AI?: Ai) — // a deploy/dev without it degrades to a transcript placeholder, never an error. W

  • durable-objects ↗

    es). // new_sqlite_classes, NOT new_classes: KV-backed Durable Objects are not on the free plan. "durable_objects": { "bindings": [{ "name": "SITE_ROOM", "class_name": "SiteRoom" }] }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice

  • workers-ai ↗

    SSION_SECRET, and the optional // DATA_TOKEN_SECRET for the glance.db shared backend) via `wrangler secret put`. // setup.sh wires the YOUR-SUBDOMAIN sentinel from the live workers.dev URL on deploy. "vars": { "APP_URL": "https://glance.YOUR-SUBDOMAIN.workers.dev", // EDIT: replace with your own (see README) "CONTENT_URL": "https://glance-content.YOUR-SUBDOMAIN.workers.dev", // EDIT: replace with your own (see README) "ALLOWED_HD": "yourcompany.com", // EDIT: replace with your ow

  • free-tier-eligible ↗

    { "$schema": "node_modules/wrangler/config-schema.json", "name": "glance", "main": "src/index.ts", "account_id": "YOUR_ACCOUNT_ID", // EDIT: replace with your own (see README) "compatibility_date": "2026-06-01", "compatibility_flags": ["nodejs_compat"], "workers_dev": true, "observability": { "enabled": true }, // Non-secret config. Secrets (GOOGLE_CLIENT_ID/SECRET, SESSION_SECRET, and the optional // DATA_TOKEN_

  • free-tier-eligible ↗

    ": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*"] }, "d1_databases": [ { "binding": "GLANCE_DB", "database_name": "glance-db", "database_id": "YOUR_D1_DATABASE_ID", // EDIT: replace with your own (see README) "migrations_dir": "drizzle" } ], // One hibernating Durable Object per site, fanning out glance.db change events to subscribed // pages (src/realtime/site-room.ts, re-exported from src/index.ts so class_name

  • free-tier-eligible ↗

    w_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice-comment transcription (Whisper). Optional at runtime (AI?: Ai) — // a deploy/dev without it degrades to a transcript placeholder, never an error. Workers AI has NO // local simulator, so `remote: true` proxies inference to the real

  • free-tier-eligible ↗

    up>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 seconds)<br> Max of 15 minutes of CPU time per [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) or [Queue Consumer](https://developers.cloudflare.co

  • free-tier-eligible ↗

    oudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/observability/metrics-analytics/#query-via-the-graphql-api), or the [Cloudflare dashboard ↗︎](https://dash.cloudflare.com/?to=/:account/workers/d1/). Select your D1 database, then vie

  • free-tier-eligible ↗

    cing/). | | Free plan<sup>1</sup> | Paid plan | | --- | --- | --- | | Keys read | 100,000 / day | 10 million/month, + $0.50/million | | Keys written | 1,000 / day | 1 million/month, + $5.00/million | | Keys deleted | 1,000 / day | 1 million/month, + $5.00/million | | List requests | 1,000 / day | 1 million/month, + $5.00/million | | Stored data | 1 GB | 1 GB, + $0.50/ GB-month | <sup>1</sup> The Workers Free plan includes limited Workers KV usage. All limits reset daily at 00:00 UTC. If you exceed any one of these limits, further operations of that type will fail with an error. Note Workers KV pricing for read, write and delete operations is on a per-key basis. Bulk read operations are billed by the amount

  • free-tier-eligible ↗

    infrequent access storage) for 1.1 GB, you will be billed for 2 GB. ### Free tier You can use the following amount of storage and operations each month for free. | | Free | | --- | --- | | Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month | | Egress (data transfer to Internet) | Free <sup>[1](#user-content-fn-1)</sup> | Caution The free tier only applies to Standard storage, and does not apply to Infrequent Access storage. ### Storage usage Storage is billed using gigabyte-month (GB-month) as the billing metric. A GB-month is calculated by averaging the *peak* storage per day over a billing period (30 days). For examp

  • free-tier-eligible ↗

    jects are available both on Workers Free and Workers Paid plans. - **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available. - **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only available to accounts that already have a key-value-backed namespace. If you wish to downgrade from a Workers Paid plan to a Workers Free plan, you must first ensure that you have deleted all Durable Object namespaces with the key-value storage backend. On Workers Free plan: - If you exceed any one of the free tier limits, further operations of that type will fail with an error. - Daily free limits reset at 00:00 UTC. ## Compute billing Durable Objects are billed for compute duration (wall-clock time) while the Durable Object is actively running or is idle in memory but unable to [hibernate](https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/). Durable Objects that are idle and eligible for hibernation are not billed for duration, even before the runtime has hibernated them. Requests to a D

  • free-tier-eligible ↗

    000 Neurons**. Our free allocation allows anyone to use a total of **10,000 Neurons per day at no charge**. To use more than 10,000 Neurons per day, you need to sign up for the [Workers Paid plan](https://developers.cloudflare.com/workers/platform/pricing/#workers). On Workers Paid, you will be charged at $0.011 / 1,000 Neurons for any usage above the free allocation of 10,000 Neurons per day. You can monitor your Neuron usage in the [Cloudflare Workers AI dashboard ↗︎](https://dash.cloudflare.com/?to=/:account/ai/workers-ai). All limits reset daily at 00:00 UTC. If you exceed any one of the above limits, further operations will fail with an error. | | Free <br> allocation | Pricing | | --- | --- | --- | |

  • free-tier-eligible ↗

    billed accordingly. | | Free plan | Paid plan | | --- | --- | --- | | Requests | 100,000 / day | 1 million / month, + $0.15/million<br> Includes HTTP requests, RPC sessions<sup>1</sup>, WebSocket messages<sup>2</sup>, and alarm invocations | | Duration<sup>3</sup> | 13,000 GB-s / day | 400,000 GB-s / month, + $12.50/million GB-s<sup>4,5</sup> | <details> <summary> Footnotes </summary> <sup>1</sup> Each <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/">RPC session</a> is billed as one request to your Durable Object. Every <a href="https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/">RPC method call</a> on a <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects stub</a> is its own RPC session and therefore a single billed request. RPC method calls can return objects (stubs) extending <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/#lifetimes-memory-and-resource-management"><code>RpcTarget</code></a> and invo

  • free-tier-eligible ↗

    /). | | Workers Free plan | Workers Paid plan | | --- | --- | --- | | Rows reads <sup>1,2</sup> | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written <sup>1,2,3,4</sup> | 100,000 / day | First 50 million / month included + $1.00 / million rows | | SQL Stored data <sup>5</sup> | 5 GB (total) | 5 GB-month, + $0.20/ GB-month | <details> <summary> Footnotes </summary> <sup>1</sup> Rows read and rows written included limits and rates match <a href="https://developers.cloudflare.com/d1/platform/pricing/">D1 pricing</a>, Cloudflare's serverless SQL database. <sup>2</sup> Key-value methods like <code>get()</code>, <code>put()</code>, <code>delete()</code>, or <code>list(

  • MIT ↗

    MIT License Copyright (c) 2026 Plivo Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRIN

  • architecture ↗

    { "$schema": "node_modules/wrangler/config-schema.json", "name": "glance", "main": "src/index.ts", "account_id": "YOUR_ACCOUNT_ID", // EDIT: replace with your own (see README) "compatibility_date": "2026-06-01", "compatibility_flags": ["nodejs_compat"], "workers_dev": true, "observability": { "enabled": true }, // Non-secret config. Secrets (GOOGLE_CLIENT_ID/SECRET, SESSION_SECRET, and the optional // DATA_TOKEN_

  • architecture ↗

    ": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*"] }, "d1_databases": [ { "binding": "GLANCE_DB", "database_name": "glance-db", "database_id": "YOUR_D1_DATABASE_ID", // EDIT: replace with your own (see README) "migrations_dir": "drizzle" } ], // One hibernating Durable Object per site, fanning out glance.db change events to subscribed // pages (src/realtime/site-room.ts, re-exported from src/index.ts so class_name

  • architecture ↗

    w_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice-comment transcription (Whisper). Optional at runtime (AI?: Ai) — // a deploy/dev without it degrades to a transcript placeholder, never an error. Workers AI has NO // local simulator, so `remote: true` proxies inference to the real

  • architecture ↗

    : "SITE_ROOM", "class_name": "SiteRoom" }] }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice-comment transcription (Whisper). Optional at runtime (AI?: Ai) — // a deploy/dev without it degrades to a transcript placeholder, never an error. W

  • architecture ↗

    es). // new_sqlite_classes, NOT new_classes: KV-backed Durable Objects are not on the free plan. "durable_objects": { "bindings": [{ "name": "SITE_ROOM", "class_name": "SiteRoom" }] }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SiteRoom"] }], "r2_buckets": [{ "binding": "GLANCE_FILES", "bucket_name": "glance-files" }], "kv_namespaces": [{ "binding": "GLANCE_SESSIONS", "id": "YOUR_KV_NAMESPACE_ID" }], // EDIT: replace with your own (see README) // Workers AI: server-side voice

  • architecture ↗

    SSION_SECRET, and the optional // DATA_TOKEN_SECRET for the glance.db shared backend) via `wrangler secret put`. // setup.sh wires the YOUR-SUBDOMAIN sentinel from the live workers.dev URL on deploy. "vars": { "APP_URL": "https://glance.YOUR-SUBDOMAIN.workers.dev", // EDIT: replace with your own (see README) "CONTENT_URL": "https://glance-content.YOUR-SUBDOMAIN.workers.dev", // EDIT: replace with your own (see README) "ALLOWED_HD": "yourcompany.com", // EDIT: replace with your ow

  • architecture ↗

    served from a separate domain, so they can't read your login session. That's why Glance runs two Workers. To report a vulnerability, see [SECURITY.md](SECURITY.md). ## Development Built with Cloudflare Workers + Hono, React Router v7, D1, R2, and KV. The CLI is written in Go. ``` packages/api Worker: API + file serving packages/web React app packages/cli glance CLI ``` For local setup and checks, see [CONTRIBUTING.md](CONTRIBUTING.md). ## License [MIT](LICENSE)

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

external SaaS target
varies

How it works

The shape of Glance on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points2
glance
packages/api/wrangler.jsonc
glance-content
packages/api/wrangler.content.jsonc
↓
App
glance
entry
Cloudflare Workers
Entrypoint: src/index.tsConfigured cron (UTC): 0 * * * *Configured cron (UTC): 0 3 * * *
glance-content
entry
Cloudflare Workers
Entrypoint: src/content.ts
↓

Configuration and workflow sources

Reviewed commit ff602b197427. Files were read as data; upstream applications and CI jobs were not executed.

Partial source coverage: 123 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.

Deployment configuration · 2 files
packages/api/wrangler.jsonc ↗

Cloudflare Workers · compatibility 2026-06-01

glance · default

Entrypoint: src/index.ts

Static assets: ../web/dist · single-page-application · Worker first: ["/api/*"]

Cron triggers (UTC): 0 * * * * · 0 3 * * *

  • GLANCE_DB → D1
  • GLANCE_SESSIONS → KV
  • GLANCE_FILES → R2
  • SITE_ROOM → Durable Objects · class SiteRoom
  • AI → Workers AI
  • ASSETS → Static assets
packages/api/wrangler.content.jsonc ↗

Cloudflare Workers · compatibility 2026-06-01

glance-content · default

Entrypoint: src/content.ts

  • GLANCE_DB → D1
  • GLANCE_FILES → R2

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

packages/api/src/index.ts ↗
  • L159 · fetch handler exported
  • L160 · scheduled handler exported · references GLANCE_DB, GLANCE_SESSIONS · calls sessionDb, purgeRetention, cachedStats, p.then
  • L40 · app.use("*")
  • L65 · app.get("/api/install")
  • L77 · app.get("/api/glance.js")
  • L83 · app.route("/api/themes")
  • L88 · app.route("/api/_data")
  • L90 · app.use("/api/*")
  • L91 · app.use("/api/*")
  • L92 · app.use("/api/*")
  • L94 · app.get("/api/health")
  • L98 · app.get("/api/config")
  • L107 · app.route("/api/auth")
  • L108 · app.route("/api/spaces")
  • L109 · app.route("/api/sites")
  • L116 · app.route("/api/sites")
  • L119 · app.route("/api/sites")
  • L121 · app.route("/api/sites")
  • L123 · app.route("/api/sites")
  • L124 · app.route("/api/comments")
  • L125 · app.route("/api/upload")
  • L127 · app.route("/api/data-token")
  • L128 · app.route("/api/users")
  • L131 · app.route("/api/avatars")
  • L132 · app.route("/api/notifications")
  • L134 · app.route("/api/api-keys")
  • L135 · app.route("/api/whats-new")
  • L136 · app.route("/api/admin")
  • L140 · app.route("/api/slack")

Environment references: c.env.CONTENT_URL · c.env.BOOTSTRAP_TOKEN · env.GLANCE_DB · env.GLANCE_SESSIONS

packages/api/src/content.ts ↗
  • L69 · app.get("/")
  • L74 · app.get("/_glance/annotate.js")
  • L77 · app.get("/_glance/annotate.css")
  • L80 · app.get("/_glance/db.js")
  • L86 · app.get("/_glance/theme/:file{[a-z0-9-]+\\.css}")
  • L96 · app.get("/_glance/theme/fonts/:file{[a-z0-9-]+\\.woff2}")
  • L115 · app.get("/_glance/og/:space/:site{[a-z0-9-]+\\.png}")
  • L137 · app.get("/_t/:token/:space/:site/*")
  • L146 · app.get("/:space/:site/*")
  • L42 · getDb calls (conditional paths may differ): c.get, sessionDb
  • L49 · getCache calls (conditional paths may differ): c.get
  • L58 · readStoredObject calls (conditional paths may differ): readFullObject, getCache, fireAndForget
  • L65 · notFound calls (conditional paths may differ): c.text
  • L149 · serve calls (conditional paths may differ): getDb, normalizePath, limit, where, innerJoin, from, db.select, eq, and, reqPath.endsWith, fetchAccessFacts, notFound, c.text, checkAccess, isSharedFromFacts, reqPath.slice, filter, all.map, p.startsWith, directoryListing
  • L333 · serveStoredObject calls (conditional paths may differ): themeHref.replace, etag.endsWith, etag.slice, c.req.header, c.env.GLANCE_FILES.head, notFound, withTheme, headers.set, view, headers.delete, c.env.GLANCE_FILES.get, decideRange, serveFullDirect, readStoredObject, transformServedHtml
  • L461 · trackView calls (conditional paths may differ): fireAndForget, recordEvent
  • L467 · restOf calls (conditional paths may differ): pathname.endsWith, map, slice, filter, pathname.split, decodeURIComponent, segs.join
  • L485 · isHtmlFile calls (conditional paths may differ): test
  • L494 · injectAnnotate calls (conditional paths may differ): replace, JSON.stringify, html.includes, html.replace
  • L518 · injectDb calls (conditional paths may differ): replace, JSON.stringify, html.includes, html.replace, test, exec, html.slice
  • L566 · transformServedHtml calls (conditional paths may differ): on, el.getAttribute, isExternalHref, el.setAttribute, rewriter.on, el.append, rewriter.onDocument, end.append, rewriter.transform
  • L597 · normalizePath calls (conditional paths may differ): rest.endsWith, join, filter, rest.split
  • L621 · markdownCsp calls (conditional paths may differ): join
  • L641 · directoryListing calls (conditional paths may differ): sort, filter, paths.map, p.slice, join, rels.map, map, full.split, escapeHtml, c.html
  • L664 · renderMarkdownDoc calls (conditional paths may differ): escapeHtml

Environment references: c.env.GLANCE_DB · c.env.GLANCE_FILES · c.env.CONTENT_TOKEN_SECRET · c.env.OG_RENDER · c.env.APP_URL

packages/api/src/db/client.ts ↗
  • L12 · sessionDb calls (conditional paths may differ): drizzle, binding.withSession

Environment references: c.env.GLANCE_DB

packages/api/src/db/repo.ts ↗
  • L23 · getUserById calls (conditional paths may differ): limit, where, from, db.select, eq
  • L39 · getUserByEmail calls (conditional paths may differ): limit, where, from, db.select, eq, email.toLowerCase
  • L52 · superadminExists calls (conditional paths may differ): limit, where, from, db.select, eq
  • L59 · superadminStatus calls (conditional paths may differ): where, from, db.select, eq, configuredEmail.toLowerCase, admins.some
  • L73 · isUniqueConstraintError calls (conditional paths may differ): String, test
  • L85 · createPersonalSpace calls (conditional paths may differ): slugifyHandle, RESERVED_SLUGS.has, Array.from, createSpace, email.split, isUniqueConstraintError
  • L109 · bootstrapSuperadminByEmail calls (conditional paths may differ): rawEmail.toLowerCase, limit, where, from, db.select, eq, set, db.update, toSessionUser, crypto.randomUUID, values, db.insert, createPersonalSpace
  • L137 · createSpace calls (conditional paths may differ): crypto.randomUUID, db.batch, values, db.insert
  • L149 · isSpaceMember calls (conditional paths may differ): limit, where, from, db.select, and, eq
  • L166 · directShareStmt calls (conditional paths may differ): where, from, db.select, and, eq
  • L175 · groupReachStmt calls (conditional paths may differ): where, innerJoin, from, db.select, eq, and
  • L185 · resolveIsShared calls (conditional paths may differ): batchAll, limit, directShareStmt, groupReachStmt
  • L199 · resolveShareRole calls (conditional paths may differ): limit, directShareStmt
  • L210 · memberSpaceIdsStmt calls (conditional paths may differ): where, from, db.select, eq
  • L224 · sharedSiteRoles calls (conditional paths may differ): batchAll, sharedSiteRoleStmts, foldSharedSiteRoles
  • L232 · sharedSiteRoleStmts calls (conditional paths may differ): directShareStmt, groupReachStmt
  • L238 · foldSharedSiteRoles calls (conditional paths may differ): roles.set
  • L249 · foldMemberSpaceIds calls (conditional paths may differ): rows.map
  • L274 · listMentionableUsers calls (conditional paths may differ): orderBy, where, from, db.select, ne, eq, innerJoin, Promise.all, ids.add, ids.delete, inArray
  • L326 · listSiteShares calls (conditional paths may differ): where, from, db.select, eq, u.map, g.map
  • L346 · replaceSiteShares calls (conditional paths may differ): batchAll, where, db.delete, eq, users.map, values, db.insert, groupIds.map
packages/api/src/lib/retention.ts ↗
  • L27 · purgeRetention calls (conditional paths may differ): cutoff, then, where, from, db.select, count, and, eq, lt, Number, onConflictDoUpdate, values, db.insert, db.delete, isNotNull
packages/api/src/lib/stats.ts ↗
  • L61 · scalarCount calls (conditional paths may differ): Number
  • L72 · computeTotals calls (conditional paths may differ): Promise.all, scalarCount, from, db.select, count, then, Number, where, isNull, eq
  • L108 · computeWindow calls (conditional paths may differ): now.getTime, dayKey, Promise.all, then, where, from, db.select, and, eq, gte, Number, groupBy, count, isNull, limit, orderBy, desc, buildSeries, topSites.map
  • L233 · readEntry calls (conditional paths may differ): kv.get, JSON.parse
  • L252 · cachedHalf calls (conditional paths may differ): compute, kv.put, JSON.stringify, now.getTime, readEntry, defer, catch, then, write
  • L288 · cachedStats calls (conditional paths may differ): Promise.all, cachedHalf, computeTotals, computeWindow
  • L304 · buildSeries calls (conditional paths may differ): Object.entries, rows.map, Number, dayKey, now.getTime, out.push, index.signups.get, index.sites.get, index.views.get, index.comments.get
packages/api/src/lib/bootstrap.ts ↗
  • L13 · secretEquals calls (conditional paths may differ): crypto.getRandomValues, crypto.subtle.importKey, Promise.all, crypto.subtle.sign, enc.encode
  • L60 · bootstrapDecision calls (conditional paths may differ): secretEquals, input.status
packages/api/src/lib/oauth.ts ↗
  • L8 · isGoogleEnabled calls (conditional paths may differ): Boolean
  • L15 · createGoogle calls (conditional paths may differ): isGoogleEnabled

Environment references: env.GOOGLE_CLIENT_ID · env.GOOGLE_CLIENT_SECRET · env.APP_URL

packages/api/src/middleware/analytics.ts ↗
  • L17 · isCliRequest calls (conditional paths may differ): bearerToken, getCookie
  • L24 · cliVersionOf calls (conditional paths may differ): bearerToken, parseCliVersion, c.req.header
packages/api/src/middleware/auth.ts ↗

    Environment references: c.env.APP_URL

    packages/api/src/routes/admin.ts ↗
    • L19 · admin.use("*")
    • L23 · admin.get("/sites")
    • L62 · admin.patch("/sites/:id/archive")
    • L72 · admin.patch("/sites/:id/restore")
    • L83 · admin.delete("/sites/:id")
    • L94 · admin.get("/spaces")
    • L113 · admin.get("/users")
    • L132 · admin.post("/users/:id/revoke-cli")
    • L145 · admin.get("/stats")

    Environment references: c.env.GLANCE_FILES · c.env.GLANCE_SESSIONS

    packages/api/src/routes/api-keys.ts ↗
    • L23 · apiKeys.use("*")
    • L37 · apiKeys.post("/")
    • L99 · apiKeys.get("/")
    • L126 · apiKeys.delete("/:id")
    • L28 · activeKeyCount calls (conditional paths may differ): toISOString, where, from, db.select, count, and, eq, isNull, gt
    packages/api/src/routes/ask.ts ↗
    • L70 · ask.use("*")
    • L72 · ask.post("/:space/:site/ask")
    • L39 · validateBody calls (conditional paths may differ): question.trim
    • L52 · gated calls (conditional paths may differ): c.get, c.req.param, limit, where, innerJoin, from, db.select, eq, and, fetchAccessFacts, siteAccessFromFacts, c.json

    Environment references: c.env.ASK_LIMITER

    packages/api/src/routes/auth.ts ↗
    • L46 · auth.get("/google")
    • L64 · auth.get("/callback")
    • L99 · auth.post("/logout")
    • L120 · auth.get("/me")
    • L133 · auth.post("/dev-login")
    • L157 · auth.post("/bootstrap")
    • L229 · auth.post("/cli/start")
    • L247 · auth.get("/cli/poll")
    • L258 · auth.post("/cli/approve")
    • L25 · safeNext calls (conditional paths may differ): next.startsWith
    • L198 · generateUserCode calls (conditional paths may differ): crypto.getRandomValues
    • L216 · isCliStartRateLimited calls (conditional paths may differ): Number, kv.get, kv.put, String
    • L277 · findOrCreateUser calls (conditional paths may differ): limit, where, from, db.select, eq, sanitizeAvatarUrl, set, db.update, toSessionUser, crypto.randomUUID, env.SUPERADMIN_EMAIL.toLowerCase, values, db.insert, createPersonalSpace

    Environment references: c.env.ALLOWED_HD · c.env.SESSION_SECRET · c.env.APP_URL · c.env.SUPERADMIN_EMAIL · c.env.GLANCE_SESSIONS · c.env.BOOTSTRAP_TOKEN · env.SUPERADMIN_EMAIL

    packages/api/src/routes/avatars.ts ↗
    • L26 · avatars.use("*")
    • L28 · avatars.get("/:userId")
    packages/api/src/routes/comment-feed.ts ↗
    • L15 · commentFeed.use("*")
    • L17 · commentFeed.get("/feed")
    packages/api/src/routes/comments.ts ↗
    • L437 · comments.use("*")
    • L448 · comments.get("/:space/:site/comments")
    • L469 · comments.get("/:space/:site/mentionable")
    • L481 · comments.get("/:space/:site/comments/audio/:commentId")
    • L525 · comments.get("/:space/:site/comments/socket")
    • L563 · comments.post("/:space/:site/comments")
    • L660 · comments.post("/:space/:site/comments/:threadId/replies")
    • L713 · comments.patch("/:space/:site/comments/:threadId")
    • L728 · comments.patch("/:space/:site/comments/:threadId/messages/:commentId")
    • L752 · comments.delete("/:space/:site/comments/:threadId/messages/:commentId")
    • L826 · comments.put("/:space/:site/comments/:threadId/messages/:commentId/reactions")
    • L847 · comments.delete("/:space/:site/comments/:threadId/messages/:commentId/reactions")
    • L91 · stripControlChars calls (conditional paths may differ): s.replace
    • L110 · siteFromFacts calls (conditional paths may differ): siteAccessFromFacts, c.get, c.json
    • L123 · gated calls (conditional paths may differ): c.req.param, fetchAccessFacts, c.get, siteFromFacts
    • L144 · siteWithUrlThread calls (conditional paths may differ): c.req.param, gated, threadByIdStmt, c.get
    • L159 · siteWithUrlComment calls (conditional paths may differ): c.get, c.req.param, gated, commentByIdStmt, threadByIdStmt, c.json, threadInSite, rows.slice
    • L182 · cleanBody calls (conditional paths may differ): trim, stripControlChars
    • L195 · notifyForComment calls (conditional paths may differ): c.get, c.req.param, fireAndForget, Array.isArray, opts.rawMentions.filter, map, listMentionableUsers, requested.filter, allowed.has, resolveCommentAudience, mentionRecipients.map, truncateSnippet, createNotifications, audience.map, toRow, deliverSlackForComment
    • L277 · deliverSlackForComment calls (conditional paths may differ): slackEnabled, c.get, usersEmailsByIds, audience.map, emails.get, deliverSlack, slackDepsFromEnv
    • L306 · pushThreadCreated calls (conditional paths may differ): c.get, buildThreadCreatedView, notifyCommentEvent
    • L325 · pushCommentCreated calls (conditional paths may differ): c.get, buildCommentCreatedView, notifyCommentEvent
    • L350 · parseThreadFields calls (conditional paths may differ): tooLong, normalizeText, stripControlChars, parseElementAnchor, parseTextContext
    • L387 · jsonField calls (conditional paths may differ): JSON.parse
    • L401 · ingestVoiceComment calls (conditional paths may differ): form.get, c.json, audioExtFromPart, part.arrayBuffer, crypto.randomUUID, Promise.all, transcribeVoice, c.env.GLANCE_FILES.put, slice, trim, stripControlChars
    • L601 · createVoiceThread calls (conditional paths may differ): catch, c.req.formData, c.json, jsonField, form.get, parseThreadFields, ingestVoiceComment, createThread, c.get, deleteKeys, pushThreadCreated, notifyForComment
    • L685 · replyVoiceComment calls (conditional paths may differ): catch, c.req.formData, c.json, ingestVoiceComment, addComment, c.get, deleteKeys, pushCommentCreated, notifyForComment
    • L787 · cleanEmoji calls (conditional paths may differ): trim, stripControlChars
    • L798 · reactionTarget calls (conditional paths may differ): c.req.param, siteWithUrlComment, reactionsByCommentStmt, c.get, catch, c.req.json, cleanEmoji, c.json

    Environment references: c.env.SLACK_BOT_TOKEN · c.env.AI · c.env.GLANCE_FILES · c.env.SITE_ROOM · c.env.DATA_TOKEN_SECRET

    packages/api/src/routes/summary.ts ↗
    • L90 · summary.use("*")
    • L92 · summary.get("/:space/:site/summary")
    • L102 · summary.post("/:space/:site/summary")
    • L57 · gated calls (conditional paths may differ): c.get, c.req.param, and, eq, limit, where, innerJoin, from, db.select, fetchAccessFacts, siteAccessFromFacts, c.json

    Environment references: c.env.SUMMARY_LIMITER · c.env.GLANCE_FILES

    packages/api/src/routes/data.ts ↗
    • L71 · dataApi.use("*")
    • L90 · dataApi.use("*")
    • L140 · dataApi.use("*")
    • L153 · dataApi.get("/_sync/changes")
    • L195 · dataApi.get("/_sync/socket")
    • L226 · dataApi.post("/:collection")
    • L267 · dataApi.get("/:collection/:docId")
    • L285 · dataApi.get("/:collection")
    • L306 · dataApi.put("/:collection/:docId")
    • L387 · dataApi.delete("/:collection/:docId")
    • L512 · dataToken.use("*")
    • L523 · dataToken.post("/:space/:site")
    • L61 · getDb calls (conditional paths may differ): c.get, sessionDb
    • L128 · credential calls (conditional paths may differ): c.req.header, trim, header.slice, isUpgrade, subprotocols
    • L409 · docWhere calls (conditional paths may differ): eq
    • L417 · readCreatorWhere calls (conditional paths may differ): readsEveryCreator, eq
    • L424 · scoped calls (conditional paths may differ): and, docWhere, eq
    • L432 · atSiteDocQuota calls (conditional paths may differ): where, from, db.select, count, eq
    • L440 · readJsonBody calls (conditional paths may differ): Number, c.req.header, Number.isFinite, c.req.text, enc.encode, JSON.parse, Array.isArray
    • L466 · clampLimit calls (conditional paths may differ): Number, Number.isFinite, Math.min, Math.floor
    • L507 · intersectCaps calls (conditional paths may differ): base.filter, ceiling.includes

    Environment references: c.env.GLANCE_DB · c.env.DATA_TOKEN_SECRET · c.env.CONTENT_URL · c.env.SITE_ROOM

    packages/api/src/routes/notifications.ts ↗
    • L12 · notifications.use("*")
    • L15 · notifications.get("/")
    • L21 · notifications.post("/read")
    packages/api/src/routes/whats-new.ts ↗
    • L14 · whatsNew.use("*")
    • L20 · whatsNew.get("/")
    • L30 · whatsNew.post("/seen")
    packages/api/src/routes/sites.ts ↗
    • L148 · sites.post("/")
    • L209 · sites.get("/mine")
    • L226 · sites.get("/shared")
    • L267 · sites.get("/team")
    • L295 · sites.get("/search")
    • L317 · sites.get("/:spaceSlug/:siteSlug/exists")
    • L339 · sites.get("/:spaceSlug/:siteSlug")
    • L482 · sites.get("/:spaceSlug/:siteSlug/shares")
    • L501 · sites.put("/:spaceSlug/:siteSlug/shares")
    • L547 · sites.patch("/:spaceSlug/:siteSlug")
    • L588 · sites.post("/:spaceSlug/:siteSlug/move")
    • L646 · sites.post("/:spaceSlug/:siteSlug/fork")
    • L759 · sites.delete("/:spaceSlug/:siteSlug")
    • L54 · escapeLike calls (conditional paths may differ): s.replace
    • L82 · searchSites calls (conditional paths may differ): escapeLike, toLowerCase, q.trim, batchAll, memberSpaceIdsStmt, sharedSiteRoleStmts, foldMemberSpaceIds, keys, foldSharedSiteRoles, eq, or, map, chunk, inArray, as, reaches.map, limit, orderBy, where, innerJoin
    • L419 · parseShareGrants calls (conditional paths may differ): Array.isArray, v.filter, groupObjs.some, roles.set, asIds, roles.has, groupObjs.map, map
    • L442 · notifyForShare calls (conditional paths may differ): c.get, c.req.param, fireAndForget, createNotifications, recipientIds.map, slackEnabled, usersEmailsByIds, emails.get, deliverSlack, slackDepsFromEnv
    • L629 · freeForkSlug calls (conditional paths may differ): map, where, from, db.select, eq, taken.has, isValidSlug

    Environment references: c.env.APP_URL · c.env.CONTENT_URL · c.env.CONTENT_TOKEN_SECRET · c.env.SLACK_BOT_TOKEN · c.env.GLANCE_FILES

    Build and deployment pipeline · 3 GitHub Actions workflows

    Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

    CI · .github/workflows/ci.yml ↗

    Triggers: push, pull_request

    build · no job dependencies declared

    1. actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
    2. oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
    3. Install dependenciesbun install --frozen-lockfile
    4. Generate Cloudflare typesbun run cf-typegen
    5. Typecheckbun run typecheck
    6. Build webbun run build:web
    7. Testbun run test
    8. Lintbun run lint

    cli · no job dependencies declared

    1. actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
    2. actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303eactions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
    3. Vet + testtest -z "$(gofmt -l .)" || { echo "gofmt drift:"; gofmt -l .; exit 1; } go vet ./... go test ./...
    4. Build sanity (all release targets)for t in "darwin arm64" "darwin amd64" "linux arm64" "linux amd64"; do set -- $t CGO_ENABLED=0 GOOS="$1" GOARCH="$2" go build -o /dev/null . || exit 1 done
    Deploy · .github/workflows/deploy.yml ↗

    Triggers: push

    deploy · no job dependencies declared

    1. actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
    2. oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
    3. Install dependenciesbun install --frozen-lockfile
    4. Wire Cloudflare binding IDs into config: "${CF_ACCOUNT_ID:?set repo Actions variable CF_ACCOUNT_ID}" : "${CF_D1_DATABASE_ID:?set repo Actions variable CF_D1_DATABASE_ID}" : "${CF_KV_NAMESPACE_ID:?set repo Actions variable CF_KV_NAMESPACE_ID}" : "${CF_WORKERS_SUBDOMAIN:?set repo Actions variable CF_WORKERS_SUBDOMAIN}" for f in packages/api/wrangler.jsonc packages/api/wrangler.content.jsonc packages/web/public/_headers; do sed -i \ -e "s/YOUR_ACCOUNT_ID/${CF_ACCOUNT_ID}/g" \ -e "s/YOUR_D1_DATABASE_ID/${CF_D1_DATABASE_ID}/g" \ -e "s/YO…
    5. Build webbun run build:web
    6. Regenerate committed bundlesbun run build:annotate bun run build:db bun run build:install bun run build:whatsnew bun run build:themes
    7. Apply D1 migrations (remote)bunx wrangler d1 migrations apply glance-db --remote
    8. Ensure D1 read replication is enabledout="$(curl -sS -X PUT "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/d1/database/${CF_D1_DATABASE_ID}" \ -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"read_replication": {"mode": "auto"}}')" echo "$out" | grep -q '"success": *true' || { echo "$out" >&2; exit 1; }
    9. Deploy main worker: "${CF_ALLOWED_HD:?set repo Actions variable CF_ALLOWED_HD}" : "${CF_SUPERADMIN_EMAIL:?set repo Actions variable CF_SUPERADMIN_EMAIL}" bunx wrangler deploy \ --var "APP_URL:${APP_URL}" \ --var "CONTENT_URL:${CONTENT_URL}" \ --var "ALLOWED_HD:${CF_ALLOWED_HD}" \ --var "SUPERADMIN_EMAIL:${CF_SUPERADMIN_EMAIL}"
    10. Deploy content workerbunx wrangler deploy --config wrangler.content.jsonc --var "APP_URL:${APP_URL}"
    release · .github/workflows/release.yml ↗

    Triggers: push

    build · no job dependencies declared

    1. actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
    2. actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303eactions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
    3. Build standalone binariesmkdir -p dist # Stamp the release tag (v1.2.3 -> 1.2.3) into the binary via -ldflags -X so it reports the # exact release version in its User-Agent (drives CLI-usage analytics) and self-update compare. v="${GITHUB_REF_NAME#v}" while read -r goos goarch out; do CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" \ go build -trimpath -ldflags="-s -w -X main.version=$v" -o "dist/$out" . # Checksum the uncompressed binary (what actually runs), then gzip to shrink the download. # The installer + self-update…
    4. Publish releasesoftprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228
    package.json ↗
    • build:web: cd packages/web && bunx vite build
    • deploy: bun run build:web && cd packages/api && bunx wrangler deploy && bunx wrangler deploy --config wrangler.content.jsonc
    packages/api/package.json ↗
    • build:annotate: bun scripts/build-annotate.ts
    • build:db: bun scripts/build-db.ts
    • build:themes: bun scripts/build-themes.ts
    • build:install: bun scripts/build-install.ts
    • build:whatsnew: bun scripts/build-whatsnew.ts
    • deploy: wrangler deploy
    • deploy:content: bun run build:annotate && bun run build:db && bun run build:themes && wrangler deploy --config wrangler.content.jsonc

    Full upstream document by @plivo-labs · README.md · snapshot ff602b1

    Glance

    Self-hosted artifacts for any coding agent. Your agent builds a page, dashboard, or app and publishes it to a live URL with one command. You leave comments in the browser, like in a Google Doc. The agent reads them and fixes the page.

    Works with Claude Code, Cursor, Codex, Cline, Aider, or anything else that can run a shell command. Runs on Cloudflare's free tier.

    Glance demo: an agent runs glance deploy and gets a URL, a reviewer highlights a number on the page and comments, a Slack DM notifies the author, the agent reads the thread with glance comments and redeploys, and the thread is resolved

      agent builds  →  glance deploy  →  URL
           ↑                               ↓
      reads comments, fixes  ←  you comment in the browser
    

    Self-host

    You need a Cloudflare account with R2 turned on (dashboard → R2 → accept the terms; it's still free). Then run:

    bun install
    bunx wrangler login
    scripts/setup.sh
    

    setup.sh creates the resources, deploys the app, and prints your URL plus a bootstrap token. Open the printed /login page and paste the token into Complete setup to become the first admin. You can run the script again safely.

    Using more than one Cloudflare account? Run export CLOUDFLARE_ACCOUNT_ID=<id> first. For manual setup or Google SSO, see DEPLOY.md.

    Use it

    curl -fsSL https://<your-instance>/api/install | sh   # installs the CLI and the agent skill
    glance login
    glance deploy ./my-report                             # file or folder → live URL
    glance comments <space/slug>                          # read review comments
    

    Run glance with no arguments to see every command.

    The install script also adds the agent skill for Claude Code. For other agents, run:

    npx skills add plivo-labs/glance
    

    CI: create an API key at /settings/keys and set GLANCE_TOKEN=glk_.... For the HTTP API, see packages/api/API.md.

    Features

    • Visibility: each site is private, members, or team. Every link requires a login; nothing is public.
    • Audio: audio files play in a built-in player. You can record audio or leave voice comments in the browser. Voice comments are transcribed, so agents read them as text.
    • Slack (optional): get comment notifications as Slack DMs, and Glance links show as preview cards in Slack. See DEPLOY.md.
    • glance.db (experimental, opt-in): a small JSON document store your pages can use directly from the browser. See SHARED_BACKEND.md.

    Security

    Uploaded HTML and JS are treated as untrusted. They are served from a separate domain, so they can't read your login session. That's why Glance runs two Workers. To report a vulnerability, see SECURITY.md.

    Development

    Built with Cloudflare Workers + Hono, React Router v7, D1, R2, and KV. The CLI is written in Go.

    packages/api   Worker: API + file serving
    packages/web   React app
    packages/cli   glance CLI
    

    For local setup and checks, see CONTRIBUTING.md.

    License

    MIT

    Frequently asked about Glance

    What is Glance?+

    Glance is a self-hosted Claude Artifacts alternative built on the Cloudflare developer platform. Publish agent-created pages and review them through shareable links and comments.

    What does Glance replace?+

    Glance is listed as an alternative to Claude Artifacts. Compare the features and tradeoffs before migrating.

    What Cloudflare primitives does Glance use?+

    Glance is built on D1, Durable Objects, KV, R2, Workers, Workers AI.

    How much does Glance cost to run?+

    The documented Glance deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs. Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits. Keep aggregate D1 use below 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; a request can touch many rows. Keep KV below 100,000 reads/day, 1,000 writes, deletes and list operations/day each, and 1 GB; cache refreshes and backups consume writes. Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account. Use the configured SQLite Durable Object classes within 100,000 requests/day, 13,000 GB-s duration/day, 5 million SQL rows read/day, 100,000 written/day and 5 GB storage; active sockets consume duration. Only ordinary Free-eligible Workers AI models are covered, within 10,000 neurons/day; optional external providers and paid-only models are excluded. Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations. Check current Cloudflare pricing before deploying.

    Is Glance open source?+

    The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/plivo-labs/glance/ff602b1974274b39be719f82833e0aefd1ed3acc/LICENSE. Source code and contributor credit are available at https://github.com/plivo-labs/glance.

    Discussion · 0

    sign in to comment →
    No comments yet — be the first.