forge-openclaw-plugin 0.2.99 → 0.2.100

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-5oyCFOns.js +1 -0
  3. package/dist/assets/ai-surface-workspace-qgk_B57-.js +1 -0
  4. package/dist/assets/atlas-panel-rfH2qOez.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-Bo2iua-a.js +1 -0
  8. package/dist/assets/calendar-rules-DA1g3QUk.js +1 -0
  9. package/dist/assets/calendar-ui-Cy1XRwzV.js +1 -0
  10. package/dist/assets/calendar-week-toolbar-DU1Q4RYj.js +1 -0
  11. package/dist/assets/charts-P7EVhIog.js +36 -0
  12. package/dist/assets/companion-sync-lab-page-CosNknOK.js +1 -0
  13. package/dist/assets/daily-metrics-dashboard-LjuGAB3f.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-BmGDB572.js +1 -0
  17. package/dist/assets/entity-notes-surface-DgEgicaE.js +1 -0
  18. package/dist/assets/execution-board-CDRXQB85.js +1 -0
  19. package/dist/assets/faceted-token-search-CE1YauRd.js +1 -0
  20. package/dist/assets/flagship-signal-deck-DDds90Gl.js +1 -0
  21. package/dist/assets/floating-action-menu-CJkI2iFy.js +1 -0
  22. package/dist/assets/forms-BFlTgZ3W.js +1 -0
  23. package/dist/assets/goal-detail-page-cJvHaLMQ.js +1 -0
  24. package/dist/assets/goals-page-f_39hvUV.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-DKb96_mj.js +1 -0
  28. package/dist/assets/health-link-options-Cpx8w7uM.js +1 -0
  29. package/dist/assets/index-BHTUu_4M.js +19 -0
  30. package/dist/assets/index-CZbuZQjw.css +1 -0
  31. package/dist/assets/insight-flow-dialog-pzAzyayN.js +1 -0
  32. package/dist/assets/insights-page-Dc9oFltJ.js +8 -0
  33. package/dist/assets/kanban-page-JAxerYh6.js +1 -0
  34. package/dist/assets/knowledge-graph-page-UQ3skqEi.js +1 -0
  35. package/dist/assets/life-force-page-BGDbQuVh.js +1 -0
  36. package/dist/assets/life-force-workspace-B1fYSXRC.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-DX6TclqM.js +1 -0
  40. package/dist/assets/{motion-DRPJkN3a.js → motion-BeD44FeG.js} +1 -1
  41. package/dist/assets/movement-page-6HP6nGJx.js +1 -0
  42. package/dist/assets/note-markdown-DiW2-5d3.js +3 -0
  43. package/dist/assets/note-tags-input-DDLXf54U.js +1 -0
  44. package/dist/assets/notes-page-BuguDjhz.js +1 -0
  45. package/dist/assets/open-in-graph-button-Cg5VrKsC.js +1 -0
  46. package/dist/assets/orbit-map-GD05-0oS.js +1 -0
  47. package/dist/assets/overview-page-DuOs2OCB.js +1 -0
  48. package/dist/assets/page-hero-CQWo1Mm_.js +1 -0
  49. package/dist/assets/pill-cluster-BJogDRDJ.js +1 -0
  50. package/dist/assets/preference-entity-handoff-button-D4WAs9pC.js +1 -0
  51. package/dist/assets/preferences-page-BaJTMU1I.js +1 -0
  52. package/dist/assets/project-collections-DvaX20q_.js +1 -0
  53. package/dist/assets/project-detail-page-drPIFZGb.js +1 -0
  54. package/dist/assets/project-management-hierarchy-page-BUbRXvny.js +1 -0
  55. package/dist/assets/project-management-section-nav-C2Ud8Zdd.js +1 -0
  56. package/dist/assets/projects-page-BGzEZUtg.js +1 -0
  57. package/dist/assets/psyche-behaviors-page-Dmm_Io9D.js +5 -0
  58. package/dist/assets/psyche-flashcards-page-BgNKJ6QJ.js +1 -0
  59. package/dist/assets/psyche-goal-map-page-DXJs98Vr.js +1 -0
  60. package/dist/assets/psyche-graph-CFgs_Bqc.js +1 -0
  61. package/dist/assets/psyche-metrics-page-zYTJDbyZ.js +1 -0
  62. package/dist/assets/psyche-mode-guide-page-XPgRfCOf.js +1 -0
  63. package/dist/assets/psyche-modes-page-B-GA8oRF.js +1 -0
  64. package/dist/assets/psyche-page--r6a3e1t.js +1 -0
  65. package/dist/assets/psyche-patterns-page-BM5-3bMm.js +5 -0
  66. package/dist/assets/psyche-questionnaire-builder-page-CJshQ-mg.js +1 -0
  67. package/dist/assets/psyche-questionnaire-detail-page-USmR5G5A.js +1 -0
  68. package/dist/assets/psyche-questionnaire-run-detail-page-D7iBCmTi.js +1 -0
  69. package/dist/assets/psyche-questionnaire-run-page-Cpil-kDh.js +1 -0
  70. package/dist/assets/psyche-questionnaires-page-C-_y3VwS.js +1 -0
  71. package/dist/assets/psyche-report-detail-page--dkSPRaj.js +3 -0
  72. package/dist/assets/psyche-reports-page-CUaOXmIN.js +1 -0
  73. package/dist/assets/psyche-schemas-HFmg37Wj.js +1 -0
  74. package/dist/assets/psyche-schemas-beliefs-page-BX6xaap3.js +9 -0
  75. package/dist/assets/psyche-screen-time-page-CAAI4mD7.js +1 -0
  76. package/dist/assets/psyche-self-observation-page-BZ6FLuwa.js +1 -0
  77. package/dist/assets/psyche-values-page-yEV6MGt8.js +5 -0
  78. package/dist/assets/query-cache-IQ8W-LNC.js +1 -0
  79. package/dist/assets/report-chain-fields-fZ8Xd4H6.js +1 -0
  80. package/dist/assets/rewards-page-C2HQjIAf.js +1 -0
  81. package/dist/assets/scheduling-rules-editor-BHOpHOrV.js +1 -0
  82. package/dist/assets/schema-badge-DyKbxb51.js +1 -0
  83. package/dist/assets/schema-visuals-D6nxjbYC.js +1 -0
  84. package/dist/assets/select-menu-BX-pZNqL.js +1 -0
  85. package/dist/assets/settings-agents-page-VuYXTiyc.js +6 -0
  86. package/dist/assets/settings-bin-page-BNzvYaOk.js +1 -0
  87. package/dist/assets/settings-calendar-page-CjSFB53S.js +5 -0
  88. package/dist/assets/settings-data-page-CGSlryuI.js +1 -0
  89. package/dist/assets/settings-logs-page-BTK5fine.js +1 -0
  90. package/dist/assets/settings-mobile-page-CRaObOGo.js +1 -0
  91. package/dist/assets/settings-models-page-DFshpYF8.js +1 -0
  92. package/dist/assets/settings-page-BP81Mb5R.js +1 -0
  93. package/dist/assets/settings-rewards-page-CDJ1PH2G.js +1 -0
  94. package/dist/assets/settings-section-nav-CCFm27r2.js +1 -0
  95. package/dist/assets/settings-users-page-TdUocFPa.js +1 -0
  96. package/dist/assets/settings-wiki-page-B2zX0QQG.js +1 -0
  97. package/dist/assets/sleep-page-cI1GMVzk.js +1 -0
  98. package/dist/assets/sports-page-06LTqp0V.js +1 -0
  99. package/dist/assets/state-B-4sS1xO.js +1 -0
  100. package/dist/assets/strategies-page-DXP9Kx8s.js +1 -0
  101. package/dist/assets/strategy-detail-page-D6mx_Mik.js +1 -0
  102. package/dist/assets/strategy-dialog-BvzomTaF.js +1 -0
  103. package/dist/assets/{table-DewbFlTh.js → table-WfAPUppN.js} +1 -1
  104. package/dist/assets/task-detail-page-BIWIggdp.js +1 -0
  105. package/dist/assets/timebox-planning-dialog-CaCnoslG.js +1 -0
  106. package/dist/assets/today-page-DO2mRPT2.js +1 -0
  107. package/dist/assets/training-load-page-CyZ0mlEr.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-BQvEjTc6.js +1 -0
  113. package/dist/assets/weekly-review-page-Tp6Q9CRj.js +1 -0
  114. package/dist/assets/weight-loss-page-BBzlhLVV.js +1 -0
  115. package/dist/assets/wiki-article-markdown-DQYohmW2.js +4 -0
  116. package/dist/assets/wiki-editor-page-Dem_3eZv.js +26 -0
  117. package/dist/assets/wiki-ingest-history-page-BxoOcCoJ.js +1 -0
  118. package/dist/assets/wiki-ingest-modal-DhguKk3J.js +1 -0
  119. package/dist/assets/wiki-page-BLRxVXkl.js +1 -0
  120. package/dist/assets/workbench-flow-page-DqMkCCTy.js +5 -0
  121. package/dist/assets/workbench-page-BWd02wPw.js +1 -0
  122. package/dist/assets/workout-detail-page-BD8u7GyL.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 +564 -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
@@ -1,14 +1,59 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useId, useRef, useState } from "react";
2
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
3
+ import { createPortal } from "react-dom";
3
4
  import { CircleHelp } from "lucide-react";
4
5
  import { cn } from "../../lib/utils.js";
6
+ const TOOLTIP_GUTTER_PX = 16;
7
+ const TOOLTIP_MAX_WIDTH_PX = 320;
8
+ const TOOLTIP_MIN_HEIGHT_BELOW_PX = 180;
9
+ const TOOLTIP_ESTIMATED_HEIGHT_PX = 220;
5
10
  export function FieldHint({ children, className }) {
6
11
  return (_jsx("div", { className: cn("text-sm leading-6 text-white/50", className), children: children }));
7
12
  }
8
13
  export function InfoTooltip({ content, title, label = "Explain this field", className, panelClassName }) {
9
14
  const [open, setOpen] = useState(false);
15
+ const [panelStyle, setPanelStyle] = useState({});
10
16
  const containerRef = useRef(null);
17
+ const triggerRef = useRef(null);
11
18
  const tooltipId = useId();
19
+ const tooltipPanel = typeof document === "undefined"
20
+ ? null
21
+ : createPortal(_jsxs("span", { id: tooltipId, role: "tooltip", "aria-hidden": !open, "data-state": open ? "open" : "closed", style: panelStyle, className: cn("pointer-events-none fixed z-[9999] grid overflow-y-auto rounded-[8px] border border-white/10 bg-[#0c111e] px-3 py-2.5 text-left font-sans text-sm normal-case leading-6 tracking-normal text-white/74 shadow-[0_18px_48px_rgba(3,8,18,0.42)] transition", open ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0", panelClassName), children: [title ? (_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.14em] text-white/58", children: title })) : null, _jsx("span", { className: "block min-w-0 whitespace-normal break-words", children: content })] }), document.body);
22
+ useLayoutEffect(() => {
23
+ if (!open) {
24
+ return;
25
+ }
26
+ const positionTooltip = () => {
27
+ const trigger = triggerRef.current;
28
+ if (!trigger) {
29
+ return;
30
+ }
31
+ const rect = trigger.getBoundingClientRect();
32
+ const width = window.innerWidth < 480
33
+ ? window.innerWidth - TOOLTIP_GUTTER_PX * 2
34
+ : Math.min(TOOLTIP_MAX_WIDTH_PX, window.innerWidth - TOOLTIP_GUTTER_PX * 2);
35
+ const centeredLeft = rect.left + rect.width / 2 - width / 2;
36
+ const maxLeft = window.innerWidth - width - TOOLTIP_GUTTER_PX;
37
+ const left = Math.max(TOOLTIP_GUTTER_PX, Math.min(centeredLeft, maxLeft));
38
+ const availableBelow = window.innerHeight - rect.bottom;
39
+ const top = availableBelow >= TOOLTIP_MIN_HEIGHT_BELOW_PX
40
+ ? rect.bottom + 8
41
+ : Math.max(TOOLTIP_GUTTER_PX, rect.top - TOOLTIP_ESTIMATED_HEIGHT_PX);
42
+ setPanelStyle({
43
+ left,
44
+ maxHeight: `calc(100vh - ${top + TOOLTIP_GUTTER_PX}px)`,
45
+ top,
46
+ width
47
+ });
48
+ };
49
+ positionTooltip();
50
+ window.addEventListener("resize", positionTooltip);
51
+ window.addEventListener("scroll", positionTooltip, true);
52
+ return () => {
53
+ window.removeEventListener("resize", positionTooltip);
54
+ window.removeEventListener("scroll", positionTooltip, true);
55
+ };
56
+ }, [open]);
12
57
  useEffect(() => {
13
58
  if (!open) {
14
59
  return;
@@ -21,9 +66,9 @@ export function InfoTooltip({ content, title, label = "Explain this field", clas
21
66
  document.addEventListener("pointerdown", handlePointerDown);
22
67
  return () => document.removeEventListener("pointerdown", handlePointerDown);
23
68
  }, [open]);
24
- return (_jsxs("span", { ref: containerRef, className: cn("relative inline-flex items-center", className), onMouseEnter: () => setOpen(true), onMouseLeave: () => setOpen(false), children: [_jsx("button", { type: "button", "aria-label": label, "aria-describedby": open ? tooltipId : undefined, "aria-expanded": open, className: "inline-flex size-5 items-center justify-center rounded-full text-white/42 transition hover:bg-white/[0.06] hover:text-white/78 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[rgba(192,193,255,0.35)]", onFocus: () => setOpen(true), onBlur: () => setOpen(false), onClick: () => setOpen((current) => !current), onKeyDown: (event) => {
69
+ return (_jsxs("span", { ref: containerRef, className: cn("relative inline-flex items-center", className), onMouseEnter: () => setOpen(true), onMouseLeave: () => setOpen(false), children: [_jsx("button", { ref: triggerRef, type: "button", "aria-label": label, "aria-describedby": open ? tooltipId : undefined, "aria-expanded": open, className: "inline-flex size-5 items-center justify-center rounded-full text-white/42 transition hover:bg-white/[0.06] hover:text-white/78 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[rgba(192,193,255,0.35)]", onFocus: () => setOpen(true), onBlur: () => setOpen(false), onClick: () => setOpen((current) => !current), onKeyDown: (event) => {
25
70
  if (event.key === "Escape") {
26
71
  setOpen(false);
27
72
  }
28
- }, children: _jsx(CircleHelp, { className: "size-3.5" }) }), _jsxs("span", { id: tooltipId, role: "tooltip", "aria-hidden": !open, "data-state": open ? "open" : "closed", className: cn("pointer-events-none absolute right-0 top-[calc(100%+0.55rem)] z-40 grid w-[min(20rem,calc(100vw-2.5rem))] max-w-[calc(100vw-2.5rem)] gap-1 rounded-[8px] border border-white/10 bg-[rgba(12,17,30,0.97)] px-3 py-2.5 text-left text-sm leading-6 text-white/74 shadow-[0_18px_48px_rgba(3,8,18,0.42)] transition", open ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0", panelClassName), children: [title ? (_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.14em] text-white/58", children: title })) : null, _jsx("span", { children: content })] })] }));
73
+ }, children: _jsx(CircleHelp, { className: "size-3.5" }) }), tooltipPanel] }));
29
74
  }
@@ -1756,6 +1756,137 @@ export function getVitalsView(userIds) {
1756
1756
  const suffix = search.size > 0 ? `?${search.toString()}` : "";
1757
1757
  return request(`/api/v1/health/vitals${suffix}`);
1758
1758
  }
1759
+ export function getWeightLossView(userIds) {
1760
+ const search = new URLSearchParams();
1761
+ appendUserIds(search, coerceUserIds(userIds));
1762
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1763
+ return request(`/api/v1/health/weight-loss${suffix}`);
1764
+ }
1765
+ export function updateNutritionTarget(patch, userIds) {
1766
+ const search = new URLSearchParams();
1767
+ appendUserIds(search, coerceUserIds(userIds));
1768
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1769
+ return request(`/api/v1/health/weight-loss/target${suffix}`, {
1770
+ method: "PATCH",
1771
+ body: JSON.stringify(patch)
1772
+ });
1773
+ }
1774
+ export function searchNutritionFoods(input) {
1775
+ const search = new URLSearchParams();
1776
+ appendUserIds(search, coerceUserIds(input.userIds));
1777
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1778
+ return request(`/api/v1/health/weight-loss/foods/search${suffix}`, {
1779
+ method: "POST",
1780
+ body: JSON.stringify({ query: input.query, limit: input.limit })
1781
+ });
1782
+ }
1783
+ export function lookupNutritionBarcode(input) {
1784
+ const search = new URLSearchParams();
1785
+ appendUserIds(search, coerceUserIds(input.userIds));
1786
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1787
+ return request(`/api/v1/health/weight-loss/foods/barcode${suffix}`, {
1788
+ method: "POST",
1789
+ body: JSON.stringify({ barcode: input.barcode })
1790
+ });
1791
+ }
1792
+ export function createNutritionFoodLog(input, userIds) {
1793
+ const search = new URLSearchParams();
1794
+ appendUserIds(search, coerceUserIds(userIds));
1795
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1796
+ return request(`/api/v1/health/weight-loss/food-logs${suffix}`, {
1797
+ method: "POST",
1798
+ body: JSON.stringify(input)
1799
+ });
1800
+ }
1801
+ export function patchNutritionFoodLog(foodLogId, patch, userIds) {
1802
+ const search = new URLSearchParams();
1803
+ appendUserIds(search, coerceUserIds(userIds));
1804
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1805
+ return request(`/api/v1/health/weight-loss/food-logs/${foodLogId}${suffix}`, {
1806
+ method: "PATCH",
1807
+ body: JSON.stringify(patch)
1808
+ });
1809
+ }
1810
+ export function deleteNutritionFoodLog(foodLogId, userIds) {
1811
+ const search = new URLSearchParams();
1812
+ appendUserIds(search, coerceUserIds(userIds));
1813
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1814
+ return request(`/api/v1/health/weight-loss/food-logs/${foodLogId}${suffix}`, { method: "DELETE" });
1815
+ }
1816
+ export function parseNutritionFoodLogWithChatGpt(input) {
1817
+ const search = new URLSearchParams();
1818
+ appendUserIds(search, coerceUserIds(input.userIds));
1819
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1820
+ return request(`/api/v1/health/weight-loss/parse${suffix}`, {
1821
+ method: "POST",
1822
+ body: JSON.stringify({
1823
+ text: input.text,
1824
+ imageDescription: input.imageDescription,
1825
+ loggedAt: input.loggedAt,
1826
+ mealLabel: input.mealLabel
1827
+ })
1828
+ });
1829
+ }
1830
+ export function createNutritionBodyCheckin(input, userIds) {
1831
+ const search = new URLSearchParams();
1832
+ appendUserIds(search, coerceUserIds(userIds));
1833
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1834
+ return request(`/api/v1/health/weight-loss/body-checkins${suffix}`, {
1835
+ method: "POST",
1836
+ body: JSON.stringify(input)
1837
+ });
1838
+ }
1839
+ export function createNutritionAppearanceCheckin(input, userIds) {
1840
+ const search = new URLSearchParams();
1841
+ appendUserIds(search, coerceUserIds(userIds));
1842
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1843
+ return request(`/api/v1/health/weight-loss/appearance-checkins${suffix}`, {
1844
+ method: "POST",
1845
+ body: JSON.stringify(input)
1846
+ });
1847
+ }
1848
+ export function createNutritionSubjectiveCheckin(input, userIds) {
1849
+ const search = new URLSearchParams();
1850
+ appendUserIds(search, coerceUserIds(userIds));
1851
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1852
+ return request(`/api/v1/health/weight-loss/subjective-checkins${suffix}`, {
1853
+ method: "POST",
1854
+ body: JSON.stringify(input)
1855
+ });
1856
+ }
1857
+ export function createNutritionGutCheckin(input, userIds) {
1858
+ const search = new URLSearchParams();
1859
+ appendUserIds(search, coerceUserIds(userIds));
1860
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1861
+ return request(`/api/v1/health/weight-loss/gut-checkins${suffix}`, {
1862
+ method: "POST",
1863
+ body: JSON.stringify(input)
1864
+ });
1865
+ }
1866
+ export function getNutritionPatterns(userIds) {
1867
+ const search = new URLSearchParams();
1868
+ appendUserIds(search, coerceUserIds(userIds));
1869
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1870
+ return request(`/api/v1/health/weight-loss/patterns${suffix}`);
1871
+ }
1872
+ export function createNutritionExperiment(input, userIds) {
1873
+ const search = new URLSearchParams();
1874
+ appendUserIds(search, coerceUserIds(userIds));
1875
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1876
+ return request(`/api/v1/health/weight-loss/experiments${suffix}`, {
1877
+ method: "POST",
1878
+ body: JSON.stringify(input)
1879
+ });
1880
+ }
1881
+ export function patchNutritionExperiment(experimentId, patch, userIds) {
1882
+ const search = new URLSearchParams();
1883
+ appendUserIds(search, coerceUserIds(userIds));
1884
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1885
+ return request(`/api/v1/health/weight-loss/experiments/${experimentId}${suffix}`, {
1886
+ method: "PATCH",
1887
+ body: JSON.stringify(patch)
1888
+ });
1889
+ }
1759
1890
  export function getMovementDay(input) {
1760
1891
  const search = new URLSearchParams();
1761
1892
  if (input?.date) {
@@ -0,0 +1 @@
1
+ export {};
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.99",
5
+ "version": "0.2.100",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
@@ -42,6 +42,7 @@
42
42
  "forge_get_calendar_overview",
43
43
  "forge_get_current_work",
44
44
  "forge_get_doctor",
45
+ "forge_get_nutrition_patterns",
45
46
  "forge_get_operator_context",
46
47
  "forge_get_operator_overview",
47
48
  "forge_get_preferences_workspace",
@@ -55,6 +56,7 @@
55
56
  "forge_get_ui_entrypoint",
56
57
  "forge_get_user_directory",
57
58
  "forge_get_weekly_review",
59
+ "forge_get_weight_loss_overview",
58
60
  "forge_get_wiki_health",
59
61
  "forge_get_wiki_page",
60
62
  "forge_get_wiki_settings",
@@ -64,8 +66,15 @@
64
66
  "forge_ingest_wiki_source",
65
67
  "forge_list_questionnaires",
66
68
  "forge_list_wiki_pages",
69
+ "forge_log_appearance_checkin",
70
+ "forge_log_body_checkin",
71
+ "forge_log_food",
72
+ "forge_log_gut_checkin",
73
+ "forge_log_subjective_food_effect",
67
74
  "forge_log_work",
75
+ "forge_lookup_nutrition_barcode",
68
76
  "forge_merge_preferences_contexts",
77
+ "forge_parse_food_log_with_chatgpt",
69
78
  "forge_post_insight",
70
79
  "forge_publish_questionnaire_draft",
71
80
  "forge_recommend_task_timeboxes",
@@ -73,7 +82,10 @@
73
82
  "forge_release_task_run",
74
83
  "forge_restore_entities",
75
84
  "forge_search_entities",
85
+ "forge_search_foods",
86
+ "forge_search_nutrition_foods",
76
87
  "forge_search_wiki",
88
+ "forge_start_nutrition_experiment",
77
89
  "forge_start_preferences_game",
78
90
  "forge_start_questionnaire_run",
79
91
  "forge_start_task_run",
@@ -82,6 +94,7 @@
82
94
  "forge_sync_calendar_connection",
83
95
  "forge_sync_wiki_vault",
84
96
  "forge_update_entities",
97
+ "forge_update_nutrition_experiment",
85
98
  "forge_update_preferences_score",
86
99
  "forge_update_questionnaire_run",
87
100
  "forge_update_sleep_session",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.99",
3
+ "version": "0.2.100",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -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);
@@ -199,8 +199,17 @@ Wiki navigation and search rule:
199
199
  Health rule:
200
200
 
201
201
  - Sleep and sports records are first-class health surfaces, not generic notes or tasks.
202
- - Use `forge_get_sleep_overview`, `forge_get_sports_overview`, and
203
- `forge_get_training_load_overview` for health review and trend reading.
202
+ - Use `forge_get_sleep_overview`, `forge_get_sports_overview`,
203
+ `forge_get_training_load_overview`, and `forge_get_weight_loss_overview`
204
+ for health review and trend reading.
205
+ - Use the dedicated nutrition tools for food/body work: `forge_search_foods`, `forge_search_nutrition_foods`,
206
+ `forge_lookup_nutrition_barcode`, `forge_log_food`,
207
+ `forge_parse_food_log_with_chatgpt`, `forge_log_body_checkin`,
208
+ `forge_log_appearance_checkin`, `forge_log_subjective_food_effect`,
209
+ `forge_log_gut_checkin`, `forge_get_nutrition_patterns`,
210
+ `forge_start_nutrition_experiment`, and `forge_update_nutrition_experiment`.
211
+ `forge_parse_food_log_with_chatgpt` must use Forge's configured `openai-codex`
212
+ ChatGPT subscription connection, not a metered OpenAI Platform API path.
204
213
  - In `forge_get_agent_onboarding.entityRouteModel.readModelOnlySurfaces`, operator,
205
214
  calendar, self-observation, sleep, sports, and training-load read models are published with
206
215
  both camelCase names and entity-style aliases where useful, including
@@ -227,6 +236,14 @@ Entity conversation rule:
227
236
  - Prefer a progression of:
228
237
  concrete example or intent -> working name -> purpose or meaning -> placement in Forge -> operational details -> linked context.
229
238
  - Use those same playbooks for action-heavy non-Psyche flows such as `work_adjustment`, `preference_judgment`, `preference_signal`, and specialized `movement`, `life_force`, or `workbench` requests so the conversation starts from what the user is trying to understand, change, add, update, link, or run before you choose the route.
239
+ - When one message combines several jobs, sequence them instead of turning them into
240
+ a broad menu: read before a correction when the current truth is uncertain,
241
+ formulate the primary Psyche record before deriving a flashcard or note, and ask
242
+ only for the missing span, wording, flow, run, node, weekday, or link that changes
243
+ the next action.
244
+ - Before deleting, archiving, invalidating, disconnecting, or replacing a record, confirm
245
+ the exact target and what should remain understandable; for Psyche records, preserve
246
+ therapeutic history unless the user clearly wants removal.
230
247
  - When the operation is not already explicit, identify the job first:
231
248
  add, update, review, compare, navigate, link, or run. Skip that meta question
232
249
  when the action is already obvious from the user's wording.
@@ -515,7 +532,7 @@ Use these rules when choosing tools.
515
532
 
516
533
  Read first with `forge_get_operator_overview`, `forge_get_operator_context`, or `forge_get_current_work` unless the user is clearly asking for one exact known record or one exact write.
517
534
 
518
- Before creating or updating an ambiguous stored entity, use `forge_search_entities` to check for duplicates.
535
+ Before creating or updating an ambiguous stored entity, use `forge_search_entities` to check for duplicates. If a likely match appears, ask whether the user wants to update that record, link to it, or save a separate new record; do not reopen the whole create flow.
519
536
 
520
537
  Use the batch entity tools for stored records:
521
538
  `forge_search_entities`, `forge_create_entities`, `forge_update_entities`, `forge_delete_entities`, `forge_restore_entities`
@@ -526,8 +543,8 @@ These tools operate on:
526
543
  Use the wiki tools for SQLite-backed memory work:
527
544
  `forge_get_wiki_settings`, `forge_list_wiki_pages`, `forge_get_wiki_page`, `forge_search_wiki`, `forge_upsert_wiki_page`, `forge_get_wiki_health`, `forge_sync_wiki_vault`, `forge_reindex_wiki_embeddings`, `forge_ingest_wiki_source`
528
545
 
529
- Use the health tools for review and reflective enrichment, not as the default CRUD architecture:
530
- `forge_get_sleep_overview`, `forge_get_sports_overview`, `forge_get_training_load_overview`, `forge_update_sleep_session`, `forge_update_workout_session`
546
+ Use the health tools for review, reflective enrichment, and nutrition evidence capture:
547
+ `forge_get_sleep_overview`, `forge_get_sports_overview`, `forge_get_training_load_overview`, `forge_get_weight_loss_overview`, `forge_update_sleep_session`, `forge_update_workout_session`, `forge_search_foods`, `forge_search_nutrition_foods`, `forge_lookup_nutrition_barcode`, `forge_log_food`, `forge_parse_food_log_with_chatgpt`, `forge_log_body_checkin`, `forge_log_appearance_checkin`, `forge_log_subjective_food_effect`, `forge_log_gut_checkin`, `forge_get_nutrition_patterns`, `forge_start_nutrition_experiment`, `forge_update_nutrition_experiment`
531
548
 
532
549
  Use the dedicated domain routes for specialized surfaces that are not simple batch entities:
533
550
 
@@ -683,6 +700,10 @@ Use the health tools when the request is about sleep or sports review:
683
700
  - `forge_get_sleep_overview` to inspect recent nights, averages, regularity, stage breakdown, and linked reflective context
684
701
  - `forge_get_sports_overview` to inspect training volume, workout types, effort trends, habit-generated sessions, and linked context
685
702
  - `forge_get_training_load_overview` to inspect cardiovascular load, HR zone balance, zone-time buckets, smart training modes, acute/chronic stress, high-intensity pressure, VO2max context, next-workout guidance, and training target fit
703
+ - `forge_get_weight_loss_overview` to inspect calorie balance, protein/fiber targets, body trend, food quality, training fuel, subjective energy, gut comfort, aesthetic look, hypotheses, and experiments
704
+ - `forge_parse_food_log_with_chatgpt` to convert rough meal text or a photo description into a candidate food log through the configured `openai-codex` ChatGPT subscription connection
705
+ - `forge_log_food`, `forge_log_body_checkin`, `forge_log_appearance_checkin`, `forge_log_subjective_food_effect`, and `forge_log_gut_checkin` to preserve the user's food, body-composition, visual-look, energy/craving/performance, and gut-health evidence
706
+ - `forge_get_nutrition_patterns`, `forge_start_nutrition_experiment`, and `forge_update_nutrition_experiment` to turn repeated food/body observations into testable N-of-1 hypotheses
686
707
  - `forge_update_sleep_session` to add sleep-quality notes, tags, or links back to Forge entities after review
687
708
  - `forge_update_workout_session` to add subjective effort, mood, meaning, tags, or links on one workout after review
688
709
  - remember that the UI route is `/sports` while the backend overview route is `/api/v1/health/fitness`; the dedicated training-load UI is `/training-load` and its backend route is `/api/v1/health/training-load`, including zone-time reporting and Combat/Base/Endurance smart modes