Bug fix (committed separately):
social-fast-coordinator: stage_transition used non-existent currentStage
New client functions:
nomgap-client: nomgapFastingCreateSession, nomgapProtocolGet,
nomgapSocialListGroupFasts (+ GroupFastDoc)
peakpulse-client: peakpulseRoutesList, peakpulseSyncStatus
lysnrai-client: lysnraiApiTokenCreate, lysnraiSessionsStats,
lysnraiTranscriptsExportBatch
chronomind-client: chronomindRoutineGet, chronomindSharedTimerShare
New MCP tools (12):
mindlyst.briefs.generate — trigger daily brief via mindlystBriefCreate
mindlyst.memory.getTriageResult — extract TriageResult sub-doc only
nomgap.fasting.createSession — start new fast with protocolId
nomgap.protocols.get — single protocol lookup
nomgap.social.listGroupFasts — list group fast sessions
peakpulse.routes.list — list GPS r
Bug fix (committed separately):
social-fast-coordinator: stage_transition used non-existent currentStage
ats social-fast-coordinator: staon
New client functions:
nomgap-client: nomgapFastingCreateSession, nomgarai nomgaens.rotate nomgapSocialListGroupFasts (+ GroupFastDoc)
d. peakpulse-client: peakpulseRoutesList, peakpulseSyncStaturg lysnrai-client: lysnraiApiTokenCreate, lysnraiSessionsStati lysnraiTranscriptsExpor us chronomind-client: chronomindRoutineGet, chrerver total: 126 tools across 17 namespaces
227 lines
7.5 KiB
TypeScript
227 lines
7.5 KiB
TypeScript
/**
|
|
* NomGap backend client — typed HTTP wrappers for the nomgap-backend (port 4013).
|
|
*
|
|
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
|
|
*/
|
|
|
|
import { config } from './config.js';
|
|
|
|
export interface NomGapClientOptions {
|
|
token?: string;
|
|
requestId?: string;
|
|
productId?: string;
|
|
}
|
|
|
|
async function nomgapFetch<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
opts: NomGapClientOptions
|
|
): Promise<T> {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
|
...(opts.productId ? { 'x-product-id': opts.productId } : { 'x-product-id': 'nomgap' }),
|
|
};
|
|
const res = await fetch(`${config.NOMGAP_BACKEND_URL}/api${path}`, {
|
|
...init,
|
|
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
|
|
signal: AbortSignal.timeout(15_000),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`nomgap-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
// ── Fasting sessions ───────────────────────────────────────────────────────
|
|
|
|
export interface FastingSessionDoc {
|
|
id: string;
|
|
userId: string;
|
|
productId: string;
|
|
protocolId: string;
|
|
startedAt: number;
|
|
targetDurationMs: number;
|
|
status: 'active' | 'completed' | 'broken';
|
|
stages: unknown[];
|
|
moodCheckins: unknown[];
|
|
waterIntake: unknown[];
|
|
metrics: {
|
|
actualDurationMs: number;
|
|
completionRatio: number;
|
|
peakAutophagyConfidence: number;
|
|
totalPausedMs: number;
|
|
moodCheckinCount: number;
|
|
averageEnergy: number | null;
|
|
averageMood: number | null;
|
|
};
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export function nomgapFastingCreateSession(
|
|
input: { protocolId: string; variables?: Record<string, string> },
|
|
opts: NomGapClientOptions
|
|
): Promise<FastingSessionDoc> {
|
|
return nomgapFetch('/fasting/sessions', { method: 'POST', body: JSON.stringify(input) }, opts);
|
|
}
|
|
|
|
export function nomgapFastingSessionsList(
|
|
params: { limit?: number; offset?: number; status?: string; from?: string; to?: string },
|
|
opts: NomGapClientOptions
|
|
): Promise<{ items: FastingSessionDoc[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
if (params.status) qs.set('status', params.status);
|
|
if (params.from) qs.set('from', params.from);
|
|
if (params.to) qs.set('to', params.to);
|
|
const q = qs.toString();
|
|
return nomgapFetch(`/fasting/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function nomgapFastingSessionGet(
|
|
sessionId: string,
|
|
opts: NomGapClientOptions
|
|
): Promise<FastingSessionDoc> {
|
|
return nomgapFetch(`/fasting/sessions/${sessionId}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function nomgapFastingGetStats(opts: NomGapClientOptions): Promise<Record<string, unknown>> {
|
|
return nomgapFetch('/fasting/stats', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function nomgapFastingGetWeeklyStats(
|
|
opts: NomGapClientOptions
|
|
): Promise<Record<string, unknown>> {
|
|
return nomgapFetch('/fasting/stats/weekly', { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Protocols ─────────────────────────────────────────────────────────────
|
|
|
|
export function nomgapProtocolsList(
|
|
opts: NomGapClientOptions
|
|
): Promise<{ protocols: unknown[]; total: number }> {
|
|
return nomgapFetch('/fasting/protocols', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function nomgapProtocolGet(
|
|
protocolId: string,
|
|
opts: NomGapClientOptions
|
|
): Promise<Record<string, unknown>> {
|
|
return nomgapFetch(`/fasting/protocols/${protocolId}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Body stages (public — no auth required) ────────────────────────────────
|
|
|
|
export function nomgapBodyStagesList(
|
|
opts: Pick<NomGapClientOptions, 'requestId'>
|
|
): Promise<{ stages: unknown[]; total: number }> {
|
|
return nomgapFetch('/fasting/stages', { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Autophagy confidence ──────────────────────────────────────────────────
|
|
|
|
export interface AutophagyConfidenceInput {
|
|
durationHours: number;
|
|
activityLevel: 'sedentary' | 'light' | 'moderate' | 'active' | 'very_active';
|
|
lastMealCarbs?: number;
|
|
sleepHours?: number;
|
|
completionHistory?: { totalFasts: number; completionRate: number };
|
|
hrvData?: { restingHR?: number; hrv?: number };
|
|
}
|
|
|
|
export interface AutophagyConfidenceResult {
|
|
confidence: number;
|
|
label: 'unlikely' | 'possible' | 'likely' | 'very_likely' | 'near_certain';
|
|
breakdown: {
|
|
duration: number;
|
|
meal: number;
|
|
activity: number;
|
|
sleep: number;
|
|
history: number;
|
|
hrv: number;
|
|
};
|
|
currentStage: string;
|
|
}
|
|
|
|
export function nomgapAutophagyConfidence(
|
|
input: AutophagyConfidenceInput,
|
|
opts: Pick<NomGapClientOptions, 'requestId'>
|
|
): Promise<AutophagyConfidenceResult> {
|
|
return nomgapFetch(
|
|
'/fasting/autophagy-confidence',
|
|
{ method: 'POST', body: JSON.stringify(input) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Push triggers ──────────────────────────────────────────────────────────
|
|
|
|
export type PushTriggerType =
|
|
| 'streak_risk'
|
|
| 'fast_milestone'
|
|
| 'stage_transition'
|
|
| 'social_invite'
|
|
| 'weekly_digest'
|
|
| 'achievement_unlocked'
|
|
| 'refeeding_reminder'
|
|
| 'electrolyte_reminder'
|
|
| 'extended_fast_warning';
|
|
|
|
export interface PushTriggerDoc {
|
|
id: string;
|
|
productId: string;
|
|
type: PushTriggerType;
|
|
userId: string;
|
|
variables: Record<string, string>;
|
|
status: 'pending' | 'sent' | 'skipped' | 'failed';
|
|
scheduledAt: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
// ── Social fasting ────────────────────────────────────────────────────────
|
|
|
|
export interface GroupFastDoc {
|
|
id: string;
|
|
userId: string;
|
|
productId: string;
|
|
sessionId: string;
|
|
memberCount: number;
|
|
status: 'active' | 'completed' | 'cancelled';
|
|
createdAt: string;
|
|
}
|
|
|
|
export function nomgapSocialListGroupFasts(
|
|
params: { limit?: number; offset?: number },
|
|
opts: NomGapClientOptions
|
|
): Promise<{ items: GroupFastDoc[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
const q = qs.toString();
|
|
return nomgapFetch(`/social/group-fasts${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function nomgapPushFire(
|
|
input: {
|
|
type: PushTriggerType;
|
|
userId: string;
|
|
variables?: Record<string, string>;
|
|
scheduledFor?: string;
|
|
},
|
|
opts: NomGapClientOptions
|
|
): Promise<PushTriggerDoc> {
|
|
return nomgapFetch('/push-triggers', { method: 'POST', body: JSON.stringify(input) }, opts);
|
|
}
|
|
|
|
export function nomgapPushGetStats(opts: NomGapClientOptions): Promise<Record<string, unknown>> {
|
|
return nomgapFetch('/push-triggers/stats', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function nomgapPushGetPending(opts: NomGapClientOptions): Promise<PushTriggerDoc[]> {
|
|
return nomgapFetch('/push-triggers/pending', { method: 'GET' }, opts);
|
|
}
|