forge-openclaw-plugin 0.2.99 → 0.2.101

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.
Files changed (149) hide show
  1. package/dist/assets/activity-copy-Bj4h9OcF.js +1 -0
  2. package/dist/assets/activity-page-CpjuNSHw.js +1 -0
  3. package/dist/assets/ai-surface-workspace-DEAFZruS.js +1 -0
  4. package/dist/assets/atlas-panel-CdVNPotj.js +1 -0
  5. package/dist/assets/{board-Ju0h0SeG.js → board-BkDRaMp6.js} +1 -1
  6. package/dist/assets/calendar-display-preferences-Cid-2RnL.js +1 -0
  7. package/dist/assets/calendar-page-DNNt6lfz.js +1 -0
  8. package/dist/assets/calendar-rules-DNJFNsxi.js +1 -0
  9. package/dist/assets/calendar-ui-Cy1XRwzV.js +1 -0
  10. package/dist/assets/calendar-week-toolbar-BbPwYeN0.js +1 -0
  11. package/dist/assets/charts-P7EVhIog.js +36 -0
  12. package/dist/assets/companion-sync-lab-page-KxEDigM6.js +1 -0
  13. package/dist/assets/daily-metrics-dashboard-B3cqJgDt.js +1 -0
  14. package/dist/assets/date-keys-Cj1G3TOn.js +1 -0
  15. package/dist/assets/entity-links-DwpxhW2H.js +1 -0
  16. package/dist/assets/entity-note-count-link-DrhjJZ4i.js +1 -0
  17. package/dist/assets/entity-notes-surface-CkcRsKJQ.js +1 -0
  18. package/dist/assets/execution-board-D07gOocB.js +1 -0
  19. package/dist/assets/faceted-token-search-BxRRcM3q.js +1 -0
  20. package/dist/assets/flagship-signal-deck-cmy82b8_.js +1 -0
  21. package/dist/assets/floating-action-menu-Fs_ZiUMo.js +1 -0
  22. package/dist/assets/forms-BFlTgZ3W.js +1 -0
  23. package/dist/assets/goal-detail-page-CqLiNz4f.js +1 -0
  24. package/dist/assets/goals-page-BTk7mg_T.js +1 -0
  25. package/dist/assets/graph-BZV40eAE.css +1 -0
  26. package/dist/assets/graph-D6JLqDbD.js +318 -0
  27. package/dist/assets/habits-page-BJxagdzx.js +1 -0
  28. package/dist/assets/health-link-options-Cpx8w7uM.js +1 -0
  29. package/dist/assets/index-CF4J4R9L.js +19 -0
  30. package/dist/assets/index-CZbuZQjw.css +1 -0
  31. package/dist/assets/insight-flow-dialog-8f3D0GuC.js +1 -0
  32. package/dist/assets/insights-page-D6rOa7uk.js +8 -0
  33. package/dist/assets/kanban-page-XQ7Se6dH.js +1 -0
  34. package/dist/assets/knowledge-graph-page-BtAg8iv3.js +1 -0
  35. package/dist/assets/life-force-page-Dy0JTS2G.js +1 -0
  36. package/dist/assets/life-force-workspace-OfyB9HJM.js +1 -0
  37. package/dist/assets/maps-B-YMMjus.css +1 -0
  38. package/dist/assets/maps-ClgJoCjz.js +803 -0
  39. package/dist/assets/metric-tile-DKpo-8xw.js +1 -0
  40. package/dist/assets/{motion-DRPJkN3a.js → motion-BeD44FeG.js} +1 -1
  41. package/dist/assets/movement-page-Bg_T_Stx.js +1 -0
  42. package/dist/assets/note-markdown-N-uxD3Xt.js +3 -0
  43. package/dist/assets/note-tags-input-Cdu7wiw6.js +1 -0
  44. package/dist/assets/notes-page-CKXnF_KU.js +1 -0
  45. package/dist/assets/open-in-graph-button-IXe9SGth.js +1 -0
  46. package/dist/assets/orbit-map-Dzi6KliQ.js +1 -0
  47. package/dist/assets/overview-page-1miYqaVS.js +1 -0
  48. package/dist/assets/page-hero-DRy5b2MU.js +1 -0
  49. package/dist/assets/pill-cluster-C9QczVJ2.js +1 -0
  50. package/dist/assets/preference-entity-handoff-button-5PzUn42S.js +1 -0
  51. package/dist/assets/preferences-page-DtNaF5Q3.js +1 -0
  52. package/dist/assets/project-collections-xPz2mlRr.js +1 -0
  53. package/dist/assets/project-detail-page-BXK5-4xW.js +1 -0
  54. package/dist/assets/project-management-hierarchy-page-DtRpMABw.js +1 -0
  55. package/dist/assets/project-management-section-nav-P3ixzPa-.js +1 -0
  56. package/dist/assets/projects-page-C5ViRuf4.js +1 -0
  57. package/dist/assets/psyche-behaviors-page-Dco46sC4.js +5 -0
  58. package/dist/assets/psyche-flashcards-page-ZcoEB8gV.js +1 -0
  59. package/dist/assets/psyche-goal-map-page-CLBAQOI0.js +1 -0
  60. package/dist/assets/psyche-graph-k4tX2tdp.js +1 -0
  61. package/dist/assets/psyche-metrics-page-CuR9oqEy.js +1 -0
  62. package/dist/assets/psyche-mode-guide-page-hIVXcCnE.js +1 -0
  63. package/dist/assets/psyche-modes-page-0lYtBlhO.js +1 -0
  64. package/dist/assets/psyche-page-VZ9k9ISp.js +1 -0
  65. package/dist/assets/psyche-patterns-page-gx5nmdGq.js +5 -0
  66. package/dist/assets/psyche-questionnaire-builder-page-Bn0TOISd.js +1 -0
  67. package/dist/assets/psyche-questionnaire-detail-page-CmVzSd_s.js +1 -0
  68. package/dist/assets/psyche-questionnaire-run-detail-page-BsMbmXCG.js +1 -0
  69. package/dist/assets/psyche-questionnaire-run-page-CgkRL2vi.js +1 -0
  70. package/dist/assets/psyche-questionnaires-page-D7V8uLXM.js +1 -0
  71. package/dist/assets/psyche-report-detail-page-OlFq57eL.js +3 -0
  72. package/dist/assets/psyche-reports-page-dVUZjna1.js +1 -0
  73. package/dist/assets/psyche-schemas-HFmg37Wj.js +1 -0
  74. package/dist/assets/psyche-schemas-beliefs-page-BCgc8FUd.js +9 -0
  75. package/dist/assets/psyche-screen-time-page-B_6BT_WN.js +1 -0
  76. package/dist/assets/psyche-self-observation-page-CEG5mluK.js +1 -0
  77. package/dist/assets/psyche-values-page-DRbRfEd6.js +5 -0
  78. package/dist/assets/query-cache-IQ8W-LNC.js +1 -0
  79. package/dist/assets/report-chain-fields-CALCV3V5.js +1 -0
  80. package/dist/assets/rewards-page-DmC4R_Ps.js +1 -0
  81. package/dist/assets/scheduling-rules-editor-D02s70hr.js +1 -0
  82. package/dist/assets/schema-badge-BZO-qNhO.js +1 -0
  83. package/dist/assets/schema-visuals-D6nxjbYC.js +1 -0
  84. package/dist/assets/select-menu-fYyreSdQ.js +1 -0
  85. package/dist/assets/settings-agents-page-C_v_hMJF.js +6 -0
  86. package/dist/assets/settings-bin-page-DY5bg81n.js +1 -0
  87. package/dist/assets/settings-calendar-page-D1CzE6cg.js +5 -0
  88. package/dist/assets/settings-data-page-CHRQFU9H.js +1 -0
  89. package/dist/assets/settings-logs-page-B04pUwEv.js +1 -0
  90. package/dist/assets/settings-mobile-page-D9kTlYDS.js +1 -0
  91. package/dist/assets/settings-models-page-D26270R2.js +1 -0
  92. package/dist/assets/settings-page-DYDTFlnv.js +1 -0
  93. package/dist/assets/settings-rewards-page-cl4vqqO_.js +1 -0
  94. package/dist/assets/settings-section-nav-DSOuht_F.js +1 -0
  95. package/dist/assets/settings-users-page-BU79JB_T.js +1 -0
  96. package/dist/assets/settings-wiki-page-DwAUlyA3.js +1 -0
  97. package/dist/assets/sleep-page-D8NbdhyS.js +1 -0
  98. package/dist/assets/sports-page-CV4Cnzwn.js +1 -0
  99. package/dist/assets/state-B-4sS1xO.js +1 -0
  100. package/dist/assets/strategies-page-C4qvXnql.js +1 -0
  101. package/dist/assets/strategy-detail-page-DJLo5rfy.js +1 -0
  102. package/dist/assets/strategy-dialog-D3AuUlVz.js +1 -0
  103. package/dist/assets/{table-DewbFlTh.js → table-WfAPUppN.js} +1 -1
  104. package/dist/assets/task-detail-page-z-9u9rF0.js +1 -0
  105. package/dist/assets/timebox-planning-dialog-DB6FLqmI.js +1 -0
  106. package/dist/assets/today-page-BKlu6gx5.js +1 -0
  107. package/dist/assets/training-load-page-CyJQqo_3.js +1 -0
  108. package/dist/assets/{ui-C2IvSrAz.js → ui-C13Nbgas.js} +4 -4
  109. package/dist/assets/use-psyche-focus-target-C1C_XjYG.js +1 -0
  110. package/dist/assets/vendor-CRS-psbw.css +1 -0
  111. package/dist/assets/vendor-DHkYh85p.js +1052 -0
  112. package/dist/assets/vitals-page-qre17Nw8.js +1 -0
  113. package/dist/assets/weekly-review-page-Cz4vkRcx.js +1 -0
  114. package/dist/assets/weight-loss-page-BQrnOI0y.js +1 -0
  115. package/dist/assets/wiki-article-markdown-DdiR2TJE.js +4 -0
  116. package/dist/assets/wiki-editor-page-DqwoqVFb.js +26 -0
  117. package/dist/assets/wiki-ingest-history-page--evBLbOw.js +1 -0
  118. package/dist/assets/wiki-ingest-modal--ohzFnj2.js +1 -0
  119. package/dist/assets/wiki-page-B_VJFBPA.js +1 -0
  120. package/dist/assets/workbench-flow-page-Du62mtJU.js +5 -0
  121. package/dist/assets/workbench-page-4MKr3iRm.js +1 -0
  122. package/dist/assets/workout-detail-page-DfUbYYw1.js +2 -0
  123. package/dist/index.html +148 -9
  124. package/dist/openclaw/tools.js +340 -0
  125. package/dist/server/server/migrations/065_weight_loss_nutrition_insights.sql +236 -0
  126. package/dist/server/server/migrations/066_watch_action_receipts.sql +20 -0
  127. package/dist/server/server/src/app.js +266 -13
  128. package/dist/server/server/src/health-weight-loss.js +1378 -0
  129. package/dist/server/server/src/health.js +188 -35
  130. package/dist/server/server/src/openapi.js +449 -0
  131. package/dist/server/server/src/services/context.js +6 -7
  132. package/dist/server/server/src/services/doctor.js +39 -4
  133. package/dist/server/server/src/services/gamification.js +146 -34
  134. package/dist/server/server/src/watch-mobile.js +576 -4
  135. package/dist/server/server/src/web.js +18 -5
  136. package/dist/server/src/components/ui/info-tooltip.js +48 -3
  137. package/dist/server/src/lib/api.js +131 -0
  138. package/dist/server/src/lib/weight-loss-types.js +1 -0
  139. package/openclaw.plugin.json +14 -1
  140. package/package.json +1 -1
  141. package/server/migrations/065_weight_loss_nutrition_insights.sql +236 -0
  142. package/server/migrations/066_watch_action_receipts.sql +20 -0
  143. package/skills/forge-openclaw/SKILL.md +26 -5
  144. package/skills/forge-openclaw/entity_conversation_playbooks.md +134 -5
  145. package/skills/forge-openclaw/psyche_entity_playbooks.md +45 -0
  146. package/dist/assets/index-Cn5Wpwau.css +0 -1
  147. package/dist/assets/index-CwvGs8n4.js +0 -91
  148. package/dist/assets/vendor-B-Lq_OG3.css +0 -1
  149. package/dist/assets/vendor-DL2K5ayT.js +0 -2186
@@ -269,6 +269,62 @@ const healthLinkInputSchema = () => Type.Object({
269
269
  entityId: Type.String({ minLength: 1 }),
270
270
  relationshipType: Type.Optional(Type.String({ minLength: 1 }))
271
271
  });
272
+ const nutritionMealItemInputSchema = () => Type.Object({
273
+ name: Type.String({ minLength: 1 }),
274
+ brand: optionalNullableString(),
275
+ quantity: Type.Number({ minimum: 0 }),
276
+ unit: optionalNullableString(),
277
+ caloriesKcal: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
278
+ proteinG: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
279
+ carbsG: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
280
+ fatG: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
281
+ fiberG: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
282
+ sugarG: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
283
+ sodiumMg: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
284
+ potassiumMg: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
285
+ caffeineMg: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
286
+ alcoholG: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
287
+ glycemicIndex: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
288
+ novaGroup: Type.Optional(Type.Union([Type.Integer(), Type.Null()])),
289
+ fermented: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
290
+ probiotic: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
291
+ fodmapLevel: Type.Optional(Type.Union([
292
+ Type.Literal("low"),
293
+ Type.Literal("medium"),
294
+ Type.Literal("high"),
295
+ Type.Null()
296
+ ])),
297
+ tags: Type.Optional(Type.Array(Type.String())),
298
+ confidence: Type.Optional(Type.Union([Type.Number(), Type.Null()]))
299
+ });
300
+ const nutritionUserScopeSchema = () => Type.Optional(Type.Array(Type.String({ minLength: 1 })));
301
+ const nutritionFoodLogSchema = () => Type.Object({
302
+ userIds: nutritionUserScopeSchema(),
303
+ loggedAt: optionalString(),
304
+ mealLabel: optionalNullableString(),
305
+ source: Type.Optional(Type.Union([
306
+ Type.Literal("manual"),
307
+ Type.Literal("barcode"),
308
+ Type.Literal("chatgpt"),
309
+ Type.Literal("photo"),
310
+ Type.Literal("import")
311
+ ])),
312
+ confirmationState: Type.Optional(Type.Union([
313
+ Type.Literal("candidate"),
314
+ Type.Literal("confirmed"),
315
+ Type.Literal("corrected"),
316
+ Type.Literal("rejected")
317
+ ])),
318
+ satietyScore: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
319
+ hungerBefore: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
320
+ hungerAfter: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
321
+ cravingScore: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
322
+ enjoymentScore: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
323
+ socialContext: optionalNullableString(),
324
+ locationContext: optionalNullableString(),
325
+ notes: optionalNullableString(),
326
+ items: Type.Array(nutritionMealItemInputSchema(), { minItems: 1 })
327
+ });
272
328
  const noteInputSchema = () => Type.Object({
273
329
  contentMarkdown: Type.String({ minLength: 1 }),
274
330
  author: optionalNullableString(),
@@ -759,6 +815,290 @@ export function registerForgePluginTools(api, config) {
759
815
  parameters: scopedReadSchema,
760
816
  path: (params) => withUserIds("/api/v1/health/training-load", params.userIds)
761
817
  });
818
+ registerReadTool(api, config, {
819
+ name: "forge_get_weight_loss_overview",
820
+ label: "Forge Weight Loss Overview",
821
+ description: "Read the weight-loss and nutrition insight surface with calorie ledger, protein/fiber targets, energy balance, body trend, subjective energy, gut comfort, aesthetic check-ins, hypotheses, experiments, and data-quality flags.",
822
+ parameters: scopedReadSchema,
823
+ path: (params) => withUserIds("/api/v1/health/weight-loss", params.userIds)
824
+ });
825
+ api.registerTool({
826
+ name: "forge_search_nutrition_foods",
827
+ label: "Forge Search Nutrition Foods",
828
+ description: "Search local, Open Food Facts, and USDA-backed nutrition foods before logging a concrete food item.",
829
+ parameters: Type.Object({
830
+ userIds: nutritionUserScopeSchema(),
831
+ query: Type.String({ minLength: 1 }),
832
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 30 }))
833
+ }),
834
+ async execute(_toolCallId, params) {
835
+ const typed = params;
836
+ return jsonResult(await runWrite(config, {
837
+ method: "POST",
838
+ path: withUserIds("/api/v1/health/weight-loss/foods/search", typed.userIds),
839
+ body: { query: typed.query, limit: typed.limit }
840
+ }));
841
+ }
842
+ });
843
+ api.registerTool({
844
+ name: "forge_search_foods",
845
+ label: "Forge Search Foods",
846
+ description: "Search local, Open Food Facts, and USDA-backed nutrition foods before logging a concrete food item. This is the short alias for forge_search_nutrition_foods.",
847
+ parameters: Type.Object({
848
+ userIds: nutritionUserScopeSchema(),
849
+ query: Type.String({ minLength: 1 }),
850
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 30 }))
851
+ }),
852
+ async execute(_toolCallId, params) {
853
+ const typed = params;
854
+ return jsonResult(await runWrite(config, {
855
+ method: "POST",
856
+ path: withUserIds("/api/v1/health/weight-loss/foods/search", typed.userIds),
857
+ body: { query: typed.query, limit: typed.limit }
858
+ }));
859
+ }
860
+ });
861
+ api.registerTool({
862
+ name: "forge_lookup_nutrition_barcode",
863
+ label: "Forge Lookup Nutrition Barcode",
864
+ description: "Lookup a packaged food by barcode through Forge's nutrition catalog adapters.",
865
+ parameters: Type.Object({
866
+ userIds: nutritionUserScopeSchema(),
867
+ barcode: Type.String({ minLength: 1 })
868
+ }),
869
+ async execute(_toolCallId, params) {
870
+ const typed = params;
871
+ return jsonResult(await runWrite(config, {
872
+ method: "POST",
873
+ path: withUserIds("/api/v1/health/weight-loss/foods/barcode", typed.userIds),
874
+ body: { barcode: typed.barcode }
875
+ }));
876
+ }
877
+ });
878
+ api.registerTool({
879
+ name: "forge_log_food",
880
+ label: "Forge Log Food",
881
+ description: "Create a confirmed or candidate food log with explicit meal items, calories, macros, quality tags, hunger, satiety, cravings, and context.",
882
+ parameters: nutritionFoodLogSchema(),
883
+ async execute(_toolCallId, params) {
884
+ const typed = params;
885
+ const { userIds: _userIds, ...body } = typed;
886
+ return jsonResult(await runWrite(config, {
887
+ method: "POST",
888
+ path: withUserIds("/api/v1/health/weight-loss/food-logs", typed.userIds),
889
+ body
890
+ }));
891
+ }
892
+ });
893
+ api.registerTool({
894
+ name: "forge_parse_food_log_with_chatgpt",
895
+ label: "Forge Parse Food Log With ChatGPT",
896
+ description: "Use Forge's openai-codex ChatGPT subscription connection to parse natural-language food text or a photo description into a candidate nutrition log. This must not use the metered OpenAI API.",
897
+ parameters: Type.Object({
898
+ userIds: nutritionUserScopeSchema(),
899
+ text: optionalString(),
900
+ imageDescription: optionalString(),
901
+ loggedAt: optionalString(),
902
+ mealLabel: optionalString()
903
+ }),
904
+ async execute(_toolCallId, params) {
905
+ const typed = params;
906
+ return jsonResult(await runWrite(config, {
907
+ method: "POST",
908
+ path: withUserIds("/api/v1/health/weight-loss/parse", typed.userIds),
909
+ body: {
910
+ text: typed.text,
911
+ imageDescription: typed.imageDescription,
912
+ loggedAt: typed.loggedAt,
913
+ mealLabel: typed.mealLabel
914
+ }
915
+ }));
916
+ }
917
+ });
918
+ api.registerTool({
919
+ name: "forge_log_body_checkin",
920
+ label: "Forge Log Body Check-In",
921
+ description: "Record body-composition check-ins such as weight, waist, hip, neck, body-fat estimate, and notes for trend calculations.",
922
+ parameters: Type.Object({
923
+ userIds: nutritionUserScopeSchema(),
924
+ checkedAt: optionalString(),
925
+ weightKg: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
926
+ waistCm: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
927
+ hipCm: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
928
+ neckCm: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
929
+ bodyFatPercent: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
930
+ photoAssetId: optionalNullableString(),
931
+ notes: optionalNullableString()
932
+ }),
933
+ async execute(_toolCallId, params) {
934
+ const typed = params;
935
+ const { userIds: _userIds, ...body } = typed;
936
+ return jsonResult(await runWrite(config, {
937
+ method: "POST",
938
+ path: withUserIds("/api/v1/health/weight-loss/body-checkins", typed.userIds),
939
+ body
940
+ }));
941
+ }
942
+ });
943
+ api.registerTool({
944
+ name: "forge_log_appearance_checkin",
945
+ label: "Forge Log Appearance Check-In",
946
+ description: "Record aesthetic-look metrics such as muscle fullness, leanness, vascularity, face puffiness, visual bloat, posture confidence, outfit fit, and overall aesthetic score.",
947
+ parameters: Type.Object({
948
+ userIds: nutritionUserScopeSchema(),
949
+ checkedAt: optionalString(),
950
+ muscleFullness: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
951
+ leanness: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
952
+ vascularity: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
953
+ facePuffiness: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
954
+ abdomenBloatLook: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
955
+ postureConfidence: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
956
+ outfitFit: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
957
+ aestheticScore: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
958
+ notes: optionalNullableString()
959
+ }),
960
+ async execute(_toolCallId, params) {
961
+ const typed = params;
962
+ const { userIds: _userIds, ...body } = typed;
963
+ return jsonResult(await runWrite(config, {
964
+ method: "POST",
965
+ path: withUserIds("/api/v1/health/weight-loss/appearance-checkins", typed.userIds),
966
+ body
967
+ }));
968
+ }
969
+ });
970
+ api.registerTool({
971
+ name: "forge_log_subjective_food_effect",
972
+ label: "Forge Log Subjective Food Effect",
973
+ description: "Record subjective food-effect metrics such as energy, mood, focus, libido, sleepiness, soreness, stress, hunger, cravings, and workout performance.",
974
+ parameters: Type.Object({
975
+ userIds: nutritionUserScopeSchema(),
976
+ checkedAt: optionalString(),
977
+ energy: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
978
+ mood: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
979
+ focus: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
980
+ libido: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
981
+ sleepiness: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
982
+ soreness: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
983
+ stress: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
984
+ hunger: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
985
+ cravings: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
986
+ workoutPerformance: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
987
+ timeRelation: optionalNullableString(),
988
+ linkedFoodLogId: optionalNullableString(),
989
+ notes: optionalNullableString()
990
+ }),
991
+ async execute(_toolCallId, params) {
992
+ const typed = params;
993
+ const { userIds: _userIds, ...body } = typed;
994
+ return jsonResult(await runWrite(config, {
995
+ method: "POST",
996
+ path: withUserIds("/api/v1/health/weight-loss/subjective-checkins", typed.userIds),
997
+ body
998
+ }));
999
+ }
1000
+ });
1001
+ api.registerTool({
1002
+ name: "forge_log_gut_checkin",
1003
+ label: "Forge Log Gut Check-In",
1004
+ description: "Record gut-health food-effect metrics such as bloating, abdominal pain, gas, reflux, nausea, stool type, stool frequency, and suspected triggers.",
1005
+ parameters: Type.Object({
1006
+ userIds: nutritionUserScopeSchema(),
1007
+ checkedAt: optionalString(),
1008
+ bloating: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1009
+ abdominalPain: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1010
+ gas: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1011
+ reflux: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1012
+ nausea: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1013
+ stoolType: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1014
+ stoolFrequency: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
1015
+ suspectedTrigger: optionalNullableString(),
1016
+ linkedFoodLogId: optionalNullableString(),
1017
+ notes: optionalNullableString()
1018
+ }),
1019
+ async execute(_toolCallId, params) {
1020
+ const typed = params;
1021
+ const { userIds: _userIds, ...body } = typed;
1022
+ return jsonResult(await runWrite(config, {
1023
+ method: "POST",
1024
+ path: withUserIds("/api/v1/health/weight-loss/gut-checkins", typed.userIds),
1025
+ body
1026
+ }));
1027
+ }
1028
+ });
1029
+ registerReadTool(api, config, {
1030
+ name: "forge_get_nutrition_patterns",
1031
+ label: "Forge Nutrition Patterns",
1032
+ description: "Read current food-effect hypotheses and nutrition experiments, including links between meals, sport fueling, energy, gut comfort, cravings, and aesthetic look.",
1033
+ parameters: scopedReadSchema,
1034
+ path: (params) => withUserIds("/api/v1/health/weight-loss/patterns", params.userIds)
1035
+ });
1036
+ api.registerTool({
1037
+ name: "forge_start_nutrition_experiment",
1038
+ label: "Forge Start Nutrition Experiment",
1039
+ description: "Create a structured N-of-1 nutrition experiment, such as carb timing, caffeine timing, low-FODMAP trial, sodium/puffiness test, fiber ramp, or pre-training fueling.",
1040
+ parameters: Type.Object({
1041
+ userIds: nutritionUserScopeSchema(),
1042
+ title: Type.String({ minLength: 1 }),
1043
+ hypothesis: Type.String({ minLength: 1 }),
1044
+ metricKey: Type.String({ minLength: 1 }),
1045
+ intervention: Type.String({ minLength: 1 }),
1046
+ baselineStart: optionalNullableString(),
1047
+ baselineEnd: optionalNullableString(),
1048
+ experimentStart: optionalNullableString(),
1049
+ experimentEnd: optionalNullableString(),
1050
+ status: Type.Optional(Type.Union([
1051
+ Type.Literal("planned"),
1052
+ Type.Literal("running"),
1053
+ Type.Literal("completed"),
1054
+ Type.Literal("abandoned")
1055
+ ])),
1056
+ successCriteria: optionalNullableString()
1057
+ }),
1058
+ async execute(_toolCallId, params) {
1059
+ const typed = params;
1060
+ const { userIds: _userIds, ...body } = typed;
1061
+ return jsonResult(await runWrite(config, {
1062
+ method: "POST",
1063
+ path: withUserIds("/api/v1/health/weight-loss/experiments", typed.userIds),
1064
+ body
1065
+ }));
1066
+ }
1067
+ });
1068
+ api.registerTool({
1069
+ name: "forge_update_nutrition_experiment",
1070
+ label: "Forge Update Nutrition Experiment",
1071
+ description: "Patch a nutrition experiment's status, dates, success criteria, intervention, hypothesis, or conclusion after new evidence arrives.",
1072
+ parameters: Type.Object({
1073
+ userIds: nutritionUserScopeSchema(),
1074
+ experimentId: Type.String({ minLength: 1 }),
1075
+ title: optionalString(),
1076
+ hypothesis: optionalString(),
1077
+ metricKey: optionalString(),
1078
+ intervention: optionalString(),
1079
+ baselineStart: optionalNullableString(),
1080
+ baselineEnd: optionalNullableString(),
1081
+ experimentStart: optionalNullableString(),
1082
+ experimentEnd: optionalNullableString(),
1083
+ status: Type.Optional(Type.Union([
1084
+ Type.Literal("planned"),
1085
+ Type.Literal("running"),
1086
+ Type.Literal("completed"),
1087
+ Type.Literal("abandoned")
1088
+ ])),
1089
+ successCriteria: optionalNullableString(),
1090
+ conclusion: optionalNullableString()
1091
+ }),
1092
+ async execute(_toolCallId, params) {
1093
+ const typed = params;
1094
+ const { userIds: _userIds, experimentId: _experimentId, ...body } = typed;
1095
+ return jsonResult(await runWrite(config, {
1096
+ method: "PATCH",
1097
+ path: withUserIds(`/api/v1/health/weight-loss/experiments/${typed.experimentId}`, typed.userIds),
1098
+ body
1099
+ }));
1100
+ }
1101
+ });
762
1102
  api.registerTool({
763
1103
  name: "forge_update_sleep_session",
764
1104
  label: "Forge Update Sleep Session",
@@ -0,0 +1,236 @@
1
+ CREATE TABLE IF NOT EXISTS nutrition_targets (
2
+ id TEXT PRIMARY KEY,
3
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
4
+ calorie_target REAL,
5
+ protein_grams_target REAL,
6
+ fiber_grams_target REAL,
7
+ carbohydrate_grams_target REAL,
8
+ fat_grams_target REAL,
9
+ weight_goal_kg REAL,
10
+ weekly_rate_goal_kg REAL,
11
+ diet_style TEXT NOT NULL DEFAULT '',
12
+ body_goal TEXT NOT NULL DEFAULT '',
13
+ notes TEXT NOT NULL DEFAULT '',
14
+ created_at TEXT NOT NULL,
15
+ updated_at TEXT NOT NULL
16
+ );
17
+
18
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_nutrition_targets_user
19
+ ON nutrition_targets(user_id);
20
+
21
+ CREATE TABLE IF NOT EXISTS nutrition_food_catalog (
22
+ id TEXT PRIMARY KEY,
23
+ source TEXT NOT NULL,
24
+ source_id TEXT NOT NULL,
25
+ barcode TEXT,
26
+ name TEXT NOT NULL,
27
+ brand TEXT NOT NULL DEFAULT '',
28
+ serving_label TEXT NOT NULL DEFAULT '',
29
+ serving_grams REAL,
30
+ calories REAL,
31
+ protein_grams REAL,
32
+ carbohydrate_grams REAL,
33
+ fat_grams REAL,
34
+ fiber_grams REAL,
35
+ sugar_grams REAL,
36
+ sodium_mg REAL,
37
+ potassium_mg REAL,
38
+ caffeine_mg REAL,
39
+ alcohol_grams REAL,
40
+ nova_group INTEGER,
41
+ nutri_score TEXT,
42
+ tags_json TEXT NOT NULL DEFAULT '[]',
43
+ nutrients_json TEXT NOT NULL DEFAULT '{}',
44
+ confidence REAL NOT NULL DEFAULT 0.65,
45
+ created_at TEXT NOT NULL,
46
+ updated_at TEXT NOT NULL
47
+ );
48
+
49
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_nutrition_food_catalog_source
50
+ ON nutrition_food_catalog(source, source_id);
51
+
52
+ CREATE INDEX IF NOT EXISTS idx_nutrition_food_catalog_barcode
53
+ ON nutrition_food_catalog(barcode);
54
+
55
+ CREATE TABLE IF NOT EXISTS nutrition_food_logs (
56
+ id TEXT PRIMARY KEY,
57
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
58
+ logged_at TEXT NOT NULL,
59
+ meal_label TEXT NOT NULL DEFAULT '',
60
+ source TEXT NOT NULL DEFAULT 'manual',
61
+ confirmation_state TEXT NOT NULL DEFAULT 'confirmed',
62
+ notes TEXT NOT NULL DEFAULT '',
63
+ place_id TEXT,
64
+ stay_id TEXT,
65
+ workout_id TEXT,
66
+ sleep_id TEXT,
67
+ day_key TEXT NOT NULL,
68
+ image_refs_json TEXT NOT NULL DEFAULT '[]',
69
+ parser_provenance_json TEXT NOT NULL DEFAULT '{}',
70
+ links_json TEXT NOT NULL DEFAULT '[]',
71
+ created_at TEXT NOT NULL,
72
+ updated_at TEXT NOT NULL
73
+ );
74
+
75
+ CREATE INDEX IF NOT EXISTS idx_nutrition_food_logs_user_time
76
+ ON nutrition_food_logs(user_id, logged_at DESC);
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_nutrition_food_logs_day
79
+ ON nutrition_food_logs(user_id, day_key);
80
+
81
+ CREATE TABLE IF NOT EXISTS nutrition_meal_items (
82
+ id TEXT PRIMARY KEY,
83
+ log_id TEXT NOT NULL REFERENCES nutrition_food_logs(id) ON DELETE CASCADE,
84
+ food_id TEXT REFERENCES nutrition_food_catalog(id) ON DELETE SET NULL,
85
+ name TEXT NOT NULL,
86
+ quantity REAL NOT NULL DEFAULT 1,
87
+ unit TEXT NOT NULL DEFAULT 'serving',
88
+ grams REAL,
89
+ calories REAL,
90
+ protein_grams REAL,
91
+ carbohydrate_grams REAL,
92
+ fat_grams REAL,
93
+ fiber_grams REAL,
94
+ sugar_grams REAL,
95
+ sodium_mg REAL,
96
+ potassium_mg REAL,
97
+ caffeine_mg REAL,
98
+ alcohol_grams REAL,
99
+ tags_json TEXT NOT NULL DEFAULT '[]',
100
+ nutrients_json TEXT NOT NULL DEFAULT '{}',
101
+ confidence REAL NOT NULL DEFAULT 0.65,
102
+ created_at TEXT NOT NULL,
103
+ updated_at TEXT NOT NULL
104
+ );
105
+
106
+ CREATE INDEX IF NOT EXISTS idx_nutrition_meal_items_log
107
+ ON nutrition_meal_items(log_id);
108
+
109
+ CREATE TABLE IF NOT EXISTS nutrition_body_checkins (
110
+ id TEXT PRIMARY KEY,
111
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
112
+ checked_at TEXT NOT NULL,
113
+ weight_kg REAL,
114
+ waist_cm REAL,
115
+ hip_cm REAL,
116
+ neck_cm REAL,
117
+ chest_cm REAL,
118
+ arm_cm REAL,
119
+ thigh_cm REAL,
120
+ body_fat_percent REAL,
121
+ clothing_fit_score INTEGER,
122
+ notes TEXT NOT NULL DEFAULT '',
123
+ created_at TEXT NOT NULL,
124
+ updated_at TEXT NOT NULL
125
+ );
126
+
127
+ CREATE INDEX IF NOT EXISTS idx_nutrition_body_checkins_user_time
128
+ ON nutrition_body_checkins(user_id, checked_at DESC);
129
+
130
+ CREATE TABLE IF NOT EXISTS nutrition_appearance_checkins (
131
+ id TEXT PRIMARY KEY,
132
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
133
+ checked_at TEXT NOT NULL,
134
+ photo_refs_json TEXT NOT NULL DEFAULT '[]',
135
+ face_puffiness INTEGER,
136
+ leanness INTEGER,
137
+ muscularity INTEGER,
138
+ posture INTEGER,
139
+ bloating_look INTEGER,
140
+ confidence_score INTEGER,
141
+ notes TEXT NOT NULL DEFAULT '',
142
+ created_at TEXT NOT NULL,
143
+ updated_at TEXT NOT NULL
144
+ );
145
+
146
+ CREATE INDEX IF NOT EXISTS idx_nutrition_appearance_checkins_user_time
147
+ ON nutrition_appearance_checkins(user_id, checked_at DESC);
148
+
149
+ CREATE TABLE IF NOT EXISTS nutrition_subjective_checkins (
150
+ id TEXT PRIMARY KEY,
151
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
152
+ checked_at TEXT NOT NULL,
153
+ meal_log_id TEXT REFERENCES nutrition_food_logs(id) ON DELETE SET NULL,
154
+ time_relation TEXT NOT NULL DEFAULT 'unspecified',
155
+ hunger INTEGER,
156
+ fullness INTEGER,
157
+ cravings INTEGER,
158
+ mood INTEGER,
159
+ energy INTEGER,
160
+ focus INTEGER,
161
+ stress INTEGER,
162
+ sleepiness INTEGER,
163
+ crash_score INTEGER,
164
+ notes TEXT NOT NULL DEFAULT '',
165
+ created_at TEXT NOT NULL,
166
+ updated_at TEXT NOT NULL
167
+ );
168
+
169
+ CREATE INDEX IF NOT EXISTS idx_nutrition_subjective_checkins_user_time
170
+ ON nutrition_subjective_checkins(user_id, checked_at DESC);
171
+
172
+ CREATE TABLE IF NOT EXISTS nutrition_gut_checkins (
173
+ id TEXT PRIMARY KEY,
174
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
175
+ checked_at TEXT NOT NULL,
176
+ meal_log_id TEXT REFERENCES nutrition_food_logs(id) ON DELETE SET NULL,
177
+ bristol_stool_type INTEGER,
178
+ stool_frequency INTEGER,
179
+ bloating INTEGER,
180
+ gas INTEGER,
181
+ reflux INTEGER,
182
+ abdominal_pain INTEGER,
183
+ urgency INTEGER,
184
+ nausea INTEGER,
185
+ constipation INTEGER,
186
+ diarrhea INTEGER,
187
+ trigger_tags_json TEXT NOT NULL DEFAULT '[]',
188
+ notes TEXT NOT NULL DEFAULT '',
189
+ created_at TEXT NOT NULL,
190
+ updated_at TEXT NOT NULL
191
+ );
192
+
193
+ CREATE INDEX IF NOT EXISTS idx_nutrition_gut_checkins_user_time
194
+ ON nutrition_gut_checkins(user_id, checked_at DESC);
195
+
196
+ CREATE TABLE IF NOT EXISTS nutrition_hypotheses (
197
+ id TEXT PRIMARY KEY,
198
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
199
+ title TEXT NOT NULL,
200
+ summary TEXT NOT NULL DEFAULT '',
201
+ status TEXT NOT NULL DEFAULT 'candidate',
202
+ confidence REAL NOT NULL DEFAULT 0.25,
203
+ evidence_count INTEGER NOT NULL DEFAULT 0,
204
+ signal_key TEXT NOT NULL DEFAULT '',
205
+ outcome_key TEXT NOT NULL DEFAULT '',
206
+ lag_window TEXT NOT NULL DEFAULT '',
207
+ evidence_json TEXT NOT NULL DEFAULT '{}',
208
+ confounders_json TEXT NOT NULL DEFAULT '[]',
209
+ suggested_action TEXT NOT NULL DEFAULT '',
210
+ created_at TEXT NOT NULL,
211
+ updated_at TEXT NOT NULL
212
+ );
213
+
214
+ CREATE INDEX IF NOT EXISTS idx_nutrition_hypotheses_user_status
215
+ ON nutrition_hypotheses(user_id, status, updated_at DESC);
216
+
217
+ CREATE TABLE IF NOT EXISTS nutrition_experiments (
218
+ id TEXT PRIMARY KEY,
219
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
220
+ hypothesis_id TEXT REFERENCES nutrition_hypotheses(id) ON DELETE SET NULL,
221
+ title TEXT NOT NULL,
222
+ status TEXT NOT NULL DEFAULT 'planned',
223
+ baseline_start TEXT,
224
+ baseline_end TEXT,
225
+ intervention_start TEXT,
226
+ intervention_end TEXT,
227
+ tracked_outcomes_json TEXT NOT NULL DEFAULT '[]',
228
+ protocol_json TEXT NOT NULL DEFAULT '{}',
229
+ adherence_json TEXT NOT NULL DEFAULT '{}',
230
+ result_summary TEXT NOT NULL DEFAULT '',
231
+ created_at TEXT NOT NULL,
232
+ updated_at TEXT NOT NULL
233
+ );
234
+
235
+ CREATE INDEX IF NOT EXISTS idx_nutrition_experiments_user_status
236
+ ON nutrition_experiments(user_id, status, updated_at DESC);
@@ -0,0 +1,20 @@
1
+ CREATE TABLE IF NOT EXISTS watch_action_receipts (
2
+ id TEXT PRIMARY KEY,
3
+ pairing_session_id TEXT REFERENCES companion_pairing_sessions(id) ON DELETE SET NULL,
4
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
5
+ action_id TEXT NOT NULL,
6
+ kind TEXT NOT NULL,
7
+ received_at TEXT NOT NULL,
8
+ processed_at TEXT NOT NULL,
9
+ status TEXT NOT NULL,
10
+ result_json TEXT NOT NULL DEFAULT '{}',
11
+ error_json TEXT NOT NULL DEFAULT '{}',
12
+ created_at TEXT NOT NULL,
13
+ UNIQUE (user_id, action_id)
14
+ );
15
+
16
+ CREATE INDEX IF NOT EXISTS idx_watch_action_receipts_user_processed
17
+ ON watch_action_receipts(user_id, processed_at DESC);
18
+
19
+ CREATE INDEX IF NOT EXISTS idx_watch_action_receipts_kind
20
+ ON watch_action_receipts(user_id, kind, processed_at DESC);