hearth-dash 1.0.0 → 1.1.0

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/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 18+
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
+ }
@@ -1,73 +1,100 @@
1
- import { spawn } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
-
4
- function spawnCmd(cmd, args, opts) {
5
- if (process.platform === "win32") {
6
- const full = [cmd, ...args].map((a) => a.includes(" ") ? `"${a}"` : a).join(" ");
7
- return spawn(full, [], { ...opts, shell: true });
8
- }
9
- return spawn(cmd, args, opts);
10
- }
11
-
1
+ import { spawn } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+
4
+ function spawnCmd(cmd, args, opts) {
5
+ if (process.platform === "win32") {
6
+ const full = [cmd, ...args].map((a) => a.includes(" ") ? `"${a}"` : a).join(" ");
7
+ return spawn(full, [], { ...opts, shell: true });
8
+ }
9
+ return spawn(cmd, args, opts);
10
+ }
11
+
12
12
  export function execWrangler(args, cwd, stdinData) {
13
- return new Promise((resolve) => {
14
- const proc = spawnCmd("npx", ["wrangler", ...args], {
15
- cwd, stdio: ["pipe", "pipe", "pipe"],
16
- env: { ...process.env, FORCE_COLOR: "0" },
17
- });
18
- let stdout = "", stderr = "";
19
- proc.stdout.on("data", (d) => { stdout += d.toString(); });
20
- proc.stderr.on("data", (d) => { stderr += d.toString(); });
21
- if (stdinData) { proc.stdin.write(stdinData); proc.stdin.end(); }
22
- else { proc.stdin.end(); }
23
- proc.on("close", (code) => resolve({ stdout: stdout.trim(), stderr: stderr.trim(), code }));
24
- });
25
- }
26
-
27
- export function parseD1CreateOutput(output) {
28
- const combined = output.stdout + "\n" + output.stderr;
29
- const match = combined.match(/database_id\s*=\s*"([^"]+)"/);
30
- return match ? match[1] : null;
31
- }
32
-
33
- export function parseDeployOutput(output) {
34
- const combined = output.stdout + "\n" + output.stderr;
35
- const match = combined.match(/https:\/\/[^\s)]+\.workers\.dev/);
36
- return match ? match[0] : null;
13
+ return new Promise((resolve) => {
14
+ const proc = spawnCmd("npx", ["wrangler", ...args], {
15
+ cwd, stdio: ["pipe", "pipe", "pipe"],
16
+ env: { ...process.env, FORCE_COLOR: "0" },
17
+ });
18
+ let stdout = "", stderr = "";
19
+ proc.stdout.on("data", (d) => { stdout += d.toString(); });
20
+ proc.stderr.on("data", (d) => { stderr += d.toString(); });
21
+ if (stdinData) { proc.stdin.write(stdinData); proc.stdin.end(); }
22
+ else { proc.stdin.end(); }
23
+ proc.on("close", (code) => resolve({ stdout: stdout.trim(), stderr: stderr.trim(), code }));
24
+ });
37
25
  }
38
26
 
39
- export async function checkWranglerAuth(cwd) {
40
- const result = await execWrangler(["whoami"], cwd);
41
- if (result.code !== 0) return false;
42
- const combined = result.stdout + result.stderr;
43
- return !combined.includes("Not logged in") && !combined.includes("not authenticated");
44
- }
45
-
46
- export async function wranglerLogin(cwd) {
27
+ export function execCommand(command, args, cwd) {
47
28
  return new Promise((resolve) => {
48
- const proc = spawnCmd("npx", ["wrangler", "login"], { cwd, stdio: "inherit" });
49
- proc.on("close", (code) => resolve(code === 0));
29
+ const proc = spawnCmd(command, args, {
30
+ cwd, stdio: ['ignore', 'pipe', 'pipe'],
31
+ env: { ...process.env, FORCE_COLOR: '0' },
32
+ });
33
+ let stdout = '', stderr = '';
34
+ proc.stdout.on('data', (data) => { stdout += data.toString(); });
35
+ proc.stderr.on('data', (data) => { stderr += data.toString(); });
36
+ proc.on('close', (code) => resolve({ stdout: stdout.trim(), stderr: stderr.trim(), code }));
50
37
  });
51
38
  }
52
-
53
- export async function setSecret(name, value, cwd) {
54
- return execWrangler(["secret", "put", name], cwd, value + "\n");
55
- }
56
-
57
- export async function executeSchema(dbName, schemaPath, cwd) {
58
- const result = await execWrangler(["d1", "execute", dbName, "--remote", "--file=" + schemaPath], cwd);
59
- if (result.code === 0) return { ok: true };
60
- const sql = readFileSync(schemaPath, "utf-8");
61
- const statements = sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
62
- for (const stmt of statements) {
63
- const r = await execWrangler(["d1", "execute", dbName, "--remote", "--command", stmt + ";"], cwd);
64
- if (r.code !== 0) return { ok: false, error: r.stderr || r.stdout };
65
- }
66
- return { ok: true };
39
+
40
+ export function parseD1CreateOutput(output) {
41
+ const combined = output.stdout + "\n" + output.stderr;
42
+ const match = combined.match(/database_id\s*=\s*"([^"]+)"/);
43
+ return match ? match[1] : null;
44
+ }
45
+
46
+ export function parseDeployOutput(output) {
47
+ const combined = output.stdout + "\n" + output.stderr;
48
+ const match = combined.match(/https:\/\/[^\s)]+\.workers\.dev/);
49
+ return match ? match[0] : null;
50
+ }
51
+
52
+ export async function checkWranglerAuth(cwd) {
53
+ const result = await execWrangler(["whoami"], cwd);
54
+ if (result.code !== 0) return false;
55
+ const combined = result.stdout + result.stderr;
56
+ return !combined.includes("Not logged in") && !combined.includes("not authenticated");
57
+ }
58
+
59
+ export async function wranglerLogin(cwd) {
60
+ return new Promise((resolve) => {
61
+ const proc = spawnCmd("npx", ["wrangler", "login"], { cwd, stdio: "inherit" });
62
+ proc.on("close", (code) => resolve(code === 0));
63
+ });
64
+ }
65
+
66
+ export async function setSecret(name, value, cwd) {
67
+ return execWrangler(["secret", "put", name], cwd, value + "\n");
68
+ }
69
+
70
+ export async function executeSchema(dbName, schemaPath, cwd) {
71
+ const result = await execWrangler(["d1", "execute", dbName, "--remote", "--file=" + schemaPath], cwd);
72
+ if (result.code === 0) return { ok: true };
73
+ const sql = readFileSync(schemaPath, "utf-8");
74
+ const statements = sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
75
+ for (const stmt of statements) {
76
+ const r = await execWrangler(["d1", "execute", dbName, "--remote", "--command", stmt + ";"], cwd);
77
+ if (r.code !== 0) return { ok: false, error: r.stderr || r.stdout };
78
+ }
79
+ return { ok: true };
80
+ }
81
+
82
+ export async function listD1Databases(cwd) {
83
+ const result = await execWrangler(["d1", "list", "--json"], cwd);
84
+ if (result.code !== 0) return [];
85
+ try { return JSON.parse(result.stdout); } catch { return []; }
67
86
  }
68
87
 
69
- export async function listD1Databases(cwd) {
70
- const result = await execWrangler(["d1", "list", "--json"], cwd);
88
+ export async function listKvNamespaces(cwd) {
89
+ const result = await execWrangler(['kv', 'namespace', 'list'], cwd);
71
90
  if (result.code !== 0) return [];
72
91
  try { return JSON.parse(result.stdout); } catch { return []; }
73
92
  }
93
+
94
+ export function parseKvCreateOutput(output) {
95
+ const combined = output.stdout + '\n' + output.stderr;
96
+ const toml = combined.match(/id\s*=\s*"([a-f0-9]{32})"/i);
97
+ if (toml) return toml[1];
98
+ const json = combined.match(/"id"\s*:\s*"([a-f0-9]{32})"/i);
99
+ return json ? json[1] : null;
100
+ }
package/oauth-entry.js ADDED
@@ -0,0 +1,41 @@
1
+ import { OAuthProvider } from '@cloudflare/workers-oauth-provider';
2
+ import { oauthApiHandler, oauthDefaultHandler, withinRateLimit } from './worker.js';
3
+
4
+ const OAUTH_SCOPES = ['hearth:read', 'hearth:write'];
5
+
6
+ function createOAuthProvider(request, env) {
7
+ const origin = new URL(request.url).origin;
8
+ const resource = `${origin}/mcp`;
9
+ return new OAuthProvider({
10
+ apiRoute: '/mcp',
11
+ apiHandler: oauthApiHandler,
12
+ defaultHandler: oauthDefaultHandler,
13
+ authorizeEndpoint: '/authorize',
14
+ tokenEndpoint: '/oauth/token',
15
+ clientRegistrationEndpoint: '/oauth/register',
16
+ async clientRegistrationCallback({ request: registrationRequest }) {
17
+ const allowed = await withinRateLimit(env, registrationRequest, 'oauth-register', 20, 3600);
18
+ if (!allowed) {
19
+ return { code: 'access_denied', description: 'Too many client registrations. Try again later.', status: 429 };
20
+ }
21
+ },
22
+ clientIdMetadataDocumentEnabled: true,
23
+ scopesSupported: OAUTH_SCOPES,
24
+ accessTokenTTL: 3600,
25
+ refreshTokenTTL: 30 * 24 * 60 * 60,
26
+ clientRegistrationTTL: 90 * 24 * 60 * 60,
27
+ resourceMetadata: {
28
+ resource,
29
+ authorization_servers: [origin],
30
+ scopes_supported: ['hearth:read'],
31
+ bearer_methods_supported: ['header'],
32
+ resource_name: 'Hearth Dash',
33
+ },
34
+ });
35
+ }
36
+
37
+ export default {
38
+ fetch(request, env, ctx) {
39
+ return createOAuthProvider(request, env).fetch(request, env, ctx);
40
+ },
41
+ };
package/package.json CHANGED
@@ -1,41 +1,47 @@
1
- {
2
- "name": "hearth-dash",
3
- "version": "1.0.0",
4
- "description": "Personal dashboard — moods, moments, food diary, weather, and more. Deploy to Cloudflare in one command.",
5
- "type": "module",
6
- "bin": {
7
- "hearth-dash": "./cli/index.js"
8
- },
9
- "scripts": {
10
- "dev": "npx wrangler dev",
11
- "deploy": "npx wrangler deploy",
12
- "db:init": "npx wrangler d1 execute hearth-dash-db --file schema.sql",
13
- "db:init:remote": "npx wrangler d1 execute hearth-dash-db --remote --file schema.sql"
14
- },
15
- "keywords": [
16
- "dashboard",
17
- "cloudflare-workers",
18
- "mcp",
19
- "mood-tracker",
20
- "food-diary",
21
- "weather",
22
- "personal"
23
- ],
24
- "author": "AIDHD",
25
- "license": "MIT",
26
- "repository": {
27
- "type": "git",
28
- "url": "https://github.com/martusha89/hearth-dash.git"
29
- },
1
+ {
2
+ "name": "hearth-dash",
3
+ "version": "1.1.0",
4
+ "description": "Personal dashboard — moods, moments, food diary, weather, and more. Deploy to Cloudflare in one command.",
5
+ "type": "module",
6
+ "bin": {
7
+ "hearth-dash": "cli/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test",
11
+ "dev": "npx wrangler dev",
12
+ "deploy": "npx wrangler deploy",
13
+ "db:init": "npx wrangler d1 execute hearth-dash-db --file schema.sql",
14
+ "db:init:remote": "npx wrangler d1 execute hearth-dash-db --remote --file schema.sql"
15
+ },
16
+ "keywords": [
17
+ "dashboard",
18
+ "cloudflare-workers",
19
+ "mcp",
20
+ "mood-tracker",
21
+ "food-diary",
22
+ "weather",
23
+ "personal"
24
+ ],
25
+ "author": "AIDHD",
26
+ "license": "SEE LICENSE IN LICENSE",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/martusha89/hearth-dash.git"
30
+ },
30
31
  "engines": {
31
- "node": ">=18"
32
- },
33
- "files": [
34
- "worker.js",
35
- "schema.sql",
36
- "wrangler.toml",
37
- "cli/",
38
- "README.md",
39
- "LICENSE"
40
- ]
41
- }
32
+ "node": ">=22"
33
+ },
34
+ "files": [
35
+ "worker.js",
36
+ "oauth-entry.js",
37
+ "schema.sql",
38
+ "wrangler.toml",
39
+ "cli/",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "dependencies": {
44
+ "@cloudflare/workers-oauth-provider": "0.10.3",
45
+ "wrangler": "4.135.0"
46
+ }
47
+ }