hearth-dash 1.1.1 → 1.1.3

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.
@@ -1,73 +1,73 @@
1
- import { readFileSync, existsSync, chmodSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { homedir } from "node:os";
4
- import { banner, bold, dim, cyan, warn, info } from "../lib/ui.js";
5
-
6
- const CONFIG_PATH = join(homedir(), ".hearth-dash", "config.json");
7
-
8
- export default async function mcpCommand(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
- const mcpUrl = config.workerUrl + "/mcp";
18
-
19
- console.log(bold(" MCP Configuration\n"));
20
-
21
- console.log(` ${bold("Endpoint:")} ${cyan(mcpUrl)}\n`);
22
- info("Hearth uses OAuth 2.1. Your dashboard password stays out of the connector URL and Claude stores only revocable tokens.");
23
- console.log();
24
-
25
- console.log(` ${bold("For Claude Code")} (add to ${dim("~/.claude.json")}):\n`);
26
- console.log(` {`);
27
- console.log(` "mcpServers": {`);
28
- console.log(` "hearth-dash": {`);
29
- console.log(` "url": "${mcpUrl}"`);
30
- console.log(` }`);
31
- console.log(` }`);
32
- console.log(` }\n`);
33
-
34
- console.log(` ${bold("For Claude Desktop")} (Settings > Developer > MCP):\n`);
35
- console.log(` Name: hearth-dash`);
36
- console.log(` URL: ${mcpUrl}\n`);
37
- console.log(` ${dim("When prompted, sign in with the Hearth dashboard password and approve access.")}\n`);
38
-
39
- console.log(` ${bold("Connector URL")} (for Claude.ai mobile):`);
40
- console.log(` ${cyan(mcpUrl)}\n`);
41
-
42
- console.log(` ${bold("Available MCP tools:")}`);
43
- const tools = [
44
- "hearth_status — Dashboard overview",
45
- "hearth_mood — Get/set moods",
46
- "hearth_note — Get/leave fridge notes",
47
- "hearth_moment — List/add moments",
48
- "hearth_date — Upcoming/add dates",
49
- "hearth_shopping_list — Get shopping list",
50
- "hearth_shopping_add — Add shopping item",
51
- "hearth_pressure — Barometric pressure data",
52
- "hearth_food_diary_today — Today's meals + water",
53
- "hearth_food_diary_history — Historical food data",
54
- "hearth_food_review — Post food review",
55
- "hearth_water_status — Water intake status",
56
- ];
57
- tools.forEach(t => console.log(` ${dim(t)}`));
58
- console.log();
59
-
60
- if (args.includes("--install")) {
61
- const { writeFileSync } = await import("node:fs");
62
- const claudeConfigPath = join(homedir(), ".claude.json");
63
- let claudeConfig = {};
64
- if (existsSync(claudeConfigPath)) {
65
- try { claudeConfig = JSON.parse(readFileSync(claudeConfigPath, "utf-8")); } catch {}
66
- }
67
- if (!claudeConfig.mcpServers) claudeConfig.mcpServers = {};
68
- claudeConfig.mcpServers["hearth-dash"] = { url: mcpUrl };
69
- writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + "\n", "utf-8");
70
- try { chmodSync(claudeConfigPath, 0o600); } catch {}
71
- info("MCP config written to " + claudeConfigPath);
72
- }
73
- }
1
+ import { readFileSync, existsSync, chmodSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { banner, bold, dim, cyan, warn, info } from "../lib/ui.js";
5
+
6
+ const CONFIG_PATH = join(homedir(), ".hearth-dash", "config.json");
7
+
8
+ export default async function mcpCommand(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
+ const mcpUrl = config.workerUrl + "/mcp";
18
+
19
+ console.log(bold(" MCP Configuration\n"));
20
+
21
+ console.log(` ${bold("Endpoint:")} ${cyan(mcpUrl)}\n`);
22
+ info("Hearth uses OAuth 2.1. Your dashboard password stays out of the connector URL and Claude stores only revocable tokens.");
23
+ console.log();
24
+
25
+ console.log(` ${bold("For Claude Code")} (add to ${dim("~/.claude.json")}):\n`);
26
+ console.log(` {`);
27
+ console.log(` "mcpServers": {`);
28
+ console.log(` "hearth-dash": {`);
29
+ console.log(` "url": "${mcpUrl}"`);
30
+ console.log(` }`);
31
+ console.log(` }`);
32
+ console.log(` }\n`);
33
+
34
+ console.log(` ${bold("For Claude Desktop")} (Settings > Developer > MCP):\n`);
35
+ console.log(` Name: hearth-dash`);
36
+ console.log(` URL: ${mcpUrl}\n`);
37
+ console.log(` ${dim("When prompted, sign in with the Hearth dashboard password and approve access.")}\n`);
38
+
39
+ console.log(` ${bold("Connector URL")} (for Claude.ai mobile):`);
40
+ console.log(` ${cyan(mcpUrl)}\n`);
41
+
42
+ console.log(` ${bold("Available MCP tools:")}`);
43
+ const tools = [
44
+ "hearth_status — Dashboard overview",
45
+ "hearth_mood — Get/set moods",
46
+ "hearth_note — Get/leave fridge notes",
47
+ "hearth_moment — List/add moments",
48
+ "hearth_date — Upcoming/add dates",
49
+ "hearth_shopping_list — Get shopping list",
50
+ "hearth_shopping_add — Add shopping item",
51
+ "hearth_pressure — Barometric pressure data",
52
+ "hearth_food_diary_today — Today's meals + water",
53
+ "hearth_food_diary_history — Historical food data",
54
+ "hearth_food_review — Post food review",
55
+ "hearth_water_status — Water intake status",
56
+ ];
57
+ tools.forEach(t => console.log(` ${dim(t)}`));
58
+ console.log();
59
+
60
+ if (args.includes("--install")) {
61
+ const { writeFileSync } = await import("node:fs");
62
+ const claudeConfigPath = join(homedir(), ".claude.json");
63
+ let claudeConfig = {};
64
+ if (existsSync(claudeConfigPath)) {
65
+ try { claudeConfig = JSON.parse(readFileSync(claudeConfigPath, "utf-8")); } catch {}
66
+ }
67
+ if (!claudeConfig.mcpServers) claudeConfig.mcpServers = {};
68
+ claudeConfig.mcpServers["hearth-dash"] = { url: mcpUrl };
69
+ writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + "\n", "utf-8");
70
+ try { chmodSync(claudeConfigPath, 0o600); } catch {}
71
+ info("MCP config written to " + claudeConfigPath);
72
+ }
73
+ }
package/cli/index.js CHANGED
@@ -1,63 +1,63 @@
1
- #!/usr/bin/env node
2
-
3
- import { bold, dim, magenta, cyan } from "./lib/ui.js";
4
-
5
- const [,, command, ...args] = process.argv;
6
-
7
- const COMMANDS = {
8
- deploy: () => import("./commands/deploy.js"),
9
- mcp: () => import("./commands/mcp.js"),
10
- config: () => import("./commands/config.js"),
11
- };
12
-
13
- async function main() {
14
- if (!command || command === "help" || command === "--help" || command === "-h") {
15
- printHelp();
16
- process.exit(0);
17
- }
18
-
19
- if (command === "--version" || command === "-v") {
20
- const { readFileSync } = await import("node:fs");
21
- const { join } = await import("node:path");
22
- const { getPackageRoot } = await import("./lib/platform.js");
23
- try {
24
- const pkg = JSON.parse(readFileSync(join(getPackageRoot(), "package.json"), "utf-8"));
25
- console.log(pkg.version);
26
- } catch { console.log("unknown"); }
27
- process.exit(0);
28
- }
29
-
30
- const loader = COMMANDS[command];
31
- if (!loader) {
32
- console.error(`Unknown command: ${command}\n`);
33
- printHelp();
34
- process.exit(1);
35
- }
36
-
37
- const mod = await loader();
38
- await mod.default(args);
39
- }
40
-
41
- function printHelp() {
42
- console.log(`
43
- ${magenta(bold("HEARTH DASH"))} ${dim("— Personal dashboard")}
44
-
45
- ${bold("Usage:")} hearth-dash <command> [options]
46
-
47
- ${bold("Commands:")}
48
- ${cyan("deploy")} Deploy to Cloudflare (Workers + D1 + R2)
49
- ${cyan("mcp")} Print MCP config for Claude Code/Desktop
50
- ${cyan("config")} View current configuration
51
-
52
- ${bold("Quick start:")}
53
- ${dim("$")} npx hearth-dash deploy
54
- ${dim("$")} npx hearth-dash mcp
55
-
56
- ${bold("Requirements:")}
57
- - Node.js 22+
58
- - Cloudflare account (free tier works)
59
- - OpenWeatherMap API key (free at ${dim("https://openweathermap.org/api")})
60
- `);
61
- }
62
-
63
- main();
1
+ #!/usr/bin/env node
2
+
3
+ import { bold, dim, magenta, cyan } from "./lib/ui.js";
4
+
5
+ const [,, command, ...args] = process.argv;
6
+
7
+ const COMMANDS = {
8
+ deploy: () => import("./commands/deploy.js"),
9
+ mcp: () => import("./commands/mcp.js"),
10
+ config: () => import("./commands/config.js"),
11
+ };
12
+
13
+ async function main() {
14
+ if (!command || command === "help" || command === "--help" || command === "-h") {
15
+ printHelp();
16
+ process.exit(0);
17
+ }
18
+
19
+ if (command === "--version" || command === "-v") {
20
+ const { readFileSync } = await import("node:fs");
21
+ const { join } = await import("node:path");
22
+ const { getPackageRoot } = await import("./lib/platform.js");
23
+ try {
24
+ const pkg = JSON.parse(readFileSync(join(getPackageRoot(), "package.json"), "utf-8"));
25
+ console.log(pkg.version);
26
+ } catch { console.log("unknown"); }
27
+ process.exit(0);
28
+ }
29
+
30
+ const loader = COMMANDS[command];
31
+ if (!loader) {
32
+ console.error(`Unknown command: ${command}\n`);
33
+ printHelp();
34
+ process.exit(1);
35
+ }
36
+
37
+ const mod = await loader();
38
+ await mod.default(args);
39
+ }
40
+
41
+ function printHelp() {
42
+ console.log(`
43
+ ${magenta(bold("HEARTH DASH"))} ${dim("— Personal dashboard")}
44
+
45
+ ${bold("Usage:")} hearth-dash <command> [options]
46
+
47
+ ${bold("Commands:")}
48
+ ${cyan("deploy")} Deploy to Cloudflare (Workers + D1 + R2)
49
+ ${cyan("mcp")} Print MCP config for Claude Code/Desktop
50
+ ${cyan("config")} View current configuration
51
+
52
+ ${bold("Quick start:")}
53
+ ${dim("$")} npx hearth-dash deploy
54
+ ${dim("$")} npx hearth-dash mcp
55
+
56
+ ${bold("Requirements:")}
57
+ - Node.js 22+
58
+ - Cloudflare account (free tier works)
59
+ - OpenWeatherMap API key (free at ${dim("https://openweathermap.org/api")})
60
+ `);
61
+ }
62
+
63
+ main();
@@ -1,12 +1,12 @@
1
- import { existsSync } from "node:fs";
2
- import { execSync } from "node:child_process";
3
-
4
- export function getPackageRoot() {
5
- const url = new URL("../..", import.meta.url);
6
- const decoded = decodeURIComponent(url.pathname);
7
- return decoded.replace(/^\/([A-Z]:)/, "$1").replace(/\/$/, "");
8
- }
9
-
10
- export function getNodeMajor() {
11
- return parseInt(process.versions.node.split(".")[0], 10);
12
- }
1
+ import { existsSync } from "node:fs";
2
+ import { execSync } from "node:child_process";
3
+
4
+ export function getPackageRoot() {
5
+ const url = new URL("../..", import.meta.url);
6
+ const decoded = decodeURIComponent(url.pathname);
7
+ return decoded.replace(/^\/([A-Z]:)/, "$1").replace(/\/$/, "");
8
+ }
9
+
10
+ export function getNodeMajor() {
11
+ return parseInt(process.versions.node.split(".")[0], 10);
12
+ }
@@ -1,44 +1,44 @@
1
- import { createInterface } from "node:readline";
2
-
3
- function rl() { return createInterface({ input: process.stdin, output: process.stdout }); }
4
-
5
- export function ask(question, defaultValue) {
6
- return new Promise((resolve) => {
7
- const r = rl();
8
- const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
9
- r.question(prompt, (a) => { r.close(); resolve(a.trim() || defaultValue || ""); });
10
- });
11
- }
12
-
13
- export function confirm(question) {
14
- return new Promise((resolve) => {
15
- const r = rl();
16
- r.question(`${question} (y/N): `, (a) => { r.close(); resolve(a.trim().toLowerCase() === "y"); });
17
- });
18
- }
19
-
20
- export function password(question) {
21
- return new Promise((resolve) => {
22
- const stdin = process.stdin;
23
- process.stdout.write(`${question}: `);
24
- if (!stdin.isTTY || !stdin.setRawMode) {
25
- const r = rl();
26
- r.question("", (a) => { r.close(); resolve(a.trim()); });
27
- return;
28
- }
29
- const wasRaw = stdin.isRaw;
30
- stdin.setRawMode(true);
31
- stdin.resume();
32
- let input = "";
33
- const onData = (buf) => {
34
- const ch = buf.toString("utf8");
35
- if (ch === "\r" || ch === "\n") {
36
- stdin.setRawMode(wasRaw || false); stdin.removeListener("data", onData); stdin.pause();
37
- process.stdout.write("\n"); resolve(input.trim());
38
- } else if (ch === "\u0003") { stdin.setRawMode(wasRaw || false); process.exit(1); }
39
- else if (ch === "\u007f" || ch === "\b") { if (input.length) { input = input.slice(0, -1); process.stdout.write("\b \b"); } }
40
- else if (ch.charCodeAt(0) >= 32) { input += ch; process.stdout.write("*"); }
41
- };
42
- stdin.on("data", onData);
43
- });
44
- }
1
+ import { createInterface } from "node:readline";
2
+
3
+ function rl() { return createInterface({ input: process.stdin, output: process.stdout }); }
4
+
5
+ export function ask(question, defaultValue) {
6
+ return new Promise((resolve) => {
7
+ const r = rl();
8
+ const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
9
+ r.question(prompt, (a) => { r.close(); resolve(a.trim() || defaultValue || ""); });
10
+ });
11
+ }
12
+
13
+ export function confirm(question) {
14
+ return new Promise((resolve) => {
15
+ const r = rl();
16
+ r.question(`${question} (y/N): `, (a) => { r.close(); resolve(a.trim().toLowerCase() === "y"); });
17
+ });
18
+ }
19
+
20
+ export function password(question) {
21
+ return new Promise((resolve) => {
22
+ const stdin = process.stdin;
23
+ process.stdout.write(`${question}: `);
24
+ if (!stdin.isTTY || !stdin.setRawMode) {
25
+ const r = rl();
26
+ r.question("", (a) => { r.close(); resolve(a.trim()); });
27
+ return;
28
+ }
29
+ const wasRaw = stdin.isRaw;
30
+ stdin.setRawMode(true);
31
+ stdin.resume();
32
+ let input = "";
33
+ const onData = (buf) => {
34
+ const ch = buf.toString("utf8");
35
+ if (ch === "\r" || ch === "\n") {
36
+ stdin.setRawMode(wasRaw || false); stdin.removeListener("data", onData); stdin.pause();
37
+ process.stdout.write("\n"); resolve(input.trim());
38
+ } else if (ch === "\u0003") { stdin.setRawMode(wasRaw || false); process.exit(1); }
39
+ else if (ch === "\u007f" || ch === "\b") { if (input.length) { input = input.slice(0, -1); process.stdout.write("\b \b"); } }
40
+ else if (ch.charCodeAt(0) >= 32) { input += ch; process.stdout.write("*"); }
41
+ };
42
+ stdin.on("data", onData);
43
+ });
44
+ }
package/cli/lib/ui.js CHANGED
@@ -1,41 +1,41 @@
1
- const isColor = process.stdout.isTTY && !process.env.NO_COLOR;
2
- const fmt = (code) => (text) => isColor ? `\x1b[${code}m${text}\x1b[0m` : text;
3
-
4
- export const bold = fmt("1");
5
- export const dim = fmt("2");
6
- export const red = fmt("31");
7
- export const green = fmt("32");
8
- export const yellow = fmt("33");
9
- export const cyan = fmt("36");
10
- export const magenta = fmt("35");
11
-
12
- export function banner() {
13
- console.log(magenta(bold("\n HEARTH DASH")));
14
- console.log(dim(" Personal dashboard\n"));
15
- }
16
-
17
- export function step(n, total, text) {
18
- console.log(cyan(`[${n}/${total}]`) + ` ${text}`);
19
- }
20
-
21
- export function success(text) { console.log(green(" \u2713 ") + text); }
22
- export function warn(text) { console.log(yellow(" ! ") + text); }
23
- export function fail(text) { console.log(red(" \u2717 ") + text); }
24
- export function info(text) { console.log(dim(" \u2192 ") + text); }
25
-
26
- const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
27
-
28
- export function spinner(text) {
29
- if (!isColor) {
30
- process.stdout.write(` ${text}...`);
31
- return { stop: (f) => console.log(` ${f || "done"}`), fail: (f) => console.log(` ${f || "failed"}`) };
32
- }
33
- let i = 0;
34
- const id = setInterval(() => {
35
- process.stdout.write(`\r ${cyan(FRAMES[i++ % FRAMES.length])} ${text}`);
36
- }, 80);
37
- return {
38
- stop(f) { clearInterval(id); process.stdout.write(`\r ${green("\u2713")} ${f || text}\n`); },
39
- fail(f) { clearInterval(id); process.stdout.write(`\r ${red("\u2717")} ${f || text}\n`); },
40
- };
41
- }
1
+ const isColor = process.stdout.isTTY && !process.env.NO_COLOR;
2
+ const fmt = (code) => (text) => isColor ? `\x1b[${code}m${text}\x1b[0m` : text;
3
+
4
+ export const bold = fmt("1");
5
+ export const dim = fmt("2");
6
+ export const red = fmt("31");
7
+ export const green = fmt("32");
8
+ export const yellow = fmt("33");
9
+ export const cyan = fmt("36");
10
+ export const magenta = fmt("35");
11
+
12
+ export function banner() {
13
+ console.log(magenta(bold("\n HEARTH DASH")));
14
+ console.log(dim(" Personal dashboard\n"));
15
+ }
16
+
17
+ export function step(n, total, text) {
18
+ console.log(cyan(`[${n}/${total}]`) + ` ${text}`);
19
+ }
20
+
21
+ export function success(text) { console.log(green(" \u2713 ") + text); }
22
+ export function warn(text) { console.log(yellow(" ! ") + text); }
23
+ export function fail(text) { console.log(red(" \u2717 ") + text); }
24
+ export function info(text) { console.log(dim(" \u2192 ") + text); }
25
+
26
+ const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
27
+
28
+ export function spinner(text) {
29
+ if (!isColor) {
30
+ process.stdout.write(` ${text}...`);
31
+ return { stop: (f) => console.log(` ${f || "done"}`), fail: (f) => console.log(` ${f || "failed"}`) };
32
+ }
33
+ let i = 0;
34
+ const id = setInterval(() => {
35
+ process.stdout.write(`\r ${cyan(FRAMES[i++ % FRAMES.length])} ${text}`);
36
+ }, 80);
37
+ return {
38
+ stop(f) { clearInterval(id); process.stdout.write(`\r ${green("\u2713")} ${f || text}\n`); },
39
+ fail(f) { clearInterval(id); process.stdout.write(`\r ${red("\u2717")} ${f || text}\n`); },
40
+ };
41
+ }