hearth-dash 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,32 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 AIDHD
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Non-Commercial License
2
+
3
+ Copyright (c) 2026 Marta Varen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to use,
7
+ copy, modify, merge, and distribute the Software, subject to the following
8
+ conditions:
9
+
10
+ 1. NON-COMMERCIAL USE ONLY. The Software may not be sold, sublicensed, or
11
+ included in any product or service that is sold, offered for a fee, or
12
+ otherwise commercialized. This includes but is not limited to: selling
13
+ the Software, offering it as a paid service, bundling it with paid
14
+ products, or using it as a component of a commercial offering.
15
+
16
+ 2. ATTRIBUTION. The above copyright notice and this permission notice shall
17
+ be included in all copies or substantial portions of the Software.
18
+
19
+ 3. SHARE-ALIKE. If you modify and distribute the Software, you must
20
+ distribute your modifications under this same license.
21
+
22
+ 4. PERSONAL AND EDUCATIONAL USE. Use of the Software for personal projects,
23
+ education, research, and non-commercial open source projects is
24
+ explicitly permitted and encouraged.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ <div align="center">
2
+
3
+ # Hearth
4
+
5
+ **A cozy personal dashboard on Cloudflare Workers**
6
+
7
+ [![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?style=for-the-badge&logo=cloudflare&logoColor=white)](#)
8
+ [![D1](https://img.shields.io/badge/D1-SQLite-22D3EE?style=for-the-badge&logo=cloudflare&logoColor=white)](#)
9
+ [![license Non-Commercial](https://img.shields.io/badge/license-Non--Commercial-A855F7?style=for-the-badge)](LICENSE)
10
+
11
+ </div>
12
+
13
+ Hearth is a small personal dashboard for two people. It runs as a Cloudflare Worker with D1 storage and an optional R2 photo bucket. The same private data is available through a standards-compliant, OAuth-protected MCP Streamable HTTP endpoint so Claude and other compatible clients can discover and use Hearth's tools.
14
+
15
+ ## Features
16
+
17
+ - Dashboard overview, moods, shared notes, moments, important dates and shopping list
18
+ - Food and water diary with private R2 photos and daily reviews
19
+ - Weather, barometric pressure history and pressure-shift alerts
20
+ - Configurable partner names
21
+ - Password-protected web dashboard with signed, expiring sessions
22
+ - Streamable HTTP MCP with JSON-RPC `initialize`, `ping`, `tools/list` and `tools/call`
23
+ - OAuth 2.1 authorization with PKCE, protected-resource discovery, CIMD and Dynamic Client Registration
24
+ - Separate `hearth:read` and `hearth:write` permissions
25
+ - Deployment and connector-configuration CLI
26
+
27
+ The MCP server exposes `hearth_status`, `hearth_mood`, `hearth_note`, `hearth_moment`, `hearth_date`, `hearth_shopping_list`, `hearth_shopping_add`, `hearth_pressure`, `hearth_food_diary_today`, `hearth_food_diary_history`, `hearth_food_review`, and `hearth_water_status`.
28
+
29
+ ## Deploy
30
+
31
+ ### Bundled CLI
32
+
33
+ ```bash
34
+ npx hearth-dash deploy
35
+ npx hearth-dash mcp
36
+ ```
37
+
38
+ The deploy command creates the D1 database, R2 bucket and OAuth KV namespace; installs the pinned runtime dependency; prompts for configuration; generates a session-signing secret; applies the schema; and deploys the Worker. It does not save the dashboard password locally.
39
+
40
+ ### Manual deployment
41
+
42
+ ```bash
43
+ npm install
44
+
45
+ # Create storage and put the returned IDs in wrangler.toml
46
+ npx wrangler d1 create hearth-dash-db
47
+ npx wrangler r2 bucket create hearth-dash-photos
48
+ npx wrangler kv namespace create hearth-dash-oauth
49
+
50
+ # Generate a SESSION_SECRET
51
+ node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"
52
+
53
+ # Optional weather integration
54
+ # (set WEATHER_API_KEY after provisioning if wanted)
55
+
56
+ # Provision the Worker first, then initialize storage and set secrets
57
+ npm run deploy
58
+ npm run db:init:remote
59
+ npx wrangler secret put DASHBOARD_PASSWORD
60
+ npx wrangler secret put SESSION_SECRET
61
+ npx wrangler secret put WEATHER_API_KEY # optional
62
+
63
+ # Activate the fully configured Worker
64
+ npm run deploy
65
+ ```
66
+
67
+ Configuration lives in `wrangler.toml`: partner names under `[vars]`, the D1 binding, OAuth KV binding, R2 bucket binding and optional `WEATHER_LAT` / `WEATHER_LON`. Keep the `global_fetch_strictly_public` compatibility flag: it lets the OAuth provider resolve Claude's Client ID Metadata Document with Cloudflare's SSRF protections. OpenWeatherMap is required only for weather and pressure features.
68
+
69
+ ## Connect Claude
70
+
71
+ After deployment, run `npx hearth-dash mcp`. The connector URL is:
72
+
73
+ ```text
74
+ https://your-worker.example/mcp
75
+ ```
76
+
77
+ There is no secret in that URL.
78
+
79
+ ### Claude.ai, Claude Desktop and Claude mobile
80
+
81
+ 1. Open **Customize → Connectors → Add custom connector**.
82
+ 2. Enter the printed `/mcp` URL.
83
+ 3. Choose **Sign in now** if Claude asks for an authentication mode.
84
+ 4. Choose **Use Claude's published identity** (recommended) or **Register automatically**. Hearth supports both CIMD and DCR. Do not enter a client secret.
85
+ 5. Claude opens Hearth's consent page. Enter the dashboard password, review the read/write permissions and approve.
86
+ 6. Claude returns through `https://claude.ai/api/mcp/auth_callback` and stores revocable OAuth tokens. The dashboard password is never sent as an MCP credential.
87
+
88
+ Claude connects from Anthropic's cloud, so the Worker must be publicly reachable.
89
+
90
+ ### Claude Code
91
+
92
+ ```bash
93
+ claude mcp add --transport http hearth-dash https://your-worker.example/mcp
94
+ ```
95
+
96
+ Then open `/mcp` inside Claude Code and complete authentication. Claude Code uses a loopback callback rather than Claude.ai's hosted callback; DCR handles its varying local port.
97
+
98
+ ## Upgrading from 1.0.1
99
+
100
+ Version 1.0.1 labelled a custom `{ "tool": ..., "params": ... }` HTTP handler as MCP. It did not implement MCP JSON-RPC or tool discovery and could not work as a Claude.ai custom connector. Version 1.1.0 replaces it with Streamable HTTP MCP and OAuth 2.1. The old payload and secret-bearing URL formats are intentionally rejected.
101
+
102
+ Existing deployments must:
103
+
104
+ 1. Install dependencies with `npm install`.
105
+ 2. Create an OAuth KV namespace and add its ID as the `OAUTH_KV` binding in `wrangler.toml`.
106
+ 3. Keep `compatibility_flags = ["global_fetch_strictly_public"]`.
107
+ 4. Set a new `SESSION_SECRET` Worker secret.
108
+ 5. Re-run `schema.sql` remotely to add the rate-limit table.
109
+ 6. Deploy `oauth-entry.js` as the Worker entrypoint.
110
+ 7. Remove and re-add the custom connector using `https://your-worker.example/mcp`.
111
+ 8. Delete the obsolete secret with `npx wrangler secret delete MCP_SECRET` after the new deployment works.
112
+
113
+ The first-visit password setup page has also been removed. A public, unclaimed setup page allowed the first visitor—not necessarily the owner—to take control of a new deployment. Configure `DASHBOARD_PASSWORD` as a Worker secret instead.
114
+
115
+ ### 1.1.1 dashboard-login fix
116
+
117
+ Version 1.1.1 keeps ordinary dashboard, login and API requests outside the OAuth provider and makes same-origin form validation resilient when a trusted Cloudflare wrapper reconstructs the internal request URL. Cross-site browser submissions remain rejected. Upgrade with `npx hearth-dash@latest deploy` if a 1.1.0 deployment returns plain `Forbidden` after submitting `/login`.
118
+
119
+ ## Security notes
120
+
121
+ - There are no functional default credentials, bearer tokens or secret-bearing connector URLs.
122
+ - OAuth uses authorization-code flow, S256 PKCE, RFC 9728 protected-resource metadata, RFC 8414 authorization-server metadata, resource-bound access tokens and refresh-token rotation from Cloudflare's maintained `workers-oauth-provider` library.
123
+ - Access tokens expire after one hour. Rotating refresh tokens have a 30-day TTL. Dynamically registered clients expire after 90 days.
124
+ - Consent requires either a valid signed dashboard session or the dashboard password. Consent POSTs use a short-lived CSRF cookie, same-origin checks and rate limiting.
125
+ - `hearth:read` and `hearth:write` are enforced at tool-call time. Read-only tokens cannot invoke write actions hidden inside mixed read/write tools.
126
+ - Session cookies are signed, expire after seven days and use the `__Host-` prefix plus `Secure`, `HttpOnly` and `SameSite=Strict`.
127
+ - Browser origins, JSON body size and tool arguments are validated. MCP, login, consent and dynamic-registration paths are rate-limited.
128
+ - Private R2 photos are served only through authenticated dashboard routes and use `private, no-store`. MCP results do not expose photo URLs.
129
+ - Compatible clients can revoke their grant through Hearth's OAuth revocation endpoint. Removing a connector always removes its locally stored token, but not every client promises server-side revocation. For emergency revocation of every OAuth grant, replace the `OAUTH_KV` binding with a fresh namespace (or delete all keys in the existing namespace) and redeploy.
130
+ - Hearth's bundled authorization screen represents one dashboard owner, not a multi-tenant identity system. Separate households should use separate deployments.
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ npm test
136
+ npx wrangler deploy --dry-run
137
+ npm run dev
138
+ ```
139
+
140
+ The unit suite tests MCP protocol behavior, tool validation, scope enforcement and dashboard sessions. OAuth discovery and the full PKCE flow should also be exercised through a local HTTPS Wrangler server or a disposable deployment before release.
141
+
142
+ Requires Node.js 22 or newer. Hearth pins the tested Wrangler release used by its deployment CLI.
143
+
144
+ ## License
145
+
146
+ Non-Commercial. Free for personal, educational and non-commercial use. See [LICENSE](LICENSE).
@@ -1,26 +1,26 @@
1
- import { readFileSync, existsSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { homedir } from "node:os";
4
- import { banner, bold, dim, cyan, warn } from "../lib/ui.js";
5
-
6
- const CONFIG_PATH = join(homedir(), ".hearth-dash", "config.json");
7
-
8
- export default async function configCommand(args) {
9
- banner();
10
-
11
- if (!existsSync(CONFIG_PATH)) {
12
- warn("No config found. Run 'hearth-dash deploy' first.");
13
- process.exit(1);
14
- }
15
-
16
- const config = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
17
-
18
- console.log(bold(" Current Configuration\n"));
19
- console.log(` ${bold("Dashboard URL:")} ${cyan(config.workerUrl || "not set")}`);
20
- console.log(` ${bold("Partner 1:")} ${config.partner1 || "not set"}`);
21
- console.log(` ${bold("Partner 2:")} ${config.partner2 || "not set"}`);
22
- console.log(` ${bold("Database ID:")} ${config.dbId ? config.dbId.substring(0, 8) + "..." : "not set"}`);
23
- console.log(` ${bold("MCP Secret:")} ${config.mcpSecret ? config.mcpSecret.substring(0, 6) + "..." : "not set"}`);
24
- console.log(` ${bold("Deployed:")} ${config.deployedAt || "never"}`);
25
- console.log(`\n ${dim("Config file: " + CONFIG_PATH)}\n`);
26
- }
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { banner, bold, dim, cyan, warn } from "../lib/ui.js";
5
+
6
+ const CONFIG_PATH = join(homedir(), ".hearth-dash", "config.json");
7
+
8
+ export default async function configCommand(args) {
9
+ banner();
10
+
11
+ if (!existsSync(CONFIG_PATH)) {
12
+ warn("No config found. Run 'hearth-dash deploy' first.");
13
+ process.exit(1);
14
+ }
15
+
16
+ const config = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
17
+
18
+ console.log(bold(" Current Configuration\n"));
19
+ console.log(` ${bold("Dashboard URL:")} ${cyan(config.workerUrl || "not set")}`);
20
+ console.log(` ${bold("Partner 1:")} ${config.partner1 || "not set"}`);
21
+ console.log(` ${bold("Partner 2:")} ${config.partner2 || "not set"}`);
22
+ console.log(` ${bold("Database ID:")} ${config.dbId ? config.dbId.substring(0, 8) + "..." : "not set"}`);
23
+ console.log(` ${bold("MCP Auth:")} OAuth 2.1`);
24
+ console.log(` ${bold("Deployed:")} ${config.deployedAt || "never"}`);
25
+ console.log(`\n ${dim("Config file: " + CONFIG_PATH)}\n`);
26
+ }