TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
Quickly move text, links and files between your devices – no login.
ProductivityTypeScript · AstroGitHub @sven42xyzOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Text, Links und Dateien schnell zwischen Geräten teilen – ohne Login. Astro + Cloudflare Workers, Durable Objects, R2.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Live: https://tee.chapter42.de
Move text, links and files between devices quickly (e.g. phone → laptop), no login.
The name comes from the Unix command tee: one input, many outputs. Whatever one device sends into a room arrives on all the others.
Rooms are deleted automatically 24 h after the last activity (at the latest 7 days after creation), including all files.
Browser ──HTTP──▶ Worker (src/worker.ts)
▲ ├─ /api/* → src/server/api.ts ──RPC──▶ Durable Object "Room" (one per room, SQLite)
│ │ │ ├─ items/meta in SQLite
│ │ └─ upload/download ──▶ R2 (rooms/<CODE>/<id>)
│ └─ everything else → Astro (SSR + static assets)
└────WebSocket (Hibernation API) ◀──────────────────────────┘
src/worker.ts. It exports the Room Durable Object and hands every non-API request to Astro (@astrojs/cloudflare/handler).src/server/room.ts), SQLite-backed:
meta (code, encrypted, created_at, last_activity) and items (text/link/file, file metadata, status pending / ready).ctx.acceptWebSocket). ping/pong is handled by setWebSocketAutoResponse, so the DO does not have to wake up for it.rooms/<CODE>/ and then the entire DO storage.FixedLengthStream, then the DO commits the item and broadcasts it to every device.cf-connecting-ip).Quota Durable Object (src/server/quota.ts, idFromName('global')):
siteverify. Without configured keys, Turnstile is disabled.src/middleware.ts) picks the language from the lang cookie, then Accept-Language, then falls back to English. ?lang=de / ?lang=en sets the cookie and redirects to the URL without the parameter. Room URLs stay language-neutral (/<CODE>). All UI strings live in src/i18n/index.ts.Rooms are end-to-end encrypted by default (src/scripts/crypto.ts, Web Crypto, AES-256-GCM):
/ABC234#k=…. Browsers never send the fragment to the server; the QR code and "copy link" include it.{ k: 'text'|'link', c } encrypted as JSON, Base64url(IV | ciphertext).TEE1 | nonce prefix[8] | chunk size, IV per chunk = prefix | chunk index, and the AAD marks the last chunk (detects reordering and truncation). Name, type and original size travel encrypted in the X-Filename header (X-Encrypted: 1).409 wrong_mode).Web Crypto is only available in secure contexts (HTTPS,
localhost). When testing overhttp://<LAN-IP>, encrypted rooms therefore do not work; unencrypted ones do.
src/server/security.ts adds security headers to every Worker response (except WebSocket upgrades):
challenges.cloudflare.com); no inline scripts or styles, frame-ancestors 'none', object-src 'none'. Astro is configured to always ship JS and CSS as files (inlineStylesheets: 'never', assetsInlineLimit: 0). Skipped in astro dev, where Vite injects styles at runtime.When adding third-party resources, extend the CSP in security.ts, otherwise the browser blocks them.
Runbook: docs/abuse.md.
Moderation Durable Object (180 days) and emailed to the operator via the send_email binding (Email Routing).ADMIN_SECRET, valid 14 days) to /admin/<CODE>: metadata and reports, never content, plus the actions block, freeze (keep up to 90 days for the authorities), unblock and delete.451 everywhere and disconnect all devices./nutzungsbedingungen.| Method & path | Purpose |
|---|---|
POST /api/rooms | Body { turnstileToken, encrypted } → { code } (Turnstile + rate limit RL_CREATE) |
GET /api/rooms/:code | Does the room exist? 200 / 404 |
GET /api/rooms/:code/ws | WebSocket for live sync |
POST /api/rooms/:code/text | { text } (links are detected) or { ciphertext } in encrypted rooms |
POST /api/rooms/:code/files | Raw body. Plain: headers X-Filename (URL-encoded) and Content-Type. Encrypted: X-Encrypted: 1, X-Filename = encrypted metadata |
GET /api/rooms/:code/files/:id | Download (Content-Disposition: attachment) |
DELETE /api/rooms/:code/items/:id | Delete an item |
POST /api/rooms/:code/report | { category, description, contact?, itemId?, goodFaith: true } → { id, blocked } |
POST /api/admin/rooms/:code | { action: block|freeze|unblock|delete, exp, sig } (signed link from the report email) |
WebSocket messages (server → client): init, item, delete, devices. See src/shared/types.ts.
Error responses are { error, message }. The client translates the error code; message is English and meant for debugging.
| Max. file size | 100 MB (src/shared/limits.ts) |
| Max. items per room | 50 |
| Max. text length | 100 KB |
| Room creation | 10 / minute / IP |
| Uploads, text, deletes | 30 / minute / IP |
| Upload volume | 2 GB / day / IP (IPv6: per /64) |
| Total file storage | 50 GB (uploads are rejected beyond that) |
| Lifetime | 24 h after the last activity, at most 7 days |
The rate limiting binding counts per Cloudflare location and is not exact. That is good enough for abuse protection here. Cloudflare limits the request body size by plan (Free/Pro: 100 MB). Higher limits would need multipart uploads.
src/
├─ worker.ts Worker entrypoint (API + Astro + export Room)
├─ middleware.ts Language detection (cookie / Accept-Language / ?lang=)
├─ server/
│ ├─ api.ts API routes, validation, rate limiting, Turnstile, R2 streaming
│ ├─ room.ts Durable Object per room (SQLite, WebSockets, alarm, block/freeze)
│ ├─ quota.ts Durable Object enforcing storage and upload limits
│ ├─ moderation.ts Durable Object for reports, signed admin links, report emails
│ └─ security.ts Security headers (CSP, HSTS, …) for every response
├─ shared/ Code shared by client and server
│ ├─ codes.ts Room code generation / validation
│ ├─ limits.ts Limits & TTL
│ ├─ types.ts Item and message types
│ ├─ legal.ts Provider details for Impressum / privacy policy
│ └─ site.ts Repo and support links
├─ i18n/index.ts UI strings DE/EN, error codes → messages
├─ pages/
│ ├─ index.astro Landing page
│ ├─ [code].astro Room view (404 for unknown codes, noindex)
│ ├─ admin/[code].astro Moderation page (signed link only)
│ ├─ nutzungsbedingungen.astro Terms of use
│ ├─ impressum.astro Legal notice
│ └─ datenschutz.astro Privacy policy
├─ scripts/
│ ├─ room-client.ts WebSocket, reconnect, uploads with progress, drag & drop, decryption
│ ├─ crypto.ts End-to-end encryption (keys, texts, chunked files)
│ ├─ report-client.ts "Report content" dialog
│ ├─ turnstile.ts Turnstile widget (explicit rendering)
│ └─ ui.ts Toast, clipboard
├─ components/ SiteHeader, SiteFooter, Logo, LangSwitch, TransferScene (animation)
├─ layouts/ Base, LegalPage
└─ styles/global.css Color tokens, light/dark mode, base styles
test/ Vitest suites (Workers runtime) and test entrypoint
scripts/smoke.mjs Smoke test against the build
og/ Template and render script for the social preview images
docs/abuse.md Runbook for reports and illegal content
Requirements: Node.js ≥ 22, a Cloudflare account for deployment.
npm install
cp .dev.vars.example .dev.vars # Turnstile test keys for local development
npm run dev # astro dev, runs in workerd incl. Durable Objects, R2 & rate limits (simulated locally)
To test the production build locally with wrangler dev:
npm run preview # astro build && wrangler dev
Local data (DO storage, R2) lives in .wrangler/state/ and can simply be deleted.
To test on a phone on the same Wi-Fi: npm run dev -- --host, then open http://<LAN-IP>:4321 (unencrypted rooms only, see above).
More scripts:
npm run check # type check (astro check)
npm run cf-typegen # regenerate env types from wrangler.jsonc (worker-configuration.d.ts)
Social preview images for shared links (public/og-de.png, public/og-en.png) are rendered from the template og/og.html:
npm run og # renders both languages with headless Chrome (macOS path, otherwise set CHROME=…)
Tip: to test the alarm locally, temporarily set
ROOM_TTL_MSinsrc/shared/limits.tsto e.g.5000.
npm test # Vitest inside the Workers runtime (Durable Objects, R2, rate limits, all local)
npm run check # type check app and tests
npm run build && npm run smoke # real build under wrangler dev: pages, security headers, no inline code
test/ covers encryption (incl. tampering), room codes, rooms and live sync, expiry, quotas, reports, signed moderation links and actions, and security headers. It uses test/worker.ts, which exports the same Durable Objects and API as src/worker.ts but skips the Astro page renderer.scripts/smoke.mjs covers the pages, which only exist after astro build..github/workflows/ci.yml) runs type check, tests, build and smoke test on every push to main and on pull requests. It needs no Cloudflare credentials and does not deploy.npx wrangler login
npx wrangler r2 bucket create tee-files --jurisdiction eu
npx wrangler r2 bucket create tee-files-preview --jurisdiction eu # for previews
npm run deploy # astro build && wrangler deploy
On the first deploy, migration v1 creates the SQLite class Room.routes in wrangler.jsonc (currently tee.chapter42.de). The zone has to be in the same Cloudflare account; the deploy then creates the DNS record itself:
"routes": [{ "pattern": "tee.chapter42.de", "custom_domain": true }]
For tee this is already done: the widget "tee Raumerstellung" exists, the site key is in wrangler.jsonc, and the secret key is stored as a secret for production and the main preview. For a fresh setup:
npx wrangler login # needs the challenge-widgets.write scope
npx wrangler turnstile widget create "tee Raumerstellung" \
--domain tee.chapter42.de --domain tee.<account>.workers.dev \
--domain main-tee.<account>.workers.dev --mode managed
wrangler.jsonc under vars.TURNSTILE_SITE_KEY and previews.vars.TURNSTILE_SITE_KEY.npx wrangler secret put TURNSTILE_SECRET_KEY
npx wrangler preview secret put TURNSTILE_SECRET_KEY --name main
npm run deployFor another custom domain, add it to the widget with wrangler turnstile widget update <sitekey> --domain ….
While either key is missing, Turnstile stays off. Rate limiting still applies.
openssl rand -base64 32 | npx wrangler secret put ADMIN_SECRET # HMAC key for moderation links
npx wrangler secret put REPORT_TO # verified Email Routing destination
Set both for the preview as well (npx wrangler preview secret put … --name main).
The previews block in wrangler.jsonc gives previews their own R2 bucket (tee-files-preview) and their own rate limit namespaces. Prerequisite: the Worker has been deployed normally at least once.
npm run deploy:preview -- --name my-feature # without --name: the current git branch
URL: https://<name>-tee.<account>.workers.dev, plus a unique URL per deployment.
Delete with npx wrangler preview delete --name <name>. wrangler preview is currently in open beta.
Live logs:
npx wrangler tail
src/shared/limits.tsratelimits in wrangler.jsonc (period must be 10 or 60 seconds)r2_buckets in wrangler.jsoncsrc/shared/legal.tsAfter changing wrangler.jsonc, regenerate the types with npm run cf-typegen. The dev and build scripts do that automatically.
Accounts, persistence beyond 24 h, multipart uploads > 100 MB, password-protected rooms. In unencrypted rooms, anyone who knows the code has access. With 28⁶ ≈ 481 million possible codes and the rate limit, guessing is impractical, but the code is not a secret. Encrypted rooms are additionally protected by the 256-bit key in the link.