@hailer/mcp 0.0.1
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/commands/tool-builder.md +37 -0
- package/.claude/commands/ws-pull.md +44 -0
- package/.claude/settings.json +8 -0
- package/.claude/settings.local.json +49 -0
- package/.claude/skills/activity-api/SKILL.md +96 -0
- package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
- package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
- package/.claude/skills/agent-building/SKILL.md +243 -0
- package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
- package/.claude/skills/agent-building/references/code-examples.md +587 -0
- package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
- package/.claude/skills/app-api/SKILL.md +219 -0
- package/.claude/skills/app-api/references/app-endpoints.md +759 -0
- package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
- package/.claude/skills/create-app-skill/SKILL.md +1101 -0
- package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
- package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
- package/.claude/skills/hailer-api/SKILL.md +283 -0
- package/.claude/skills/hailer-api/references/activities.md +620 -0
- package/.claude/skills/hailer-api/references/authentication.md +216 -0
- package/.claude/skills/hailer-api/references/datasets.md +437 -0
- package/.claude/skills/hailer-api/references/files.md +301 -0
- package/.claude/skills/hailer-api/references/insights.md +469 -0
- package/.claude/skills/hailer-api/references/workflows.md +720 -0
- package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
- package/.claude/skills/insight-api/SKILL.md +185 -0
- package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
- package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
- package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
- package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
- package/.claude/skills/local-first-skill/SKILL.md +570 -0
- package/.claude/skills/mcp-tools/SKILL.md +419 -0
- package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
- package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
- package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
- package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
- package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
- package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
- package/.claude/skills/remove-app-skill/SKILL.md +985 -0
- package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
- package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
- package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
- package/.claude/skills/skill-testing/README.md +137 -0
- package/.claude/skills/skill-testing/SKILL.md +348 -0
- package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
- package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
- package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
- package/.claude/skills/tool-builder/SKILL.md +328 -0
- package/.claude/skills/update-app-skill/SKILL.md +970 -0
- package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
- package/.env.example +81 -0
- package/.mcp.json +13 -0
- package/README.md +297 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +74 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +5 -0
- package/dist/client/adaptive-documentation-bot.d.ts +108 -0
- package/dist/client/adaptive-documentation-bot.js +475 -0
- package/dist/client/adaptive-documentation-types.d.ts +66 -0
- package/dist/client/adaptive-documentation-types.js +9 -0
- package/dist/client/agent-activity-bot.d.ts +51 -0
- package/dist/client/agent-activity-bot.js +166 -0
- package/dist/client/agent-tracker.d.ts +499 -0
- package/dist/client/agent-tracker.js +659 -0
- package/dist/client/description-updater.d.ts +56 -0
- package/dist/client/description-updater.js +259 -0
- package/dist/client/log-parser.d.ts +72 -0
- package/dist/client/log-parser.js +387 -0
- package/dist/client/mcp-client.d.ts +50 -0
- package/dist/client/mcp-client.js +532 -0
- package/dist/client/message-processor.d.ts +35 -0
- package/dist/client/message-processor.js +352 -0
- package/dist/client/multi-bot-manager.d.ts +24 -0
- package/dist/client/multi-bot-manager.js +74 -0
- package/dist/client/providers/anthropic-provider.d.ts +19 -0
- package/dist/client/providers/anthropic-provider.js +631 -0
- package/dist/client/providers/llm-provider.d.ts +47 -0
- package/dist/client/providers/llm-provider.js +367 -0
- package/dist/client/providers/openai-provider.d.ts +23 -0
- package/dist/client/providers/openai-provider.js +621 -0
- package/dist/client/simple-llm-caller.d.ts +19 -0
- package/dist/client/simple-llm-caller.js +100 -0
- package/dist/client/skill-generator.d.ts +81 -0
- package/dist/client/skill-generator.js +386 -0
- package/dist/client/test-adaptive-bot.d.ts +9 -0
- package/dist/client/test-adaptive-bot.js +82 -0
- package/dist/client/token-pricing.d.ts +38 -0
- package/dist/client/token-pricing.js +127 -0
- package/dist/client/token-tracker.d.ts +232 -0
- package/dist/client/token-tracker.js +457 -0
- package/dist/client/token-usage-bot.d.ts +53 -0
- package/dist/client/token-usage-bot.js +153 -0
- package/dist/client/tool-executor.d.ts +69 -0
- package/dist/client/tool-executor.js +159 -0
- package/dist/client/tool-schema-loader.d.ts +60 -0
- package/dist/client/tool-schema-loader.js +178 -0
- package/dist/client/types.d.ts +69 -0
- package/dist/client/types.js +7 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +296 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.js +147 -0
- package/dist/lib/context-manager.d.ts +111 -0
- package/dist/lib/context-manager.js +431 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +277 -0
- package/dist/lib/materialize.d.ts +3 -0
- package/dist/lib/materialize.js +101 -0
- package/dist/lib/normalizedName.d.ts +7 -0
- package/dist/lib/normalizedName.js +48 -0
- package/dist/lib/prompt-length-manager.d.ts +81 -0
- package/dist/lib/prompt-length-manager.js +457 -0
- package/dist/lib/terminal-prompt.d.ts +9 -0
- package/dist/lib/terminal-prompt.js +108 -0
- package/dist/mcp/UserContextCache.d.ts +56 -0
- package/dist/mcp/UserContextCache.js +163 -0
- package/dist/mcp/auth.d.ts +2 -0
- package/dist/mcp/auth.js +29 -0
- package/dist/mcp/hailer-clients.d.ts +42 -0
- package/dist/mcp/hailer-clients.js +246 -0
- package/dist/mcp/signal-handler.d.ts +45 -0
- package/dist/mcp/signal-handler.js +317 -0
- package/dist/mcp/tool-registry.d.ts +100 -0
- package/dist/mcp/tool-registry.js +306 -0
- package/dist/mcp/tools/activity.d.ts +15 -0
- package/dist/mcp/tools/activity.js +955 -0
- package/dist/mcp/tools/app.d.ts +20 -0
- package/dist/mcp/tools/app.js +1488 -0
- package/dist/mcp/tools/discussion.d.ts +19 -0
- package/dist/mcp/tools/discussion.js +950 -0
- package/dist/mcp/tools/file.d.ts +15 -0
- package/dist/mcp/tools/file.js +119 -0
- package/dist/mcp/tools/insight.d.ts +17 -0
- package/dist/mcp/tools/insight.js +806 -0
- package/dist/mcp/tools/skill.d.ts +10 -0
- package/dist/mcp/tools/skill.js +279 -0
- package/dist/mcp/tools/user.d.ts +10 -0
- package/dist/mcp/tools/user.js +108 -0
- package/dist/mcp/tools/workflow-template.d.ts +19 -0
- package/dist/mcp/tools/workflow-template.js +822 -0
- package/dist/mcp/tools/workflow.d.ts +18 -0
- package/dist/mcp/tools/workflow.js +1362 -0
- package/dist/mcp/utils/api-errors.d.ts +45 -0
- package/dist/mcp/utils/api-errors.js +160 -0
- package/dist/mcp/utils/data-transformers.d.ts +102 -0
- package/dist/mcp/utils/data-transformers.js +194 -0
- package/dist/mcp/utils/file-upload.d.ts +33 -0
- package/dist/mcp/utils/file-upload.js +148 -0
- package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
- package/dist/mcp/utils/hailer-api-client.js +323 -0
- package/dist/mcp/utils/index.d.ts +13 -0
- package/dist/mcp/utils/index.js +39 -0
- package/dist/mcp/utils/logger.d.ts +42 -0
- package/dist/mcp/utils/logger.js +103 -0
- package/dist/mcp/utils/types.d.ts +286 -0
- package/dist/mcp/utils/types.js +7 -0
- package/dist/mcp/workspace-cache.d.ts +42 -0
- package/dist/mcp/workspace-cache.js +97 -0
- package/dist/mcp-server.d.ts +42 -0
- package/dist/mcp-server.js +280 -0
- package/package.json +56 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Building Hailer Apps
|
|
3
|
+
description: Complete guide for building Hailer apps with the @hailer/app-sdk - covers data loading, field access, API patterns, and common pitfalls
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Building Hailer Apps Skill
|
|
7
|
+
|
|
8
|
+
Complete guide for building Hailer apps using the `@hailer/app-sdk` package. This skill covers critical patterns and gotchas that prevent data loading issues.
|
|
9
|
+
|
|
10
|
+
## ⚠️ CRITICAL: Hailer App SDK vs MCP API
|
|
11
|
+
|
|
12
|
+
**IMPORTANT:** The Hailer App SDK (used in React/frontend apps) is DIFFERENT from the MCP API (used by Claude Code tools).
|
|
13
|
+
|
|
14
|
+
### Hailer App SDK (Frontend Apps)
|
|
15
|
+
```typescript
|
|
16
|
+
// Available on window.Hailer in browser apps
|
|
17
|
+
const hailer = await window.Hailer.init({ appId: '...' });
|
|
18
|
+
|
|
19
|
+
// NO hailer.api - access directly:
|
|
20
|
+
hailer.activity.list(workflowId, phaseId, { limit: 100 })
|
|
21
|
+
hailer.activity.get(activityId)
|
|
22
|
+
hailer.activity.create(workflowId, data)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### MCP API (Claude Code Tools)
|
|
26
|
+
```typescript
|
|
27
|
+
// Used by Claude Code MCP tools
|
|
28
|
+
await list_activities({ workflowId, phaseId, fields: [...] })
|
|
29
|
+
await show_activity_by_id({ activityId })
|
|
30
|
+
await create_activity({ workflowId, fields: {...} })
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Key Difference:** Frontend apps use `hailer.activity.list()` directly, NOT `hailer.api.activity.list()`!
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Data Loading Pattern
|
|
38
|
+
|
|
39
|
+
### ✅ CORRECT Pattern
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { useEffect, useState } from 'react';
|
|
43
|
+
|
|
44
|
+
function MyComponent({ hailer }) {
|
|
45
|
+
const [data, setData] = useState([]);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
loadData();
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
async function loadData() {
|
|
52
|
+
// ✅ Call with 3 POSITIONAL PARAMETERS
|
|
53
|
+
const result = await hailer.activity.list(
|
|
54
|
+
'691ffdf84217e9e8434e5693', // 1st: workflowId
|
|
55
|
+
'691ffdf84217e9e8434e569f', // 2nd: phaseId
|
|
56
|
+
{ limit: 100 } // 3rd: options object
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
setData(result || []);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### ❌ WRONG Patterns
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// ❌ WRONG: Using object with named params
|
|
68
|
+
await hailer.activity.list({
|
|
69
|
+
process: workflowId,
|
|
70
|
+
phase: phaseId,
|
|
71
|
+
limit: 100
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ❌ WRONG: Using hailer.api (doesn't exist in frontend SDK)
|
|
75
|
+
await hailer.api.activity.list(workflowId, phaseId, options);
|
|
76
|
+
|
|
77
|
+
// ❌ WRONG: Missing options object (3rd param)
|
|
78
|
+
await hailer.activity.list(workflowId, phaseId);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### SDK Method Signature
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
hailer.activity.list(
|
|
85
|
+
workflowId: string,
|
|
86
|
+
phaseId: string,
|
|
87
|
+
options?: {
|
|
88
|
+
limit?: number,
|
|
89
|
+
skip?: number,
|
|
90
|
+
filters?: object
|
|
91
|
+
}
|
|
92
|
+
): Promise<Activity[]>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 2. Field Access - The Critical Issue
|
|
98
|
+
|
|
99
|
+
### Understanding Field Structure
|
|
100
|
+
|
|
101
|
+
**Activities returned from API have fields keyed by FIELD IDs, NOT readable keys!**
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ❌ WRONG: Fields are NOT keyed by readable names
|
|
105
|
+
{
|
|
106
|
+
_id: "691ffe874217e9e8434e5800",
|
|
107
|
+
name: "Jude Bellingham Jr",
|
|
108
|
+
fields: {
|
|
109
|
+
playerName: { value: "Jude" }, // ❌ WRONG - This doesn't exist!
|
|
110
|
+
jerseyNumber: { value: 26 } // ❌ WRONG - This doesn't exist!
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ✅ CORRECT: Fields are keyed by field IDs
|
|
115
|
+
{
|
|
116
|
+
_id: "691ffe874217e9e8434e5800",
|
|
117
|
+
name: "Jude Bellingham Jr",
|
|
118
|
+
fields: {
|
|
119
|
+
"691ffdf84217e9e8434e5694": { type: "text", value: "Jude Bellingham Jr" },
|
|
120
|
+
"691ffdf84217e9e8434e5695": { type: "numeric", value: 26 },
|
|
121
|
+
"691ffdf84217e9e8434e5696": { type: "textpredefinedoptions", value: "Midfielder" }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Step 1: Get Field IDs Using MCP Tools
|
|
127
|
+
|
|
128
|
+
**ALWAYS use MCP tools to get field schema BEFORE building components:**
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// In Claude Code, use MCP tool:
|
|
132
|
+
await get_workflow_schema({
|
|
133
|
+
workflowId: "691ffdf84217e9e8434e5693",
|
|
134
|
+
phaseId: "691ffdf84217e9e8434e569f"
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Returns field mappings:
|
|
138
|
+
// Player Name → 691ffdf84217e9e8434e5694
|
|
139
|
+
// Jersey Number → 691ffdf84217e9e8434e5695
|
|
140
|
+
// Position → 691ffdf84217e9e8434e5696
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Step 2: Create Field ID Constants
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// src/fieldIds.ts
|
|
147
|
+
export const PLAYER_FIELDS = {
|
|
148
|
+
PLAYER_NAME: '691ffdf84217e9e8434e5694',
|
|
149
|
+
JERSEY_NUMBER: '691ffdf84217e9e8434e5695',
|
|
150
|
+
POSITION: '691ffdf84217e9e8434e5696',
|
|
151
|
+
DATE_OF_BIRTH: '691ffdf84217e9e8434e5697',
|
|
152
|
+
NATIONALITY: '691ffdf84217e9e8434e5698',
|
|
153
|
+
HEIGHT: '691ffdf84217e9e8434e5699',
|
|
154
|
+
WEIGHT: '691ffdf84217e9e8434e569a',
|
|
155
|
+
} as const;
|
|
156
|
+
|
|
157
|
+
// Helper function to safely get field value
|
|
158
|
+
export function getFieldValue<T = any>(
|
|
159
|
+
fields: Record<string, { type: string; value: T }>,
|
|
160
|
+
fieldId: string
|
|
161
|
+
): T | undefined {
|
|
162
|
+
return fields[fieldId]?.value;
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Step 3: Use Field IDs in Components
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { PLAYER_FIELDS, getFieldValue } from './fieldIds';
|
|
170
|
+
|
|
171
|
+
function PlayerCard({ player }) {
|
|
172
|
+
return (
|
|
173
|
+
<div>
|
|
174
|
+
{/* ✅ CORRECT: Use getFieldValue with field ID */}
|
|
175
|
+
<h3>{getFieldValue(player.fields, PLAYER_FIELDS.PLAYER_NAME)}</h3>
|
|
176
|
+
<div>#{getFieldValue(player.fields, PLAYER_FIELDS.JERSEY_NUMBER)}</div>
|
|
177
|
+
<div>{getFieldValue(player.fields, PLAYER_FIELDS.POSITION)}</div>
|
|
178
|
+
|
|
179
|
+
{/* ❌ WRONG: This won't work! */}
|
|
180
|
+
<h3>{player.fields.playerName?.value}</h3>
|
|
181
|
+
<div>#{player.fields.jerseyNumber?.value}</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 3. Complete Working Example
|
|
190
|
+
|
|
191
|
+
### Full Component with Proper Data Loading
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { useState, useEffect } from 'react';
|
|
195
|
+
import { PLAYER_FIELDS, getFieldValue } from '../fieldIds';
|
|
196
|
+
|
|
197
|
+
const WORKFLOW_IDS = {
|
|
198
|
+
PLAYERS: '691ffdf84217e9e8434e5693',
|
|
199
|
+
ACTIVE_PHASE: '691ffdf84217e9e8434e569f',
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default function PlayerList({ hailer }) {
|
|
203
|
+
const [players, setPlayers] = useState([]);
|
|
204
|
+
const [loading, setLoading] = useState(true);
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
loadPlayers();
|
|
208
|
+
}, []);
|
|
209
|
+
|
|
210
|
+
async function loadPlayers() {
|
|
211
|
+
try {
|
|
212
|
+
setLoading(true);
|
|
213
|
+
|
|
214
|
+
// ✅ Call with 3 positional parameters
|
|
215
|
+
const result = await hailer.activity.list(
|
|
216
|
+
WORKFLOW_IDS.PLAYERS,
|
|
217
|
+
WORKFLOW_IDS.ACTIVE_PHASE,
|
|
218
|
+
{ limit: 100 }
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
console.log(`Loaded ${result?.length || 0} players`);
|
|
222
|
+
setPlayers(result || []);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Failed to load players:', error);
|
|
225
|
+
} finally {
|
|
226
|
+
setLoading(false);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (loading) {
|
|
231
|
+
return <div>Loading...</div>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className="player-list">
|
|
236
|
+
{players.map(player => (
|
|
237
|
+
<div key={player._id} className="player-card">
|
|
238
|
+
{/* ✅ Use getFieldValue with field IDs */}
|
|
239
|
+
<h3>{getFieldValue(player.fields, PLAYER_FIELDS.PLAYER_NAME) || player.name}</h3>
|
|
240
|
+
<div className="jersey">
|
|
241
|
+
#{getFieldValue(player.fields, PLAYER_FIELDS.JERSEY_NUMBER) || '?'}
|
|
242
|
+
</div>
|
|
243
|
+
<div className="position">
|
|
244
|
+
{getFieldValue(player.fields, PLAYER_FIELDS.POSITION)}
|
|
245
|
+
</div>
|
|
246
|
+
<div className="stats">
|
|
247
|
+
<span>Height: {getFieldValue(player.fields, PLAYER_FIELDS.HEIGHT)} cm</span>
|
|
248
|
+
<span>Weight: {getFieldValue(player.fields, PLAYER_FIELDS.WEIGHT)} kg</span>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
))}
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 4. Common Field Types
|
|
260
|
+
|
|
261
|
+
### Field Type Reference
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// text
|
|
265
|
+
{ type: "text", value: "John Doe" }
|
|
266
|
+
|
|
267
|
+
// numeric
|
|
268
|
+
{ type: "numeric", value: 42 }
|
|
269
|
+
|
|
270
|
+
// numericunit
|
|
271
|
+
{ type: "numericunit", value: 180 } // e.g., 180 cm
|
|
272
|
+
|
|
273
|
+
// date
|
|
274
|
+
{ type: "date", value: 1036454400000 } // timestamp
|
|
275
|
+
|
|
276
|
+
// textpredefinedoptions (dropdown)
|
|
277
|
+
{ type: "textpredefinedoptions", value: "Active" }
|
|
278
|
+
|
|
279
|
+
// country
|
|
280
|
+
{ type: "country", value: { code: "US", name: "United States" } }
|
|
281
|
+
|
|
282
|
+
// activitylink (reference to another activity)
|
|
283
|
+
{ type: "activitylink", value: { _id: "...", name: "..." } }
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Type-Safe Field Access
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// For simple types
|
|
290
|
+
const name = getFieldValue<string>(fields, PLAYER_FIELDS.PLAYER_NAME);
|
|
291
|
+
const age = getFieldValue<number>(fields, PLAYER_FIELDS.AGE);
|
|
292
|
+
|
|
293
|
+
// For complex types (country)
|
|
294
|
+
const country = getFieldValue<{ code: string; name: string }>(
|
|
295
|
+
fields,
|
|
296
|
+
PLAYER_FIELDS.NATIONALITY
|
|
297
|
+
);
|
|
298
|
+
console.log(country?.name);
|
|
299
|
+
|
|
300
|
+
// For activity links
|
|
301
|
+
const team = getFieldValue<{ _id: string; name: string }>(
|
|
302
|
+
fields,
|
|
303
|
+
PLAYER_FIELDS.CURRENT_TEAM
|
|
304
|
+
);
|
|
305
|
+
console.log(team?.name);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 5. Development Workflow
|
|
311
|
+
|
|
312
|
+
### Step-by-Step: Building a Hailer App Component
|
|
313
|
+
|
|
314
|
+
1. **Use MCP tool to get schema**
|
|
315
|
+
```typescript
|
|
316
|
+
await get_workflow_schema({
|
|
317
|
+
workflowId: "...",
|
|
318
|
+
phaseId: "..."
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
2. **Create field ID constants**
|
|
323
|
+
```typescript
|
|
324
|
+
// src/fieldIds.ts
|
|
325
|
+
export const WORKFLOW_FIELDS = {
|
|
326
|
+
FIELD_NAME: 'field-id-from-schema',
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
3. **Create component with proper data loading**
|
|
331
|
+
```typescript
|
|
332
|
+
// Use 3-param signature for hailer.activity.list()
|
|
333
|
+
const data = await hailer.activity.list(workflowId, phaseId, options);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
4. **Access fields using field IDs**
|
|
337
|
+
```typescript
|
|
338
|
+
// Use getFieldValue(fields, FIELD_ID)
|
|
339
|
+
const value = getFieldValue(item.fields, WORKFLOW_FIELDS.FIELD_NAME);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
5. **Test and verify data loads**
|
|
343
|
+
```typescript
|
|
344
|
+
console.log('Loaded data:', data);
|
|
345
|
+
console.log('First item fields:', data[0]?.fields);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## 6. Debugging Data Loading Issues
|
|
351
|
+
|
|
352
|
+
### Quick Diagnostics
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
async function loadPlayers() {
|
|
356
|
+
try {
|
|
357
|
+
console.log('=== DEBUG DATA LOADING ===');
|
|
358
|
+
|
|
359
|
+
// 1. Check hailer object
|
|
360
|
+
console.log('hailer:', hailer);
|
|
361
|
+
console.log('hailer.activity:', hailer.activity);
|
|
362
|
+
console.log('hailer.activity.list:', typeof hailer.activity.list);
|
|
363
|
+
|
|
364
|
+
// 2. Call API
|
|
365
|
+
const result = await hailer.activity.list(workflowId, phaseId, { limit: 10 });
|
|
366
|
+
|
|
367
|
+
// 3. Check result structure
|
|
368
|
+
console.log('Result:', result);
|
|
369
|
+
console.log('Result length:', result?.length);
|
|
370
|
+
console.log('First item:', result?.[0]);
|
|
371
|
+
console.log('First item fields:', result?.[0]?.fields);
|
|
372
|
+
console.log('Field keys:', Object.keys(result?.[0]?.fields || {}));
|
|
373
|
+
|
|
374
|
+
return result;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error('Load failed:', error);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Common Issues and Solutions
|
|
382
|
+
|
|
383
|
+
| Issue | Symptom | Solution |
|
|
384
|
+
|-------|---------|----------|
|
|
385
|
+
| `Cannot read properties of undefined (reading 'activity')` | `hailer.api` doesn't exist | Use `hailer.activity`, NOT `hailer.api.activity` |
|
|
386
|
+
| `"processId" must be a string` | Passing object instead of positional params | Use 3 positional params: `list(workflowId, phaseId, options)` |
|
|
387
|
+
| Fields showing as `undefined` | Using readable keys like `fields.playerName` | Use field IDs: `fields['691ffdf...']` or `getFieldValue()` |
|
|
388
|
+
| Empty data displayed | Data loads but doesn't render | Check field access - use correct field IDs |
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 7. Loading Data from Multiple Phases
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
async function loadAllPlayers() {
|
|
396
|
+
const allPlayers = [];
|
|
397
|
+
|
|
398
|
+
// Load from multiple phases
|
|
399
|
+
const phases = [
|
|
400
|
+
{ id: '691ffdf84217e9e8434e569f', name: 'Active' },
|
|
401
|
+
{ id: '691ffdf84217e9e8434e56a0', name: 'On Loan' },
|
|
402
|
+
{ id: '691ffdf84217e9e8434e56a1', name: 'Injured' },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
for (const phase of phases) {
|
|
406
|
+
try {
|
|
407
|
+
const result = await hailer.activity.list(
|
|
408
|
+
WORKFLOW_IDS.PLAYERS,
|
|
409
|
+
phase.id,
|
|
410
|
+
{ limit: 100 }
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
console.log(`Loaded ${result?.length || 0} players from ${phase.name}`);
|
|
414
|
+
|
|
415
|
+
if (result && Array.isArray(result)) {
|
|
416
|
+
allPlayers.push(...result);
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error(`Failed to load ${phase.name}:`, error);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return allPlayers;
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## 8. Best Practices
|
|
430
|
+
|
|
431
|
+
### ✅ DO
|
|
432
|
+
|
|
433
|
+
1. **Always get schema first** - Use MCP `get_workflow_schema` tool
|
|
434
|
+
2. **Create field ID constants** - Store in `src/fieldIds.ts`
|
|
435
|
+
3. **Use helper functions** - `getFieldValue()` for safe access
|
|
436
|
+
4. **Log during development** - Verify data structure
|
|
437
|
+
5. **Handle errors** - Wrap API calls in try-catch
|
|
438
|
+
6. **Use TypeScript** - Type your field values
|
|
439
|
+
|
|
440
|
+
### ❌ DON'T
|
|
441
|
+
|
|
442
|
+
1. **Don't use readable keys** - `fields.playerName` won't work
|
|
443
|
+
2. **Don't use hailer.api** - It doesn't exist in frontend SDK
|
|
444
|
+
3. **Don't pass objects to list()** - Use positional params
|
|
445
|
+
4. **Don't assume field structure** - Always check schema first
|
|
446
|
+
5. **Don't forget error handling** - API calls can fail
|
|
447
|
+
6. **Don't hardcode field IDs inline** - Use constants
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## 9. TypeScript Definitions
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// src/types.ts
|
|
455
|
+
export interface Activity {
|
|
456
|
+
_id: string;
|
|
457
|
+
name: string;
|
|
458
|
+
fields: Record<string, FieldValue>;
|
|
459
|
+
currentPhase: string;
|
|
460
|
+
process: string;
|
|
461
|
+
created: number;
|
|
462
|
+
updated: number;
|
|
463
|
+
active: boolean;
|
|
464
|
+
discussion?: string;
|
|
465
|
+
files?: any[];
|
|
466
|
+
followers?: string[];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export interface FieldValue<T = any> {
|
|
470
|
+
type: string;
|
|
471
|
+
value: T;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Hailer client type
|
|
475
|
+
export interface HailerClient {
|
|
476
|
+
activity: {
|
|
477
|
+
list(workflowId: string, phaseId: string, options?: {
|
|
478
|
+
limit?: number;
|
|
479
|
+
skip?: number;
|
|
480
|
+
filters?: any;
|
|
481
|
+
}): Promise<Activity[]>;
|
|
482
|
+
|
|
483
|
+
get(activityId: string): Promise<Activity>;
|
|
484
|
+
|
|
485
|
+
create(workflowId: string, data: {
|
|
486
|
+
name: string;
|
|
487
|
+
fields?: Record<string, any>;
|
|
488
|
+
phaseId?: string;
|
|
489
|
+
}): Promise<Activity>;
|
|
490
|
+
|
|
491
|
+
update(activityId: string, data: Partial<Activity>): Promise<Activity>;
|
|
492
|
+
|
|
493
|
+
remove(activityId: string): Promise<void>;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// Other modules...
|
|
497
|
+
workflow: any;
|
|
498
|
+
insight: any;
|
|
499
|
+
user: any;
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## 10. Testing Checklist
|
|
506
|
+
|
|
507
|
+
Before deploying your Hailer app:
|
|
508
|
+
|
|
509
|
+
- [ ] Data loads successfully (`hailer.activity.list()` works)
|
|
510
|
+
- [ ] Field IDs are correct (checked with `get_workflow_schema`)
|
|
511
|
+
- [ ] Field values display properly (using `getFieldValue()`)
|
|
512
|
+
- [ ] All required fields are accessible
|
|
513
|
+
- [ ] Error handling is in place
|
|
514
|
+
- [ ] Loading states are handled
|
|
515
|
+
- [ ] Console has no errors
|
|
516
|
+
- [ ] Data refreshes correctly
|
|
517
|
+
- [ ] Multiple phases work (if applicable)
|
|
518
|
+
- [ ] TypeScript types are correct
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Summary
|
|
523
|
+
|
|
524
|
+
**Key Points to Remember:**
|
|
525
|
+
|
|
526
|
+
1. **SDK vs MCP API** - Frontend uses `hailer.activity`, NOT `hailer.api.activity`
|
|
527
|
+
2. **3 Positional Params** - `list(workflowId, phaseId, options)`
|
|
528
|
+
3. **Field IDs, Not Keys** - Fields are keyed by IDs, not readable names
|
|
529
|
+
4. **Get Schema First** - Always use MCP `get_workflow_schema` tool
|
|
530
|
+
5. **Use Constants** - Store field IDs in `src/fieldIds.ts`
|
|
531
|
+
6. **Helper Functions** - Use `getFieldValue()` for safe access
|
|
532
|
+
|
|
533
|
+
**Critical Pattern:**
|
|
534
|
+
```typescript
|
|
535
|
+
// 1. Get schema (in Claude Code)
|
|
536
|
+
await get_workflow_schema({ workflowId, phaseId });
|
|
537
|
+
|
|
538
|
+
// 2. Create constants
|
|
539
|
+
export const FIELDS = { NAME: 'field-id-from-schema' };
|
|
540
|
+
|
|
541
|
+
// 3. Load data
|
|
542
|
+
const data = await hailer.activity.list(workflowId, phaseId, { limit: 100 });
|
|
543
|
+
|
|
544
|
+
// 4. Access fields
|
|
545
|
+
const name = getFieldValue(data[0].fields, FIELDS.NAME);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Follow these patterns and your Hailer app will load data correctly from the start!
|