@affectively/aeon-pages 1.3.0

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 (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,545 @@
1
+ /**
2
+ * Aeon Flux Navigation Tools
3
+ *
4
+ * MCP tools for Cyrano to navigate and personalize the site.
5
+ * Supports auto-accept mode for seamless navigation suggestions.
6
+ */
7
+
8
+ import type { Tool, TextContent } from '@modelcontextprotocol/sdk/types.js';
9
+
10
+ // ============================================================================
11
+ // State (would be connected to real site state in production)
12
+ // ============================================================================
13
+
14
+ interface NavigationState {
15
+ currentRoute: string;
16
+ previousRoutes: string[];
17
+ pendingSuggestions: Array<{
18
+ route: string;
19
+ reason: string;
20
+ autoAccept: boolean;
21
+ timestamp: number;
22
+ }>;
23
+ autoAcceptEnabled: boolean;
24
+ sessionId: string;
25
+ }
26
+
27
+ let state: NavigationState = {
28
+ currentRoute: '/',
29
+ previousRoutes: [],
30
+ pendingSuggestions: [],
31
+ autoAcceptEnabled: false,
32
+ sessionId: '',
33
+ };
34
+
35
+ // Event emitter for navigation events (to be wired to site)
36
+ type NavigationEventHandler = (event: {
37
+ type: 'navigate' | 'suggest' | 'personalize' | 'invoke_tool';
38
+ data: unknown;
39
+ }) => void;
40
+
41
+ const eventHandlers: Set<NavigationEventHandler> = new Set();
42
+
43
+ export function onNavigationEvent(handler: NavigationEventHandler): () => void {
44
+ eventHandlers.add(handler);
45
+ return () => eventHandlers.delete(handler);
46
+ }
47
+
48
+ function emit(
49
+ type: NavigationEventHandler extends (e: infer E) => void ? E['type'] : never,
50
+ data: unknown,
51
+ ): void {
52
+ for (const handler of eventHandlers) {
53
+ handler({ type, data });
54
+ }
55
+ }
56
+
57
+ // ============================================================================
58
+ // Tool Definitions
59
+ // ============================================================================
60
+
61
+ export const navigateTool: Tool = {
62
+ name: 'navigate',
63
+ description: `Navigate to a route in the aeon-flux site. Use this to take the user directly to a page.
64
+
65
+ Examples:
66
+ - navigate({ route: '/breathing/4-7-8' }) - Go to breathing exercise
67
+ - navigate({ route: '/insights' }) - Go to insights dashboard
68
+ - navigate({ route: '/learning/emotions/joy' }) - Go to joy learning page`,
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ route: {
73
+ type: 'string',
74
+ description:
75
+ 'The route to navigate to (e.g., "/breathing/4-7-8", "/insights")',
76
+ },
77
+ autoAccept: {
78
+ type: 'boolean',
79
+ description:
80
+ 'Whether to navigate immediately without user confirmation (default: false)',
81
+ default: false,
82
+ },
83
+ },
84
+ required: ['route'],
85
+ },
86
+ };
87
+
88
+ export const suggestRouteTool: Tool = {
89
+ name: 'suggest_route',
90
+ description: `Suggest a route to the user. Shows a gentle suggestion with a reason.
91
+ The user can accept or dismiss the suggestion.
92
+
93
+ Use this when Cyrano wants to recommend a page based on context.
94
+
95
+ Examples:
96
+ - suggest_route({ route: '/breathing', reason: 'You seem stressed - this might help' })
97
+ - suggest_route({ route: '/insights/patterns', reason: 'Your weekly patterns are ready' })`,
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ route: {
102
+ type: 'string',
103
+ description: 'The route to suggest',
104
+ },
105
+ reason: {
106
+ type: 'string',
107
+ description: 'Why Cyrano is suggesting this route',
108
+ },
109
+ autoAccept: {
110
+ type: 'boolean',
111
+ description:
112
+ 'If true and user has auto-accept enabled, navigate immediately',
113
+ default: false,
114
+ },
115
+ },
116
+ required: ['route', 'reason'],
117
+ },
118
+ };
119
+
120
+ export const getCurrentRouteTool: Tool = {
121
+ name: 'get_current_route',
122
+ description: `Get the current route the user is on. Use this to understand context.`,
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {},
126
+ },
127
+ };
128
+
129
+ export const getSitemapTool: Tool = {
130
+ name: 'get_sitemap',
131
+ description: `Get the site structure. Useful for finding relevant pages.
132
+
133
+ Examples:
134
+ - get_sitemap() - Get full sitemap
135
+ - get_sitemap({ filter: 'breathing' }) - Get pages related to breathing
136
+ - get_sitemap({ filter: 'tools' }) - Get all tool pages`,
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ filter: {
141
+ type: 'string',
142
+ description: 'Optional filter to narrow down results',
143
+ },
144
+ },
145
+ },
146
+ };
147
+
148
+ export const speculateTool: Tool = {
149
+ name: 'speculate',
150
+ description: `Get likely next routes for prefetching. Helps optimize navigation.
151
+
152
+ Based on current context and user patterns, returns probable next destinations.`,
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ depth: {
157
+ type: 'number',
158
+ description: 'How many routes to speculate (default: 3)',
159
+ default: 3,
160
+ },
161
+ },
162
+ },
163
+ };
164
+
165
+ export const personalizeTool: Tool = {
166
+ name: 'personalize',
167
+ description: `Apply personalization to the current page based on user context.
168
+
169
+ Examples:
170
+ - personalize({ theme: 'dark' }) - Switch to dark theme
171
+ - personalize({ accent: '#4A90D9' }) - Set accent color (e.g., calming blue)
172
+ - personalize({ density: 'comfortable' }) - More spacing for stressed users`,
173
+ inputSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ theme: {
177
+ type: 'string',
178
+ enum: ['light', 'dark'],
179
+ description: 'Theme mode',
180
+ },
181
+ accent: {
182
+ type: 'string',
183
+ description: 'Accent color in hex format',
184
+ },
185
+ density: {
186
+ type: 'string',
187
+ enum: ['compact', 'normal', 'comfortable'],
188
+ description: 'Layout density',
189
+ },
190
+ },
191
+ },
192
+ };
193
+
194
+ export const invokeToolTool: Tool = {
195
+ name: 'invoke_tool',
196
+ description: `Invoke a specific tool in the application.
197
+
198
+ Cyrano can suggest and invoke tools based on user context.
199
+ 250+ tools available including breathing exercises, journaling, insights, etc.
200
+
201
+ Examples:
202
+ - invoke_tool({ toolId: 'breathing/4-7-8' }) - Start 4-7-8 breathing
203
+ - invoke_tool({ toolId: 'grounding/5-4-3-2-1' }) - Start grounding exercise
204
+ - invoke_tool({ toolId: 'journaling/freeform' }) - Open journaling
205
+ - invoke_tool({ toolId: 'insights/patterns' }) - Show patterns analysis`,
206
+ inputSchema: {
207
+ type: 'object',
208
+ properties: {
209
+ toolId: {
210
+ type: 'string',
211
+ description: 'The tool ID to invoke (e.g., "breathing/4-7-8")',
212
+ },
213
+ params: {
214
+ type: 'object',
215
+ description: 'Optional parameters to pass to the tool',
216
+ additionalProperties: true,
217
+ },
218
+ },
219
+ required: ['toolId'],
220
+ },
221
+ };
222
+
223
+ // ============================================================================
224
+ // Tool Handlers
225
+ // ============================================================================
226
+
227
+ export async function handleNavigate(args: {
228
+ route: string;
229
+ autoAccept?: boolean;
230
+ }): Promise<{ content: TextContent[] }> {
231
+ const { route, autoAccept = false } = args;
232
+
233
+ // Update state
234
+ state.previousRoutes.push(state.currentRoute);
235
+ state.currentRoute = route;
236
+
237
+ // Emit navigation event
238
+ emit('navigate', { route, autoAccept });
239
+
240
+ return {
241
+ content: [
242
+ {
243
+ type: 'text',
244
+ text: autoAccept
245
+ ? `Navigated to ${route}`
246
+ : `Navigation to ${route} initiated. Awaiting user confirmation.`,
247
+ },
248
+ ],
249
+ };
250
+ }
251
+
252
+ export async function handleSuggestRoute(args: {
253
+ route: string;
254
+ reason: string;
255
+ autoAccept?: boolean;
256
+ }): Promise<{ content: TextContent[] }> {
257
+ const { route, reason, autoAccept = false } = args;
258
+
259
+ // Add to pending suggestions
260
+ state.pendingSuggestions.push({
261
+ route,
262
+ reason,
263
+ autoAccept,
264
+ timestamp: Date.now(),
265
+ });
266
+
267
+ // Emit suggestion event
268
+ emit('suggest', { route, reason, autoAccept });
269
+
270
+ // If auto-accept is enabled and requested, navigate immediately
271
+ if (autoAccept && state.autoAcceptEnabled) {
272
+ state.previousRoutes.push(state.currentRoute);
273
+ state.currentRoute = route;
274
+
275
+ return {
276
+ content: [
277
+ {
278
+ type: 'text',
279
+ text: `Auto-navigated to ${route}: ${reason}`,
280
+ },
281
+ ],
282
+ };
283
+ }
284
+
285
+ return {
286
+ content: [
287
+ {
288
+ type: 'text',
289
+ text: `Suggested ${route}: ${reason}. Awaiting user response.`,
290
+ },
291
+ ],
292
+ };
293
+ }
294
+
295
+ export async function handleGetCurrentRoute(): Promise<{
296
+ content: TextContent[];
297
+ }> {
298
+ return {
299
+ content: [
300
+ {
301
+ type: 'text',
302
+ text: JSON.stringify(
303
+ {
304
+ currentRoute: state.currentRoute,
305
+ previousRoutes: state.previousRoutes.slice(-5),
306
+ pendingSuggestions: state.pendingSuggestions,
307
+ },
308
+ null,
309
+ 2,
310
+ ),
311
+ },
312
+ ],
313
+ };
314
+ }
315
+
316
+ export async function handleGetSitemap(args: {
317
+ filter?: string;
318
+ }): Promise<{ content: TextContent[] }> {
319
+ const { filter } = args;
320
+
321
+ // Simplified sitemap structure (would be loaded from sitemapRAG in production)
322
+ const sitemap = [
323
+ // Core pages
324
+ { route: '/', title: 'Home', category: 'core' },
325
+ { route: '/dashboard', title: 'Dashboard', category: 'core' },
326
+ { route: '/insights', title: 'Insights', category: 'core' },
327
+ { route: '/chat', title: 'Chat with Cyrano', category: 'core' },
328
+
329
+ // Tools - Breathing
330
+ { route: '/breathing', title: 'Breathing Exercises', category: 'tools' },
331
+ { route: '/breathing/4-7-8', title: '4-7-8 Breathing', category: 'tools' },
332
+ { route: '/breathing/box', title: 'Box Breathing', category: 'tools' },
333
+ {
334
+ route: '/breathing/coherent',
335
+ title: 'Coherent Breathing',
336
+ category: 'tools',
337
+ },
338
+
339
+ // Tools - Grounding
340
+ { route: '/grounding', title: 'Grounding Exercises', category: 'tools' },
341
+ {
342
+ route: '/grounding/5-4-3-2-1',
343
+ title: '5-4-3-2-1 Grounding',
344
+ category: 'tools',
345
+ },
346
+ { route: '/grounding/body-scan', title: 'Body Scan', category: 'tools' },
347
+
348
+ // Tools - Journaling
349
+ { route: '/journaling', title: 'Journaling', category: 'tools' },
350
+ {
351
+ route: '/journaling/freeform',
352
+ title: 'Freeform Journaling',
353
+ category: 'tools',
354
+ },
355
+ {
356
+ route: '/journaling/gratitude',
357
+ title: 'Gratitude Journal',
358
+ category: 'tools',
359
+ },
360
+
361
+ // Learning
362
+ { route: '/learning', title: 'Learning', category: 'learning' },
363
+ { route: '/learning/emotions', title: 'Emotions', category: 'learning' },
364
+ { route: '/learning/emotions/joy', title: 'Joy', category: 'learning' },
365
+ {
366
+ route: '/learning/emotions/sadness',
367
+ title: 'Sadness',
368
+ category: 'learning',
369
+ },
370
+ { route: '/learning/emotions/anger', title: 'Anger', category: 'learning' },
371
+ { route: '/learning/emotions/fear', title: 'Fear', category: 'learning' },
372
+
373
+ // Settings
374
+ { route: '/settings', title: 'Settings', category: 'settings' },
375
+ {
376
+ route: '/settings/privacy',
377
+ title: 'Privacy Settings',
378
+ category: 'settings',
379
+ },
380
+ {
381
+ route: '/settings/notifications',
382
+ title: 'Notification Settings',
383
+ category: 'settings',
384
+ },
385
+
386
+ // Account
387
+ { route: '/account', title: 'Account', category: 'account' },
388
+ { route: '/account/profile', title: 'Profile', category: 'account' },
389
+ ];
390
+
391
+ // Apply filter if provided
392
+ const filteredSitemap = filter
393
+ ? sitemap.filter(
394
+ (page) =>
395
+ page.route.includes(filter) ||
396
+ page.title.toLowerCase().includes(filter.toLowerCase()) ||
397
+ page.category.includes(filter),
398
+ )
399
+ : sitemap;
400
+
401
+ return {
402
+ content: [
403
+ {
404
+ type: 'text',
405
+ text: JSON.stringify(filteredSitemap, null, 2),
406
+ },
407
+ ],
408
+ };
409
+ }
410
+
411
+ export async function handleSpeculate(args: {
412
+ depth?: number;
413
+ }): Promise<{ content: TextContent[] }> {
414
+ const { depth = 3 } = args;
415
+
416
+ // Speculation based on current route (simplified - would use heuristic adapter in production)
417
+ const speculations: Record<string, string[]> = {
418
+ '/': ['/dashboard', '/breathing', '/insights'],
419
+ '/dashboard': ['/insights', '/breathing', '/chat'],
420
+ '/breathing': ['/breathing/4-7-8', '/breathing/box', '/grounding'],
421
+ '/breathing/4-7-8': ['/dashboard', '/breathing', '/insights'],
422
+ '/insights': ['/dashboard', '/learning', '/settings'],
423
+ '/learning': ['/learning/emotions', '/dashboard', '/insights'],
424
+ '/settings': ['/settings/privacy', '/account', '/dashboard'],
425
+ };
426
+
427
+ const likelyRoutes = speculations[state.currentRoute] || [
428
+ '/dashboard',
429
+ '/insights',
430
+ '/breathing',
431
+ ];
432
+
433
+ return {
434
+ content: [
435
+ {
436
+ type: 'text',
437
+ text: JSON.stringify(
438
+ {
439
+ currentRoute: state.currentRoute,
440
+ likelyNextRoutes: likelyRoutes.slice(0, depth),
441
+ confidence: 0.7,
442
+ },
443
+ null,
444
+ 2,
445
+ ),
446
+ },
447
+ ],
448
+ };
449
+ }
450
+
451
+ export async function handlePersonalize(args: {
452
+ theme?: 'light' | 'dark';
453
+ accent?: string;
454
+ density?: 'compact' | 'normal' | 'comfortable';
455
+ }): Promise<{ content: TextContent[] }> {
456
+ // Emit personalization event
457
+ emit('personalize', args);
458
+
459
+ const applied: string[] = [];
460
+ if (args.theme) applied.push(`theme: ${args.theme}`);
461
+ if (args.accent) applied.push(`accent: ${args.accent}`);
462
+ if (args.density) applied.push(`density: ${args.density}`);
463
+
464
+ return {
465
+ content: [
466
+ {
467
+ type: 'text',
468
+ text: `Applied personalization: ${applied.join(', ')}`,
469
+ },
470
+ ],
471
+ };
472
+ }
473
+
474
+ export async function handleInvokeTool(args: {
475
+ toolId: string;
476
+ params?: Record<string, unknown>;
477
+ }): Promise<{ content: TextContent[] }> {
478
+ const { toolId, params } = args;
479
+
480
+ // Emit tool invocation event
481
+ emit('invoke_tool', { toolId, params });
482
+
483
+ // Parse tool category and name
484
+ const [category, name] = toolId.split('/');
485
+
486
+ return {
487
+ content: [
488
+ {
489
+ type: 'text',
490
+ text: JSON.stringify(
491
+ {
492
+ status: 'invoked',
493
+ toolId,
494
+ category,
495
+ name,
496
+ params: params || {},
497
+ timestamp: Date.now(),
498
+ },
499
+ null,
500
+ 2,
501
+ ),
502
+ },
503
+ ],
504
+ };
505
+ }
506
+
507
+ // ============================================================================
508
+ // State Management
509
+ // ============================================================================
510
+
511
+ export function setNavigationState(newState: Partial<NavigationState>): void {
512
+ state = { ...state, ...newState };
513
+ }
514
+
515
+ export function getNavigationState(): NavigationState {
516
+ return { ...state };
517
+ }
518
+
519
+ export function enableAutoAccept(enabled: boolean): void {
520
+ state.autoAcceptEnabled = enabled;
521
+ }
522
+
523
+ export function clearPendingSuggestions(): void {
524
+ state.pendingSuggestions = [];
525
+ }
526
+
527
+ export function acceptSuggestion(route: string): boolean {
528
+ const index = state.pendingSuggestions.findIndex((s) => s.route === route);
529
+ if (index === -1) return false;
530
+
531
+ state.pendingSuggestions.splice(index, 1);
532
+ state.previousRoutes.push(state.currentRoute);
533
+ state.currentRoute = route;
534
+
535
+ emit('navigate', { route, fromSuggestion: true });
536
+ return true;
537
+ }
538
+
539
+ export function dismissSuggestion(route: string): boolean {
540
+ const index = state.pendingSuggestions.findIndex((s) => s.route === route);
541
+ if (index === -1) return false;
542
+
543
+ state.pendingSuggestions.splice(index, 1);
544
+ return true;
545
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist"]
21
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@affectively/aeon-pages-react",
3
+ "version": "0.2.0",
4
+ "description": "React bindings for @affectively/aeon-pages",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc --declaration --emitDeclarationOnly",
17
+ "dev": "bun --watch ./src/index.ts",
18
+ "test": "bun test",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "dependencies": {
26
+ "@affectively/aeon-pages-runtime": "^0.3.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=18.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5.7.0",
33
+ "@types/react": "^18.0.0"
34
+ },
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/affectively/aeon-pages"
39
+ }
40
+ }