wellness-nourish 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/LICENSE +21 -0
- package/README.md +146 -0
- package/SECURITY.md +9 -0
- package/dist/cli/commands.d.ts +3 -0
- package/dist/cli/commands.js +467 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +9 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/nourish-prompts.d.ts +2 -0
- package/dist/prompts/nourish-prompts.js +17 -0
- package/dist/prompts/nourish-prompts.js.map +1 -0
- package/dist/providers/open-food-facts.d.ts +5 -0
- package/dist/providers/open-food-facts.js +141 -0
- package/dist/providers/open-food-facts.js.map +1 -0
- package/dist/providers/usda.d.ts +6 -0
- package/dist/providers/usda.js +157 -0
- package/dist/providers/usda.js.map +1 -0
- package/dist/resources/nourish-resources.d.ts +2 -0
- package/dist/resources/nourish-resources.js +26 -0
- package/dist/resources/nourish-resources.js.map +1 -0
- package/dist/schemas/common.d.ts +224 -0
- package/dist/schemas/common.js +224 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/services/agent-manifest.d.ts +27 -0
- package/dist/services/agent-manifest.js +80 -0
- package/dist/services/agent-manifest.js.map +1 -0
- package/dist/services/capabilities.d.ts +12 -0
- package/dist/services/capabilities.js +27 -0
- package/dist/services/capabilities.js.map +1 -0
- package/dist/services/config.d.ts +3 -0
- package/dist/services/config.js +32 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/connection-status.d.ts +13 -0
- package/dist/services/connection-status.js +34 -0
- package/dist/services/connection-status.js.map +1 -0
- package/dist/services/food-normalization.d.ts +3 -0
- package/dist/services/food-normalization.js +14 -0
- package/dist/services/food-normalization.js.map +1 -0
- package/dist/services/format.d.ts +13 -0
- package/dist/services/format.js +58 -0
- package/dist/services/format.js.map +1 -0
- package/dist/services/goals-store.d.ts +7 -0
- package/dist/services/goals-store.js +87 -0
- package/dist/services/goals-store.js.map +1 -0
- package/dist/services/hydration-store.d.ts +12 -0
- package/dist/services/hydration-store.js +96 -0
- package/dist/services/hydration-store.js.map +1 -0
- package/dist/services/intake-store.d.ts +15 -0
- package/dist/services/intake-store.js +170 -0
- package/dist/services/intake-store.js.map +1 -0
- package/dist/services/meal-estimator.d.ts +22 -0
- package/dist/services/meal-estimator.js +178 -0
- package/dist/services/meal-estimator.js.map +1 -0
- package/dist/services/nutrients.d.ts +4 -0
- package/dist/services/nutrients.js +31 -0
- package/dist/services/nutrients.js.map +1 -0
- package/dist/services/portion-engine.d.ts +4 -0
- package/dist/services/portion-engine.js +50 -0
- package/dist/services/portion-engine.js.map +1 -0
- package/dist/services/privacy-audit.d.ts +12 -0
- package/dist/services/privacy-audit.js +19 -0
- package/dist/services/privacy-audit.js.map +1 -0
- package/dist/services/summary.d.ts +28 -0
- package/dist/services/summary.js +84 -0
- package/dist/services/summary.js.map +1 -0
- package/dist/services/usage-guide.d.ts +12 -0
- package/dist/services/usage-guide.js +57 -0
- package/dist/services/usage-guide.js.map +1 -0
- package/dist/tools/nourish-tools.d.ts +2 -0
- package/dist/tools/nourish-tools.js +603 -0
- package/dist/tools/nourish-tools.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/examples/claude-desktop.json +13 -0
- package/examples/codex.json +13 -0
- package/examples/cursor.json +13 -0
- package/examples/hermes.md +18 -0
- package/examples/openclaw.md +18 -0
- package/examples/windsurf.json +13 -0
- package/fixtures/open-food-facts/barcode-peanut-butter.json +23 -0
- package/fixtures/usda/food-banana.json +16 -0
- package/fixtures/usda/search-banana.json +17 -0
- package/package.json +52 -0
- package/server.json +16 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface NourishConnectionStatus {
|
|
2
|
+
ok: true;
|
|
3
|
+
fixture_mode: boolean;
|
|
4
|
+
local_dir: string;
|
|
5
|
+
intake_store_exists: boolean;
|
|
6
|
+
usda_api_key_configured: boolean;
|
|
7
|
+
usda_mode: "api_key" | "demo_key_or_fixture";
|
|
8
|
+
open_food_facts_enabled: boolean;
|
|
9
|
+
cache_ttl_seconds: number;
|
|
10
|
+
max_results: number;
|
|
11
|
+
next_steps: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function buildConnectionStatus(): NourishConnectionStatus;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getConfig } from "./config.js";
|
|
4
|
+
export function buildConnectionStatus() {
|
|
5
|
+
const config = getConfig();
|
|
6
|
+
const usdaApiKeyConfigured = Boolean(config.usda_api_key);
|
|
7
|
+
return {
|
|
8
|
+
ok: true,
|
|
9
|
+
fixture_mode: config.fixture_mode,
|
|
10
|
+
local_dir: config.local_dir,
|
|
11
|
+
intake_store_exists: existsSync(join(config.local_dir, "intake.jsonl")),
|
|
12
|
+
usda_api_key_configured: usdaApiKeyConfigured,
|
|
13
|
+
usda_mode: usdaApiKeyConfigured ? "api_key" : "demo_key_or_fixture",
|
|
14
|
+
open_food_facts_enabled: config.off_enabled,
|
|
15
|
+
cache_ttl_seconds: config.cache_ttl_seconds,
|
|
16
|
+
max_results: config.max_results,
|
|
17
|
+
next_steps: buildNextSteps(usdaApiKeyConfigured, config.off_enabled),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function buildNextSteps(usdaApiKeyConfigured, openFoodFactsEnabled) {
|
|
21
|
+
const nextSteps = ["Use nourish_search_food for generic foods."];
|
|
22
|
+
if (openFoodFactsEnabled) {
|
|
23
|
+
nextSteps.push("Use nourish_lookup_barcode for packaged foods.");
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
nextSteps.push("Set NOURISH_OFF_ENABLED=1 to enable packaged-food barcode lookup.");
|
|
27
|
+
}
|
|
28
|
+
nextSteps.push("Confirm before logging estimated meals.");
|
|
29
|
+
if (!usdaApiKeyConfigured) {
|
|
30
|
+
nextSteps.push("Set FDC_API_KEY for higher USDA FoodData Central quota; DEMO_KEY or fixture mode can still be used for light checks.");
|
|
31
|
+
}
|
|
32
|
+
return nextSteps;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=connection-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-status.js","sourceRoot":"","sources":["../../src/services/connection-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAexC,MAAM,UAAU,qBAAqB;IACnC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAE1D,OAAO;QACL,EAAE,EAAE,IAAI;QACR,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,mBAAmB,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACvE,uBAAuB,EAAE,oBAAoB;QAC7C,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB;QACnE,uBAAuB,EAAE,MAAM,CAAC,WAAW;QAC3C,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,cAAc,CAAC,oBAAoB,EAAE,MAAM,CAAC,WAAW,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,oBAA6B,EAAE,oBAA6B;IAClF,MAAM,SAAS,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAEjE,IAAI,oBAAoB,EAAE,CAAC;QACzB,SAAS,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IACnE,CAAC;SAAM,CAAC;QACN,SAAS,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;IACtF,CAAC;IAED,SAAS,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IAE1D,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC1B,SAAS,CAAC,IAAI,CACZ,sHAAsH,CACvH,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function foodCompleteness(food) {
|
|
2
|
+
const numericNutrientCount = Object.values(food.nutrients_per_100g).filter((value) => Number.isFinite(value)).length;
|
|
3
|
+
if (numericNutrientCount >= 4 && food.serving !== undefined && food.barcode !== undefined) {
|
|
4
|
+
return "high";
|
|
5
|
+
}
|
|
6
|
+
if (numericNutrientCount >= 3) {
|
|
7
|
+
return "medium";
|
|
8
|
+
}
|
|
9
|
+
return "low";
|
|
10
|
+
}
|
|
11
|
+
export function makeFoodId(source, sourceId) {
|
|
12
|
+
return `${source}:${sourceId}`;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=food-normalization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"food-normalization.js","sourceRoot":"","sources":["../../src/services/food-normalization.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAC9B,IAAkE;IAElE,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CACnF,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CACvB,CAAC,MAAM,CAAC;IAET,IAAI,oBAAoB,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1F,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,oBAAoB,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,QAAgB;IACzD,OAAO,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ResponseFormat } from "../types.js";
|
|
2
|
+
export interface McpTextResponse {
|
|
3
|
+
isError?: boolean;
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export declare function makeResponse(payload: unknown, responseFormat?: ResponseFormat, markdown?: string): McpTextResponse;
|
|
10
|
+
export declare function makeError(message: string): McpTextResponse;
|
|
11
|
+
export declare function makeError(code: string, message: string, responseFormat?: ResponseFormat): McpTextResponse;
|
|
12
|
+
export declare function bulletList(title: string, values: Record<string, unknown> | unknown): string;
|
|
13
|
+
export declare function compactTable(rows: Array<Record<string, unknown>>, columns?: string[]): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function makeResponse(payload, responseFormat = "json", markdown) {
|
|
2
|
+
const text = responseFormat === "markdown"
|
|
3
|
+
? (markdown ?? bulletList("Nourish", payload))
|
|
4
|
+
: JSON.stringify(payload);
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: "text", text }],
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function makeError(first, second, responseFormat = "json") {
|
|
10
|
+
const code = second === undefined ? "NOURISH_ERROR" : first;
|
|
11
|
+
const message = second ?? first;
|
|
12
|
+
const payload = {
|
|
13
|
+
ok: false,
|
|
14
|
+
error: {
|
|
15
|
+
code,
|
|
16
|
+
message,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const markdown = `# Error\n\n- **code**: ${code}\n- **message**: ${message}`;
|
|
20
|
+
const response = makeResponse(payload, responseFormat, markdown);
|
|
21
|
+
return {
|
|
22
|
+
...response,
|
|
23
|
+
isError: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function bulletList(title, values) {
|
|
27
|
+
const lines = [`# ${title}`];
|
|
28
|
+
const entries = values !== null && typeof values === "object" && !Array.isArray(values)
|
|
29
|
+
? Object.entries(values)
|
|
30
|
+
: [["value", values]];
|
|
31
|
+
for (const [key, value] of entries) {
|
|
32
|
+
lines.push(`- ${key}: ${formatValue(value)}`);
|
|
33
|
+
}
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
export function compactTable(rows, columns) {
|
|
37
|
+
if (rows.length === 0) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
const headers = columns ?? Object.keys(rows[0] ?? {});
|
|
41
|
+
const header = `| ${headers.join(" | ")} |`;
|
|
42
|
+
const divider = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
43
|
+
const body = rows.map((row) => `| ${headers.map((key) => formatValue(row[key])).join(" | ")} |`);
|
|
44
|
+
return [header, divider, ...body].join("\n");
|
|
45
|
+
}
|
|
46
|
+
function formatValue(value) {
|
|
47
|
+
if (value === null || value === undefined) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
return value.map((entry) => formatValue(entry)).join(", ");
|
|
55
|
+
}
|
|
56
|
+
return JSON.stringify(value);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/services/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NourishGoals, NutrientMap } from "../types.js";
|
|
2
|
+
export interface UpdateGoalsInput {
|
|
3
|
+
daily?: NutrientMap;
|
|
4
|
+
hydration_ml?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function getGoals(path?: string): Promise<NourishGoals>;
|
|
7
|
+
export declare function updateGoals(input: UpdateGoalsInput): Promise<NourishGoals>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { getConfig } from "./config.js";
|
|
6
|
+
function goalsPath() {
|
|
7
|
+
return join(getConfig().local_dir, "goals.json");
|
|
8
|
+
}
|
|
9
|
+
async function ensureStoreDir(path) {
|
|
10
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
export async function getGoals(path = goalsPath()) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await fs.readFile(path, "utf8");
|
|
15
|
+
return normalizeGoals(JSON.parse(raw));
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === "ENOENT") {
|
|
19
|
+
return { daily: {} };
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function updateGoals(input) {
|
|
25
|
+
const path = goalsPath();
|
|
26
|
+
const current = await getGoals(path);
|
|
27
|
+
const next = {
|
|
28
|
+
daily: {
|
|
29
|
+
...current.daily,
|
|
30
|
+
...cleanNutrients(input.daily),
|
|
31
|
+
},
|
|
32
|
+
updated_at: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
const hydration = input.hydration_ml ?? current.hydration_ml;
|
|
35
|
+
if (typeof hydration === "number" && Number.isFinite(hydration) && hydration > 0) {
|
|
36
|
+
next.hydration_ml = hydration;
|
|
37
|
+
}
|
|
38
|
+
await writeGoalsAtomically(next, path);
|
|
39
|
+
return next;
|
|
40
|
+
}
|
|
41
|
+
async function writeGoalsAtomically(goals, path) {
|
|
42
|
+
await ensureStoreDir(path);
|
|
43
|
+
const tempPath = `${path}.${randomUUID()}.tmp`;
|
|
44
|
+
await fs.writeFile(tempPath, `${JSON.stringify(goals, null, 2)}\n`, "utf8");
|
|
45
|
+
await fs.rename(tempPath, path);
|
|
46
|
+
}
|
|
47
|
+
function normalizeGoals(value) {
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return { daily: {} };
|
|
50
|
+
}
|
|
51
|
+
const goals = {
|
|
52
|
+
daily: cleanNutrients(isRecord(value.daily) ? value.daily : undefined),
|
|
53
|
+
};
|
|
54
|
+
if (typeof value.hydration_ml === "number" && Number.isFinite(value.hydration_ml) && value.hydration_ml > 0) {
|
|
55
|
+
goals.hydration_ml = value.hydration_ml;
|
|
56
|
+
}
|
|
57
|
+
if (typeof value.updated_at === "string" && value.updated_at.length > 0) {
|
|
58
|
+
goals.updated_at = value.updated_at;
|
|
59
|
+
}
|
|
60
|
+
return goals;
|
|
61
|
+
}
|
|
62
|
+
function cleanNutrients(value) {
|
|
63
|
+
const clean = {};
|
|
64
|
+
if (!isRecord(value)) {
|
|
65
|
+
return clean;
|
|
66
|
+
}
|
|
67
|
+
for (const key of [
|
|
68
|
+
"calories_kcal",
|
|
69
|
+
"protein_g",
|
|
70
|
+
"carbohydrates_g",
|
|
71
|
+
"fat_g",
|
|
72
|
+
"fiber_g",
|
|
73
|
+
"sugar_g",
|
|
74
|
+
"saturated_fat_g",
|
|
75
|
+
"sodium_mg",
|
|
76
|
+
]) {
|
|
77
|
+
const amount = value[key];
|
|
78
|
+
if (typeof amount === "number" && Number.isFinite(amount) && amount >= 0) {
|
|
79
|
+
clean[key] = amount;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return clean;
|
|
83
|
+
}
|
|
84
|
+
function isRecord(value) {
|
|
85
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=goals-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goals-store.js","sourceRoot":"","sources":["../../src/services/goals-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HydrationEntry, HydrationSummary } from "../types.js";
|
|
2
|
+
export interface LogWaterInput {
|
|
3
|
+
timestamp?: string;
|
|
4
|
+
amount_ml: number;
|
|
5
|
+
source?: HydrationEntry["source"];
|
|
6
|
+
notes?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function logWater(input: LogWaterInput): Promise<HydrationEntry>;
|
|
9
|
+
export declare function listWaterEntries(filter?: {
|
|
10
|
+
date?: string;
|
|
11
|
+
}): Promise<HydrationEntry[]>;
|
|
12
|
+
export declare function buildHydrationSummary(date?: string): Promise<HydrationSummary>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { getConfig } from "./config.js";
|
|
6
|
+
import { getGoals } from "./goals-store.js";
|
|
7
|
+
import { roundNutrient } from "./nutrients.js";
|
|
8
|
+
let mutationQueue = Promise.resolve();
|
|
9
|
+
function hydrationPath() {
|
|
10
|
+
return join(getConfig().local_dir, "hydration.jsonl");
|
|
11
|
+
}
|
|
12
|
+
function withMutationLock(operation) {
|
|
13
|
+
const run = mutationQueue.then(operation, operation);
|
|
14
|
+
mutationQueue = run.then(() => undefined, () => undefined);
|
|
15
|
+
return run;
|
|
16
|
+
}
|
|
17
|
+
async function ensureStoreDir(path) {
|
|
18
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
async function readHydrationEntries(path = hydrationPath()) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = await fs.readFile(path, "utf8");
|
|
23
|
+
return raw
|
|
24
|
+
.split("\n")
|
|
25
|
+
.filter((line) => line.trim().length > 0)
|
|
26
|
+
.map((line) => JSON.parse(line));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === "ENOENT") {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function writeHydrationEntriesAtomically(entries, path = hydrationPath()) {
|
|
36
|
+
await ensureStoreDir(path);
|
|
37
|
+
const tempPath = `${path}.${randomUUID()}.tmp`;
|
|
38
|
+
const data = entries.length === 0 ? "" : `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
|
|
39
|
+
await fs.writeFile(tempPath, data, "utf8");
|
|
40
|
+
await fs.rename(tempPath, path);
|
|
41
|
+
}
|
|
42
|
+
export async function logWater(input) {
|
|
43
|
+
if (!Number.isFinite(input.amount_ml) || input.amount_ml <= 0) {
|
|
44
|
+
throw new Error("amount_ml must be a positive number.");
|
|
45
|
+
}
|
|
46
|
+
return withMutationLock(async () => {
|
|
47
|
+
const path = hydrationPath();
|
|
48
|
+
const entries = await readHydrationEntries(path);
|
|
49
|
+
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
50
|
+
const entryBase = {
|
|
51
|
+
id: `water_${randomUUID()}`,
|
|
52
|
+
timestamp,
|
|
53
|
+
date: timestamp.slice(0, 10),
|
|
54
|
+
amount_ml: roundNutrient(input.amount_ml),
|
|
55
|
+
source: input.source ?? "manual",
|
|
56
|
+
};
|
|
57
|
+
const entry = input.notes === undefined
|
|
58
|
+
? entryBase
|
|
59
|
+
: {
|
|
60
|
+
...entryBase,
|
|
61
|
+
notes: input.notes,
|
|
62
|
+
};
|
|
63
|
+
entries.push(entry);
|
|
64
|
+
await writeHydrationEntriesAtomically(entries, path);
|
|
65
|
+
return entry;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export async function listWaterEntries(filter = {}) {
|
|
69
|
+
const entries = await readHydrationEntries();
|
|
70
|
+
if (filter.date === undefined) {
|
|
71
|
+
return entries;
|
|
72
|
+
}
|
|
73
|
+
return entries.filter((entry) => entry.date === filter.date);
|
|
74
|
+
}
|
|
75
|
+
export async function buildHydrationSummary(date = todayDate()) {
|
|
76
|
+
const entries = await listWaterEntries({ date });
|
|
77
|
+
const goals = await getGoals();
|
|
78
|
+
const total = roundNutrient(entries.reduce((sum, entry) => sum + entry.amount_ml, 0));
|
|
79
|
+
const summaryBase = {
|
|
80
|
+
date,
|
|
81
|
+
total_ml: total,
|
|
82
|
+
entries,
|
|
83
|
+
};
|
|
84
|
+
if (typeof goals.hydration_ml !== "number" || goals.hydration_ml <= 0) {
|
|
85
|
+
return summaryBase;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...summaryBase,
|
|
89
|
+
goal_ml: goals.hydration_ml,
|
|
90
|
+
progress_percent: roundNutrient((total / goals.hydration_ml) * 100),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function todayDate() {
|
|
94
|
+
return new Date().toISOString().slice(0, 10);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=hydration-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydration-store.js","sourceRoot":"","sources":["../../src/services/hydration-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IntakeEntry } from "../types.js";
|
|
2
|
+
export type AddIntakeEntryInput = Omit<IntakeEntry, "id" | "timestamp" | "date"> & Partial<Pick<IntakeEntry, "timestamp">>;
|
|
3
|
+
export type UpdateIntakeEntryPatch = Partial<Pick<IntakeEntry, "meal_type" | "quantity" | "unit" | "timestamp" | "notes" | "tags">>;
|
|
4
|
+
export declare function addIntakeEntry(input: AddIntakeEntryInput): Promise<IntakeEntry>;
|
|
5
|
+
export declare function listIntakeEntries(filter?: {
|
|
6
|
+
date?: string;
|
|
7
|
+
}): Promise<IntakeEntry[]>;
|
|
8
|
+
export declare function updateIntakeEntry(id: string, patch: UpdateIntakeEntryPatch): Promise<IntakeEntry>;
|
|
9
|
+
export declare function deleteIntakeEntry(id: string): Promise<boolean>;
|
|
10
|
+
export declare function clearIntakeDay(date: string): Promise<{
|
|
11
|
+
date: string;
|
|
12
|
+
deleted_entries: number;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function exportIntakeData(): Promise<string>;
|
|
15
|
+
export declare function exportIntakeCsvData(): Promise<string>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { getConfig } from "./config.js";
|
|
6
|
+
let mutationQueue = Promise.resolve();
|
|
7
|
+
function storePath() {
|
|
8
|
+
return join(getConfig().local_dir, "intake.jsonl");
|
|
9
|
+
}
|
|
10
|
+
function withMutationLock(operation) {
|
|
11
|
+
const run = mutationQueue.then(operation, operation);
|
|
12
|
+
mutationQueue = run.then(() => undefined, () => undefined);
|
|
13
|
+
return run;
|
|
14
|
+
}
|
|
15
|
+
async function ensureStoreDir(path) {
|
|
16
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
async function readEntries(path = storePath()) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await fs.readFile(path, "utf8");
|
|
21
|
+
return raw
|
|
22
|
+
.split("\n")
|
|
23
|
+
.filter((line) => line.trim().length > 0)
|
|
24
|
+
.map((line) => JSON.parse(line));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code === "ENOENT") {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function writeEntriesAtomically(entries, path = storePath()) {
|
|
34
|
+
await ensureStoreDir(path);
|
|
35
|
+
const tempPath = `${path}.${randomUUID()}.tmp`;
|
|
36
|
+
const data = entries.length === 0 ? "" : `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
|
|
37
|
+
await fs.writeFile(tempPath, data, "utf8");
|
|
38
|
+
await fs.rename(tempPath, path);
|
|
39
|
+
}
|
|
40
|
+
export async function addIntakeEntry(input) {
|
|
41
|
+
return withMutationLock(async () => {
|
|
42
|
+
const path = storePath();
|
|
43
|
+
const entries = await readEntries(path);
|
|
44
|
+
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
45
|
+
const entry = {
|
|
46
|
+
...input,
|
|
47
|
+
id: `intake_${randomUUID()}`,
|
|
48
|
+
timestamp,
|
|
49
|
+
date: timestamp.slice(0, 10),
|
|
50
|
+
};
|
|
51
|
+
entries.push(entry);
|
|
52
|
+
await writeEntriesAtomically(entries, path);
|
|
53
|
+
return entry;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function listIntakeEntries(filter = {}) {
|
|
57
|
+
const entries = await readEntries();
|
|
58
|
+
if (filter.date === undefined) {
|
|
59
|
+
return entries;
|
|
60
|
+
}
|
|
61
|
+
return entries.filter((entry) => entry.date === filter.date);
|
|
62
|
+
}
|
|
63
|
+
export async function updateIntakeEntry(id, patch) {
|
|
64
|
+
return withMutationLock(async () => {
|
|
65
|
+
const path = storePath();
|
|
66
|
+
const entries = await readEntries(path);
|
|
67
|
+
const index = entries.findIndex((entry) => entry.id === id);
|
|
68
|
+
if (index === -1) {
|
|
69
|
+
throw new Error(`Intake entry not found: ${id}`);
|
|
70
|
+
}
|
|
71
|
+
const current = entries[index];
|
|
72
|
+
if (current === undefined) {
|
|
73
|
+
throw new Error(`Intake entry not found: ${id}`);
|
|
74
|
+
}
|
|
75
|
+
const timestamp = patch.timestamp ?? current.timestamp;
|
|
76
|
+
const updated = {
|
|
77
|
+
...current,
|
|
78
|
+
...patch,
|
|
79
|
+
timestamp,
|
|
80
|
+
date: timestamp.slice(0, 10),
|
|
81
|
+
};
|
|
82
|
+
entries[index] = updated;
|
|
83
|
+
await writeEntriesAtomically(entries, path);
|
|
84
|
+
return updated;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export async function deleteIntakeEntry(id) {
|
|
88
|
+
return withMutationLock(async () => {
|
|
89
|
+
const path = storePath();
|
|
90
|
+
const entries = await readEntries(path);
|
|
91
|
+
const nextEntries = entries.filter((entry) => entry.id !== id);
|
|
92
|
+
if (nextEntries.length === entries.length) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
await writeEntriesAtomically(nextEntries, path);
|
|
96
|
+
return true;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export async function clearIntakeDay(date) {
|
|
100
|
+
return withMutationLock(async () => {
|
|
101
|
+
const path = storePath();
|
|
102
|
+
const entries = await readEntries(path);
|
|
103
|
+
const nextEntries = entries.filter((entry) => entry.date !== date);
|
|
104
|
+
const deletedEntries = entries.length - nextEntries.length;
|
|
105
|
+
if (deletedEntries > 0) {
|
|
106
|
+
await writeEntriesAtomically(nextEntries, path);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
date,
|
|
110
|
+
deleted_entries: deletedEntries,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export async function exportIntakeData() {
|
|
115
|
+
try {
|
|
116
|
+
return await fs.readFile(storePath(), "utf8");
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error.code === "ENOENT") {
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export async function exportIntakeCsvData() {
|
|
126
|
+
const entries = await listIntakeEntries();
|
|
127
|
+
const headers = [
|
|
128
|
+
"id",
|
|
129
|
+
"timestamp",
|
|
130
|
+
"date",
|
|
131
|
+
"meal_type",
|
|
132
|
+
"food_name",
|
|
133
|
+
"quantity",
|
|
134
|
+
"unit",
|
|
135
|
+
"grams_estimate",
|
|
136
|
+
"calories_kcal",
|
|
137
|
+
"protein_g",
|
|
138
|
+
"carbohydrates_g",
|
|
139
|
+
"fat_g",
|
|
140
|
+
"confidence",
|
|
141
|
+
"source_trace",
|
|
142
|
+
"notes",
|
|
143
|
+
];
|
|
144
|
+
const rows = entries.map((entry) => [
|
|
145
|
+
entry.id,
|
|
146
|
+
entry.timestamp,
|
|
147
|
+
entry.date,
|
|
148
|
+
entry.meal_type,
|
|
149
|
+
entry.food_ref?.name ?? entry.custom_food?.name ?? "",
|
|
150
|
+
entry.quantity,
|
|
151
|
+
entry.unit,
|
|
152
|
+
entry.grams_estimate ?? "",
|
|
153
|
+
entry.nutrients.calories_kcal ?? "",
|
|
154
|
+
entry.nutrients.protein_g ?? "",
|
|
155
|
+
entry.nutrients.carbohydrates_g ?? "",
|
|
156
|
+
entry.nutrients.fat_g ?? "",
|
|
157
|
+
entry.confidence,
|
|
158
|
+
entry.source_trace,
|
|
159
|
+
entry.notes ?? "",
|
|
160
|
+
].map(csvCell));
|
|
161
|
+
return `${headers.join(",")}\n${rows.map((row) => row.join(",")).join("\n")}${rows.length > 0 ? "\n" : ""}`;
|
|
162
|
+
}
|
|
163
|
+
function csvCell(value) {
|
|
164
|
+
const text = String(value);
|
|
165
|
+
if (!/[",\n]/.test(text)) {
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
return `"${text.replaceAll("\"", "\"\"")}"`;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=intake-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intake-store.js","sourceRoot":"","sources":["../../src/services/intake-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MealType, NutrientMap } from "../types.js";
|
|
2
|
+
export interface EstimatedMealItem {
|
|
3
|
+
name: string;
|
|
4
|
+
quantity: number;
|
|
5
|
+
grams: number;
|
|
6
|
+
nutrients: NutrientMap;
|
|
7
|
+
}
|
|
8
|
+
export interface MealEstimate {
|
|
9
|
+
text: string;
|
|
10
|
+
locale: string;
|
|
11
|
+
meal_type: MealType;
|
|
12
|
+
items: EstimatedMealItem[];
|
|
13
|
+
total_nutrients: NutrientMap;
|
|
14
|
+
confidence: number;
|
|
15
|
+
unresolved: string[];
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function estimateMeal(input: {
|
|
19
|
+
text: string;
|
|
20
|
+
meal_type: MealType;
|
|
21
|
+
locale: string;
|
|
22
|
+
}): Promise<MealEstimate>;
|