@hailer/mcp 0.1.8 → 0.1.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 (135) hide show
  1. package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
  2. package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
  3. package/.claude/agents/agent-kenji-data-reader.md +5 -3
  4. package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
  5. package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
  6. package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
  7. package/CLAUDE.md +6 -2
  8. package/REFACTOR_STATUS.md +127 -0
  9. package/dist/cli.js +0 -0
  10. package/dist/client/agents/base.d.ts +202 -0
  11. package/dist/client/agents/base.js +737 -0
  12. package/dist/client/agents/definitions.d.ts +53 -0
  13. package/dist/client/agents/definitions.js +178 -0
  14. package/dist/client/agents/orchestrator.d.ts +119 -0
  15. package/dist/client/agents/orchestrator.js +760 -0
  16. package/dist/client/agents/specialist.d.ts +86 -0
  17. package/dist/client/agents/specialist.js +340 -0
  18. package/dist/client/bot-manager.d.ts +44 -0
  19. package/dist/client/bot-manager.js +173 -0
  20. package/dist/client/chat-agent-daemon.d.ts +464 -0
  21. package/dist/client/chat-agent-daemon.js +1774 -0
  22. package/dist/client/daemon-factory.d.ts +106 -0
  23. package/dist/client/daemon-factory.js +301 -0
  24. package/dist/client/factory.d.ts +107 -0
  25. package/dist/client/factory.js +304 -0
  26. package/dist/client/index.d.ts +17 -0
  27. package/dist/client/index.js +38 -0
  28. package/dist/client/multi-bot-manager.d.ts +18 -0
  29. package/dist/client/multi-bot-manager.js +88 -1
  30. package/dist/client/orchestrator-daemon.d.ts +87 -0
  31. package/dist/client/orchestrator-daemon.js +444 -0
  32. package/dist/client/services/agent-registry.d.ts +108 -0
  33. package/dist/client/services/agent-registry.js +630 -0
  34. package/dist/client/services/conversation-manager.d.ts +50 -0
  35. package/dist/client/services/conversation-manager.js +136 -0
  36. package/dist/client/services/mcp-client.d.ts +48 -0
  37. package/dist/client/services/mcp-client.js +105 -0
  38. package/dist/client/services/message-classifier.d.ts +37 -0
  39. package/dist/client/services/message-classifier.js +187 -0
  40. package/dist/client/services/message-formatter.d.ts +84 -0
  41. package/dist/client/services/message-formatter.js +353 -0
  42. package/dist/client/services/session-logger.d.ts +106 -0
  43. package/dist/client/services/session-logger.js +446 -0
  44. package/dist/client/services/tool-executor.d.ts +41 -0
  45. package/dist/client/services/tool-executor.js +169 -0
  46. package/dist/client/services/workspace-schema-cache.d.ts +149 -0
  47. package/dist/client/services/workspace-schema-cache.js +732 -0
  48. package/dist/client/specialist-daemon.d.ts +77 -0
  49. package/dist/client/specialist-daemon.js +197 -0
  50. package/dist/client/specialists.d.ts +53 -0
  51. package/dist/client/specialists.js +178 -0
  52. package/dist/client/tool-schema-loader.d.ts +4 -3
  53. package/dist/client/tool-schema-loader.js +54 -8
  54. package/dist/client/types.d.ts +283 -55
  55. package/dist/client/types.js +113 -2
  56. package/dist/config.d.ts +1 -1
  57. package/dist/config.js +1 -1
  58. package/dist/core.d.ts +10 -2
  59. package/dist/core.js +43 -27
  60. package/dist/lib/logger.js +15 -3
  61. package/dist/mcp/UserContextCache.js +2 -2
  62. package/dist/mcp/hailer-clients.js +5 -5
  63. package/dist/mcp/signal-handler.js +27 -5
  64. package/dist/mcp/tools/activity.js +137 -65
  65. package/dist/mcp/tools/app-core.js +4 -140
  66. package/dist/mcp/tools/app-marketplace.js +15 -260
  67. package/dist/mcp/tools/app-member.js +2 -73
  68. package/dist/mcp/tools/app-scaffold.js +146 -87
  69. package/dist/mcp/tools/discussion.js +348 -73
  70. package/dist/mcp/tools/insight.js +74 -190
  71. package/dist/mcp/tools/workflow.js +20 -94
  72. package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
  73. package/dist/mcp/utils/hailer-api-client.js +24 -10
  74. package/dist/mcp-server.d.ts +4 -0
  75. package/dist/mcp-server.js +24 -4
  76. package/dist/routes/agents.d.ts +44 -0
  77. package/dist/routes/agents.js +311 -0
  78. package/dist/services/agent-credential-store.d.ts +73 -0
  79. package/dist/services/agent-credential-store.js +212 -0
  80. package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
  81. package/lineup-manager/dist/assets/index-e168f265.js +600 -0
  82. package/lineup-manager/dist/index.html +15 -0
  83. package/lineup-manager/dist/manifest.json +17 -0
  84. package/lineup-manager/dist/vite.svg +1 -0
  85. package/package.json +1 -1
  86. package/dist/client/adaptive-documentation-bot.d.ts +0 -106
  87. package/dist/client/adaptive-documentation-bot.js +0 -464
  88. package/dist/client/adaptive-documentation-types.d.ts +0 -66
  89. package/dist/client/adaptive-documentation-types.js +0 -9
  90. package/dist/client/agent-activity-bot.d.ts +0 -51
  91. package/dist/client/agent-activity-bot.js +0 -166
  92. package/dist/client/agent-tracker.d.ts +0 -499
  93. package/dist/client/agent-tracker.js +0 -659
  94. package/dist/client/description-updater.d.ts +0 -56
  95. package/dist/client/description-updater.js +0 -259
  96. package/dist/client/log-parser.d.ts +0 -72
  97. package/dist/client/log-parser.js +0 -387
  98. package/dist/client/mcp-assistant.d.ts +0 -21
  99. package/dist/client/mcp-assistant.js +0 -58
  100. package/dist/client/mcp-client.d.ts +0 -50
  101. package/dist/client/mcp-client.js +0 -538
  102. package/dist/client/message-processor.d.ts +0 -35
  103. package/dist/client/message-processor.js +0 -357
  104. package/dist/client/providers/anthropic-provider.d.ts +0 -19
  105. package/dist/client/providers/anthropic-provider.js +0 -645
  106. package/dist/client/providers/assistant-provider.d.ts +0 -17
  107. package/dist/client/providers/assistant-provider.js +0 -51
  108. package/dist/client/providers/llm-provider.d.ts +0 -47
  109. package/dist/client/providers/llm-provider.js +0 -367
  110. package/dist/client/providers/openai-provider.d.ts +0 -23
  111. package/dist/client/providers/openai-provider.js +0 -630
  112. package/dist/client/simple-llm-caller.d.ts +0 -19
  113. package/dist/client/simple-llm-caller.js +0 -100
  114. package/dist/client/skill-generator.d.ts +0 -81
  115. package/dist/client/skill-generator.js +0 -386
  116. package/dist/client/test-adaptive-bot.d.ts +0 -9
  117. package/dist/client/test-adaptive-bot.js +0 -82
  118. package/dist/client/token-pricing.d.ts +0 -38
  119. package/dist/client/token-pricing.js +0 -127
  120. package/dist/client/token-tracker.d.ts +0 -232
  121. package/dist/client/token-tracker.js +0 -457
  122. package/dist/client/token-usage-bot.d.ts +0 -53
  123. package/dist/client/token-usage-bot.js +0 -153
  124. package/dist/client/tool-executor.d.ts +0 -69
  125. package/dist/client/tool-executor.js +0 -159
  126. package/dist/lib/materialize.d.ts +0 -3
  127. package/dist/lib/materialize.js +0 -101
  128. package/dist/lib/normalizedName.d.ts +0 -7
  129. package/dist/lib/normalizedName.js +0 -48
  130. package/dist/lib/terminal-prompt.d.ts +0 -9
  131. package/dist/lib/terminal-prompt.js +0 -108
  132. package/dist/mcp/tools/skill.d.ts +0 -10
  133. package/dist/mcp/tools/skill.js +0 -279
  134. package/dist/mcp/tools/workflow-template.d.ts +0 -19
  135. package/dist/mcp/tools/workflow-template.js +0 -822
@@ -25,7 +25,9 @@ Load `hailer-api` for field type reference.
25
25
  2. **STRING for activitylink/dropdown** - Never arrays.
26
26
  3. **Timestamps for dates** - Unix ms, not strings.
27
27
  4. **Orchestrator provides IDs** - I don't fetch schema.
28
- 5. **JSON ONLY** - Output closing brace, then STOP. Zero prose after JSON.
28
+ 5. **BULK: `_id` not `activityId`** - In activities array, use `_id` key.
29
+ 6. **OMIT unused params** - Don't pass empty `[]` or `""`, just omit.
30
+ 7. **JSON ONLY** - Output closing brace, then STOP. Zero prose after JSON.
29
31
  </rules>
30
32
 
31
33
  <field-types>
@@ -14,27 +14,26 @@ Orchestrator MUST provide: Workflow ID(s), Phase ID(s), Field IDs + types.
14
14
  If missing: STOP and request.
15
15
  </pre-flight>
16
16
 
17
- <skills>
18
- Load `hailer-app-builder` for full templates and patterns.
19
- </skills>
20
-
21
17
  <execution>
18
+ **STEP 0 - MANDATORY:** Read `.claude/skills/hailer-app-builder/SKILL.md` FIRST. This contains critical patterns. Do NOT skip.
19
+
22
20
  1. Enable: node .claude/hooks/app-edit-guard.cjs --agent-on
23
21
  2. Scaffold: scaffold_hailer_app({ projectName, template: "react-ts-style" })
24
22
  3. Create: src/types/index.ts, src/utils/fields.ts, src/constants/fields.ts
25
- 4. Modify: src/App.tsx (NEVER main.tsx)
23
+ 4. Modify: src/App.tsx (NEVER main.tsx) - FOLLOW SKILL PATTERNS EXACTLY
26
24
  5. BUILD LOOP: npm run build → fix → repeat until pass
27
25
  6. Disable: node .claude/hooks/app-edit-guard.cjs --agent-off
28
26
  </execution>
29
27
 
30
28
  <rules>
31
- 1. **NEVER FABRICATE** - Must call tools.
32
- 2. **Import**: `import useHailer from './hailer/use-hailer'` (local, default!)
33
- 3. **useEffect dep**: `[inside]` NEVER `[hailer]` (infinite loop)
34
- 4. **Hooks at TOP**: Before any early returns.
35
- 5. **Fields optional**: `fields?: Record<string, { value: unknown }>`
36
- 6. **Theme**: `useColorModeValue('white', 'gray.700')` - no fake tokens.
37
- 7. **JSON ONLY** - Output closing brace, then STOP. Zero prose after JSON.
29
+ 1. **READ SKILL FIRST** - Use Read tool on `.claude/skills/hailer-app-builder/SKILL.md` before ANY code. No exceptions.
30
+ 2. **NEVER FABRICATE** - Must call tools.
31
+ 3. **Import**: `import useHailer from './hailer/use-hailer'` (local, default!)
32
+ 4. **useEffect dep**: `[inside]` ONLY. NEVER include `hailer` or `config` (causes infinite loops)
33
+ 5. **Hooks at TOP**: Before any early returns.
34
+ 6. **Fields optional**: `fields?: Record<string, { value: unknown }>`
35
+ 7. **Theme**: `useColorModeValue('white', 'gray.700')` - no fake tokens.
36
+ 8. **JSON ONLY** - Output closing brace, then STOP. Zero prose after JSON.
38
37
  </rules>
39
38
 
40
39
  <sdk-api>
@@ -2,7 +2,7 @@
2
2
  name: agent-kenji-data-reader
3
3
  description: LOCAL-FIRST data retrieval for SDK v0.8.4 - reads workspace/ before API. Knows about workflows, fields, phases, templates, functions, teams, groups, and insights.\n\n<example>\nuser: "What fields does Tasks have?"\nassistant: {"status":"success","result":{"fields":["taskName","project","dueDate"]},"source":"local","summary":"Read fields.ts"}\n</example>
4
4
  model: haiku
5
- tools: Read, Glob, mcp__hailer__list_workflows_minimal, mcp__hailer__count_activities, mcp__hailer__list_activities, mcp__hailer__list_workflow_phases
5
+ tools: Read, Glob, mcp__hailer__list_workflows_minimal, mcp__hailer__count_activities, mcp__hailer__list_activities, mcp__hailer__list_workflow_phases, mcp__hailer__get_workflow_schema
6
6
  ---
7
7
 
8
8
  <identity>
@@ -28,7 +28,8 @@ Load `json-only-output` to avoid prose after JSON responses.
28
28
  <rules>
29
29
  1. **NEVER FABRICATE** - Must call tools.
30
30
  2. **LOCAL FIRST** - Check workspace/ before API.
31
- 3. **JSON ONLY** - Output closing brace, then STOP. Zero prose after JSON.
31
+ 3. **VERIFY FIELD IDS** - If local files missing/stale, use get_workflow_schema to confirm field IDs before passing to other agents.
32
+ 4. **JSON ONLY** - Output closing brace, then STOP. Zero prose after JSON.
32
33
  </rules>
33
34
 
34
35
  <local-paths>
@@ -48,7 +49,7 @@ workspace/[Workflow]_[id]/main.test.ts → function field tests
48
49
  </local-paths>
49
50
 
50
51
  <decision-tree>
51
- Field schema? → Read workspace/[workflow]/fields.ts
52
+ Field schema? → Read workspace/[workflow]/fields.ts → IF MISSING: get_workflow_schema (API)
52
53
  Phase names? → Read workspace/[workflow]/phases.ts
53
54
  Workflow list? → Read workspace/workflows.ts
54
55
  Workflow settings? → Read workspace/[workflow]/main.ts
@@ -63,6 +64,7 @@ Workflow counts? → list_workflows_minimal (API)
63
64
  Phase IDs for API? → list_workflow_phases (API)
64
65
  Activity data? → list_activities (API)
65
66
  Activity counts? → count_activities (API)
67
+ Field IDs (no local)? → get_workflow_schema (API)
66
68
  </decision-tree>
67
69
 
68
70
  <protocol>
@@ -0,0 +1,506 @@
1
+ ---
2
+ name: hailer-app-builder
3
+ description: Patterns for building Hailer apps with @hailer/app-sdk
4
+ ---
5
+
6
+ # Hailer App Builder Skill
7
+
8
+ Patterns and templates for building Hailer apps with @hailer/app-sdk.
9
+
10
+ <sdk-setup>
11
+ ## Hook Import (CRITICAL)
12
+
13
+ ```typescript
14
+ // CORRECT - local default import
15
+ import useHailer from './hailer/use-hailer';
16
+
17
+ // WRONG - will fail build
18
+ import { useHailer } from '@hailer/app-sdk';
19
+ ```
20
+
21
+ ## Hook Usage
22
+
23
+ ```typescript
24
+ function App() {
25
+ const { inside, hailer } = useHailer();
26
+
27
+ // CORRECT dependency array
28
+ useEffect(() => {
29
+ // fetch data
30
+ }, [inside]); // Use [inside] NOT [hailer]
31
+
32
+ // Early return AFTER hooks
33
+ if (!inside) return <Text>Open this app inside Hailer</Text>;
34
+
35
+ return <Box>...</Box>;
36
+ }
37
+ ```
38
+ </sdk-setup>
39
+
40
+ <sdk-api>
41
+ ## Activity API
42
+
43
+ ```typescript
44
+ // List activities from workflow phase
45
+ const activities = await hailer.activity.list(workflowId, phaseId, {
46
+ limit: 100,
47
+ fields: ['fieldId1', 'fieldId2'], // Optional: specific fields only
48
+ });
49
+
50
+ // Get single activity
51
+ const activity = await hailer.activity.get(activityId);
52
+
53
+ // Activity structure
54
+ interface Activity {
55
+ _id: string;
56
+ name: string;
57
+ fields?: Record<string, { value: unknown }>;
58
+ created?: number;
59
+ updated?: number;
60
+ }
61
+ ```
62
+
63
+ ## Insight API
64
+
65
+ ```typescript
66
+ // Get insight data (SQL query results)
67
+ const data = await hailer.insight.get(insightId, { update: true });
68
+
69
+ // Response structure
70
+ interface InsightResponse {
71
+ columns: string[];
72
+ rows: any[][];
73
+ }
74
+ ```
75
+
76
+ ## Workflow API
77
+
78
+ ```typescript
79
+ // List all workflows
80
+ const workflows = await hailer.workflow.list();
81
+
82
+ // Get single workflow
83
+ const workflow = await hailer.workflow.get(workflowId);
84
+ ```
85
+ </sdk-api>
86
+
87
+ <field-patterns>
88
+ ## Extracting Field Values
89
+
90
+ ```typescript
91
+ // Fields are optional and nested
92
+ interface Activity {
93
+ fields?: Record<string, { value: unknown }>;
94
+ }
95
+
96
+ // Safe extraction helper
97
+ function getFieldValue<T>(activity: Activity, fieldId: string, defaultValue: T): T {
98
+ return (activity.fields?.[fieldId]?.value as T) ?? defaultValue;
99
+ }
100
+
101
+ // Usage
102
+ const name = getFieldValue(activity, 'fieldId123', '');
103
+ const count = getFieldValue(activity, 'fieldId456', 0);
104
+ const date = getFieldValue(activity, 'fieldId789', null);
105
+ ```
106
+
107
+ ## Field Types
108
+
109
+ ```typescript
110
+ // Text field
111
+ const text = activity.fields?.['fieldId']?.value as string;
112
+
113
+ // Number field
114
+ const num = activity.fields?.['fieldId']?.value as number;
115
+
116
+ // Date field (timestamp)
117
+ const date = activity.fields?.['fieldId']?.value as number;
118
+ const formatted = new Date(date).toLocaleDateString();
119
+
120
+ // Enum/Select field
121
+ const status = activity.fields?.['fieldId']?.value as string;
122
+
123
+ // ActivityLink field (reference to another activity)
124
+ interface ActivityLinkValue {
125
+ _id: string;
126
+ name: string;
127
+ }
128
+ const linked = activity.fields?.['fieldId']?.value as ActivityLinkValue;
129
+ const linkedName = linked?.name || 'Unknown';
130
+
131
+ // User field
132
+ interface UserValue {
133
+ _id: string;
134
+ firstname: string;
135
+ lastname: string;
136
+ }
137
+ const user = activity.fields?.['fieldId']?.value as UserValue;
138
+ const userName = user ? `${user.firstname} ${user.lastname}` : 'Unknown';
139
+ ```
140
+ </field-patterns>
141
+
142
+ <component-templates>
143
+ ## Activity Table
144
+
145
+ ```typescript
146
+ import { Table, Thead, Tbody, Tr, Th, Td, Box, Spinner, Text } from '@chakra-ui/react';
147
+
148
+ interface Activity {
149
+ _id: string;
150
+ name: string;
151
+ fields?: Record<string, { value: unknown }>;
152
+ }
153
+
154
+ interface Props {
155
+ activities: Activity[];
156
+ loading: boolean;
157
+ columns: { fieldId: string; label: string }[];
158
+ }
159
+
160
+ function ActivityTable({ activities, loading, columns }: Props) {
161
+ if (loading) return <Spinner />;
162
+ if (activities.length === 0) return <Text>No data</Text>;
163
+
164
+ return (
165
+ <Table variant="simple" size="sm">
166
+ <Thead>
167
+ <Tr>
168
+ <Th>Name</Th>
169
+ {columns.map(col => (
170
+ <Th key={col.fieldId}>{col.label}</Th>
171
+ ))}
172
+ </Tr>
173
+ </Thead>
174
+ <Tbody>
175
+ {activities.map(activity => (
176
+ <Tr key={activity._id}>
177
+ <Td>{activity.name}</Td>
178
+ {columns.map(col => (
179
+ <Td key={col.fieldId}>
180
+ {String(activity.fields?.[col.fieldId]?.value ?? '-')}
181
+ </Td>
182
+ ))}
183
+ </Tr>
184
+ ))}
185
+ </Tbody>
186
+ </Table>
187
+ );
188
+ }
189
+ ```
190
+
191
+ ## Activity Card
192
+
193
+ ```typescript
194
+ import { Box, Heading, Text, VStack, useColorModeValue } from '@chakra-ui/react';
195
+
196
+ interface Props {
197
+ activity: Activity;
198
+ fieldId: string;
199
+ fieldLabel: string;
200
+ }
201
+
202
+ function ActivityCard({ activity, fieldId, fieldLabel }: Props) {
203
+ const bg = useColorModeValue('white', 'gray.700');
204
+ const borderColor = useColorModeValue('gray.200', 'gray.600');
205
+
206
+ return (
207
+ <Box
208
+ p={4}
209
+ bg={bg}
210
+ borderRadius="md"
211
+ border="1px"
212
+ borderColor={borderColor}
213
+ >
214
+ <VStack align="start" spacing={2}>
215
+ <Heading size="sm">{activity.name}</Heading>
216
+ <Text fontSize="sm" color="gray.500">
217
+ {fieldLabel}: {String(activity.fields?.[fieldId]?.value ?? '-')}
218
+ </Text>
219
+ </VStack>
220
+ </Box>
221
+ );
222
+ }
223
+ ```
224
+
225
+ ## Stats Card
226
+
227
+ ```typescript
228
+ import { Stat, StatLabel, StatNumber, StatHelpText, Box, useColorModeValue } from '@chakra-ui/react';
229
+
230
+ interface Props {
231
+ label: string;
232
+ value: number | string;
233
+ helpText?: string;
234
+ }
235
+
236
+ function StatsCard({ label, value, helpText }: Props) {
237
+ const bg = useColorModeValue('white', 'gray.700');
238
+
239
+ return (
240
+ <Box p={4} bg={bg} borderRadius="md" shadow="sm">
241
+ <Stat>
242
+ <StatLabel>{label}</StatLabel>
243
+ <StatNumber>{value}</StatNumber>
244
+ {helpText && <StatHelpText>{helpText}</StatHelpText>}
245
+ </Stat>
246
+ </Box>
247
+ );
248
+ }
249
+ ```
250
+ </component-templates>
251
+
252
+ <app-template>
253
+ ## Full App Template
254
+
255
+ ```typescript
256
+ import { useEffect, useState } from 'react';
257
+ import {
258
+ Box,
259
+ Heading,
260
+ Text,
261
+ Spinner,
262
+ Table,
263
+ Thead,
264
+ Tbody,
265
+ Tr,
266
+ Th,
267
+ Td,
268
+ VStack,
269
+ useColorModeValue,
270
+ } from '@chakra-ui/react';
271
+ import useHailer from './hailer/use-hailer';
272
+
273
+ // Field IDs from workflow schema (provided by orchestrator)
274
+ const FIELDS = {
275
+ NAME_FIELD: 'fieldId123',
276
+ STATUS_FIELD: 'fieldId456',
277
+ } as const;
278
+
279
+ interface Activity {
280
+ _id: string;
281
+ name: string;
282
+ fields?: Record<string, { value: unknown }>;
283
+ }
284
+
285
+ function App() {
286
+ const { inside, hailer } = useHailer();
287
+ const [activities, setActivities] = useState<Activity[]>([]);
288
+ const [loading, setLoading] = useState(true);
289
+ const [error, setError] = useState<string | null>(null);
290
+
291
+ const bg = useColorModeValue('gray.50', 'gray.800');
292
+
293
+ // Fetch data when inside Hailer
294
+ useEffect(() => {
295
+ if (!inside) return;
296
+
297
+ async function fetchData() {
298
+ try {
299
+ setLoading(true);
300
+ const data = await hailer.activity.list(
301
+ 'workflowId', // Replace with actual workflow ID
302
+ 'phaseId', // Replace with actual phase ID
303
+ { limit: 100 }
304
+ );
305
+ setActivities(data);
306
+ } catch (err) {
307
+ setError(err instanceof Error ? err.message : 'Failed to load data');
308
+ } finally {
309
+ setLoading(false);
310
+ }
311
+ }
312
+
313
+ fetchData();
314
+ }, [inside]); // IMPORTANT: [inside] not [hailer]
315
+
316
+ // Early return AFTER hooks
317
+ if (!inside) {
318
+ return (
319
+ <Box p={8} textAlign="center">
320
+ <Text>Please open this app inside Hailer</Text>
321
+ </Box>
322
+ );
323
+ }
324
+
325
+ if (loading) {
326
+ return (
327
+ <Box p={8} textAlign="center">
328
+ <Spinner size="xl" />
329
+ </Box>
330
+ );
331
+ }
332
+
333
+ if (error) {
334
+ return (
335
+ <Box p={8} textAlign="center">
336
+ <Text color="red.500">{error}</Text>
337
+ </Box>
338
+ );
339
+ }
340
+
341
+ return (
342
+ <Box p={4} bg={bg} minH="100vh">
343
+ <VStack spacing={4} align="stretch">
344
+ <Heading size="lg">Dashboard</Heading>
345
+
346
+ <Table variant="simple" size="sm">
347
+ <Thead>
348
+ <Tr>
349
+ <Th>Name</Th>
350
+ <Th>Status</Th>
351
+ </Tr>
352
+ </Thead>
353
+ <Tbody>
354
+ {activities.map(activity => (
355
+ <Tr key={activity._id}>
356
+ <Td>{activity.name}</Td>
357
+ <Td>{String(activity.fields?.[FIELDS.STATUS_FIELD]?.value ?? '-')}</Td>
358
+ </Tr>
359
+ ))}
360
+ </Tbody>
361
+ </Table>
362
+ </VStack>
363
+ </Box>
364
+ );
365
+ }
366
+
367
+ export default App;
368
+ ```
369
+ </app-template>
370
+
371
+ <theme-patterns>
372
+ ## Hailer Theme Colors
373
+
374
+ ```typescript
375
+ // Dark mode support
376
+ const bg = useColorModeValue('white', 'gray.700');
377
+ const borderColor = useColorModeValue('gray.200', 'gray.600');
378
+ const textColor = useColorModeValue('gray.800', 'white');
379
+ const mutedColor = useColorModeValue('gray.500', 'gray.400');
380
+
381
+ // Valid color tokens (DO NOT invent tokens)
382
+ // gray.50, gray.100, ..., gray.900
383
+ // white, black
384
+ // red.500, green.500, blue.500, yellow.500, purple.500
385
+ ```
386
+
387
+ ## Common UI Patterns
388
+
389
+ ```typescript
390
+ // Page container
391
+ <Box p={4} bg={useColorModeValue('gray.50', 'gray.800')} minH="100vh">
392
+
393
+ // Card
394
+ <Box p={4} bg={useColorModeValue('white', 'gray.700')} borderRadius="md" shadow="sm">
395
+
396
+ // Section with border
397
+ <Box p={4} border="1px" borderColor={useColorModeValue('gray.200', 'gray.600')} borderRadius="md">
398
+ ```
399
+ </theme-patterns>
400
+
401
+ <build-fixes>
402
+ ## Common Build Errors
403
+
404
+ ### Cannot find module '@hailer/app-sdk'
405
+ ```typescript
406
+ // WRONG
407
+ import { useHailer } from '@hailer/app-sdk';
408
+
409
+ // CORRECT
410
+ import useHailer from './hailer/use-hailer';
411
+ ```
412
+
413
+ ### has no exported member 'useHailer'
414
+ ```typescript
415
+ // WRONG - named import
416
+ import { useHailer } from './hailer/use-hailer';
417
+
418
+ // CORRECT - default import
419
+ import useHailer from './hailer/use-hailer';
420
+ ```
421
+
422
+ ### fields possibly undefined
423
+ ```typescript
424
+ // WRONG
425
+ const value = activity.fields[fieldId].value;
426
+
427
+ // CORRECT
428
+ const value = activity.fields?.[fieldId]?.value;
429
+ ```
430
+
431
+ ### Infinite re-render loop
432
+ ```typescript
433
+ // WRONG - hailer changes every render
434
+ useEffect(() => { ... }, [hailer]);
435
+
436
+ // CORRECT - inside is stable
437
+ useEffect(() => { ... }, [inside]);
438
+ ```
439
+
440
+ ### Hooks order error
441
+ ```typescript
442
+ // WRONG - early return before hook
443
+ if (!inside) return <Text>Error</Text>;
444
+ const [data, setData] = useState([]); // Error!
445
+
446
+ // CORRECT - hooks first, then early return
447
+ const [data, setData] = useState([]);
448
+ if (!inside) return <Text>Error</Text>;
449
+ ```
450
+ </build-fixes>
451
+
452
+ <file-structure>
453
+ ## Required Files
454
+
455
+ ```
456
+ src/
457
+ App.tsx # Main component (EDIT THIS)
458
+ main.tsx # Entry point (NEVER EDIT)
459
+ hailer/
460
+ use-hailer.ts # SDK hook (generated)
461
+ types/
462
+ index.ts # Type definitions (CREATE)
463
+ utils/
464
+ fields.ts # Field helpers (CREATE)
465
+ constants/
466
+ fields.ts # Field ID constants (CREATE)
467
+ ```
468
+
469
+ ## Constants File Pattern
470
+
471
+ ```typescript
472
+ // src/constants/fields.ts
473
+ export const WORKFLOW_ID = 'workflowId123';
474
+ export const PHASE_ID = 'phaseId456';
475
+
476
+ export const FIELDS = {
477
+ NAME: 'fieldId001',
478
+ STATUS: 'fieldId002',
479
+ DATE: 'fieldId003',
480
+ } as const;
481
+ ```
482
+
483
+ ## Types File Pattern
484
+
485
+ ```typescript
486
+ // src/types/index.ts
487
+ export interface Activity {
488
+ _id: string;
489
+ name: string;
490
+ fields?: Record<string, { value: unknown }>;
491
+ created?: number;
492
+ updated?: number;
493
+ }
494
+
495
+ export interface ActivityLinkValue {
496
+ _id: string;
497
+ name: string;
498
+ }
499
+
500
+ export interface UserValue {
501
+ _id: string;
502
+ firstname: string;
503
+ lastname: string;
504
+ }
505
+ ```
506
+ </file-structure>