@mongrov/analytics 0.1.0-alpha.8 → 0.1.0-alpha.9

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 (66) hide show
  1. package/dist/tools/__fakes__/engine.d.ts +25 -0
  2. package/dist/tools/__fakes__/engine.d.ts.map +1 -0
  3. package/dist/tools/__fakes__/engine.js +37 -0
  4. package/dist/tools/__fakes__/engine.js.map +1 -0
  5. package/dist/tools/audit.d.ts +31 -0
  6. package/dist/tools/audit.d.ts.map +1 -0
  7. package/dist/tools/audit.js +157 -0
  8. package/dist/tools/audit.js.map +1 -0
  9. package/dist/tools/authorize.d.ts +36 -0
  10. package/dist/tools/authorize.d.ts.map +1 -0
  11. package/dist/tools/authorize.js +87 -0
  12. package/dist/tools/authorize.js.map +1 -0
  13. package/dist/tools/budget.d.ts +15 -0
  14. package/dist/tools/budget.d.ts.map +1 -0
  15. package/dist/tools/budget.js +64 -0
  16. package/dist/tools/budget.js.map +1 -0
  17. package/dist/tools/factory.d.ts +43 -0
  18. package/dist/tools/factory.d.ts.map +1 -0
  19. package/dist/tools/factory.js +137 -0
  20. package/dist/tools/factory.js.map +1 -0
  21. package/dist/tools/formatters.d.ts +17 -0
  22. package/dist/tools/formatters.d.ts.map +1 -0
  23. package/dist/tools/formatters.js +30 -0
  24. package/dist/tools/formatters.js.map +1 -0
  25. package/dist/tools/impls/activity.d.ts +15 -0
  26. package/dist/tools/impls/activity.d.ts.map +1 -0
  27. package/dist/tools/impls/activity.js +61 -0
  28. package/dist/tools/impls/activity.js.map +1 -0
  29. package/dist/tools/impls/anomaly.d.ts +21 -0
  30. package/dist/tools/impls/anomaly.d.ts.map +1 -0
  31. package/dist/tools/impls/anomaly.js +70 -0
  32. package/dist/tools/impls/anomaly.js.map +1 -0
  33. package/dist/tools/impls/compare.d.ts +21 -0
  34. package/dist/tools/impls/compare.d.ts.map +1 -0
  35. package/dist/tools/impls/compare.js +68 -0
  36. package/dist/tools/impls/compare.js.map +1 -0
  37. package/dist/tools/impls/hrv.d.ts +15 -0
  38. package/dist/tools/impls/hrv.d.ts.map +1 -0
  39. package/dist/tools/impls/hrv.js +36 -0
  40. package/dist/tools/impls/hrv.js.map +1 -0
  41. package/dist/tools/impls/insights.d.ts +18 -0
  42. package/dist/tools/impls/insights.d.ts.map +1 -0
  43. package/dist/tools/impls/insights.js +41 -0
  44. package/dist/tools/impls/insights.js.map +1 -0
  45. package/dist/tools/impls/sleep.d.ts +15 -0
  46. package/dist/tools/impls/sleep.d.ts.map +1 -0
  47. package/dist/tools/impls/sleep.js +43 -0
  48. package/dist/tools/impls/sleep.js.map +1 -0
  49. package/dist/tools/index.d.ts +13 -1
  50. package/dist/tools/index.d.ts.map +1 -1
  51. package/dist/tools/index.js +21 -1
  52. package/dist/tools/index.js.map +1 -1
  53. package/dist/tools/rate-limit.d.ts +26 -0
  54. package/dist/tools/rate-limit.d.ts.map +1 -0
  55. package/dist/tools/rate-limit.js +67 -0
  56. package/dist/tools/rate-limit.js.map +1 -0
  57. package/dist/tools/types.d.ts +121 -0
  58. package/dist/tools/types.d.ts.map +1 -0
  59. package/dist/tools/types.js +16 -0
  60. package/dist/tools/types.js.map +1 -0
  61. package/dist/tools/wrap.d.ts +53 -0
  62. package/dist/tools/wrap.d.ts.map +1 -0
  63. package/dist/tools/wrap.js +135 -0
  64. package/dist/tools/wrap.js.map +1 -0
  65. package/package.json +18 -15
  66. package/LICENSE +0 -21
@@ -0,0 +1,137 @@
1
+ /**
2
+ * `createAnalyticsTools` — assemble the 6 wrapped AI SDK tools into
3
+ * a single map ready to hand to `streamText({ tools })` / `generateText`.
4
+ *
5
+ * Wires:
6
+ * - rate limiter (or `null` if `rateLimit: false`)
7
+ * - authorize hook (default `familyScopeAuthorize`, optionally with
8
+ * an injected `familyMembersProvider`)
9
+ * - audit writer (batched, optional)
10
+ * - output budget (default 4096 bytes / 100 rows)
11
+ *
12
+ * Per-request context threading: the AI SDK's `execute(input)` has no
13
+ * ctx arg. We hold a mutable `currentCtx` container and expose
14
+ * `setContext(ctx)`. Consumers (zivaone_app) call `setContext` before
15
+ * each LLM turn — matching how `@mongrov/ai`'s `AIProvider` threads
16
+ * per-request scopes. This design keeps rate-limiter buckets + audit
17
+ * writer state shared across requests rather than rebuilding the
18
+ * whole tool map per turn.
19
+ */
20
+ import { createAuditWriter } from './audit';
21
+ import { familyScopeAuthorize } from './authorize';
22
+ import { createRateLimiter } from './rate-limit';
23
+ import { DEFAULT_OUTPUT_BUDGET, DEFAULT_RATE_LIMIT, } from './types';
24
+ import { makeTool } from './wrap';
25
+ import { getActivityTotal, getActivityTotalInputSchema, } from './impls/activity';
26
+ import { detectAnomaly, detectAnomalyInputSchema, } from './impls/anomaly';
27
+ import { compareTrend, compareTrendInputSchema, } from './impls/compare';
28
+ import { getHRV, getHRVInputSchema } from './impls/hrv';
29
+ import { getInsights, getInsightsInputSchema, } from './impls/insights';
30
+ import { getSleepSummary, getSleepSummaryInputSchema, } from './impls/sleep';
31
+ const DESCRIPTIONS = {
32
+ getHRV: 'Return the requester\'s or a family member\'s average HRV per day '
33
+ + 'over the last N days (1..90). Args: userId, days.',
34
+ getSleepSummary: 'Return nightly sleep duration and efficiency for a user '
35
+ + 'over the last N days (1..30). Args: userId, days.',
36
+ getActivityTotal: 'Return total steps and active minutes per day for a user '
37
+ + 'over the last N days (1..30). Args: userId, days.',
38
+ compareTrend: 'Compare a user\'s metric average across two windows. Args: '
39
+ + 'userId, metric (hrv_ms|sleep_total_minutes|activity_steps), '
40
+ + 'currentWindowDays, priorWindowDays (each 1..30).',
41
+ detectAnomaly: 'Flag statistical outliers (>= stddevThreshold from baseline) '
42
+ + 'in a user\'s metric over the last lookbackDays. Args: userId, metric '
43
+ + '(hrv_ms|sleep_total_minutes|activity_steps), lookbackDays (7..90), '
44
+ + 'stddevThreshold (1..4, default 2).',
45
+ getInsights: 'Return recent AI-generated insights for a user. Args: userId, '
46
+ + 'days (1..30, default 7), optional severity (info|warn|critical).',
47
+ };
48
+ export function createAnalyticsTools(config) {
49
+ const budget = {
50
+ ...DEFAULT_OUTPUT_BUDGET,
51
+ ...(config.outputBudget ?? {}),
52
+ };
53
+ const rateLimiter = config.rateLimit === false
54
+ ? null
55
+ : createRateLimiter({
56
+ config: config.rateLimit ?? DEFAULT_RATE_LIMIT,
57
+ clock: config.clock,
58
+ });
59
+ const authorize = config.authorize
60
+ ?? familyScopeAuthorize(config.analytics, {
61
+ familyMembersProvider: config.familyMembersProvider,
62
+ });
63
+ const audit = createAuditWriter({
64
+ analytics: config.analytics,
65
+ enabled: config.audit?.enabled ?? true,
66
+ batchSize: config.audit?.batchSize,
67
+ flushIntervalMs: config.audit?.flushIntervalMs,
68
+ logger: config.logger,
69
+ });
70
+ let currentCtx = null;
71
+ const ctxProvider = () => currentCtx;
72
+ const shared = {
73
+ analytics: config.analytics,
74
+ authorize,
75
+ rateLimiter,
76
+ audit,
77
+ budget,
78
+ logger: config.logger,
79
+ ctxProvider,
80
+ clock: config.clock,
81
+ };
82
+ const tools = {
83
+ getHRV: makeTool({
84
+ ...shared,
85
+ name: 'getHRV',
86
+ description: DESCRIPTIONS.getHRV,
87
+ inputSchema: getHRVInputSchema,
88
+ impl: getHRV,
89
+ }),
90
+ getSleepSummary: makeTool({
91
+ ...shared,
92
+ name: 'getSleepSummary',
93
+ description: DESCRIPTIONS.getSleepSummary,
94
+ inputSchema: getSleepSummaryInputSchema,
95
+ impl: getSleepSummary,
96
+ }),
97
+ getActivityTotal: makeTool({
98
+ ...shared,
99
+ name: 'getActivityTotal',
100
+ description: DESCRIPTIONS.getActivityTotal,
101
+ inputSchema: getActivityTotalInputSchema,
102
+ impl: getActivityTotal,
103
+ }),
104
+ compareTrend: makeTool({
105
+ ...shared,
106
+ name: 'compareTrend',
107
+ description: DESCRIPTIONS.compareTrend,
108
+ inputSchema: compareTrendInputSchema,
109
+ impl: compareTrend,
110
+ }),
111
+ detectAnomaly: makeTool({
112
+ ...shared,
113
+ name: 'detectAnomaly',
114
+ description: DESCRIPTIONS.detectAnomaly,
115
+ inputSchema: detectAnomalyInputSchema,
116
+ impl: detectAnomaly,
117
+ }),
118
+ getInsights: makeTool({
119
+ ...shared,
120
+ name: 'getInsights',
121
+ description: DESCRIPTIONS.getInsights,
122
+ inputSchema: getInsightsInputSchema,
123
+ impl: getInsights,
124
+ }),
125
+ };
126
+ return {
127
+ tools,
128
+ setContext(ctx) {
129
+ currentCtx = ctx;
130
+ },
131
+ auditWriter: audit,
132
+ async close() {
133
+ await audit.close();
134
+ },
135
+ };
136
+ }
137
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/tools/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared string helpers for tool output formatting.
3
+ *
4
+ * No I/O; safe to import from any impl. Used to compute the `bytes`
5
+ * field of `ToolResult` and to render deltas consistently across
6
+ * tools.
7
+ */
8
+ export declare function formatBytes(s: string): number;
9
+ /**
10
+ * Format a signed percentage delta between two numbers. Returns `n/a`
11
+ * when the prior baseline is zero (avoids Infinity / NaN in tool
12
+ * output).
13
+ */
14
+ export declare function deltaPct(current: number, prior: number): string;
15
+ /** Population stddev; returns 0 when the sample is empty. */
16
+ export declare function popStddev(values: number[]): number;
17
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/tools/formatters.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAMlD"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared string helpers for tool output formatting.
3
+ *
4
+ * No I/O; safe to import from any impl. Used to compute the `bytes`
5
+ * field of `ToolResult` and to render deltas consistently across
6
+ * tools.
7
+ */
8
+ export function formatBytes(s) {
9
+ return new TextEncoder().encode(s).byteLength;
10
+ }
11
+ /**
12
+ * Format a signed percentage delta between two numbers. Returns `n/a`
13
+ * when the prior baseline is zero (avoids Infinity / NaN in tool
14
+ * output).
15
+ */
16
+ export function deltaPct(current, prior) {
17
+ if (prior === 0)
18
+ return 'n/a';
19
+ const pct = ((current - prior) / prior) * 100;
20
+ return `${pct >= 0 ? '+' : ''}${pct.toFixed(1)}%`;
21
+ }
22
+ /** Population stddev; returns 0 when the sample is empty. */
23
+ export function popStddev(values) {
24
+ if (values.length === 0)
25
+ return 0;
26
+ const mean = values.reduce((a, v) => a + v, 0) / values.length;
27
+ const variance = values.reduce((a, v) => a + (v - mean) ** 2, 0) / values.length;
28
+ return Math.sqrt(variance);
29
+ }
30
+ //# sourceMappingURL=formatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../../src/tools/formatters.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,KAAa;IACrD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7B,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAA;IAC7C,OAAO,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;AACnD,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,SAAS,CAAC,MAAgB;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;IAC9D,MAAM,QAAQ,GACV,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;IACnE,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAC5B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { ToolImpl } from '../types';
3
+ export declare const getActivityTotalInputSchema: z.ZodObject<{
4
+ userId: z.ZodString;
5
+ days: z.ZodNumber;
6
+ }, "strip", z.ZodTypeAny, {
7
+ days: number;
8
+ userId: string;
9
+ }, {
10
+ days: number;
11
+ userId: string;
12
+ }>;
13
+ export type GetActivityTotalInput = z.infer<typeof getActivityTotalInputSchema>;
14
+ export declare const getActivityTotal: ToolImpl<GetActivityTotalInput>;
15
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../../src/tools/impls/activity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,2BAA2B;;;;;;;;;EAGtC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CACzC,OAAO,2BAA2B,CACnC,CAAA;AAaD,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,qBAAqB,CAiE5D,CAAA"}
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes } from '../formatters';
3
+ export const getActivityTotalInputSchema = z.object({
4
+ userId: z.string(),
5
+ days: z.number().int().min(1).max(90),
6
+ });
7
+ export const getActivityTotal = async (input, ctx) => {
8
+ const params = {
9
+ userId: input.userId,
10
+ brand: ctx.brand,
11
+ familyId: ctx.familyId,
12
+ days: input.days,
13
+ };
14
+ const steps = await ctx.analytics.execute(`SELECT date_trunc('day', ts)::VARCHAR AS day, SUM(steps)::INTEGER AS steps
15
+ FROM activity
16
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
17
+ AND ts >= now() - INTERVAL ($days) DAY
18
+ GROUP BY 1 ORDER BY 1`, params);
19
+ const buckets = await ctx.analytics.execute(`SELECT date_trunc('day', ts)::VARCHAR AS day,
20
+ SUM(calories)::DOUBLE AS calories,
21
+ SUM(distance_km)::DOUBLE AS distance_km
22
+ FROM activity_bucket
23
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
24
+ AND ts >= now() - INTERVAL ($days) DAY
25
+ GROUP BY 1 ORDER BY 1`, params);
26
+ if (steps.length === 0 && buckets.length === 0) {
27
+ return finalize('No activity data for the requested window.', 0);
28
+ }
29
+ const bucketByDay = new Map(buckets.map(b => [b.day, b]));
30
+ const days = new Set([
31
+ ...steps.map(s => s.day),
32
+ ...buckets.map(b => b.day),
33
+ ]);
34
+ const orderedDays = Array.from(days).sort();
35
+ let totalSteps = 0;
36
+ let totalCalories = 0;
37
+ let totalDistance = 0;
38
+ const perDay = [];
39
+ for (const day of orderedDays) {
40
+ const s = steps.find(r => r.day === day)?.steps ?? 0;
41
+ const b = bucketByDay.get(day);
42
+ totalSteps += s;
43
+ totalCalories += b?.calories ?? 0;
44
+ totalDistance += b?.distance_km ?? 0;
45
+ const parts = [`${s} steps`];
46
+ if (b?.calories != null)
47
+ parts.push(`${b.calories.toFixed(0)} kcal`);
48
+ if (b?.distance_km != null)
49
+ parts.push(`${b.distance_km.toFixed(2)} km`);
50
+ perDay.push(` ${day}: ${parts.join(', ')}`);
51
+ }
52
+ const text = `Activity, last ${input.days} days:\n${perDay.join('\n')}\n`
53
+ + ` Totals: ${totalSteps} steps, `
54
+ + `${totalCalories.toFixed(0)} kcal, `
55
+ + `${totalDistance.toFixed(2)} km`;
56
+ return finalize(text, orderedDays.length);
57
+ };
58
+ function finalize(text, rowCount) {
59
+ return { text, rowCount, bytes: formatBytes(text) };
60
+ }
61
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.js","sourceRoot":"","sources":["../../../src/tools/impls/activity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAG3C,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;CACtC,CAAC,CAAA;AAiBF,MAAM,CAAC,MAAM,gBAAgB,GAAoC,KAAK,EACpE,KAAK,EACL,GAAG,EACH,EAAE;IACF,MAAM,MAAM,GAAG;QACb,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAA;IAED,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,CACvC;;;;2BAIuB,EACvB,MAAM,CACP,CAAA;IAED,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,CACzC;;;;;;2BAMuB,EACvB,MAAM,CACP,CAAA;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,QAAQ,CAAC,4CAA4C,EAAE,CAAC,CAAC,CAAA;IAClE,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACzD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS;QAC3B,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACxB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;KAC3B,CAAC,CAAA;IACF,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;IAE3C,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,CAAA;QACpD,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC9B,UAAU,IAAI,CAAC,CAAA;QACf,aAAa,IAAI,CAAC,EAAE,QAAQ,IAAI,CAAC,CAAA;QACjC,aAAa,IAAI,CAAC,EAAE,WAAW,IAAI,CAAC,CAAA;QACpC,MAAM,KAAK,GAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACtC,IAAI,CAAC,EAAE,QAAQ,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACpE,IAAI,CAAC,EAAE,WAAW,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QACxE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC9C,CAAC;IAED,MAAM,IAAI,GACN,kBAAkB,KAAK,CAAC,IAAI,WAAW,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;UAC5D,aAAa,UAAU,UAAU;UACjC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;UACpC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;IAEpC,OAAO,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;AAC3C,CAAC,CAAA;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAA;AACrD,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ import type { ToolImpl } from '../types';
3
+ export declare const detectAnomalyInputSchema: z.ZodObject<{
4
+ userId: z.ZodString;
5
+ metric: z.ZodEnum<["hrv_ms", "sleep_total_minutes", "activity_steps"]>;
6
+ lookbackDays: z.ZodNumber;
7
+ stddevThreshold: z.ZodDefault<z.ZodNumber>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
10
+ userId: string;
11
+ lookbackDays: number;
12
+ stddevThreshold: number;
13
+ }, {
14
+ metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
15
+ userId: string;
16
+ lookbackDays: number;
17
+ stddevThreshold?: number | undefined;
18
+ }>;
19
+ export type DetectAnomalyInput = z.infer<typeof detectAnomalyInputSchema>;
20
+ export declare const detectAnomaly: ToolImpl<DetectAnomalyInput>;
21
+ //# sourceMappingURL=anomaly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anomaly.d.ts","sourceRoot":"","sources":["../../../src/tools/impls/anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;EAKnC,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AA+CzE,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,kBAAkB,CA4CtD,CAAA"}
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes, popStddev } from '../formatters';
3
+ export const detectAnomalyInputSchema = z.object({
4
+ userId: z.string(),
5
+ metric: z.enum(['hrv_ms', 'sleep_total_minutes', 'activity_steps']),
6
+ lookbackDays: z.number().int().min(7).max(90),
7
+ stddevThreshold: z.number().min(1).max(4).default(2),
8
+ });
9
+ const METRIC_SPECS = {
10
+ hrv_ms: {
11
+ sql: lookbackDays => `SELECT date_trunc('day', ts)::VARCHAR AS day, AVG(hrv_ms)::DOUBLE AS value
12
+ FROM hrv
13
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
14
+ AND hrv_ms IS NOT NULL
15
+ AND ts >= now() - INTERVAL (${lookbackDays}) DAY
16
+ GROUP BY 1 ORDER BY 1`,
17
+ unit: 'ms',
18
+ label: 'HRV',
19
+ },
20
+ sleep_total_minutes: {
21
+ sql: lookbackDays => `SELECT night_of::VARCHAR AS day, SUM(total_minutes)::DOUBLE AS value
22
+ FROM sleep_session
23
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
24
+ AND ts_start >= now() - INTERVAL (${lookbackDays}) DAY
25
+ GROUP BY night_of ORDER BY night_of`,
26
+ unit: 'min',
27
+ label: 'sleep total',
28
+ },
29
+ activity_steps: {
30
+ sql: lookbackDays => `SELECT date_trunc('day', ts)::VARCHAR AS day, SUM(steps)::DOUBLE AS value
31
+ FROM activity
32
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
33
+ AND ts >= now() - INTERVAL (${lookbackDays}) DAY
34
+ GROUP BY 1 ORDER BY 1`,
35
+ unit: 'steps',
36
+ label: 'activity',
37
+ },
38
+ };
39
+ export const detectAnomaly = async (input, ctx) => {
40
+ const spec = METRIC_SPECS[input.metric];
41
+ const rows = await ctx.analytics.execute(spec.sql(input.lookbackDays), {
42
+ userId: input.userId,
43
+ brand: ctx.brand,
44
+ familyId: ctx.familyId,
45
+ });
46
+ if (rows.length === 0) {
47
+ return finalize(`No ${spec.label} data for the last ${input.lookbackDays} days.`, 0);
48
+ }
49
+ const values = rows.map(r => r.value);
50
+ const mean = values.reduce((a, v) => a + v, 0) / values.length;
51
+ const stddev = popStddev(values);
52
+ const gate = input.stddevThreshold * stddev;
53
+ const outliers = rows.filter(r => Math.abs(r.value - mean) > gate);
54
+ const outlierText = outliers.length === 0
55
+ ? ' No outliers detected.'
56
+ : outliers
57
+ .map(r => ` ${r.day}: ${r.value.toFixed(1)}${spec.unit} `
58
+ + `(${(r.value - mean).toFixed(1)}${spec.unit} from mean)`)
59
+ .join('\n');
60
+ const text = `${spec.label} anomalies (last ${input.lookbackDays}d, `
61
+ + `threshold ${input.stddevThreshold}σ):\n`
62
+ + ` baseline: mean ${mean.toFixed(1)}${spec.unit}, `
63
+ + `stddev ${stddev.toFixed(1)}${spec.unit}\n`
64
+ + `${outlierText}`;
65
+ return finalize(text, outliers.length);
66
+ };
67
+ function finalize(text, rowCount) {
68
+ return { text, rowCount, bytes: formatBytes(text) };
69
+ }
70
+ //# sourceMappingURL=anomaly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anomaly.js","sourceRoot":"","sources":["../../../src/tools/impls/anomaly.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ import type { ToolImpl } from '../types';
3
+ export declare const compareTrendInputSchema: z.ZodObject<{
4
+ userId: z.ZodString;
5
+ metric: z.ZodEnum<["hrv_ms", "sleep_total_minutes", "activity_steps"]>;
6
+ currentWindowDays: z.ZodNumber;
7
+ priorWindowDays: z.ZodNumber;
8
+ }, "strip", z.ZodTypeAny, {
9
+ metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
10
+ userId: string;
11
+ currentWindowDays: number;
12
+ priorWindowDays: number;
13
+ }, {
14
+ metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
15
+ userId: string;
16
+ currentWindowDays: number;
17
+ priorWindowDays: number;
18
+ }>;
19
+ export type CompareTrendInput = z.infer<typeof compareTrendInputSchema>;
20
+ export declare const compareTrend: ToolImpl<CompareTrendInput>;
21
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../src/tools/impls/compare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;EAKlC,CAAA;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAiDvE,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,iBAAiB,CAuCpD,CAAA"}
@@ -0,0 +1,68 @@
1
+ import { z } from 'zod';
2
+ import { deltaPct, formatBytes } from '../formatters';
3
+ export const compareTrendInputSchema = z.object({
4
+ userId: z.string(),
5
+ metric: z.enum(['hrv_ms', 'sleep_total_minutes', 'activity_steps']),
6
+ currentWindowDays: z.number().int().min(1).max(30),
7
+ priorWindowDays: z.number().int().min(1).max(30),
8
+ });
9
+ const METRIC_SPECS = {
10
+ hrv_ms: {
11
+ sql: (windowDays, offsetDays) => `SELECT AVG(hrv_ms)::DOUBLE AS value
12
+ FROM hrv
13
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
14
+ AND hrv_ms IS NOT NULL
15
+ AND ts >= now() - INTERVAL (${windowDays + offsetDays}) DAY
16
+ AND ts < now() - INTERVAL (${offsetDays}) DAY`,
17
+ unit: 'ms',
18
+ label: 'HRV',
19
+ },
20
+ sleep_total_minutes: {
21
+ sql: (windowDays, offsetDays) => `SELECT AVG(total_minutes)::DOUBLE AS value
22
+ FROM sleep_session
23
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
24
+ AND ts_start >= now() - INTERVAL (${windowDays + offsetDays}) DAY
25
+ AND ts_start < now() - INTERVAL (${offsetDays}) DAY`,
26
+ unit: 'min',
27
+ label: 'sleep total',
28
+ },
29
+ activity_steps: {
30
+ sql: (windowDays, offsetDays) => `SELECT AVG(daily_steps)::DOUBLE AS value FROM (
31
+ SELECT date_trunc('day', ts) AS day, SUM(steps) AS daily_steps
32
+ FROM activity
33
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
34
+ AND ts >= now() - INTERVAL (${windowDays + offsetDays}) DAY
35
+ AND ts < now() - INTERVAL (${offsetDays}) DAY
36
+ GROUP BY 1
37
+ )`,
38
+ unit: 'steps/day',
39
+ label: 'activity',
40
+ },
41
+ };
42
+ export const compareTrend = async (input, ctx) => {
43
+ const spec = METRIC_SPECS[input.metric];
44
+ const params = {
45
+ userId: input.userId,
46
+ brand: ctx.brand,
47
+ familyId: ctx.familyId,
48
+ };
49
+ const currentRows = await ctx.analytics.execute(spec.sql(input.currentWindowDays, 0), params);
50
+ const priorRows = await ctx.analytics.execute(spec.sql(input.priorWindowDays, input.currentWindowDays), params);
51
+ const current = currentRows[0]?.value ?? null;
52
+ const prior = priorRows[0]?.value ?? null;
53
+ if (current == null && prior == null) {
54
+ return finalize(`Insufficient ${spec.label} data to compare the requested windows.`, 0);
55
+ }
56
+ const text = `${spec.label} trend (${input.metric}):\n`
57
+ + ` current ${input.currentWindowDays}d: ${fmt(current)}${spec.unit}\n`
58
+ + ` prior ${input.priorWindowDays}d: ${fmt(prior)}${spec.unit}\n`
59
+ + ` delta: ${current != null && prior != null ? deltaPct(current, prior) : 'n/a'}`;
60
+ return finalize(text, (current == null ? 0 : 1) + (prior == null ? 0 : 1));
61
+ };
62
+ function fmt(v) {
63
+ return v == null ? 'n/a' : v.toFixed(1);
64
+ }
65
+ function finalize(text, rowCount) {
66
+ return { text, rowCount, bytes: formatBytes(text) };
67
+ }
68
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/tools/impls/compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { ToolImpl } from '../types';
3
+ export declare const getHRVInputSchema: z.ZodObject<{
4
+ userId: z.ZodString;
5
+ days: z.ZodNumber;
6
+ }, "strip", z.ZodTypeAny, {
7
+ days: number;
8
+ userId: string;
9
+ }, {
10
+ days: number;
11
+ userId: string;
12
+ }>;
13
+ export type GetHRVInput = z.infer<typeof getHRVInputSchema>;
14
+ export declare const getHRV: ToolImpl<GetHRVInput>;
15
+ //# sourceMappingURL=hrv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hrv.d.ts","sourceRoot":"","sources":["../../../src/tools/impls/hrv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,iBAAiB;;;;;;;;;EAG5B,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAO3D,eAAO,MAAM,MAAM,EAAE,QAAQ,CAAC,WAAW,CAiCxC,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { deltaPct, formatBytes } from '../formatters';
3
+ export const getHRVInputSchema = z.object({
4
+ userId: z.string(),
5
+ days: z.number().int().min(1).max(90),
6
+ });
7
+ export const getHRV = async (input, ctx) => {
8
+ const rows = await ctx.analytics.execute(`SELECT date_trunc('day', ts)::VARCHAR AS day, AVG(hrv_ms)::DOUBLE AS avg_hrv
9
+ FROM hrv
10
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
11
+ AND ts >= now() - INTERVAL ($days) DAY
12
+ AND hrv_ms IS NOT NULL
13
+ GROUP BY 1 ORDER BY 1`, {
14
+ userId: input.userId,
15
+ brand: ctx.brand,
16
+ familyId: ctx.familyId,
17
+ days: input.days,
18
+ });
19
+ if (rows.length === 0) {
20
+ return finalize('No HRV data for the requested window.', 0);
21
+ }
22
+ const values = rows.map(r => r.avg_hrv);
23
+ const baseline = values.reduce((a, v) => a + v, 0) / values.length;
24
+ const latest = values[values.length - 1];
25
+ const daily = rows
26
+ .map(r => ` ${r.day}: ${r.avg_hrv.toFixed(1)}ms`)
27
+ .join('\n');
28
+ const text = `HRV, last ${input.days} days:\n${daily}\n`
29
+ + ` ${input.days}-day avg: ${baseline.toFixed(1)}ms `
30
+ + `(latest ${latest.toFixed(1)}ms, ${deltaPct(latest, baseline)} vs avg)`;
31
+ return finalize(text, rows.length);
32
+ };
33
+ function finalize(text, rowCount) {
34
+ return { text, rowCount, bytes: formatBytes(text) };
35
+ }
36
+ //# sourceMappingURL=hrv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hrv.js","sourceRoot":"","sources":["../../../src/tools/impls/hrv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAGrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;CACtC,CAAC,CAAA;AASF,MAAM,CAAC,MAAM,MAAM,GAA0B,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAChE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,CACtC;;;;;2BAKuB,EACvB;QACE,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CACF,CAAA;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,QAAQ,CAAC,uCAAuC,EAAE,CAAC,CAAC,CAAA;IAC7D,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;IAClE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACxC,MAAM,KAAK,GAAG,IAAI;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;SACjD,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,IAAI,GACN,aAAa,KAAK,CAAC,IAAI,WAAW,KAAK,IAAI;UAC3C,KAAK,KAAK,CAAC,IAAI,aAAa,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;UACpD,WAAW,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAA;IAE3E,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC,CAAA;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAA;AACrD,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import type { ToolImpl } from '../types';
3
+ export declare const getInsightsInputSchema: z.ZodObject<{
4
+ userId: z.ZodString;
5
+ days: z.ZodDefault<z.ZodNumber>;
6
+ severity: z.ZodOptional<z.ZodEnum<["info", "warn", "critical"]>>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ days: number;
9
+ userId: string;
10
+ severity?: "info" | "warn" | "critical" | undefined;
11
+ }, {
12
+ userId: string;
13
+ days?: number | undefined;
14
+ severity?: "info" | "warn" | "critical" | undefined;
15
+ }>;
16
+ export type GetInsightsInput = z.infer<typeof getInsightsInputSchema>;
17
+ export declare const getInsights: ToolImpl<GetInsightsInput>;
18
+ //# sourceMappingURL=insights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.d.ts","sourceRoot":"","sources":["../../../src/tools/impls/insights.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;EAIjC,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAUrE,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,gBAAgB,CAuClD,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes } from '../formatters';
3
+ export const getInsightsInputSchema = z.object({
4
+ userId: z.string(),
5
+ days: z.number().int().min(1).max(30).default(7),
6
+ severity: z.enum(['info', 'warn', 'critical']).optional(),
7
+ });
8
+ export const getInsights = async (input, ctx) => {
9
+ const params = {
10
+ userId: input.userId,
11
+ brand: ctx.brand,
12
+ familyId: ctx.familyId,
13
+ days: input.days,
14
+ };
15
+ let severityClause = '';
16
+ if (input.severity) {
17
+ severityClause = ' AND severity = $severity';
18
+ params.severity = input.severity;
19
+ }
20
+ const rows = await ctx.analytics.execute(`SELECT id, ts::VARCHAR AS ts, severity, title, body
21
+ FROM insight
22
+ WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
23
+ AND ts >= now() - INTERVAL ($days) DAY${severityClause}
24
+ ORDER BY ts DESC`, params);
25
+ if (rows.length === 0) {
26
+ const scope = input.severity ? ` (${input.severity})` : '';
27
+ return finalize(`No insights${scope} in the last ${input.days} days.`, 0);
28
+ }
29
+ const bullets = rows
30
+ .map((r) => {
31
+ const body = r.body ? ` — ${r.body}` : '';
32
+ return ` [${r.severity}] ${r.ts}: ${r.title}${body}`;
33
+ })
34
+ .join('\n');
35
+ const text = `Insights, last ${input.days} days:\n${bullets}`;
36
+ return finalize(text, rows.length);
37
+ };
38
+ function finalize(text, rowCount) {
39
+ return { text, rowCount, bytes: formatBytes(text) };
40
+ }
41
+ //# sourceMappingURL=insights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.js","sourceRoot":"","sources":["../../../src/tools/impls/insights.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAG3C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC1D,CAAC,CAAA;AAYF,MAAM,CAAC,MAAM,WAAW,GAA+B,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1E,MAAM,MAAM,GAA4B;QACtC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAA;IACD,IAAI,cAAc,GAAG,EAAE,CAAA;IACvB,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,cAAc,GAAG,2BAA2B,CAAA;QAC5C,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;IAClC,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,CACtC;;;+CAG2C,cAAc;sBACvC,EAClB,MAAM,CACP,CAAA;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;QAC1D,OAAO,QAAQ,CACb,cAAc,KAAK,gBAAgB,KAAK,CAAC,IAAI,QAAQ,EACrD,CAAC,CACF,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI;SACjB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QACzC,OAAO,MAAM,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,GAAG,IAAI,EAAE,CAAA;IACvD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,IAAI,GAAG,kBAAkB,KAAK,CAAC,IAAI,WAAW,OAAO,EAAE,CAAA;IAC7D,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC,CAAA;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAA;AACrD,CAAC"}