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
See the upstream repository for the original creator and contributors.
Maintain this project? Maintainer verification →Cloudflare hosting
Free tier eligible within limits
The documented mindash 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.
- For any Free Browser Rendering use, cap browser time at ten minutes/day and at most three concurrent sessions; optional scraped widgets can be disabled.
- Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Sources checked 01/10/2026
Repository snapshot: c0ae079. Hosting eligibility reflects the deployment documentation and listed assumptions.
- geckoboard ↗
It has the compact, glanceable feel of a personal homepage, but it is also useful as a lightweight operations board. The app runs as
- workers ↗
{ "$schema": "node_modules/wrangler/config-schema.json", "name": "mindash", "main": "src/index.ts", "compatibility_date": "2026-08-01", // PRODUCTION posture, shipped as-is (the one-click deploy uses this file // unmodified): global_fetch_strictly_public is required by the CIMD // authentication lane, which src/index.ts enables unconditionally. // The flag breaks ALL outbound fetch in local workerd, so local dev and
- d1 ↗
at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_WITH_YOUR_D1_ID", "migrations_dir": "migrations" } ], "rules": [ { "type": "Text", "globs": ["**/*.yaml", "**/*.css", "**/*.sql", "**/*.client.js"], "fallthrough": true } ] }
- kv ↗
or deploys: the Deploy to Cloudflare button provisions these // automatically, or create them yourself: // npx wrangler kv namespace create CACHE // npx wrangler kv namespace create OAUTH_KV // npx wrangler d1 create mindash { "binding": "CACHE", "id": "REPLACE_WITH_YOUR_KV_ID" }, // Dedicated namespace owned by workers-oauth-provider (clients, grants, // hashed tokens). Separate from CACHE so cache lifecycle can never touch // authorization state. { "binding
- r2 ↗
// Theme assets only (background image, logo) — uploaded via the editor, // served at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_WITH_YOUR_D1_ID", "migrations_dir": "migrations" } ], "rules": [ { "type": "Text", "globs": ["**/*.yaml", "**/*.css", "**/*.sql", "**/*.client.js"], "fallthrough": true } ] }
- browser-rendering ↗
), at the cost of requiring a wrangler login and spending // real browser-minutes during dev. Drop it to render locally. "browser": { "binding": "BROWSER", "remote": true }, "r2_buckets": [ // Theme assets only (background image, logo) — uploaded via the editor, // served at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_W
- free-tier-eligible ↗
{ "$schema": "node_modules/wrangler/config-schema.json", "name": "mindash", "main": "src/index.ts", "compatibility_date": "2026-08-01", // PRODUCTION posture, shipped as-is (the one-click deploy uses this file // unmodified): global_fetch_strictly_public is required by the CIMD // authentication lane, which src/index.ts enables unconditionally. // The flag breaks ALL outbound fetch in local workerd, so local dev and
- free-tier-eligible ↗
at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_WITH_YOUR_D1_ID", "migrations_dir": "migrations" } ], "rules": [ { "type": "Text", "globs": ["**/*.yaml", "**/*.css", "**/*.sql", "**/*.client.js"], "fallthrough": true } ] }
- free-tier-eligible ↗
or deploys: the Deploy to Cloudflare button provisions these // automatically, or create them yourself: // npx wrangler kv namespace create CACHE // npx wrangler kv namespace create OAUTH_KV // npx wrangler d1 create mindash { "binding": "CACHE", "id": "REPLACE_WITH_YOUR_KV_ID" }, // Dedicated namespace owned by workers-oauth-provider (clients, grants, // hashed tokens). Separate from CACHE so cache lifecycle can never touch // authorization state. { "binding
- 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 ↗
all methods. | | Workers Free | Workers Paid | | --- | --- | --- | | Browser hours | 10 minutes per day | 10 hours per month, then $0.09 per additional hour | | Concurrent browsers (Browser Sessions only) | 3 browsers | 10 browsers ([averaged monthly](#how-is-the-number-of-concurrent-browsers-calculated)), then $2.00 per additional browser | To view or change your plan, go to the **Workers plans** page in the Cloudflare dashboard: [Go to **Workers plans** ↗](https://dash.cloudflare.com/?to=/:account/workers/plans) ## Examples of Workers Paid pricing #### Example: Quick Actions pricing If a Workers Paid user uses Quick Actions for 50 hours during the month, the estimated cost for the month is as follows.
- MIT ↗
MIT License Copyright (c) 2026 Daniel Yang 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 NONINFRI
- architecture ↗
{ "$schema": "node_modules/wrangler/config-schema.json", "name": "mindash", "main": "src/index.ts", "compatibility_date": "2026-08-01", // PRODUCTION posture, shipped as-is (the one-click deploy uses this file // unmodified): global_fetch_strictly_public is required by the CIMD // authentication lane, which src/index.ts enables unconditionally. // The flag breaks ALL outbound fetch in local workerd, so local dev and
- architecture ↗
at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_WITH_YOUR_D1_ID", "migrations_dir": "migrations" } ], "rules": [ { "type": "Text", "globs": ["**/*.yaml", "**/*.css", "**/*.sql", "**/*.client.js"], "fallthrough": true } ] }
- architecture ↗
or deploys: the Deploy to Cloudflare button provisions these // automatically, or create them yourself: // npx wrangler kv namespace create CACHE // npx wrangler kv namespace create OAUTH_KV // npx wrangler d1 create mindash { "binding": "CACHE", "id": "REPLACE_WITH_YOUR_KV_ID" }, // Dedicated namespace owned by workers-oauth-provider (clients, grants, // hashed tokens). Separate from CACHE so cache lifecycle can never touch // authorization state. { "binding
- architecture ↗
// Theme assets only (background image, logo) — uploaded via the editor, // served at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_WITH_YOUR_D1_ID", "migrations_dir": "migrations" } ], "rules": [ { "type": "Text", "globs": ["**/*.yaml", "**/*.css", "**/*.sql", "**/*.client.js"], "fallthrough": true } ] }
- architecture ↗
), at the cost of requiring a wrangler login and spending // real browser-minutes during dev. Drop it to render locally. "browser": { "binding": "BROWSER", "remote": true }, "r2_buckets": [ // Theme assets only (background image, logo) — uploaded via the editor, // served at /asset/<key> with immutable caching. { "binding": "ASSETS", "bucket_name": "mindash-assets" } ], "d1_databases": [ { "binding": "DB", "database_name": "mindash", "database_id": "REPLACE_W
Upstream screenshot · ddyy/mindash repository contributors ↗. Depicts the upstream project. We have not deployed and tested a fresh installation here.
What it can replace
Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.
Assembling a private dashboard from RSS, API data and notes; a complete commercial connector catalog and managed enterprise operations are excluded.
See supporting source ↗How it works
The shape of mindash 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 ↗Configuration and workflow sources
Reviewed commit c0ae079f5816. Files were read as data; upstream applications and CI jobs were not executed.
Partial source coverage: 52 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.
Deployment configuration · 1 files
Cloudflare Workers · compatibility 2026-08-01
mindash · default
Entrypoint: src/index.ts
Cron triggers (UTC): */2 * * * *
DB→ D1CACHE→ KVOAUTH_KV→ KVASSETS→ R2BROWSER→ Browser Rendering
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.
- L545 · fetch handler exported · references DB · calls staticRoute, ensureSchema, dcrGuard, provider.fetch, run, bind, env.DB.prepare
- L577 · scheduled handler exported · calls ensureSchema, ctx.waitUntil, sweep, pushSweep, catch, then, provider.purgeExpiredData, console.log, JSON.stringify, String
- L38 · staticRoute calls (conditional paths may differ): url.searchParams.get
- L392 · requireSession calls (conditional paths may differ): sameOriginOk, json, getSession
- L504 · dcrLimits calls (conditional paths may differ): Number
- L512 · dcrGuard calls (conditional paths may differ): Date.now, dcrLimits, JSON.stringify, run, bind, env.DB.prepare, crypto.randomUUID, env.DB.batch
Environment references: env.ASSETS · env.DB
- L13 · cssColor calls (conditional paths may differ): v.startsWith
- L24 · themeCssVars calls (conditional paths may differ): parts.push, cssColor, Math.max, Math.round, parts.join
- L52 · cssString calls (conditional paths may differ): v.replace, toString, c.charCodeAt
- L58 · themeBodyCss calls (conditional paths may differ): cssString
- L79 · imgSrcFor calls (conditional paths may differ): filter, r.startsWith, origins.add, cfg.widgets.some, join
- L145 · loadWidgetData calls (conditional paths may differ): JSON.parse, key.startsWith, env.CACHE.get
- L178 · cardClass calls (conditional paths may differ): join, filter
- L183 · accentStyle calls (conditional paths may differ): cssColor
- L205 · stamp calls (conditional paths may differ): relativeTime, Math.round
- L254 · failedStamp calls (conditional paths may differ): relativeTime
- L262 · widgetSection calls (conditional paths may differ): render, getModule, String, stamp, Date.now, failedStamp, cardClass, accentStyle
- L316 · staticSection calls (conditional paths may differ): cardClass, accentStyle
- L326 · formActionFor calls (conditional paths may differ): cfg.widgets.flatMap, origins.join
- L333 · hasLiveWidgets calls (conditional paths may differ): cfg.widgets.some
- L343 · renderMain calls (conditional paths may differ): page.rows.flatMap, r.columns.flatMap, map, pageWidgets.filter, pullIds.slice, join, chunk.map, all, bind, env.DB.prepare, rows.set, Date.now, Promise.all, pageWidgets.map, isPullWidget, rows.get, loadWidgetData, sections.set, widgetSection, cardClass
- L507 · frameSrcFor calls (conditional paths may differ): cfg.widgets.flatMap, frameOrigins.join
- L516 · pageSlugs calls (conditional paths may differ): cfg.pages.map, replace, p.name.toLowerCase, seen.has, seen.add
- L540 · renderPage calls (conditional paths may differ): Promise.all, getConfig, cloudflareAnalytics, pageSlugs, slugs.indexOf, Number, url.searchParams.get, Number.isInteger, effectiveTheme, Response.redirect, renderMain, filter, cfg.pages.map, navPages.map, pubTitle, pageTitle, av, themeCssVars, themeBodyCss, hasLiveWidgets
Environment references: env.CACHE · env.DB
- L22 · logAttempt calls (conditional paths may differ): run, bind, env.DB.prepare, Date.now, error.slice, console.log, JSON.stringify, String
- L62 · backoffDelayMs calls (conditional paths may differ): Math.max, Math.min
- L84 · sweep calls (conditional paths may differ): getConfig, frozenWidgetIds, filter, cfg.widgets.filter, frozen.has, env.DB.batch, pullWidgets.map, bind, env.DB.prepare, logRetentionDays, catch, run, Date.now, logMaxPerWidget, batchJobs, Array.from, Math.min, queue.shift, runJob, console.log
- L149 · batchJobs calls (conditional paths may differ): getModule, jobs.push, mod.batch.groupKey, groups.get, Math.max, group.widgets.push, groups.set, groups.values, group.widgets.slice
- L171 · claimRefresh calls (conditional paths may differ): Date.now, crypto.randomUUID, first, bind, env.DB.prepare
- L189 · failClaim calls (conditional paths may differ): String, Date.now, backoffDelayMs, run, bind, env.DB.prepare, console.log, JSON.stringify, logAttempt
- L215 · publishClaim calls (conditional paths may differ): Date.now, JSON.stringify, failClaim, run, bind, env.DB.prepare, console.log, logAttempt
- L241 · runJob calls (conditional paths may differ): claimRefresh, claims.push, getModule, mod.batch.fetch, claims.map, Promise.all, failClaim, results.has, publishClaim, results.get, mod.fetchData
- L281 · refreshOne calls (conditional paths may differ): runJob, getModule
- L290 · forceRefresh calls (conditional paths may differ): getConfig, cfg.widgets.find, isPullWidget, run, bind, env.DB.prepare, refreshOne, first
Environment references: env.DB
- L12 · pushSweep calls (conditional paths may differ): Date.now, getConfig, first, bind, env.DB.prepare, Math.max, nextOccurrenceAfter, Math.floor, console.log, JSON.stringify, stmts.push, crypto.randomUUID, env.DB.batch, String
Environment references: env.DB
- L19 · json calls (conditional paths may differ): JSON.stringify
- L26 · tokenMatches calls (conditional paths may differ): crypto.subtle.digest, enc.encode, crypto.subtle.timingSafeEqual
- L33 · readBoundedBody calls (conditional paths may differ): req.body.getReader, reader.read, reader.cancel, chunks.push, buf.set, decode
- L57 · ensureState calls (conditional paths may differ): run, bind, env.DB.prepare, first
- L76 · handlePush calls (conditional paths may differ): exec, json, Boolean, widgets.find, getConfig, req.headers.get, auth.startsWith, trim, auth.slice, first, bind, env.DB.prepare, sha256Hex, tokenMatches, Date.now, readBoundedBody, body.trim, url.searchParams.get, text.startsWith, JSON.parse
Environment references: env.DB
- L38 · currentEpoch calls (conditional paths may differ): first, env.DB.prepare
- L44 · readJson calls (conditional paths may differ): startsWith, req.headers.get, req.json
- L54 · b64uToBytes calls (conditional paths may differ): replace, s.replace, atob, b64.padEnd, bin.charCodeAt
- L62 · bytesToB64u calls (conditional paths may differ): String.fromCharCode, replace, btoa
- L73 · passkeyAddOptions calls (conditional paths may differ): readJson, String, json, currentEpoch, all, env.DB.prepare, generateRegistrationOptions, Uint8Array.from, encode, existing.map, randomToken, Date.now, run, bind
- L104 · passkeyAddVerify calls (conditional paths may differ): readJson, String, json, Date.now, first, bind, env.DB.prepare, verifyRegistrationResponse, env.DB.batch, bytesToB64u, join
- L166 · passkeyRemove calls (conditional paths may differ): Date.now, run, bind, env.DB.prepare
- L197 · handleAuth calls (conditional paths may differ): json, sameOriginOk, registerOptions, registerVerify, loginOptions, loginVerify
- L219 · setupMode calls (conditional paths may differ): first, env.DB.prepare
- L229 · registerOptions calls (conditional paths may differ): readJson, setupMode, json, sha256Hex, first, bind, env.DB.prepare, currentEpoch, all, generateRegistrationOptions, Uint8Array.from, encode, existing.map, randomToken, Date.now, run
- L281 · registerVerify calls (conditional paths may differ): readJson, json, Date.now, first, bind, env.DB.prepare, verifyRegistrationResponse, String, bytesToB64u, join, stmts.push, env.DB.batch
- L384 · loginOptions calls (conditional paths may differ): currentEpoch, all, env.DB.prepare, json, generateAuthenticationOptions, results.map, randomToken, Date.now, run, bind
- L412 · loginVerify calls (conditional paths may differ): readJson, json, Date.now, first, bind, env.DB.prepare, verifyAuthenticationResponse, b64uToBytes, String, run, createSession, JSON.stringify, sessionCookie
Environment references: env.DB
- L11 · cookieValue calls (conditional paths may differ): req.headers.get, header.split, part.indexOf, trim, part.slice
- L24 · getSession calls (conditional paths may differ): cookieValue, sha256Hex, Date.now, first, bind, env.DB.prepare
- L47 · createSession calls (conditional paths may differ): randomToken, sha256Hex, Date.now, run, bind, env.DB.prepare
- L71 · sessionCookie calls (conditional paths may differ): Math.floor
- L81 · revokeSession calls (conditional paths may differ): run, bind, env.DB.prepare, Date.now
Environment references: env.DB
- L1 · sha256Hex calls (conditional paths may differ): crypto.subtle.digest, encode, join, map, padStart, b.toString
- L6 · randomToken calls (conditional paths may differ): crypto.getRandomValues, String.fromCharCode, replace, btoa
- L13 · json calls (conditional paths may differ): JSON.stringify
- L25 · sameOriginOk calls (conditional paths may differ): req.headers.get
- L32 · resolveStaticToken calls (conditional paths may differ): sha256Hex, first, bind, env.DB.prepare, map, row.scopes.split, s.startsWith
- L57 · publishOutcome calls (conditional paths may differ): text, JSON.stringify, errText
- L161 · callTool calls (conditional paths may differ): token.scopes.has, getCurrentConfig, redactDoc, text, JSON.stringify, docToYaml, forceRefresh, String, errText, Number.isInteger, JSON.parse, Number, yamlToRaw, first, bind, env.DB.prepare, unredactDoc, vd, publishOutcome, publishConfig
- L270 · dispatchMcp calls (conditional paths may differ): JSON.stringify, req.json, rpcError, Array.isArray, rpcResult, String, callTool, errText
- L311 · rpcResult calls (conditional paths may differ): JSON.stringify
- L317 · rpcError calls (conditional paths may differ): JSON.stringify
Environment references: env.DB
- L28 · csrfToken calls (conditional paths may differ): sha256Hex
- L34 · page calls (conditional paths may differ): av, themeCssVars, globalHeader, imgSrcFor
- L80 · settingsPage calls (conditional paths may differ): Promise.all, csrfToken, all, env.DB.prepare, getConfig, listCredentials, vaultKeyStatus, listConnections, logRetentionDays, logMaxPerWidget, cloudflareAnalytics, creds.map, c.credential_id.slice, slice, toISOString, grants.map, tokens.map, map, cfg.widgets.filter, pushTokens.map
- L426 · logPage calls (conditional paths may differ): url.searchParams.get, Number, Number.isSafeInteger, getConfig, cfg.widgets.map, pageOf.set, pageOf.get, canonical.set, p.name.toLowerCase, toLowerCase, labelOf, optionOf, map, cfg.widgets.filter, isPullWidget, byId.has, rawSelection.startsWith, canonical.get, rawSelection.trim, selection.startsWith
- L768 · addCredentialAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, putCredential, map, form.getAll, normalizeOrigin
- L790 · removeCredentialAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, deleteCredential
- L800 · createPushTokenAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, widgets.find, getConfig, crypto.getRandomValues, replace, btoa, String.fromCharCode, run, bind, env.DB.prepare, sha256Hex, Date.now
- L822 · revokePushTokenAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, run, bind, env.DB.prepare, Date.now
- L837 · connectMcpAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, startConnect, Response.redirect
- L855 · removeMcpConnectionAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, deleteConnection
- L864 · removePasskeyAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, passkeyRemove
- L876 · revokeGrantAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, run, bind, env.DB.prepare, Date.now, env.OAUTH_PROVIDER.listUserGrants, env.OAUTH_PROVIDER.revokeGrant, console.log, JSON.stringify
- L899 · revokeTokenAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, settingsPage, run, bind, env.DB.prepare, Date.now
- L915 · setAnalyticsAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, setCloudflareAnalytics, Response.redirect
- L927 · setRetentionAction calls (conditional paths may differ): req.formData, String, form.get, csrfToken, setLogRetentionDays, Number, setLogMaxPerWidget, Response.redirect
Environment references: env.DB · env.OAUTH_PROVIDER
- L16 · editorPage calls (conditional paths may differ): getCurrentConfig, listCredentials, csrfToken, Object.fromEntries, WIDGET_FORMS.map, map, creds.filter, c.widgetTypes.includes, listConnections, av, themeCssVars, globalHeader, replace, JSON.stringify
- L29 · stripTmpIds calls (conditional paths may differ): JSON.parse, JSON.stringify, w.id.startsWith
- L46 · validateDraft calls (conditional paths may differ): validateDoc, String
- L54 · readBody calls (conditional paths may differ): req.json
- L67 · previewTolerant calls (conditional paths may differ): JSON.parse, JSON.stringify, parseProbeWidget, replace, String, test, msg.slice
- L113 · editorPreview calls (conditional paths may differ): readBody, json, validateDraft, previewTolerant, validateDoc, Number, renderMain, frameSrcFor
- L131 · positions calls (conditional paths may differ): doc.pages.forEach, p.rows.forEach, r.columns.forEach, c.widgets.forEach, map.set, String
- L143 · widgetLabel calls (conditional paths may differ): String
- L152 · summarize calls (conditional paths may differ): classifyDiff, map, docWidgets, String, positions, diff.needsSources.map, summary.push, widgetLabel, draftBy.get, baseBy.get, basePos.get, draftPos.get, JSON.stringify, r.startsWith, r.slice, base.pages.map, basePageTheme.get, basePageTheme.has, basePageIcon.has, basePageIcon.get
- L240 · editorDiff calls (conditional paths may differ): readBody, json, validateDraft, getCurrentConfig, summarize
- L250 · editorYaml calls (conditional paths may differ): readBody, json, validateDraft, docToYaml
- L258 · editorParse calls (conditional paths may differ): readBody, json, yamlToRaw, String, validateDraft
- L272 · editorSave calls (conditional paths may differ): readBody, json, String, csrfToken, Number, Number.isInteger, getCurrentConfig, first, bind, env.DB.prepare, summarize, validateDoc, JSON.parse, rebaseDraft, stripTmpIds, JSON.stringify, publishConfig, filter, docWidgets, resR.createdIds.includes
- L401 · sortKeysDeep calls (conditional paths may differ): Array.isArray, v.map, sort, Object.keys, sortKeysDeep
- L414 · widgetLabelOf calls (conditional paths may differ): String
- L424 · widgetIndex calls (conditional paths may differ): out.set
- L436 · layoutSignature calls (conditional paths may differ): JSON.stringify, pages.map, map
- L467 · rebaseDraft calls (conditional paths may differ): layoutSignature, JSON.parse, JSON.stringify, widgetIndex, baseByG.get, mergedByG.get, stable, Object.keys, Object.assign, conflicts.push, widgetLabelOf, draftByG.has, mergedByG.has, get, filter, forEach, dest.widgets.push, baseBy.get, changedInDraft.add, changedInCurrent.add
- L663 · editorRefresh calls (conditional paths may differ): readBody, json, forceRefresh
- L673 · editorGeocode calls (conditional paths may differ): readBody, slice, trim, String, json, safeFetchJson, encodeURIComponent, map
- L696 · editorHistory calls (conditional paths may differ): readBody, getCurrentConfig, all, bind, env.DB.prepare, results.map, validateDoc, JSON.parse, map, results.slice, docs.get, summarize, json
- L737 · editorRestore calls (conditional paths may differ): readBody, String, csrfToken, json, Number, Number.isInteger, getCurrentConfig, first, env.DB.prepare, rollbackConfig, filter, docWidgets, res.createdIds.includes, ctx.waitUntil, then, Promise.allSettled, newPulls.map, forceRefresh
- L777 · editorSample calls (conditional paths may differ): readBody, json, getCurrentConfig, parseProbeWidget, String, mod.fetchSampleRoot, slice, leaves.push, Array.isArray, forEach, v.slice, walk, Object.entries, test
- L830 · looksLikeSvg calls (conditional paths may differ): decode, buf.subarray, test, trimStart, head.replace
- L862 · editorCreateCredential calls (conditional paths may differ): req.headers.get, csrfToken, json, req.json, String, CREDENTIAL_WIDGET_TYPES.includes, widgetType.slice, putCredential, listCredentials, trim, map, creds.filter, c.widgetTypes.includes
- L896 · editorUploadAsset calls (conditional paths may differ): req.headers.get, csrfToken, json, url.searchParams.get, Number, req.arrayBuffer, sniffImage, looksLikeSvg, checkSvg, decode, crypto.subtle.digest, join, map, digest.slice, padStart, b.toString, env.ASSETS.put
- L934 · editorCoinSearch calls (conditional paths may differ): readBody, slice, trim, String, json, safeFetchJson, encodeURIComponent, map, filter, toUpperCase
- L958 · editorSymbolSearch calls (conditional paths may differ): readBody, slice, trim, String, json, safeFetchText, encodeURIComponent, JSON.parse, test, map, filter
- L990 · editorYtSearch calls (conditional paths may differ): readBody, slice, trim, String, json, test, safeFetchText, feedUrl, exec, replace, q.startsWith, encodeURIComponent
- L1036 · editorMcpTools calls (conditional paths may differ): readBody, json, listTools, String
- L1050 · editorProbe calls (conditional paths may differ): readBody, json, getCurrentConfig, parseProbeWidget, String, isPullWidget, getModule, mod.fetchData, mod.render
Environment references: env.DB · env.ASSETS
- L71 · errorRedirect calls (conditional paths may differ): u.searchParams.set, Response.redirect, u.toString
- L78 · currentEpoch calls (conditional paths may differ): first, env.DB.prepare
- L84 · authorizePage calls (conditional paths may differ): env.OAUTH_PROVIDER.parseAuthRequest, consentShell, String, env.OAUTH_PROVIDER.lookupClient, oauthReq.scope.filter, errorRedirect, Date.now, randomToken, run, bind, env.DB.prepare, JSON.stringify, oauthReq.scope.join, currentEpoch, oauthReq.scope.includes, csrfToken, oauthReq.scope.map
- L167 · loadPending calls (conditional paths may differ): first, bind, env.DB.prepare, Date.now
- L177 · authorizeApprove calls (conditional paths may differ): req.formData, String, form.get, csrfToken, consentShell, loadPending, pending.scopes.split, scopes.includes, currentEpoch, errorRedirect, JSON.parse, run, bind, env.DB.prepare, Date.now, env.OAUTH_PROVIDER.completeAuthorization, Response.redirect
- L221 · authorizeDeny calls (conditional paths may differ): req.formData, String, form.get, csrfToken, consentShell, loadPending, run, bind, env.DB.prepare, Date.now, errorRedirect, JSON.parse
- L238 · stepupOptions calls (conditional paths may differ): catch, req.json, loadPending, String, json, all, env.DB.prepare, generateAuthenticationOptions, results.map, run, bind
- L260 · stepupVerify calls (conditional paths may differ): catch, req.json, loadPending, String, json, first, bind, env.DB.prepare, replace, cred.public_key.replace, atob, b64.padEnd, bin.charCodeAt, verifyAuthenticationResponse, run, Date.now
Environment references: env.DB · env.OAUTH_PROVIDER
- L16 · statementsOf calls (conditional paths may differ): join, filter, sql.split, startsWith, ln.trim, map, noComments.split, s.trim
- L36 · expectationsOf calls (conditional paths may differ): ADD_COLUMN.exec, columns.push, CREATE_OBJECT.exec, objects.push
- L51 · columnExists calls (conditional paths may differ): first, bind, env.DB.prepare
- L59 · objectExists calls (conditional paths may differ): first, bind, env.DB.prepare
- L69 · ensureSchema calls (conditional paths may differ): catch, run
- L77 · run calls (conditional paths may differ): run, env.DB.prepare, all, results.map, done.has, statementsOf, ADD_COLUMN.exec, columnExists, expectationsOf, objectExists, bind, console.log, JSON.stringify
Environment references: env.DB
- L59 · isPlaceholderDoc calls (conditional paths may differ): doc.pages.flatMap, p.rows.flatMap, r.columns.flatMap, String
- L77 · setupDoc calls (conditional paths may differ): seedRaw, JSON.parse, JSON.stringify, Array.isArray, clocks.some, replace, pop, timezone.split, slice
- L120 · page calls (conditional paths may differ): av
- L162 · cfTimezone calls (conditional paths may differ): cf
- L177 · cfUnit calls (conditional paths may differ): cf, FAHRENHEIT.has, country.toUpperCase
- L182 · cfCoords calls (conditional paths may differ): cf, Number, Number.isFinite
- L194 · geocodeCity calls (conditional paths may differ): safeFetchJson, encodeURIComponent, Number.isFinite
- L208 · setupPage calls (conditional paths may differ): csrfToken, cfTimezone, cf, cfUnit, page
- L253 · setupApply calls (conditional paths may differ): req.formData, String, form.get, csrfToken, setupPage, getCurrentConfig, isPlaceholderDoc, page, trim, raw.slice, slice, cfCoords, geocodeCity, first, env.DB.prepare, publishConfig, setupDoc, Response.redirect, toString
Environment references: env.DB
- L15 · djb2 calls (conditional paths may differ): s.charCodeAt, h.toString
Build and deployment pipeline · 1 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.
Triggers: push, pull_request
test · no job dependencies declared
- actions/checkout@v4
actions/checkout@v4 - actions/setup-node@v4
actions/setup-node@v4 - Shell command
npm ci - Shell command
npm run check - Shell command
npm test
integration · no job dependencies declared
- actions/checkout@v4
actions/checkout@v4 - actions/setup-node@v4
actions/setup-node@v4 - Shell command
npm ci - Shell command
bash test/integration/dcr.sh - Shell command
node test/integration/auth.mjs - Shell command
node test/integration/webauthn.mjs - Shell command
node test/integration/log.mjs
migrate:local: wrangler d1 migrations apply mindash --local
Repository README
View original on GitHub ↗Full upstream document by @ddyy · README.md · snapshot c0ae079
mindash
mindash is a self-hosted dashboard you can edit visually — or hand to an AI agent. It pulls from feeds, APIs, web pages, and MCP tools; accepts live updates from jobs and automations; and keeps the last good data visible when an upstream fails.
It has the compact, glanceable feel of a personal homepage, but it is also useful as a lightweight operations board. The app runs as one Cloudflare Worker with server-rendered dashboard pages, passkey ownership, encrypted credentials, and versioned runtime config.
Live demo → — explore Home, Ops, Misc, and an Everything page containing all twenty widget types.

What makes mindash different
- Edit it three ways — use the visual editor, edit the YAML document, or connect an agent through MCP. The MCP server supports Claude.ai remote connectors through OAuth 2.1 as well as static tokens for header-capable clients.
- Pull and push data — read RSS, APIs, JavaScript-rendered web pages, and remote MCP tools; monitor sites; or push heartbeats and log lines directly from cron jobs, CI, and automations.
- Fail gracefully — scheduled refreshes happen away from page views. A failed upstream leaves the last successful payload on screen and marks it stale instead of turning the card into an error message.
- Own the whole thing — passkeys, encrypted origin-bound credentials, public and private pages, scoped agent access, compare-and-set updates, and 30-version history with restore.
- Use it every day — responsive themes, kiosk mode, a new-tab extension, and one-click deployment to your own Cloudflare account.
| Home — default theme | Operations — terminal preset |
Personal — paper preset |
|---|---|---|
![]() |
![]() |
![]() |
Three pages, three built-in themes. All of it was created, themed, and populated through the MCP API — no editor clicks involved — and remains fully editable in the visual editor.
Install
Three ways in, easiest first:
1. One click - the Deploy to Cloudflare button above. It copies this repo into your GitHub account, provisions the KV/D1/R2 resources on your Cloudflare account, and deploys. The Worker bootstraps its own database schema on the first visit - nothing to run.
2. npm create - the standard Workers scaffolder accepts this repo as a template:
npm create cloudflare@latest my-dashboard -- --template=ddyy/mindash
3. Clone it:
git clone https://github.com/ddyy/mindash && cd mindash
npm install
npm run dev
First run
A fresh instance has no owner and no dashboard yet, so it shows one screen: create the first passkey to claim it (no token needed - that window closes the moment a passkey exists; every later enrollment needs a one-time token). Signing in then lands on setup, which asks two things:
- Timezone - pre-filled from your browser. Clocks, countdowns, and
calendars inherit it, so events land at the right hour instead of UTC.
Any widget can still override it, and
timezone:at the top of the config document is the same setting. - Example widgets or an empty page - the examples are a working dashboard to edit (news, weather, clocks, an MCP call); empty is one blank page and the gallery.
Setup only ever writes over an untouched instance - once a dashboard has content, it refuses and points at the editor.
Local dev needs no resource setup (state is name-keyed on disk). To
deploy a cloned copy, create the four resources and paste their ids
into wrangler.jsonc where marked:
npx wrangler kv namespace create CACHE
npx wrangler kv namespace create OAUTH_KV
npx wrangler d1 create mindash
npx wrangler r2 bucket create mindash-assets
npx wrangler deploy
However you install: open the instance and it offers a one-time
claim - create the first passkey and you own it. Then hit Edit and
build your dashboard (or point an agent at /mcp).
Use it as your browser home
- Homepage / startup page (no extension needed): set your instance URL as the browser homepage and startup page - Chrome: Settings > On startup; Firefox: Settings > Home.
- New tab page: browsers need an extension for that - this repo
ships one in
extension/. Download the packaged extension (or use the folder from a clone), unzip it, then load it unpacked fromchrome://extensions(Developer mode → Load unpacked) and pick the extractedextensionfolder. Enter your instance URL once and every new tab is your dashboard.storageis its only permission and your URL is the only thing it stores. The search and bookmarks widgets are built for exactly this.
Widgets
Twenty types, each a self-describing def in src/widgets/ (validation,
editor form, fetch, render, CSS in one file):
- Feeds — RSS/Atom (multiple feeds merged newest-first, entity decoding), YouTube channels/playlists (keyless via YouTube's RSS, thumbnails, @handle search), Hacker News
- Personal — weather (geocoded search, C/F), calendar (iCal with simple recurrence + EXDATE), bookmarks (optional favicons via one fixed icon origin), search box (DuckDuckGo, Google, Bing, Brave, Startpage, Ecosia, Kagi, Wikipedia, and YouTube presets — each setting the engine's own query parameter — or a custom engine), notes (safe markdown subset), world clock, countdown (timezone-aware, DST-correct)
- Markets — crypto (CoinGecko, searchable coin picker) and stocks (Yahoo, symbol search) with colored day deltas
- Display — JSON API (dot-path field mapping with a live field picker), web scrape (Browser Rendering loads the page in headless Chromium; CSS selectors map elements to a list - works on JS-only pages and sites that block plain server fetches), image (direct URL with webcam cache-busting, R2 upload, or JSON-resolved like xkcd/APOD with a pinned image origin), sandboxed iframe embeds
- Monitoring — site monitor (HEAD checks with latency, per-site up/down history bars; a failing site is a red row, never a broken card)
- Integration — MCP widget (call a tool on any Streamable-HTTP MCP server — unauthenticated, static bearer, or OAuth via a one-time Settings sign-in; fields, markdown, or link-list rendering), push heartbeats (cron jobs report in; late/missed turn red), push log (lines POSTed in - cron output, CI results, agent updates)
Pull widgets are prefetched by a cron sweep behind fenced D1 leases and
cached in D1 rows; a page view never fetches an upstream. A failed fetch
leaves the last good data in place, so the card keeps working and its
stamp says whose timestamp it is - showing data from 3h ago under the
failure, or updated 3h ago · overdue when data ages far past its
interval with nothing logged as failing. Healthy cards stay unmarked.
Recipes
The JSON API widget's list mapping covers many "dedicated" widgets with plain config. GitHub releases:
- type: json-api
title: workers-sdk releases
url: https://api.github.com/repos/cloudflare/workers-sdk/releases?per_page=8
refresh_interval: 2h
items: "."
item_title: tag_name
item_url: html_url
item_meta: published_at
items: "." maps the response root (the releases array); each row links
its tag_name to the release page. Swap /releases for /tags or
/issues and rebind the paths for other GitHub lists. Unauthenticated
GitHub API calls are rate-limited per source IP, which Workers share -
if a card flakes, save a GitHub token under Settings - API credentials
and select it in the widget's Credential field.
Reddit blocks plain server fetches from datacenter IPs (its .json
and .rss endpoints 403 from Cloudflare), but a full browser passes -
so a subreddit is just a scrape recipe:
- type: scrape
title: r/selfhosted
url: https://www.reddit.com/r/selfhosted/
item_selector: a[slot="full-post-link"]
refresh_interval: 30m
(Use www.reddit.com, not old.reddit.com - the old UI now bounces logged-out visitors to a login wall from many IPs.)
Editor
/settings/editor — outline · live preview · inspector, with YAML as an
escape hatch. Drag-and-drop plus keyboard/button equivalents everywhere,
draggable column widths snapping to named fractions, live draft probes
(new widgets show real data before saving), semantic save summaries,
version history with restore, undo, and a Theme panel.

Theming
Global theme + named presets, selectable per page (built-in palettes:
nord, solarized-dark, gruvbox, catppuccin, paper, terminal — or copy one
and customize). Colors (hex, native pickers), fonts, font/title sizes,
corner radius, card opacity, background image and logo (uploaded to R2 or
external URL), dashboard title. Per page: fit-to-screen layouts (rows
take height fractions), public sharing (no session, noindex, share link),
descriptions, and kiosk/fullscreen viewing (?kiosk=1 or the ⛶ button;
page switching stays in fullscreen).
Security model
- Rendering: escaping by construction (
htmltagged template); CSPdefault-src 'none'with exact per-feature allowlists (frame-srcfrom iframe widgets,form-actionfrom search engines,img-srcfrom theme/image/favicon origins). Pages ship only first-party scripts (script-src 'self'; connect-src 'self'): the clock/countdown ticker andui.js(background refresh, fullscreen page switching) — no third-party script can ever load, on public or private pages. - Outbound fetches: https/public-only with bounded bodies, deadlines, and manual redirects; credentialed requests never follow redirects.
- Credentials: never in config. API/MCP bearer credentials live in an
encrypted D1 vault (AES-GCM; master key auto-generated into KV, or a
MASTER_KEYWorker secret if you prefer) - added once in Settings, referenced by name from config. Each credential is pinned to its widget types and exact destination origin, and the pin is the AEAD associated data: retargeting a credential breaks decryption instead of leaking it. Credential-less MCP widgets may call any public https server - a call that carries no credential carries no authority worth allowlisting. Heartbeat push tokens are per-widget D1 rows (hash only, created in Settings, shown once);PUSH_TOKEN_*Worker secrets still work as a legacy fallback. - Auth: passkeys only (single-use enroll/recover tokens, owner epoch revocation). Config writes are compare-and-set with semantic diff classification: layout-scope vs sources-scope (source URLs, schedules, making a page public, external theme images).
MCP
mindash speaks MCP in both directions. As a client, the MCP widget calls a tool on any public Streamable-HTTP server each refresh — unauthenticated, with a vault credential, or through an OAuth connection (Settings → MCP connections runs the full discovery → registration → PKCE flow in one click and auto-refreshes tokens).
As a server: a stateless Streamable-HTTP endpoint at POST /mcp.
Two auth lanes:
- OAuth 2.1 (claude.ai remote connectors): the Worker is its own
authorization server — discovery, DCR (rate-limited, https/loopback
redirects only), PKCE S256, consent with passkey step-up for
config:sources, revocable grants in/settings. - Static bearer tokens for header-capable clients:
./scripts/seed-mcp-token.sh layout my-agent # ordering/titles/theme
./scripts/seed-mcp-token.sh sources my-agent # + sources/create/remove/public
claude mcp add --transport http mindash http://localhost:8787/mcp \
--header "Authorization: Bearer <token>"
Tools: list_config, update_config (full document — pages, themes,
layout), add_widget, update_widget, remove_widget,
refresh_widget, rollback_config, snapshot_config. Mutations take
base_version and return a structured conflict when stale.
Push widgets
Create a heartbeat or log widget, then mint its bearer token under Settings → Push tokens (shown once, stored hashed). Then:
# one-shot ping (claims its scheduled occurrence when in window)
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" -d '{"bytes": 12345}' \
http://localhost:8787/push/backup-demo
# explicit failure
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
"http://localhost:8787/push/backup-demo?status=fail"
# timed run: /start returns a run_id, completion targets it
RID=$(curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8787/push/backup-demo/start | jq -r .run_id)
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
"http://localhost:8787/push/backup-demo?rid=$RID"
The cron sweep materializes timeout rows for missed occurrences (bounded catch-up) and times out started runs past their deadline.
Log widgets take lines instead of pings - plain text or JSON, newest 100 kept:
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
-d 'backup finished: 12.4GB in 3m' \
"http://localhost:8787/push/deploy-log"
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"text": "disk 91% full", "level": "warn"}' \
http://localhost:8787/push/deploy-log
Auth setup
A fresh instance has a claim window: until the first passkey exists,
/login offers a tokenless "create the first passkey" flow — whoever
enrolls first owns the instance. Deploy and claim in the same sitting.
If you want a custom domain, attach it before claiming: a passkey is
bound to the domain that created it, so one made on *.workers.dev will
not sign you in on your own domain. The login page says so while the
choice is still free. Claiming first is recoverable, not fatal — enroll
again on the new domain with a one-time token.
Every enrollment after the first requires a one-time token:
./scripts/seed-token.sh enroll # one-time token; enroll a passkey at /login
./scripts/seed-token.sh recover # account-reset ceremony token
Runtime config
Config is a versioned document in D1 (compare-and-set, copy-forward
rollback, 30-version history). Edit it in the editor, over MCP, or as
YAML. config.yaml is only the first-boot seed. Widget ids are
server-assigned — never write them by hand.
Dev
npm install
npm run migrate:local
npm run dev # wrangler dev --test-scheduled on :8787
npm test # node:test suite (esbuild-bundled, no framework)
npm run check # tsc
curl "http://localhost:8787/cdn-cgi/handler/scheduled" # trigger a sweep
Client scripts (*.client.js) are real JavaScript files imported as text
via the wrangler Text rule — no template-literal escaping layer. Push
tokens are created in Settings (.dev.vars only matters for the legacy
PUSH_TOKEN_* lane).
Deploy
Resource creation and the first deploy are the clone instructions above (one authoritative sequence — the Worker migrates its own schema on first request, so no separate migration step). After the first deploy:
npx wrangler deploy # subsequent deploys
./scripts/seed-token.sh enroll --remote # extra passkeys after the first claim
Create the KV namespaces and D1 database on first deploy (ids in
wrangler.jsonc). Nothing else to edit: the checked-in config is the
production posture, including the global_fetch_strictly_public
compatibility flag and the CIMD authentication lane. That flag breaks
outbound fetch in local workerd, so npm run dev and the integration
suites drop it on the command line - local development needs no config
changes either.
Frequently asked about mindash
What is mindash?+
mindash is a self-hosted Geckoboard alternative built on the Cloudflare developer platform. Build a private dashboard from feeds, API data and notes on Cloudflare.
What does mindash replace?+
mindash is listed as an alternative to Geckoboard. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does mindash use?+
mindash is built on Browser Rendering, D1, KV, R2, Workers.
How much does mindash cost to run?+
The documented mindash 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. For any Free Browser Rendering use, cap browser time at ten minutes/day and at most three concurrent sessions; optional scraped widgets can be disabled. 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 mindash open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/ddyy/mindash/c0ae079f5816f604fd63498a989248432f4e5e31/LICENSE. Source code and contributor credit are available at https://github.com/ddyy/mindash.





Discussion · 0
sign in to comment →