healthy-developer 0.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/dist/chunk-KFGEBBKV.js +87 -0
- package/dist/cli.js +162 -0
- package/dist/mcp-server.js +13859 -0
- package/package.json +31 -0
- package/scripts/session-start.sh +27 -0
- package/scripts/user-prompt-submit.sh +54 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/utils/config.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
12
|
+
var DEFAULT_CONFIG = {
|
|
13
|
+
waterIntervalMinutes: 30,
|
|
14
|
+
walkIntervalMinutes: 60,
|
|
15
|
+
dailyLiters: 2,
|
|
16
|
+
language: "es",
|
|
17
|
+
enabled: true
|
|
18
|
+
};
|
|
19
|
+
var CONFIG_DIR = path.join(os.homedir(), ".healthy-developer");
|
|
20
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
21
|
+
var STATE_FILE = path.join(CONFIG_DIR, "state.json");
|
|
22
|
+
var SCRIPTS_DIR = path.join(CONFIG_DIR, "scripts");
|
|
23
|
+
function readConfig() {
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf8");
|
|
26
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
27
|
+
} catch {
|
|
28
|
+
return DEFAULT_CONFIG;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function writeConfig(config) {
|
|
32
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
|
+
const current = readConfig();
|
|
34
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ ...current, ...config }, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/utils/state.ts
|
|
38
|
+
import fs2 from "fs";
|
|
39
|
+
var DEFAULT_STATE = {
|
|
40
|
+
lastWaterReminder: 0,
|
|
41
|
+
lastWalkReminder: 0,
|
|
42
|
+
waterCountToday: 0,
|
|
43
|
+
lastResetDate: ""
|
|
44
|
+
};
|
|
45
|
+
function readState() {
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs2.readFileSync(STATE_FILE, "utf8");
|
|
48
|
+
const state = { ...DEFAULT_STATE, ...JSON.parse(raw) };
|
|
49
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
50
|
+
if (state.lastResetDate !== today) {
|
|
51
|
+
state.waterCountToday = 0;
|
|
52
|
+
state.lastResetDate = today;
|
|
53
|
+
writeState(state);
|
|
54
|
+
}
|
|
55
|
+
return state;
|
|
56
|
+
} catch {
|
|
57
|
+
return DEFAULT_STATE;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function writeState(state) {
|
|
61
|
+
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
62
|
+
const current = readState();
|
|
63
|
+
fs2.writeFileSync(STATE_FILE, JSON.stringify({ ...current, ...state }, null, 2));
|
|
64
|
+
}
|
|
65
|
+
function markWaterReminder() {
|
|
66
|
+
const state = readState();
|
|
67
|
+
writeState({
|
|
68
|
+
lastWaterReminder: Math.floor(Date.now() / 1e3),
|
|
69
|
+
waterCountToday: state.waterCountToday + 1
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function markWalkReminder() {
|
|
73
|
+
writeState({ lastWalkReminder: Math.floor(Date.now() / 1e3) });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
__export,
|
|
78
|
+
CONFIG_DIR,
|
|
79
|
+
STATE_FILE,
|
|
80
|
+
SCRIPTS_DIR,
|
|
81
|
+
readConfig,
|
|
82
|
+
writeConfig,
|
|
83
|
+
readState,
|
|
84
|
+
writeState,
|
|
85
|
+
markWaterReminder,
|
|
86
|
+
markWalkReminder
|
|
87
|
+
};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_DIR,
|
|
4
|
+
SCRIPTS_DIR,
|
|
5
|
+
STATE_FILE,
|
|
6
|
+
readConfig,
|
|
7
|
+
readState,
|
|
8
|
+
writeConfig
|
|
9
|
+
} from "./chunk-KFGEBBKV.js";
|
|
10
|
+
|
|
11
|
+
// src/cli.ts
|
|
12
|
+
import * as p from "@clack/prompts";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import os from "os";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
var command = process.argv[2];
|
|
20
|
+
if (!command || command === "install") {
|
|
21
|
+
await runInstall();
|
|
22
|
+
} else if (command === "serve") {
|
|
23
|
+
const { default: mcp } = await import("./mcp-server.js");
|
|
24
|
+
} else if (command === "status") {
|
|
25
|
+
showStatus();
|
|
26
|
+
} else if (command === "disable") {
|
|
27
|
+
writeConfig({ enabled: false });
|
|
28
|
+
console.log(chalk.yellow("healthy-developer disabled."));
|
|
29
|
+
} else if (command === "enable") {
|
|
30
|
+
writeConfig({ enabled: true });
|
|
31
|
+
console.log(chalk.green("healthy-developer enabled."));
|
|
32
|
+
} else {
|
|
33
|
+
console.log(`Usage: healthy-developer [install|serve|status|enable|disable]`);
|
|
34
|
+
}
|
|
35
|
+
async function runInstall() {
|
|
36
|
+
console.log();
|
|
37
|
+
p.intro(chalk.green("\u{1F331} healthy-developer \u2014 setup"));
|
|
38
|
+
const config = await p.group(
|
|
39
|
+
{
|
|
40
|
+
waterInterval: () => p.text({
|
|
41
|
+
message: "\xBFCada cu\xE1ntos minutos recordarte tomar agua?",
|
|
42
|
+
initialValue: "30",
|
|
43
|
+
validate: (v) => isNaN(Number(v)) || Number(v) < 1 ? "Ingres\xE1 un n\xFAmero v\xE1lido" : void 0
|
|
44
|
+
}),
|
|
45
|
+
dailyLiters: () => p.text({
|
|
46
|
+
message: "\xBFCu\xE1ntos litros por d\xEDa quer\xE9s tomar?",
|
|
47
|
+
initialValue: "2",
|
|
48
|
+
validate: (v) => isNaN(Number(v)) || Number(v) < 0.5 ? "Ingres\xE1 un n\xFAmero v\xE1lido" : void 0
|
|
49
|
+
}),
|
|
50
|
+
walkInterval: () => p.text({
|
|
51
|
+
message: "\xBFCada cu\xE1ntos minutos recordarte caminar?",
|
|
52
|
+
initialValue: "60",
|
|
53
|
+
validate: (v) => isNaN(Number(v)) || Number(v) < 1 ? "Ingres\xE1 un n\xFAmero v\xE1lido" : void 0
|
|
54
|
+
}),
|
|
55
|
+
language: () => p.select({
|
|
56
|
+
message: "\xBFEn qu\xE9 idioma quer\xE9s los recordatorios?",
|
|
57
|
+
options: [
|
|
58
|
+
{ value: "es", label: "Espa\xF1ol" },
|
|
59
|
+
{ value: "en", label: "English" }
|
|
60
|
+
]
|
|
61
|
+
})
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
onCancel: () => {
|
|
65
|
+
p.cancel("Instalaci\xF3n cancelada.");
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
const spin = p.spinner();
|
|
71
|
+
spin.start("Guardando configuraci\xF3n...");
|
|
72
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
73
|
+
writeConfig({
|
|
74
|
+
waterIntervalMinutes: Number(config.waterInterval),
|
|
75
|
+
walkIntervalMinutes: Number(config.walkInterval),
|
|
76
|
+
dailyLiters: Number(config.dailyLiters),
|
|
77
|
+
language: config.language,
|
|
78
|
+
enabled: true
|
|
79
|
+
});
|
|
80
|
+
if (!fs.existsSync(STATE_FILE)) {
|
|
81
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
STATE_FILE,
|
|
84
|
+
JSON.stringify({ lastWaterReminder: 0, lastWalkReminder: 0, waterCountToday: 0, lastResetDate: today }, null, 2)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
spin.stop("Configuraci\xF3n guardada.");
|
|
88
|
+
spin.start("Instalando scripts...");
|
|
89
|
+
const srcScripts = path.join(__dirname, "..", "scripts");
|
|
90
|
+
fs.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
91
|
+
for (const file of ["user-prompt-submit.sh", "session-start.sh"]) {
|
|
92
|
+
const src = path.join(srcScripts, file);
|
|
93
|
+
const dest = path.join(SCRIPTS_DIR, file);
|
|
94
|
+
if (fs.existsSync(src)) {
|
|
95
|
+
fs.copyFileSync(src, dest);
|
|
96
|
+
fs.chmodSync(dest, 493);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
spin.stop("Scripts instalados.");
|
|
100
|
+
spin.start("Configurando hooks en Claude Code...");
|
|
101
|
+
const claudeSettings = path.join(os.homedir(), ".claude", "settings.json");
|
|
102
|
+
try {
|
|
103
|
+
const raw = fs.existsSync(claudeSettings) ? fs.readFileSync(claudeSettings, "utf8") : "{}";
|
|
104
|
+
const settings = JSON.parse(raw);
|
|
105
|
+
settings.hooks = settings.hooks ?? {};
|
|
106
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];
|
|
107
|
+
const hasSessionHook = settings.hooks.SessionStart.some(
|
|
108
|
+
(h) => h.hooks?.some((hh) => hh.command?.includes("healthy-developer"))
|
|
109
|
+
);
|
|
110
|
+
if (!hasSessionHook) {
|
|
111
|
+
settings.hooks.SessionStart.push({
|
|
112
|
+
matcher: "startup|clear",
|
|
113
|
+
hooks: [{ type: "command", command: path.join(SCRIPTS_DIR, "session-start.sh"), timeout: 5 }]
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit ?? [];
|
|
117
|
+
const hasPromptHook = settings.hooks.UserPromptSubmit.some(
|
|
118
|
+
(h) => h.hooks?.some((hh) => hh.command?.includes("healthy-developer"))
|
|
119
|
+
);
|
|
120
|
+
if (!hasPromptHook) {
|
|
121
|
+
settings.hooks.UserPromptSubmit.push({
|
|
122
|
+
hooks: [{ type: "command", command: path.join(SCRIPTS_DIR, "user-prompt-submit.sh"), timeout: 2 }]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
fs.writeFileSync(claudeSettings, JSON.stringify(settings, null, 2));
|
|
126
|
+
spin.stop("Hooks registrados en Claude Code.");
|
|
127
|
+
} catch (e) {
|
|
128
|
+
spin.stop(chalk.yellow("No se pudo patchear Claude Code settings. Pod\xE9s hacerlo manualmente."));
|
|
129
|
+
}
|
|
130
|
+
const mcpSnippet = JSON.stringify(
|
|
131
|
+
{
|
|
132
|
+
"healthy-developer": {
|
|
133
|
+
command: "npx",
|
|
134
|
+
args: ["healthy-developer", "serve"]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
null,
|
|
138
|
+
2
|
|
139
|
+
);
|
|
140
|
+
p.note(mcpSnippet, "Para Cursor / Windsurf / Zed \u2014 agreg\xE1 esto a tu config MCP:");
|
|
141
|
+
p.outro(chalk.green("\u2705 Listo! Reinici\xE1 Claude Code para activarlo."));
|
|
142
|
+
}
|
|
143
|
+
function showStatus() {
|
|
144
|
+
const config = readConfig();
|
|
145
|
+
const state = readState();
|
|
146
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
147
|
+
const waterElapsed = state.lastWaterReminder === 0 ? null : Math.floor((now - state.lastWaterReminder) / 60);
|
|
148
|
+
const walkElapsed = state.lastWalkReminder === 0 ? null : Math.floor((now - state.lastWalkReminder) / 60);
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(chalk.bold("healthy-developer \u2014 status"));
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(` Enabled: ${config.enabled ? chalk.green("yes") : chalk.red("no")}`);
|
|
153
|
+
console.log(` Language: ${config.language}`);
|
|
154
|
+
console.log(` Water every: ${config.waterIntervalMinutes} min`);
|
|
155
|
+
console.log(` Walk every: ${config.walkIntervalMinutes} min`);
|
|
156
|
+
console.log(` Daily goal: ${config.dailyLiters}L`);
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(` Water today: ${state.waterCountToday} reminders`);
|
|
159
|
+
console.log(` Last water: ${waterElapsed === null ? "never" : `${waterElapsed} min ago`}`);
|
|
160
|
+
console.log(` Last walk: ${walkElapsed === null ? "never" : `${walkElapsed} min ago`}`);
|
|
161
|
+
console.log();
|
|
162
|
+
}
|