learning_ai_common_plat/services/mcp-server/src/lib/chronomind-client.ts

117 lines
3.9 KiB
TypeScript

/**
* ChronoMind backend client — typed HTTP wrappers for the chronomind-backend (port 4011).
*
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
*/
import { config } from './config.js';
export interface ChronoMindClientOptions {
token?: string;
requestId?: string;
}
async function chronomindFetch<T>(
path: string,
init: RequestInit,
opts: ChronoMindClientOptions
): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
};
const res = await fetch(`${config.CHRONOMIND_BACKEND_URL}${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(`chronomind-backend ${init.method ?? 'GET'} ${path}${res.status}: ${body}`);
}
return res.json() as Promise<T>;
}
// ── Timers ─────────────────────────────────────────────────────────────────
export interface TimerDoc {
id: string;
userId: string;
productId: string;
label: string;
description?: string;
type: 'alarm' | 'countdown' | 'pomodoro' | 'event';
state: 'idle' | 'active' | 'paused' | 'warning' | 'fired' | 'completed';
urgency: 'critical' | 'important' | 'standard' | 'gentle' | 'passive';
duration?: number;
targetTime?: string;
category?: string;
createdAt: string;
lastSyncedAt: string;
syncVersion: number;
}
export function chronomindTimersList(
params: { limit?: number; offset?: number; type?: string; state?: string },
opts: ChronoMindClientOptions
): Promise<{ items: TimerDoc[]; 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.type) qs.set('type', params.type);
if (params.state) qs.set('state', params.state);
const q = qs.toString();
return chronomindFetch(`/timers${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
}
export function chronomindSyncStatus(opts: ChronoMindClientOptions): Promise<{
userId: string;
productId: string;
totalTimers: number;
active: number;
pending: number;
unsyncedCount: number;
lastSyncedAt: string | null;
generatedAt: string;
}> {
return chronomindFetch('/timers/sync-status', { method: 'GET' }, opts);
}
// ── Routines ───────────────────────────────────────────────────────────────
export interface RoutineDoc {
id: string;
userId: string;
productId: string;
name: string;
description?: string;
steps: unknown[];
totalDurationMinutes: number;
status: 'idle' | 'running' | 'paused' | 'completed';
isTemplate: boolean;
category?: string;
createdAt: string;
lastSyncedAt: string;
syncVersion: number;
}
export function chronomindRoutinesList(
params: { limit?: number; offset?: number; isTemplate?: boolean },
opts: ChronoMindClientOptions
): Promise<{ items: RoutineDoc[]; 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.isTemplate !== undefined) qs.set('isTemplate', String(params.isTemplate));
const q = qs.toString();
return chronomindFetch(`/routines${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
}
export function chronomindTimerDelete(
timerId: string,
opts: ChronoMindClientOptions
): Promise<{ success: boolean }> {
return chronomindFetch(`/timers/${timerId}`, { method: 'DELETE' }, opts);
}