@hailer/mcp 1.1.12 → 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 (271) hide show
  1. package/CHANGELOG.md +0 -7
  2. package/{.claude → dist}/CLAUDE.md +2 -2
  3. package/dist/app.js +18 -5
  4. package/dist/bot/bot-config.d.ts +10 -1
  5. package/dist/bot/bot-config.js +64 -3
  6. package/dist/bot/bot-manager.d.ts +2 -0
  7. package/dist/bot/bot-manager.js +9 -2
  8. package/dist/bot/bot.d.ts +33 -0
  9. package/dist/bot/bot.js +461 -160
  10. package/dist/bot/services/message-classifier.js +17 -0
  11. package/dist/bot/services/permission-guard.d.ts +52 -0
  12. package/dist/bot/services/permission-guard.js +149 -0
  13. package/dist/bot/services/types.d.ts +5 -0
  14. package/dist/bot/services/typing-indicator.d.ts +6 -1
  15. package/dist/bot/services/typing-indicator.js +19 -3
  16. package/dist/cli.js +0 -0
  17. package/dist/config.d.ts +6 -1
  18. package/dist/config.js +43 -0
  19. package/dist/core.js +3 -6
  20. package/dist/lib/discussion-lock.d.ts +42 -0
  21. package/dist/lib/discussion-lock.js +110 -0
  22. package/dist/mcp/UserContextCache.d.ts +5 -0
  23. package/dist/mcp/UserContextCache.js +51 -19
  24. package/dist/mcp/hailer-clients.d.ts +19 -1
  25. package/dist/mcp/hailer-clients.js +158 -24
  26. package/dist/mcp/session-store.d.ts +68 -0
  27. package/dist/mcp/session-store.js +169 -0
  28. package/dist/mcp/signal-handler.js +2 -0
  29. package/dist/mcp/tool-registry.d.ts +17 -4
  30. package/dist/mcp/tool-registry.js +37 -7
  31. package/dist/mcp/tools/activity.js +99 -7
  32. package/dist/mcp/tools/app-scaffold.js +304 -336
  33. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  34. package/dist/mcp/tools/bot-config/constants.js +94 -0
  35. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  36. package/dist/mcp/tools/bot-config/core.js +2456 -0
  37. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  38. package/dist/mcp/tools/bot-config/index.js +59 -0
  39. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  40. package/dist/mcp/tools/bot-config/tools.js +15 -0
  41. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  42. package/dist/mcp/tools/bot-config/types.js +6 -0
  43. package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
  44. package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
  45. package/dist/mcp/tools/company.d.ts +9 -0
  46. package/dist/mcp/tools/company.js +88 -0
  47. package/dist/mcp/tools/discussion.js +68 -0
  48. package/dist/mcp/tools/document.d.ts +11 -0
  49. package/dist/mcp/tools/document.js +741 -0
  50. package/dist/mcp/tools/investigate.d.ts +9 -0
  51. package/dist/mcp/tools/investigate.js +254 -0
  52. package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
  53. package/dist/mcp/tools/workflow-permissions.js +204 -0
  54. package/dist/mcp/tools/workflow.js +57 -18
  55. package/dist/mcp/utils/index.d.ts +2 -0
  56. package/dist/mcp/utils/index.js +12 -1
  57. package/dist/mcp/utils/role-utils.d.ts +74 -0
  58. package/dist/mcp/utils/role-utils.js +151 -0
  59. package/dist/mcp/utils/types.d.ts +43 -1
  60. package/dist/mcp/utils/types.js +14 -0
  61. package/dist/mcp/webhook-handler.d.ts +4 -0
  62. package/dist/mcp/webhook-handler.js +8 -0
  63. package/dist/mcp-server.d.ts +23 -2
  64. package/dist/mcp-server.js +639 -127
  65. package/dist/plugins/vipunen/client.d.ts +150 -0
  66. package/dist/plugins/vipunen/client.js +535 -0
  67. package/dist/plugins/vipunen/config/schema-config.json +19 -0
  68. package/dist/plugins/vipunen/config/schema-doc.json +22 -0
  69. package/dist/plugins/vipunen/index.d.ts +41 -0
  70. package/dist/plugins/vipunen/index.js +88 -0
  71. package/dist/plugins/vipunen/tools.d.ts +26 -0
  72. package/dist/plugins/vipunen/tools.js +501 -0
  73. package/dist/stdio-server.d.ts +14 -0
  74. package/dist/stdio-server.js +101 -0
  75. package/package.json +2 -1
  76. package/.claude/agents/agent-ada-skill-builder.md +0 -94
  77. package/.claude/agents/agent-alejandro-function-fields.md +0 -342
  78. package/.claude/agents/agent-bjorn-config-audit.md +0 -103
  79. package/.claude/agents/agent-builder-agent-creator.md +0 -130
  80. package/.claude/agents/agent-code-simplifier.md +0 -53
  81. package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
  82. package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
  83. package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
  84. package/.claude/agents/agent-helga-workflow-config.md +0 -204
  85. package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
  86. package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
  87. package/.claude/agents/agent-ivan-monolith.md +0 -154
  88. package/.claude/agents/agent-kenji-data-reader.md +0 -86
  89. package/.claude/agents/agent-lars-code-inspector.md +0 -102
  90. package/.claude/agents/agent-marco-mockup-builder.md +0 -110
  91. package/.claude/agents/agent-marcus-api-documenter.md +0 -323
  92. package/.claude/agents/agent-marketplace-publisher.md +0 -280
  93. package/.claude/agents/agent-marketplace-reviewer.md +0 -309
  94. package/.claude/agents/agent-permissions-handler.md +0 -208
  95. package/.claude/agents/agent-simple-writer.md +0 -48
  96. package/.claude/agents/agent-svetlana-code-review.md +0 -171
  97. package/.claude/agents/agent-tanya-test-runner.md +0 -333
  98. package/.claude/agents/agent-ui-designer.md +0 -100
  99. package/.claude/agents/agent-viktor-sql-insights.md +0 -212
  100. package/.claude/agents/agent-web-search.md +0 -55
  101. package/.claude/agents/agent-yevgeni-discussions.md +0 -45
  102. package/.claude/agents/agent-zara-zapier.md +0 -159
  103. package/.claude/commands/app-squad.md +0 -135
  104. package/.claude/commands/audit-squad.md +0 -158
  105. package/.claude/commands/autoplan.md +0 -563
  106. package/.claude/commands/cleanup-squad.md +0 -98
  107. package/.claude/commands/config-squad.md +0 -106
  108. package/.claude/commands/crud-squad.md +0 -87
  109. package/.claude/commands/data-squad.md +0 -97
  110. package/.claude/commands/debug-squad.md +0 -303
  111. package/.claude/commands/doc-squad.md +0 -65
  112. package/.claude/commands/handoff.md +0 -137
  113. package/.claude/commands/health.md +0 -49
  114. package/.claude/commands/help.md +0 -29
  115. package/.claude/commands/help:agents.md +0 -151
  116. package/.claude/commands/help:commands.md +0 -78
  117. package/.claude/commands/help:faq.md +0 -79
  118. package/.claude/commands/help:plugins.md +0 -50
  119. package/.claude/commands/help:skills.md +0 -93
  120. package/.claude/commands/help:tools.md +0 -75
  121. package/.claude/commands/hotfix-squad.md +0 -112
  122. package/.claude/commands/integration-squad.md +0 -82
  123. package/.claude/commands/janitor-squad.md +0 -167
  124. package/.claude/commands/learn-auto.md +0 -120
  125. package/.claude/commands/learn.md +0 -120
  126. package/.claude/commands/mcp-list.md +0 -27
  127. package/.claude/commands/onboard-squad.md +0 -140
  128. package/.claude/commands/plan-workspace.md +0 -732
  129. package/.claude/commands/prd.md +0 -130
  130. package/.claude/commands/project-status.md +0 -82
  131. package/.claude/commands/publish.md +0 -138
  132. package/.claude/commands/recap.md +0 -69
  133. package/.claude/commands/restore.md +0 -64
  134. package/.claude/commands/review-squad.md +0 -152
  135. package/.claude/commands/save.md +0 -24
  136. package/.claude/commands/stats.md +0 -19
  137. package/.claude/commands/swarm.md +0 -210
  138. package/.claude/commands/tool-builder.md +0 -39
  139. package/.claude/commands/ws-pull.md +0 -44
  140. package/.claude/hooks/_shared-memory.cjs +0 -305
  141. package/.claude/hooks/_utils.cjs +0 -108
  142. package/.claude/hooks/agent-failure-detector.cjs +0 -383
  143. package/.claude/hooks/agent-usage-logger.cjs +0 -204
  144. package/.claude/hooks/app-edit-guard.cjs +0 -494
  145. package/.claude/hooks/auto-learn.cjs +0 -304
  146. package/.claude/hooks/bash-guard.cjs +0 -272
  147. package/.claude/hooks/builder-mode-manager.cjs +0 -354
  148. package/.claude/hooks/bulk-activity-guard.cjs +0 -271
  149. package/.claude/hooks/context-watchdog.cjs +0 -230
  150. package/.claude/hooks/delegation-reminder.cjs +0 -465
  151. package/.claude/hooks/design-system-lint.cjs +0 -271
  152. package/.claude/hooks/post-scaffold-hook.cjs +0 -181
  153. package/.claude/hooks/prompt-guard.cjs +0 -354
  154. package/.claude/hooks/publish-template-guard.cjs +0 -147
  155. package/.claude/hooks/session-start.cjs +0 -35
  156. package/.claude/hooks/shared-memory-writer.cjs +0 -147
  157. package/.claude/hooks/skill-injector.cjs +0 -140
  158. package/.claude/hooks/skill-usage-logger.cjs +0 -258
  159. package/.claude/hooks/src-edit-guard.cjs +0 -240
  160. package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
  161. package/.claude/settings.json +0 -257
  162. package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
  163. package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
  164. package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
  165. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
  166. package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
  167. package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
  168. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
  169. package/.claude/skills/agent-structure/SKILL.md +0 -98
  170. package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
  171. package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
  172. package/.claude/skills/delegation-routing/SKILL.md +0 -202
  173. package/.claude/skills/frontend-design/SKILL.md +0 -254
  174. package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
  175. package/.claude/skills/hailer-api-client/SKILL.md +0 -518
  176. package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
  177. package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
  178. package/.claude/skills/hailer-design-system/SKILL.md +0 -235
  179. package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
  180. package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
  181. package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
  182. package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
  183. package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
  184. package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
  185. package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
  186. package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
  187. package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
  188. package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
  189. package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
  190. package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
  191. package/.claude/skills/integration-patterns/SKILL.md +0 -421
  192. package/.claude/skills/json-only-output/SKILL.md +0 -72
  193. package/.claude/skills/lsp-setup/SKILL.md +0 -160
  194. package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
  195. package/.claude/skills/optional-parameters/SKILL.md +0 -72
  196. package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
  197. package/.claude/skills/testing-patterns/SKILL.md +0 -630
  198. package/.claude/skills/tool-builder/SKILL.md +0 -250
  199. package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
  200. package/.claude/skills/tool-response-verification/SKILL.md +0 -92
  201. package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
  202. package/.mcp.json +0 -13
  203. package/.opencode/agent/agent-ada-skill-builder.md +0 -35
  204. package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
  205. package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
  206. package/.opencode/agent/agent-builder-agent-creator.md +0 -39
  207. package/.opencode/agent/agent-code-simplifier.md +0 -31
  208. package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
  209. package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
  210. package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
  211. package/.opencode/agent/agent-helga-workflow-config.md +0 -203
  212. package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
  213. package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
  214. package/.opencode/agent/agent-ivan-monolith.md +0 -46
  215. package/.opencode/agent/agent-kenji-data-reader.md +0 -53
  216. package/.opencode/agent/agent-lars-code-inspector.md +0 -28
  217. package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
  218. package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
  219. package/.opencode/agent/agent-marketplace-publisher.md +0 -44
  220. package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
  221. package/.opencode/agent/agent-permissions-handler.md +0 -50
  222. package/.opencode/agent/agent-simple-writer.md +0 -45
  223. package/.opencode/agent/agent-svetlana-code-review.md +0 -39
  224. package/.opencode/agent/agent-tanya-test-runner.md +0 -57
  225. package/.opencode/agent/agent-ui-designer.md +0 -56
  226. package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
  227. package/.opencode/agent/agent-web-search.md +0 -42
  228. package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
  229. package/.opencode/agent/agent-zara-zapier.md +0 -53
  230. package/.opencode/commands/app-squad.md +0 -135
  231. package/.opencode/commands/audit-squad.md +0 -158
  232. package/.opencode/commands/autoplan.md +0 -563
  233. package/.opencode/commands/cleanup-squad.md +0 -98
  234. package/.opencode/commands/config-squad.md +0 -106
  235. package/.opencode/commands/crud-squad.md +0 -87
  236. package/.opencode/commands/data-squad.md +0 -97
  237. package/.opencode/commands/debug-squad.md +0 -303
  238. package/.opencode/commands/doc-squad.md +0 -65
  239. package/.opencode/commands/handoff.md +0 -137
  240. package/.opencode/commands/health.md +0 -49
  241. package/.opencode/commands/help-agents.md +0 -151
  242. package/.opencode/commands/help-commands.md +0 -32
  243. package/.opencode/commands/help-faq.md +0 -29
  244. package/.opencode/commands/help-plugins.md +0 -28
  245. package/.opencode/commands/help-skills.md +0 -7
  246. package/.opencode/commands/help-tools.md +0 -40
  247. package/.opencode/commands/help.md +0 -28
  248. package/.opencode/commands/hotfix-squad.md +0 -112
  249. package/.opencode/commands/integration-squad.md +0 -82
  250. package/.opencode/commands/janitor-squad.md +0 -167
  251. package/.opencode/commands/learn-auto.md +0 -120
  252. package/.opencode/commands/learn.md +0 -120
  253. package/.opencode/commands/mcp-list.md +0 -27
  254. package/.opencode/commands/onboard-squad.md +0 -140
  255. package/.opencode/commands/plan-workspace.md +0 -732
  256. package/.opencode/commands/prd.md +0 -131
  257. package/.opencode/commands/project-status.md +0 -82
  258. package/.opencode/commands/publish.md +0 -138
  259. package/.opencode/commands/recap.md +0 -69
  260. package/.opencode/commands/restore.md +0 -64
  261. package/.opencode/commands/review-squad.md +0 -152
  262. package/.opencode/commands/save.md +0 -24
  263. package/.opencode/commands/stats.md +0 -19
  264. package/.opencode/commands/swarm.md +0 -210
  265. package/.opencode/commands/tool-builder.md +0 -39
  266. package/.opencode/commands/ws-pull.md +0 -44
  267. package/.opencode/opencode.json +0 -28
  268. package/SESSION-HANDOFF.md +0 -68
  269. package/inbox/2026-03-04-bot-config-patterns.md +0 -24
  270. package/scripts/postinstall.cjs +0 -64
  271. 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