@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.
- package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
- package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
- package/.claude/agents/agent-kenji-data-reader.md +5 -3
- package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
- package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
- package/CLAUDE.md +6 -2
- package/REFACTOR_STATUS.md +127 -0
- package/dist/cli.js +0 -0
- package/dist/client/agents/base.d.ts +202 -0
- package/dist/client/agents/base.js +737 -0
- package/dist/client/agents/definitions.d.ts +53 -0
- package/dist/client/agents/definitions.js +178 -0
- package/dist/client/agents/orchestrator.d.ts +119 -0
- package/dist/client/agents/orchestrator.js +760 -0
- package/dist/client/agents/specialist.d.ts +86 -0
- package/dist/client/agents/specialist.js +340 -0
- package/dist/client/bot-manager.d.ts +44 -0
- package/dist/client/bot-manager.js +173 -0
- package/dist/client/chat-agent-daemon.d.ts +464 -0
- package/dist/client/chat-agent-daemon.js +1774 -0
- package/dist/client/daemon-factory.d.ts +106 -0
- package/dist/client/daemon-factory.js +301 -0
- package/dist/client/factory.d.ts +107 -0
- package/dist/client/factory.js +304 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +38 -0
- package/dist/client/multi-bot-manager.d.ts +18 -0
- package/dist/client/multi-bot-manager.js +88 -1
- package/dist/client/orchestrator-daemon.d.ts +87 -0
- package/dist/client/orchestrator-daemon.js +444 -0
- package/dist/client/services/agent-registry.d.ts +108 -0
- package/dist/client/services/agent-registry.js +630 -0
- package/dist/client/services/conversation-manager.d.ts +50 -0
- package/dist/client/services/conversation-manager.js +136 -0
- package/dist/client/services/mcp-client.d.ts +48 -0
- package/dist/client/services/mcp-client.js +105 -0
- package/dist/client/services/message-classifier.d.ts +37 -0
- package/dist/client/services/message-classifier.js +187 -0
- package/dist/client/services/message-formatter.d.ts +84 -0
- package/dist/client/services/message-formatter.js +353 -0
- package/dist/client/services/session-logger.d.ts +106 -0
- package/dist/client/services/session-logger.js +446 -0
- package/dist/client/services/tool-executor.d.ts +41 -0
- package/dist/client/services/tool-executor.js +169 -0
- package/dist/client/services/workspace-schema-cache.d.ts +149 -0
- package/dist/client/services/workspace-schema-cache.js +732 -0
- package/dist/client/specialist-daemon.d.ts +77 -0
- package/dist/client/specialist-daemon.js +197 -0
- package/dist/client/specialists.d.ts +53 -0
- package/dist/client/specialists.js +178 -0
- package/dist/client/tool-schema-loader.d.ts +4 -3
- package/dist/client/tool-schema-loader.js +54 -8
- package/dist/client/types.d.ts +283 -55
- package/dist/client/types.js +113 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/core.d.ts +10 -2
- package/dist/core.js +43 -27
- package/dist/lib/logger.js +15 -3
- package/dist/mcp/UserContextCache.js +2 -2
- package/dist/mcp/hailer-clients.js +5 -5
- package/dist/mcp/signal-handler.js +27 -5
- package/dist/mcp/tools/activity.js +137 -65
- package/dist/mcp/tools/app-core.js +4 -140
- package/dist/mcp/tools/app-marketplace.js +15 -260
- package/dist/mcp/tools/app-member.js +2 -73
- package/dist/mcp/tools/app-scaffold.js +146 -87
- package/dist/mcp/tools/discussion.js +348 -73
- package/dist/mcp/tools/insight.js +74 -190
- package/dist/mcp/tools/workflow.js +20 -94
- package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
- package/dist/mcp/utils/hailer-api-client.js +24 -10
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +24 -4
- package/dist/routes/agents.d.ts +44 -0
- package/dist/routes/agents.js +311 -0
- package/dist/services/agent-credential-store.d.ts +73 -0
- package/dist/services/agent-credential-store.js +212 -0
- package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
- package/lineup-manager/dist/assets/index-e168f265.js +600 -0
- package/lineup-manager/dist/index.html +15 -0
- package/lineup-manager/dist/manifest.json +17 -0
- package/lineup-manager/dist/vite.svg +1 -0
- package/package.json +1 -1
- package/dist/client/adaptive-documentation-bot.d.ts +0 -106
- package/dist/client/adaptive-documentation-bot.js +0 -464
- package/dist/client/adaptive-documentation-types.d.ts +0 -66
- package/dist/client/adaptive-documentation-types.js +0 -9
- package/dist/client/agent-activity-bot.d.ts +0 -51
- package/dist/client/agent-activity-bot.js +0 -166
- package/dist/client/agent-tracker.d.ts +0 -499
- package/dist/client/agent-tracker.js +0 -659
- package/dist/client/description-updater.d.ts +0 -56
- package/dist/client/description-updater.js +0 -259
- package/dist/client/log-parser.d.ts +0 -72
- package/dist/client/log-parser.js +0 -387
- package/dist/client/mcp-assistant.d.ts +0 -21
- package/dist/client/mcp-assistant.js +0 -58
- package/dist/client/mcp-client.d.ts +0 -50
- package/dist/client/mcp-client.js +0 -538
- package/dist/client/message-processor.d.ts +0 -35
- package/dist/client/message-processor.js +0 -357
- package/dist/client/providers/anthropic-provider.d.ts +0 -19
- package/dist/client/providers/anthropic-provider.js +0 -645
- package/dist/client/providers/assistant-provider.d.ts +0 -17
- package/dist/client/providers/assistant-provider.js +0 -51
- package/dist/client/providers/llm-provider.d.ts +0 -47
- package/dist/client/providers/llm-provider.js +0 -367
- package/dist/client/providers/openai-provider.d.ts +0 -23
- package/dist/client/providers/openai-provider.js +0 -630
- package/dist/client/simple-llm-caller.d.ts +0 -19
- package/dist/client/simple-llm-caller.js +0 -100
- package/dist/client/skill-generator.d.ts +0 -81
- package/dist/client/skill-generator.js +0 -386
- package/dist/client/test-adaptive-bot.d.ts +0 -9
- package/dist/client/test-adaptive-bot.js +0 -82
- package/dist/client/token-pricing.d.ts +0 -38
- package/dist/client/token-pricing.js +0 -127
- package/dist/client/token-tracker.d.ts +0 -232
- package/dist/client/token-tracker.js +0 -457
- package/dist/client/token-usage-bot.d.ts +0 -53
- package/dist/client/token-usage-bot.js +0 -153
- package/dist/client/tool-executor.d.ts +0 -69
- package/dist/client/tool-executor.js +0 -159
- package/dist/lib/materialize.d.ts +0 -3
- package/dist/lib/materialize.js +0 -101
- package/dist/lib/normalizedName.d.ts +0 -7
- package/dist/lib/normalizedName.js +0 -48
- package/dist/lib/terminal-prompt.d.ts +0 -9
- package/dist/lib/terminal-prompt.js +0 -108
- package/dist/mcp/tools/skill.d.ts +0 -10
- package/dist/mcp/tools/skill.js +0 -279
- package/dist/mcp/tools/workflow-template.d.ts +0 -19
- 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. **
|
|
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. **
|
|
32
|
-
2. **
|
|
33
|
-
3. **
|
|
34
|
-
4. **
|
|
35
|
-
5. **
|
|
36
|
-
6. **
|
|
37
|
-
7. **
|
|
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. **
|
|
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>
|