@hailer/mcp 1.1.11 → 1.1.13

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 (252) hide show
  1. package/dist/app.js +18 -5
  2. package/dist/bot/bot-config.d.ts +12 -1
  3. package/dist/bot/bot-config.js +98 -14
  4. package/dist/bot/bot-manager.d.ts +13 -3
  5. package/dist/bot/bot-manager.js +80 -25
  6. package/dist/bot/bot.d.ts +46 -0
  7. package/dist/bot/bot.js +542 -166
  8. package/dist/bot/services/message-classifier.js +17 -0
  9. package/dist/bot/services/permission-guard.d.ts +52 -0
  10. package/dist/bot/services/permission-guard.js +149 -0
  11. package/dist/bot/services/types.d.ts +5 -0
  12. package/dist/bot/services/typing-indicator.d.ts +6 -1
  13. package/dist/bot/services/typing-indicator.js +19 -3
  14. package/dist/config.d.ts +6 -1
  15. package/dist/config.js +43 -0
  16. package/dist/core.js +3 -6
  17. package/dist/mcp/UserContextCache.d.ts +5 -0
  18. package/dist/mcp/UserContextCache.js +51 -19
  19. package/dist/mcp/hailer-clients.d.ts +19 -1
  20. package/dist/mcp/hailer-clients.js +157 -20
  21. package/dist/mcp/session-store.d.ts +68 -0
  22. package/dist/mcp/session-store.js +169 -0
  23. package/dist/mcp/signal-handler.js +12 -12
  24. package/dist/mcp/tool-registry.d.ts +17 -4
  25. package/dist/mcp/tool-registry.js +37 -7
  26. package/dist/mcp/tools/activity.js +99 -7
  27. package/dist/mcp/tools/app-scaffold.js +304 -336
  28. package/dist/mcp/tools/company.d.ts +9 -0
  29. package/dist/mcp/tools/company.js +88 -0
  30. package/dist/mcp/tools/discussion.js +68 -0
  31. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  32. package/dist/mcp/tools/workflow-permissions.js +204 -0
  33. package/dist/mcp/tools/workflow.js +57 -18
  34. package/dist/mcp/utils/index.d.ts +2 -0
  35. package/dist/mcp/utils/index.js +12 -1
  36. package/dist/mcp/utils/role-utils.d.ts +74 -0
  37. package/dist/mcp/utils/role-utils.js +151 -0
  38. package/dist/mcp/utils/types.d.ts +43 -1
  39. package/dist/mcp/utils/types.js +14 -0
  40. package/dist/mcp/webhook-handler.d.ts +6 -0
  41. package/dist/mcp/webhook-handler.js +11 -0
  42. package/dist/mcp-server.d.ts +23 -2
  43. package/dist/mcp-server.js +639 -111
  44. package/dist/plugins/vipunen/client.d.ts +150 -0
  45. package/dist/plugins/vipunen/client.js +535 -0
  46. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  47. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  48. package/dist/plugins/vipunen/index.d.ts +41 -0
  49. package/dist/plugins/vipunen/index.js +88 -0
  50. package/dist/plugins/vipunen/tools.d.ts +26 -0
  51. package/dist/plugins/vipunen/tools.js +501 -0
  52. package/package.json +2 -1
  53. package/.claude/.context-watchdog.json +0 -1
  54. package/.claude/.session-checked +0 -1
  55. package/.claude/CLAUDE.md +0 -370
  56. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  57. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  58. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  59. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  60. package/.claude/agents/agent-code-simplifier.md +0 -53
  61. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  62. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  63. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  64. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  65. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  66. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  67. package/.claude/agents/agent-ivan-monolith.md +0 -154
  68. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  69. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  70. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  71. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  72. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  73. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  74. package/.claude/agents/agent-permissions-handler.md +0 -208
  75. package/.claude/agents/agent-simple-writer.md +0 -48
  76. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  77. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  78. package/.claude/agents/agent-ui-designer.md +0 -100
  79. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  80. package/.claude/agents/agent-web-search.md +0 -55
  81. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  82. package/.claude/agents/agent-zara-zapier.md +0 -159
  83. package/.claude/commands/app-squad.md +0 -135
  84. package/.claude/commands/audit-squad.md +0 -158
  85. package/.claude/commands/autoplan.md +0 -563
  86. package/.claude/commands/cleanup-squad.md +0 -98
  87. package/.claude/commands/config-squad.md +0 -106
  88. package/.claude/commands/crud-squad.md +0 -87
  89. package/.claude/commands/data-squad.md +0 -97
  90. package/.claude/commands/debug-squad.md +0 -303
  91. package/.claude/commands/doc-squad.md +0 -65
  92. package/.claude/commands/handoff.md +0 -137
  93. package/.claude/commands/health.md +0 -49
  94. package/.claude/commands/help.md +0 -29
  95. package/.claude/commands/help:agents.md +0 -151
  96. package/.claude/commands/help:commands.md +0 -78
  97. package/.claude/commands/help:faq.md +0 -79
  98. package/.claude/commands/help:plugins.md +0 -50
  99. package/.claude/commands/help:skills.md +0 -93
  100. package/.claude/commands/help:tools.md +0 -75
  101. package/.claude/commands/hotfix-squad.md +0 -112
  102. package/.claude/commands/integration-squad.md +0 -82
  103. package/.claude/commands/janitor-squad.md +0 -167
  104. package/.claude/commands/learn-auto.md +0 -120
  105. package/.claude/commands/learn.md +0 -120
  106. package/.claude/commands/mcp-list.md +0 -27
  107. package/.claude/commands/onboard-squad.md +0 -140
  108. package/.claude/commands/plan-workspace.md +0 -732
  109. package/.claude/commands/prd.md +0 -130
  110. package/.claude/commands/project-status.md +0 -82
  111. package/.claude/commands/publish.md +0 -138
  112. package/.claude/commands/recap.md +0 -69
  113. package/.claude/commands/restore.md +0 -64
  114. package/.claude/commands/review-squad.md +0 -152
  115. package/.claude/commands/save.md +0 -24
  116. package/.claude/commands/stats.md +0 -19
  117. package/.claude/commands/swarm.md +0 -210
  118. package/.claude/commands/tool-builder.md +0 -39
  119. package/.claude/commands/ws-pull.md +0 -44
  120. package/.claude/hooks/_shared-memory.cjs +0 -305
  121. package/.claude/hooks/_utils.cjs +0 -108
  122. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  123. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  124. package/.claude/hooks/app-edit-guard.cjs +0 -494
  125. package/.claude/hooks/auto-learn.cjs +0 -304
  126. package/.claude/hooks/bash-guard.cjs +0 -272
  127. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  128. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  129. package/.claude/hooks/context-watchdog.cjs +0 -230
  130. package/.claude/hooks/delegation-reminder.cjs +0 -465
  131. package/.claude/hooks/design-system-lint.cjs +0 -271
  132. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  133. package/.claude/hooks/prompt-guard.cjs +0 -354
  134. package/.claude/hooks/publish-template-guard.cjs +0 -147
  135. package/.claude/hooks/session-start.cjs +0 -35
  136. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  137. package/.claude/hooks/skill-injector.cjs +0 -140
  138. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  139. package/.claude/hooks/src-edit-guard.cjs +0 -240
  140. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  141. package/.claude/settings.json +0 -257
  142. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  143. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  144. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  145. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  146. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  147. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  148. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  149. package/.claude/skills/agent-structure/SKILL.md +0 -98
  150. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  151. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  152. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  153. package/.claude/skills/frontend-design/SKILL.md +0 -254
  154. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  155. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  156. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  157. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  158. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  159. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  160. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  161. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  162. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  163. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  164. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  165. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  166. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  167. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  168. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  169. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  170. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  171. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  172. package/.claude/skills/json-only-output/SKILL.md +0 -72
  173. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  174. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  175. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  176. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  177. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  178. package/.claude/skills/tool-builder/SKILL.md +0 -250
  179. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  180. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  181. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  182. package/.hailer-mcp-port +0 -1
  183. package/.mcp.json +0 -13
  184. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  185. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  186. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  187. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  188. package/.opencode/agent/agent-code-simplifier.md +0 -31
  189. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  190. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  191. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  192. package/.opencode/agent/agent-helga-workflow-config.md +0 -204
  193. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  194. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  195. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  196. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  197. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  198. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  199. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  200. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  201. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  202. package/.opencode/agent/agent-permissions-handler.md +0 -50
  203. package/.opencode/agent/agent-simple-writer.md +0 -45
  204. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  205. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  206. package/.opencode/agent/agent-ui-designer.md +0 -56
  207. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  208. package/.opencode/agent/agent-web-search.md +0 -42
  209. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  210. package/.opencode/agent/agent-zara-zapier.md +0 -53
  211. package/.opencode/commands/app-squad.md +0 -135
  212. package/.opencode/commands/audit-squad.md +0 -158
  213. package/.opencode/commands/autoplan.md +0 -563
  214. package/.opencode/commands/cleanup-squad.md +0 -98
  215. package/.opencode/commands/config-squad.md +0 -106
  216. package/.opencode/commands/crud-squad.md +0 -87
  217. package/.opencode/commands/data-squad.md +0 -97
  218. package/.opencode/commands/debug-squad.md +0 -303
  219. package/.opencode/commands/doc-squad.md +0 -65
  220. package/.opencode/commands/handoff.md +0 -137
  221. package/.opencode/commands/health.md +0 -49
  222. package/.opencode/commands/help-agents.md +0 -151
  223. package/.opencode/commands/help-commands.md +0 -32
  224. package/.opencode/commands/help-faq.md +0 -29
  225. package/.opencode/commands/help-plugins.md +0 -28
  226. package/.opencode/commands/help-skills.md +0 -7
  227. package/.opencode/commands/help-tools.md +0 -40
  228. package/.opencode/commands/help.md +0 -28
  229. package/.opencode/commands/hotfix-squad.md +0 -112
  230. package/.opencode/commands/integration-squad.md +0 -82
  231. package/.opencode/commands/janitor-squad.md +0 -167
  232. package/.opencode/commands/learn-auto.md +0 -120
  233. package/.opencode/commands/learn.md +0 -120
  234. package/.opencode/commands/mcp-list.md +0 -27
  235. package/.opencode/commands/onboard-squad.md +0 -140
  236. package/.opencode/commands/plan-workspace.md +0 -732
  237. package/.opencode/commands/prd.md +0 -131
  238. package/.opencode/commands/project-status.md +0 -82
  239. package/.opencode/commands/publish.md +0 -138
  240. package/.opencode/commands/recap.md +0 -69
  241. package/.opencode/commands/restore.md +0 -64
  242. package/.opencode/commands/review-squad.md +0 -152
  243. package/.opencode/commands/save.md +0 -24
  244. package/.opencode/commands/stats.md +0 -19
  245. package/.opencode/commands/swarm.md +0 -210
  246. package/.opencode/commands/tool-builder.md +0 -39
  247. package/.opencode/commands/ws-pull.md +0 -44
  248. package/.opencode/opencode.json +0 -21
  249. package/inbox/failures.log +0 -1
  250. package/inbox/usage.jsonl +0 -4
  251. package/scripts/postinstall.cjs +0 -64
  252. package/scripts/test-hal-tools.ts +0 -154
@@ -1,787 +0,0 @@
1
- ---
2
- name: SDK-insight-queries
3
- description: SQL query patterns for Hailer insights - aggregations, filtering, dates, CASE statements
4
- version: 1.4.1
5
- triggers: Insight queries, GROUP BY, COUNT, SUM, date formatting, CASE WHEN
6
- ---
7
-
8
- # Insight Query Patterns
9
-
10
- <critical-rules>
11
- ## ⚠️ TIMESTAMPS IN INSIGHTS ARE SECONDS (NOT MILLISECONDS!)
12
-
13
- **IMPORTANT:** While the Hailer API uses milliseconds, **insights expose datetime fields as SECONDS** (10-digit numbers like `1768291200`).
14
-
15
- **For date comparisons in WHERE clauses:**
16
- ```sql
17
- -- CORRECT (comparing seconds to seconds)
18
- WHERE ajankohtaEnd > strftime('%s', 'now')
19
-
20
- -- WRONG (comparing seconds to milliseconds - will fail!)
21
- WHERE ajankohtaEnd > strftime('%s', 'now') * 1000
22
- ```
23
-
24
- **For formatting dates with strftime:**
25
- ```sql
26
- -- CORRECT (field is already in seconds)
27
- strftime('%Y-%m-%d', dateField, 'unixepoch')
28
-
29
- -- WRONG (double-dividing - will show wrong dates)
30
- strftime('%Y-%m-%d', dateField / 1000, 'unixepoch')
31
- ```
32
-
33
- **Quick rules for insights:**
34
- - `dateField` → already in seconds, use directly with strftime
35
- - `strftime('%s', 'now')` → returns seconds, compare directly with dateField
36
- - Do NOT multiply or divide by 1000!
37
-
38
- **Test before deploying:** Create a diagnostic query to verify:
39
- ```sql
40
- SELECT dateField, strftime('%s', 'now') as now_sec,
41
- (dateField > strftime('%s', 'now')) as is_future
42
- FROM source LIMIT 5
43
- ```
44
- </critical-rules>
45
-
46
- ---
47
-
48
- ## Production Insight Guidelines
49
-
50
- **If an insight is used by an app, document it:**
51
-
52
- ```typescript
53
- {
54
- _id: "...",
55
- name: "Tilavaraukset - Public", // Add (APP) suffix if used by app
56
- // Or: name: "Tilavaraukset - Public (tilavaraus-app)",
57
- query: `SELECT /* Used by tilavaraus-app - do not change field names! */
58
- id, name, varattu_tila, ajankohtaStart, ajankohtaEnd
59
- FROM varaukset
60
- WHERE phaseId = '...'`
61
- }
62
- ```
63
-
64
- **Why:** Prevents breaking production apps when modifying insights.
65
-
66
- **Before modifying production insights:**
67
- 1. Create a TEST duplicate with same sources
68
- 2. Test changes on the duplicate
69
- 3. Apply working changes to production
70
-
71
- ---
72
-
73
- ## Query Basics
74
-
75
- Hailer insights use **SQLite syntax**. Queries run over sources you define.
76
-
77
- ```typescript
78
- {
79
- sources: [
80
- {
81
- workflowId: WorkflowIds.tasks_abc,
82
- name: 't', // Alias for queries
83
- fields: [
84
- { name: 'title', meta: 'name' },
85
- { name: 'id', meta: '_id' },
86
- { name: 'priority', fieldId: Tasks_FieldIds.priority_def }
87
- ]
88
- }
89
- ],
90
- query: 'SELECT title, priority FROM t WHERE priority = "High"'
91
- }
92
- ```
93
-
94
- ### Source Field Naming Convention
95
-
96
- **Use real field names** in source definitions, not generic names:
97
-
98
- ```typescript
99
- // GOOD - Real field names, matches Hailer UI
100
- fields: [
101
- { name: 'nro', fieldId: FieldIds.invoiceNumber },
102
- { name: 'pvm', fieldId: FieldIds.invoiceDate },
103
- { name: 'asiakas', fieldId: FieldIds.customer }
104
- ]
105
- // Query: SELECT nro, pvm, asiakas FROM invoices
106
-
107
- // BAD - Generic names, confusing
108
- fields: [
109
- { name: 'activityId', fieldId: FieldIds.invoiceNumber },
110
- { name: 'date', fieldId: FieldIds.invoiceDate },
111
- { name: 'link', fieldId: FieldIds.customer }
112
- ]
113
- ```
114
-
115
- Benefits of real names:
116
- - Queries are readable and self-documenting
117
- - Field names match Hailer UI labels
118
- - Easier debugging and maintenance
119
-
120
- ---
121
-
122
- ## Available Meta Fields
123
-
124
- | Meta | Description | Example |
125
- |------|-------------|---------|
126
- | `_id` | Activity ID | Required for JOINs |
127
- | `name` | Activity name | Display name |
128
- | `created` | Created timestamp | Unix seconds |
129
- | `updated` | Updated timestamp | Unix seconds |
130
- | `phaseId` | Current phase ID | For phase filtering |
131
- | `phaseName` | Current phase name | Human readable |
132
- | `workflowId` | Workflow ID | |
133
- | `workflowName` | Workflow name | |
134
- | `createdBy` | Creator user ID | |
135
- | `uid` | User ID | |
136
- | `team` | Team ID | |
137
- | `priority` | Priority value | |
138
- | `phaseLastMove` | Last phase change | Unix seconds |
139
-
140
- ---
141
-
142
- ## Aggregations
143
-
144
- ### COUNT
145
- ```sql
146
- SELECT
147
- phaseName,
148
- COUNT(*) as count
149
- FROM tasks
150
- GROUP BY phaseName
151
- ```
152
-
153
- ### SUM
154
- ```sql
155
- SELECT
156
- projectName,
157
- SUM(hours) as total_hours
158
- FROM timesheets
159
- GROUP BY projectName
160
- ```
161
-
162
- ### AVG, MIN, MAX
163
- ```sql
164
- SELECT
165
- AVG(amount) as avg_amount,
166
- MIN(amount) as min_amount,
167
- MAX(amount) as max_amount
168
- FROM orders
169
- ```
170
-
171
- ### COUNT with DISTINCT
172
- ```sql
173
- SELECT
174
- COUNT(DISTINCT customerId) as unique_customers
175
- FROM orders
176
- ```
177
-
178
- ---
179
-
180
- ## Filtering
181
-
182
- ### WHERE Clauses
183
- ```sql
184
- -- Text match
185
- WHERE priority = "High"
186
-
187
- -- Numeric comparison
188
- WHERE amount > 1000
189
-
190
- -- NULL check
191
- WHERE assignee IS NOT NULL
192
-
193
- -- Multiple conditions
194
- WHERE priority = "High" AND status != "Done"
195
-
196
- -- IN list
197
- WHERE phaseName IN ("Todo", "In Progress")
198
-
199
- -- LIKE pattern
200
- WHERE title LIKE "%urgent%"
201
- ```
202
-
203
- ### HAVING (filter after GROUP BY)
204
- ```sql
205
- SELECT
206
- customerId,
207
- COUNT(*) as order_count
208
- FROM orders
209
- GROUP BY customerId
210
- HAVING COUNT(*) > 5
211
- ```
212
-
213
- ---
214
-
215
- ## Date Handling
216
-
217
- **CRITICAL:** In Hailer insights, all date/time fields are exposed as **Unix timestamps in SECONDS** (10-digit numbers like `1770050954`).
218
-
219
- This is different from the Hailer API which uses milliseconds! The insight layer converts automatically.
220
-
221
- ```sql
222
- -- CORRECT - field is already in seconds, use directly
223
- strftime('%Y-%m-%d', dateField, 'unixepoch') AS date_formatted
224
-
225
- -- WRONG - do NOT divide by 1000 (will show wrong dates!)
226
- strftime('%Y-%m-%d', dateField / 1000, 'unixepoch') AS date_formatted
227
- ```
228
-
229
- ### Range Fields (daterange, datetimerange, timerange)
230
-
231
- For range field types, Hailer automatically exposes **Start** and **End** suffixes.
232
-
233
- | Field Type | In Insight SQL |
234
- |------------|----------------|
235
- | `date` | Seconds (10-digit) |
236
- | `datetime` | Seconds (10-digit) |
237
- | `time` | Seconds (10-digit) |
238
- | `daterange` | Start/End in seconds |
239
- | `datetimerange` | Start/End in seconds |
240
- | `timerange` | Start/End in seconds |
241
-
242
- **Note:** `timerange` stores full timestamps, not just time. To extract just the time portion, use SQLite time functions.
243
-
244
- **In sources:** Use just the field name:
245
- ```typescript
246
- fields: [
247
- { name: 'ajankohta', fieldId: Event_FieldIds.ajankohta_abc }, // daterange/datetimerange
248
- { name: 'tyoaika', fieldId: Event_FieldIds.tyoaika_def } // timerange
249
- ]
250
- ```
251
-
252
- **In queries:** Access with Start/End suffixes (already in seconds):
253
- ```sql
254
- SELECT
255
- -- All fields are already in seconds - use directly with strftime
256
- ajankohtaStart,
257
- ajankohtaEnd,
258
- strftime('%d.%m.%Y', ajankohtaStart, 'unixepoch') as start_date,
259
- strftime('%d.%m.%Y', ajankohtaEnd, 'unixepoch') as end_date,
260
-
261
- -- Timerange: extract just the time portion
262
- tyoaikaStart,
263
- tyoaikaEnd,
264
- strftime('%H:%M', tyoaikaStart, 'unixepoch') as start_time,
265
- strftime('%H:%M', tyoaikaEnd, 'unixepoch') as end_time,
266
-
267
- -- Duration in seconds (both are already seconds)
268
- (tyoaikaEnd - tyoaikaStart) as duration_seconds,
269
- (tyoaikaEnd - tyoaikaStart) / 60 as duration_minutes
270
- FROM events
271
- WHERE ajankohtaStart >= strftime('%s', 'now')
272
- ```
273
-
274
- **Naming convention:** `{fieldName}Start` and `{fieldName}End` are auto-generated.
275
-
276
- **IMPORTANT: When to convert vs keep raw:**
277
- - **Display only** (human-readable reports) → Use `strftime()` to format
278
- - **Data for Hailer apps** → App receives seconds from insight, may need to multiply by 1000 for API calls
279
- - **Comparisons** → Compare directly with `strftime('%s', ...)` which also returns seconds
280
-
281
- ### Format Date (Finnish)
282
- ```sql
283
- strftime('%d.%m.%Y', dateField, 'unixepoch') AS date_formatted
284
- ```
285
-
286
- ### Format DateTime
287
- ```sql
288
- strftime('%d.%m.%Y %H:%M', created, 'unixepoch') AS datetime_formatted
289
- ```
290
-
291
- ### Timezone Handling (Finland: +2/+3)
292
- ```sql
293
- strftime(
294
- '%d.%m.%Y %H:%M',
295
- created,
296
- 'unixepoch',
297
- CASE
298
- WHEN created >= (
299
- SELECT strftime('%s', date(strftime('%Y', created, 'unixepoch') || '-03-31', 'weekday 0', '-7 days', '03:00'))
300
- )
301
- AND created < (
302
- SELECT strftime('%s', date(strftime('%Y', created, 'unixepoch') || '-10-31', 'weekday 0', '-7 days', '04:00'))
303
- )
304
- THEN '+3 hours'
305
- ELSE '+2 hours'
306
- END
307
- ) AS local_time
308
- ```
309
-
310
- ### Filter by Date Range
311
- ```sql
312
- -- Last 30 days (both sides are seconds)
313
- WHERE dateField >= strftime('%s', 'now', '-30 days')
314
-
315
- -- This year
316
- WHERE strftime('%Y', dateField, 'unixepoch') = strftime('%Y', 'now')
317
-
318
- -- Specific month
319
- WHERE strftime('%Y-%m', dateField, 'unixepoch') = '2024-01'
320
-
321
- -- Between two dates
322
- WHERE dateField >= strftime('%s', '2024-01-01')
323
- AND dateField < strftime('%s', '2024-02-01')
324
- ```
325
-
326
- ### Group by Month
327
- ```sql
328
- SELECT
329
- strftime('%Y-%m', created, 'unixepoch') as month,
330
- COUNT(*) as count
331
- FROM orders
332
- GROUP BY strftime('%Y-%m', created, 'unixepoch')
333
- ORDER BY month
334
- ```
335
-
336
- ---
337
-
338
- ## CASE Statements
339
-
340
- ### Simple CASE
341
- ```sql
342
- SELECT
343
- title,
344
- CASE priority
345
- WHEN 'High' THEN '🔴'
346
- WHEN 'Medium' THEN '🟡'
347
- WHEN 'Low' THEN '🟢'
348
- ELSE '⚪'
349
- END as priority_icon
350
- FROM tasks
351
- ```
352
-
353
- ### Searched CASE
354
- ```sql
355
- SELECT
356
- title,
357
- CASE
358
- WHEN amount > 10000 THEN 'Large'
359
- WHEN amount > 1000 THEN 'Medium'
360
- ELSE 'Small'
361
- END as size_category
362
- FROM orders
363
- ```
364
-
365
- ### CASE in Aggregation
366
- ```sql
367
- SELECT
368
- SUM(CASE WHEN phaseName = 'Done' THEN 1 ELSE 0 END) as completed,
369
- SUM(CASE WHEN phaseName != 'Done' THEN 1 ELSE 0 END) as pending,
370
- COUNT(*) as total
371
- FROM tasks
372
- ```
373
-
374
- ---
375
-
376
- ## JOINs
377
-
378
- ### LEFT JOIN (recommended)
379
- ```sql
380
- SELECT
381
- t.title,
382
- p.name as project_name
383
- FROM tasks t
384
- LEFT JOIN projects p ON t.projectId = p.id
385
- ```
386
-
387
- **Requirements:**
388
- - Both sources need `{ name: 'id', meta: '_id' }`
389
- - Join on activitylink field = target id
390
-
391
- ### Multiple JOINs
392
- ```sql
393
- SELECT
394
- t.title,
395
- p.name as project_name,
396
- c.name as customer_name
397
- FROM tasks t
398
- LEFT JOIN projects p ON t.projectId = p.id
399
- LEFT JOIN customers c ON p.customerId = c.id
400
- ```
401
-
402
- ### User ID → Username Conversion
403
-
404
- **IMPORTANT:** The `user` table is a **pseudo-table** in Hailer insights. You don't need to declare it as a source - just JOIN directly.
405
-
406
- ```sql
407
- -- Convert user ID fields to display names
408
- SELECT
409
- t.title,
410
- COALESCE(u.name, '') AS Assignee_Name,
411
- COALESCE(m.name, '') AS Manager_Name
412
- FROM tasks t
413
- LEFT JOIN user AS u ON u._id = t.assignee
414
- LEFT JOIN user AS m ON m._id = t.manager
415
- ```
416
-
417
- **Pattern for multiple user fields:**
418
- ```sql
419
- -- Each user field needs its own JOIN with unique alias
420
- LEFT JOIN user AS em ON em._id = t.event_manager
421
- LEFT JOIN user AS pm ON pm._id = t.production_manager
422
- LEFT JOIN user AS tm ON tm._id = t.ticketing_manager
423
-
424
- -- Then select with COALESCE for clean output
425
- COALESCE(em.name, '') AS Event_Manager,
426
- COALESCE(pm.name, '') AS Production_Manager,
427
- COALESCE(tm.name, '') AS Ticketing_Manager
428
- ```
429
-
430
- **Why COALESCE?** User fields can be empty - COALESCE returns empty string instead of NULL for cleaner display.
431
-
432
- **Common mistake:** Trying to add `user` as a source - don't do this! It's built-in.
433
-
434
- See `insight-join-patterns` skill for detailed JOIN patterns.
435
-
436
- ---
437
-
438
- ## Sorting & Limiting
439
-
440
- ### ORDER BY
441
- ```sql
442
- SELECT * FROM tasks
443
- ORDER BY created DESC
444
-
445
- -- Multiple columns
446
- ORDER BY priority ASC, created DESC
447
- ```
448
-
449
- ### LIMIT
450
- ```sql
451
- SELECT * FROM tasks
452
- ORDER BY created DESC
453
- LIMIT 10
454
-
455
- -- With offset (pagination)
456
- LIMIT 10 OFFSET 20
457
- ```
458
-
459
- ---
460
-
461
- ## String Functions
462
-
463
- ```sql
464
- -- Concatenate
465
- SELECT title || ' - ' || phaseName as combined FROM tasks
466
-
467
- -- Upper/Lower
468
- SELECT UPPER(title), LOWER(status) FROM tasks
469
-
470
- -- Substring
471
- SELECT SUBSTR(title, 1, 50) as short_title FROM tasks
472
-
473
- -- Replace
474
- SELECT REPLACE(title, 'OLD', 'NEW') FROM tasks
475
-
476
- -- Trim
477
- SELECT TRIM(title) FROM tasks
478
-
479
- -- Length
480
- SELECT title, LENGTH(title) as title_length FROM tasks
481
- ```
482
-
483
- ---
484
-
485
- ## NULL Handling
486
-
487
- ```sql
488
- -- COALESCE (first non-null)
489
- SELECT COALESCE(assignee, 'Unassigned') as assignee FROM tasks
490
-
491
- -- IFNULL (SQLite specific)
492
- SELECT IFNULL(amount, 0) as amount FROM orders
493
-
494
- -- NULLIF (return NULL if equal)
495
- SELECT NULLIF(status, 'None') as status FROM tasks
496
- ```
497
-
498
- ---
499
-
500
- ## Subqueries
501
-
502
- ```sql
503
- -- In WHERE
504
- SELECT * FROM tasks
505
- WHERE projectId IN (
506
- SELECT id FROM projects WHERE status = 'Active'
507
- )
508
-
509
- -- As column
510
- SELECT
511
- title,
512
- (SELECT COUNT(*) FROM subtasks s WHERE s.parentId = t.id) as subtask_count
513
- FROM tasks t
514
- ```
515
-
516
- ---
517
-
518
- ## Common Patterns
519
-
520
- ### Count by Phase
521
- ```sql
522
- SELECT
523
- phaseName,
524
- COUNT(*) as count
525
- FROM tasks
526
- GROUP BY phaseName
527
- ORDER BY count DESC
528
- ```
529
-
530
- ### Sum by Customer
531
- ```sql
532
- SELECT
533
- c.name as customer,
534
- SUM(o.amount) as total
535
- FROM orders o
536
- LEFT JOIN customers c ON o.customerId = c.id
537
- GROUP BY c.name
538
- ORDER BY total DESC
539
- ```
540
-
541
- ### Monthly Trend
542
- ```sql
543
- SELECT
544
- strftime('%Y-%m', created, 'unixepoch') as month,
545
- COUNT(*) as count,
546
- SUM(amount) as total
547
- FROM orders
548
- WHERE created > strftime('%s', 'now', '-12 months')
549
- GROUP BY month
550
- ORDER BY month
551
- ```
552
-
553
- ### Completion Rate
554
- ```sql
555
- SELECT
556
- ROUND(
557
- 100.0 * SUM(CASE WHEN phaseName = 'Done' THEN 1 ELSE 0 END) / COUNT(*),
558
- 1
559
- ) as completion_percent
560
- FROM tasks
561
- ```
562
-
563
- ---
564
-
565
- ## Advanced Patterns
566
-
567
- ### Window Functions
568
-
569
- Window functions perform calculations across rows without collapsing them (unlike GROUP BY).
570
-
571
- ```sql
572
- -- Row numbers (ranking)
573
- SELECT
574
- name,
575
- amount,
576
- ROW_NUMBER() OVER (ORDER BY amount DESC) as rank
577
- FROM orders
578
-
579
- -- Running total
580
- SELECT
581
- name,
582
- amount,
583
- SUM(amount) OVER (ORDER BY created) as running_total
584
- FROM orders
585
-
586
- -- Rank within groups (top 3 per customer)
587
- SELECT * FROM (
588
- SELECT
589
- customerName,
590
- name,
591
- amount,
592
- ROW_NUMBER() OVER (PARTITION BY customerName ORDER BY amount DESC) as rank
593
- FROM orders
594
- )
595
- WHERE rank <= 3
596
-
597
- -- Compare to previous row
598
- SELECT
599
- name,
600
- amount,
601
- LAG(amount) OVER (ORDER BY created) as previous_amount,
602
- amount - LAG(amount) OVER (ORDER BY created) as change
603
- FROM orders
604
-
605
- -- Percentage of total
606
- SELECT
607
- phaseName,
608
- COUNT(*) as count,
609
- ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) as percent
610
- FROM tasks
611
- GROUP BY phaseName
612
- ```
613
-
614
- **Available window functions:**
615
- - `ROW_NUMBER()`, `RANK()`, `DENSE_RANK()` - rankings
616
- - `LAG(col)`, `LEAD(col)` - previous/next row values
617
- - `SUM() OVER`, `COUNT() OVER`, `AVG() OVER` - running aggregates
618
- - `FIRST_VALUE()`, `LAST_VALUE()` - boundary values
619
- - `NTILE(n)` - divide into n buckets
620
-
621
- ### CTEs (WITH Clauses)
622
-
623
- CTEs make complex queries readable by breaking them into named steps.
624
-
625
- ```sql
626
- -- Simple CTE
627
- WITH active_orders AS (
628
- SELECT * FROM orders
629
- WHERE phaseName != 'Cancelled'
630
- )
631
- SELECT customerName, SUM(amount) as total
632
- FROM active_orders
633
- GROUP BY customerName
634
-
635
- -- Multiple CTEs
636
- WITH
637
- monthly_sales AS (
638
- SELECT
639
- strftime('%Y-%m', created, 'unixepoch') as month,
640
- SUM(amount) as total
641
- FROM orders
642
- GROUP BY month
643
- ),
644
- monthly_avg AS (
645
- SELECT AVG(total) as avg_total FROM monthly_sales
646
- )
647
- SELECT
648
- ms.month,
649
- ms.total,
650
- ma.avg_total,
651
- CASE WHEN ms.total > ma.avg_total THEN 'Above' ELSE 'Below' END as vs_avg
652
- FROM monthly_sales ms, monthly_avg ma
653
- ORDER BY ms.month
654
-
655
- -- CTE for cleaner JOINs
656
- WITH
657
- customer_totals AS (
658
- SELECT customerId, SUM(amount) as total_spent
659
- FROM orders
660
- GROUP BY customerId
661
- )
662
- SELECT
663
- c.name,
664
- ct.total_spent
665
- FROM customers c
666
- LEFT JOIN customer_totals ct ON c.id = ct.customerId
667
- ORDER BY ct.total_spent DESC
668
- ```
669
-
670
- ### UNION (Combining Results)
671
-
672
- Combine results from multiple queries.
673
-
674
- ```sql
675
- -- UNION (removes duplicates)
676
- SELECT name, 'Task' as type FROM tasks
677
- UNION
678
- SELECT name, 'Project' as type FROM projects
679
-
680
- -- UNION ALL (keeps duplicates, faster)
681
- SELECT name, amount, 'Invoice' as type FROM invoices
682
- UNION ALL
683
- SELECT name, amount, 'Quote' as type FROM quotes
684
-
685
- -- Combined report from multiple workflows
686
- SELECT
687
- 'This Month' as period,
688
- COUNT(*) as count,
689
- SUM(amount) as total
690
- FROM orders
691
- WHERE created >= strftime('%s', 'now', 'start of month')
692
- UNION ALL
693
- SELECT
694
- 'Last Month' as period,
695
- COUNT(*) as count,
696
- SUM(amount) as total
697
- FROM orders
698
- WHERE created >= strftime('%s', 'now', 'start of month', '-1 month')
699
- AND created < strftime('%s', 'now', 'start of month')
700
- ```
701
-
702
- **Note:** UNION requires same number of columns with compatible types.
703
-
704
- ---
705
-
706
- ## Testing Queries
707
-
708
- Always preview before adding to insights.ts:
709
-
710
- ```javascript
711
- mcp__hailer__preview_insight({
712
- sources: [...],
713
- query: "SELECT ..."
714
- })
715
- ```
716
-
717
- If preview returns data without errors, add to `workspace/insights.ts`.
718
-
719
- ---
720
-
721
- ## Common Mistakes
722
-
723
- | Wrong | Right |
724
- |-------|-------|
725
- | Using `"` for identifiers | Use `"` for strings, backticks for identifiers if needed |
726
- | Forgetting `_id` meta for JOINs | Always include `{ name: 'id', meta: '_id' }` |
727
- | INNER JOIN for optional links | Use LEFT JOIN (links can be null) |
728
- | Raw timestamps in output | Format with `strftime()` |
729
- | Skipping preview | Always test with `preview_insight` first |
730
- | Hardcoding workflow IDs | Use `WorkflowIds` enum |
731
- | Generic field names (`date`, `id`) | Use real names (`pvm`, `nro`) that match Hailer UI |
732
- | Using `/ 1000` for dates | Insight fields are already SECONDS - use directly with `strftime()` |
733
- | Using `* 1000` in WHERE | Both sides are seconds - compare directly |
734
- | `t.ajankohta` for range fields | Use `t.ajankohtaStart` and `t.ajankohtaEnd` (auto-suffixed for daterange, datetimerange, timerange) |
735
-
736
- ---
737
-
738
- ## Date Mistakes & What They Look Like
739
-
740
- ### Mistake 1: Using `/ 1000` (OLD HABIT - DON'T DO THIS!)
741
- ```sql
742
- -- WRONG (insight fields are already in seconds!)
743
- strftime('%Y-%m-%d', created / 1000, 'unixepoch')
744
- -- Result: "1970-01-21" (wrong - divided seconds by 1000!)
745
-
746
- -- CORRECT (use field directly)
747
- strftime('%Y-%m-%d', created, 'unixepoch')
748
- -- Result: "2024-01-15"
749
- ```
750
-
751
- ### Mistake 2: Using `* 1000` in WHERE
752
- ```sql
753
- -- WRONG (multiplying seconds by 1000 makes comparison fail)
754
- WHERE created >= strftime('%s', 'now', '-30 days') * 1000
755
- -- 1705312800 >= 1705312800000 → always false!
756
-
757
- -- CORRECT (both sides are seconds)
758
- WHERE created >= strftime('%s', 'now', '-30 days')
759
- -- 1705312800 >= 1705312800 → correct comparison
760
- ```
761
-
762
- ### Mistake 3: timerange is NOT minutes
763
- ```sql
764
- -- WRONG (thinking timerange is minutes from midnight)
765
- WHERE tyoaikaStart < 480 -- "before 8am"
766
-
767
- -- CORRECT (timerange is timestamp in seconds - extract hour)
768
- WHERE strftime('%H', tyoaikaStart, 'unixepoch') < '08'
769
- ```
770
-
771
- ### Mistake 4: Timezone confusion
772
- ```sql
773
- -- WRONG (UTC time, Finland is +2/+3)
774
- strftime('%H:%M', created, 'unixepoch')
775
- -- Shows "08:30" when local time is "10:30"
776
-
777
- -- CORRECT (add timezone offset)
778
- strftime('%H:%M', created, 'unixepoch', '+2 hours')
779
- -- Shows "10:30" (or use +3 during summer time)
780
- ```
781
-
782
- ### Quick Debug
783
- If your dates look wrong:
784
- 1. **Years like 1970** → using `/ 1000` when you shouldn't (field is already seconds)
785
- 2. **No rows returned** → using `* 1000` in WHERE (comparing seconds to huge number)
786
- 3. **Times off by 2-3 hours** → timezone not applied
787
- 4. **Duration calculations** → subtract seconds directly: `(endField - startField) / 60` for minutes