@kelpie/server 0.7.0 → 0.8.0

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 (185) hide show
  1. package/dist/lib/consumerEmailDomains.d.ts +2 -0
  2. package/dist/lib/consumerEmailDomains.d.ts.map +1 -0
  3. package/dist/lib/consumerEmailDomains.js +62 -0
  4. package/dist/lib/consumerEmailDomains.js.map +1 -0
  5. package/dist/lib/email.d.ts +7 -0
  6. package/dist/lib/email.d.ts.map +1 -1
  7. package/dist/lib/email.js.map +1 -1
  8. package/dist/lib/emailContent.d.ts +33 -0
  9. package/dist/lib/emailContent.d.ts.map +1 -0
  10. package/dist/lib/emailContent.js +54 -0
  11. package/dist/lib/emailContent.js.map +1 -0
  12. package/dist/lib/emailDomainAutoLink.d.ts +27 -0
  13. package/dist/lib/emailDomainAutoLink.d.ts.map +1 -0
  14. package/dist/lib/emailDomainAutoLink.js +119 -0
  15. package/dist/lib/emailDomainAutoLink.js.map +1 -0
  16. package/dist/lib/ids.d.ts +2 -0
  17. package/dist/lib/ids.d.ts.map +1 -1
  18. package/dist/lib/ids.js +2 -0
  19. package/dist/lib/ids.js.map +1 -1
  20. package/dist/modules/attachedRecords.d.ts.map +1 -1
  21. package/dist/modules/attachedRecords.js +4 -2
  22. package/dist/modules/attachedRecords.js.map +1 -1
  23. package/dist/modules/auth/preferences.d.ts +11 -1
  24. package/dist/modules/auth/preferences.d.ts.map +1 -1
  25. package/dist/modules/auth/preferences.js +11 -0
  26. package/dist/modules/auth/preferences.js.map +1 -1
  27. package/dist/modules/auth/routes.d.ts.map +1 -1
  28. package/dist/modules/auth/routes.js +18 -0
  29. package/dist/modules/auth/routes.js.map +1 -1
  30. package/dist/modules/auth/schema.d.ts +20 -0
  31. package/dist/modules/auth/schema.d.ts.map +1 -1
  32. package/dist/modules/auth/schema.js +11 -1
  33. package/dist/modules/auth/schema.js.map +1 -1
  34. package/dist/modules/auth/service.d.ts.map +1 -1
  35. package/dist/modules/auth/service.js +27 -3
  36. package/dist/modules/auth/service.js.map +1 -1
  37. package/dist/modules/companies/repository.d.ts +2 -0
  38. package/dist/modules/companies/repository.d.ts.map +1 -1
  39. package/dist/modules/companies/repository.js +1 -0
  40. package/dist/modules/companies/repository.js.map +1 -1
  41. package/dist/modules/companies/routes.d.ts +2 -0
  42. package/dist/modules/companies/routes.d.ts.map +1 -1
  43. package/dist/modules/companies/routes.js +11 -0
  44. package/dist/modules/companies/routes.js.map +1 -1
  45. package/dist/modules/companies/schema.d.ts +17 -0
  46. package/dist/modules/companies/schema.d.ts.map +1 -1
  47. package/dist/modules/companies/schema.js +5 -1
  48. package/dist/modules/companies/schema.js.map +1 -1
  49. package/dist/modules/companies/service.d.ts +1 -0
  50. package/dist/modules/companies/service.d.ts.map +1 -1
  51. package/dist/modules/companies/service.js +22 -0
  52. package/dist/modules/companies/service.js.map +1 -1
  53. package/dist/modules/core.d.ts.map +1 -1
  54. package/dist/modules/core.js +11 -0
  55. package/dist/modules/core.js.map +1 -1
  56. package/dist/modules/email-domain-linker/index.d.ts +12 -0
  57. package/dist/modules/email-domain-linker/index.d.ts.map +1 -0
  58. package/dist/modules/email-domain-linker/index.js +34 -0
  59. package/dist/modules/email-domain-linker/index.js.map +1 -0
  60. package/dist/modules/email-domain-linker/routes.d.ts +14 -0
  61. package/dist/modules/email-domain-linker/routes.d.ts.map +1 -0
  62. package/dist/modules/email-domain-linker/routes.js +15 -0
  63. package/dist/modules/email-domain-linker/routes.js.map +1 -0
  64. package/dist/modules/email-domain-linker/service.d.ts +37 -0
  65. package/dist/modules/email-domain-linker/service.d.ts.map +1 -0
  66. package/dist/modules/email-domain-linker/service.js +63 -0
  67. package/dist/modules/email-domain-linker/service.js.map +1 -0
  68. package/dist/modules/eventCatalogs.d.ts +1 -0
  69. package/dist/modules/eventCatalogs.d.ts.map +1 -1
  70. package/dist/modules/eventCatalogs.js +1 -0
  71. package/dist/modules/eventCatalogs.js.map +1 -1
  72. package/dist/modules/lists/events.d.ts +42 -0
  73. package/dist/modules/lists/events.d.ts.map +1 -0
  74. package/dist/modules/lists/events.js +16 -0
  75. package/dist/modules/lists/events.js.map +1 -0
  76. package/dist/modules/lists/index.d.ts +10 -0
  77. package/dist/modules/lists/index.d.ts.map +1 -0
  78. package/dist/modules/lists/index.js +35 -0
  79. package/dist/modules/lists/index.js.map +1 -0
  80. package/dist/modules/lists/repository.d.ts +69 -0
  81. package/dist/modules/lists/repository.d.ts.map +1 -0
  82. package/dist/modules/lists/repository.js +165 -0
  83. package/dist/modules/lists/repository.js.map +1 -0
  84. package/dist/modules/lists/routes.d.ts +51 -0
  85. package/dist/modules/lists/routes.d.ts.map +1 -0
  86. package/dist/modules/lists/routes.js +160 -0
  87. package/dist/modules/lists/routes.js.map +1 -0
  88. package/dist/modules/lists/schema.d.ts +264 -0
  89. package/dist/modules/lists/schema.d.ts.map +1 -0
  90. package/dist/modules/lists/schema.js +73 -0
  91. package/dist/modules/lists/schema.js.map +1 -0
  92. package/dist/modules/lists/service.d.ts +70 -0
  93. package/dist/modules/lists/service.d.ts.map +1 -0
  94. package/dist/modules/lists/service.js +214 -0
  95. package/dist/modules/lists/service.js.map +1 -0
  96. package/dist/modules/lists/tools.d.ts +4 -0
  97. package/dist/modules/lists/tools.d.ts.map +1 -0
  98. package/dist/modules/lists/tools.js +86 -0
  99. package/dist/modules/lists/tools.js.map +1 -0
  100. package/dist/modules/people/repository.d.ts +11 -0
  101. package/dist/modules/people/repository.d.ts.map +1 -1
  102. package/dist/modules/people/repository.js +16 -0
  103. package/dist/modules/people/repository.js.map +1 -1
  104. package/dist/modules/people/service.d.ts.map +1 -1
  105. package/dist/modules/people/service.js +18 -0
  106. package/dist/modules/people/service.js.map +1 -1
  107. package/dist/modules/positions/routes.d.ts.map +1 -1
  108. package/dist/modules/positions/routes.js +4 -2
  109. package/dist/modules/positions/routes.js.map +1 -1
  110. package/dist/modules/sample-data/fixture.d.ts +139 -0
  111. package/dist/modules/sample-data/fixture.d.ts.map +1 -0
  112. package/dist/modules/sample-data/fixture.js +461 -0
  113. package/dist/modules/sample-data/fixture.js.map +1 -0
  114. package/dist/modules/sample-data/index.d.ts +11 -0
  115. package/dist/modules/sample-data/index.d.ts.map +1 -0
  116. package/dist/modules/sample-data/index.js +43 -0
  117. package/dist/modules/sample-data/index.js.map +1 -0
  118. package/dist/modules/sample-data/routes.d.ts +15 -0
  119. package/dist/modules/sample-data/routes.d.ts.map +1 -0
  120. package/dist/modules/sample-data/routes.js +24 -0
  121. package/dist/modules/sample-data/routes.js.map +1 -0
  122. package/dist/modules/sample-data/service.d.ts +38 -0
  123. package/dist/modules/sample-data/service.d.ts.map +1 -0
  124. package/dist/modules/sample-data/service.js +412 -0
  125. package/dist/modules/sample-data/service.js.map +1 -0
  126. package/dist/modules/sample-data/tools.d.ts +4 -0
  127. package/dist/modules/sample-data/tools.d.ts.map +1 -0
  128. package/dist/modules/sample-data/tools.js +22 -0
  129. package/dist/modules/sample-data/tools.js.map +1 -0
  130. package/dist/modules/smtp-email/index.d.ts +1 -0
  131. package/dist/modules/smtp-email/index.d.ts.map +1 -1
  132. package/dist/modules/smtp-email/index.js +1 -0
  133. package/dist/modules/smtp-email/index.js.map +1 -1
  134. package/dist/modules/workspace/service.d.ts.map +1 -1
  135. package/dist/modules/workspace/service.js +10 -1
  136. package/dist/modules/workspace/service.js.map +1 -1
  137. package/dist/schema/index.d.ts +1 -0
  138. package/dist/schema/index.d.ts.map +1 -1
  139. package/dist/schema/index.js +1 -0
  140. package/dist/schema/index.js.map +1 -1
  141. package/migrations/0021_certain_maggott.sql +30 -0
  142. package/migrations/0022_stormy_manta.sql +1 -0
  143. package/migrations/0023_funny_maddog.sql +1 -0
  144. package/migrations/meta/0021_snapshot.json +5389 -0
  145. package/migrations/meta/0022_snapshot.json +5396 -0
  146. package/migrations/meta/0023_snapshot.json +5403 -0
  147. package/migrations/meta/_journal.json +21 -0
  148. package/package.json +3 -2
  149. package/src/lib/consumerEmailDomains.ts +62 -0
  150. package/src/lib/email.ts +7 -0
  151. package/src/lib/emailContent.ts +91 -0
  152. package/src/lib/emailDomainAutoLink.ts +165 -0
  153. package/src/lib/ids.ts +2 -0
  154. package/src/modules/attachedRecords.ts +9 -2
  155. package/src/modules/auth/preferences.ts +13 -1
  156. package/src/modules/auth/routes.ts +22 -0
  157. package/src/modules/auth/schema.ts +12 -2
  158. package/src/modules/auth/service.ts +37 -3
  159. package/src/modules/companies/repository.ts +3 -0
  160. package/src/modules/companies/routes.ts +13 -0
  161. package/src/modules/companies/schema.ts +5 -1
  162. package/src/modules/companies/service.ts +25 -0
  163. package/src/modules/core.ts +11 -0
  164. package/src/modules/email-domain-linker/index.ts +40 -0
  165. package/src/modules/email-domain-linker/routes.ts +40 -0
  166. package/src/modules/email-domain-linker/service.ts +122 -0
  167. package/src/modules/eventCatalogs.ts +1 -0
  168. package/src/modules/lists/events.ts +41 -0
  169. package/src/modules/lists/index.ts +41 -0
  170. package/src/modules/lists/repository.ts +313 -0
  171. package/src/modules/lists/routes.ts +247 -0
  172. package/src/modules/lists/schema.ts +85 -0
  173. package/src/modules/lists/service.ts +404 -0
  174. package/src/modules/lists/tools.ts +128 -0
  175. package/src/modules/people/repository.ts +26 -0
  176. package/src/modules/people/service.ts +20 -0
  177. package/src/modules/positions/routes.ts +4 -2
  178. package/src/modules/sample-data/fixture.ts +627 -0
  179. package/src/modules/sample-data/index.ts +50 -0
  180. package/src/modules/sample-data/routes.ts +49 -0
  181. package/src/modules/sample-data/service.ts +590 -0
  182. package/src/modules/sample-data/tools.ts +32 -0
  183. package/src/modules/smtp-email/index.ts +8 -1
  184. package/src/modules/workspace/service.ts +13 -1
  185. package/src/schema/index.ts +1 -0
@@ -0,0 +1,627 @@
1
+ import type { AccountType, CompanyStage, IcpFit, SizeBand } from '@kelpie/schemas'
2
+ import type { CandidateStatus, InterviewStage, RoleStatus } from '@kelpie/schemas'
3
+ import type { Influence, PreferredChannel, Relationship } from '@kelpie/schemas'
4
+ import type { PipelineKind, PlanItemStatus, RecordTargetType } from '@kelpie/schemas'
5
+
6
+ /**
7
+ * The sample workspace, as a self-contained fixture.
8
+ *
9
+ * Cross-record links use string keys (`companyKey`, `personKey`, `dealKey`),
10
+ * not ids. The installer resolves them into real ids at insert time. This is
11
+ * data, not migrations: a workspace owner may delete or edit any row later.
12
+ *
13
+ * Kept small on purpose. A reader should be able to see the whole company in one
14
+ * scroll, and a workspace clearing it out should not be a chore.
15
+ */
16
+
17
+ export interface FixtureCompany {
18
+ readonly key: string
19
+ readonly name: string
20
+ readonly domain: string | null
21
+ readonly industry: string | null
22
+ readonly description: string
23
+ readonly stage: CompanyStage
24
+ readonly sizeBand: SizeBand
25
+ readonly hq: string | null
26
+ readonly website: string | null
27
+ readonly accountType: AccountType
28
+ readonly icpFit: IcpFit
29
+ readonly techStack: readonly string[]
30
+ readonly summary: string
31
+ readonly tags: readonly string[]
32
+ }
33
+
34
+ export interface FixturePerson {
35
+ readonly key: string
36
+ readonly name: string
37
+ readonly email: string | null
38
+ readonly location: string | null
39
+ readonly preferredChannel: PreferredChannel
40
+ readonly influence: Influence
41
+ readonly relationship: Relationship
42
+ readonly summary: string
43
+ readonly tags: readonly string[]
44
+ }
45
+
46
+ export interface FixturePosition {
47
+ readonly personKey: string
48
+ readonly companyKey: string
49
+ readonly title: string
50
+ }
51
+
52
+ export interface FixtureDeal {
53
+ readonly key: string
54
+ readonly name: string
55
+ readonly companyKey: string
56
+ readonly stageSlug: string
57
+ readonly valueCents: number | null
58
+ readonly currency: string | null
59
+ readonly expectedClose: string | null
60
+ readonly whyWin: string
61
+ readonly risks: string
62
+ readonly summary: string
63
+ readonly tags: readonly string[]
64
+ readonly peopleKeys: readonly string[]
65
+ }
66
+
67
+ export interface FixturePlanItem {
68
+ readonly targetType: PipelineKind
69
+ readonly targetDealKey: string
70
+ readonly date: string
71
+ readonly title: string
72
+ readonly status: PlanItemStatus
73
+ }
74
+
75
+ export interface FixtureNote {
76
+ readonly targetType: RecordTargetType
77
+ readonly targetKey: string
78
+ readonly body: string
79
+ readonly pinned: boolean
80
+ }
81
+
82
+ export interface FixtureOpportunity {
83
+ readonly key: string
84
+ readonly name: string
85
+ readonly kind: string
86
+ readonly stageSlug: string
87
+ /** Nullable: a grant or speaking slot need not belong to a company on file. */
88
+ readonly companyKey: string | null
89
+ readonly expectedClose: string | null
90
+ readonly summary: string
91
+ readonly tags: readonly string[]
92
+ }
93
+
94
+ export interface FixtureRaise {
95
+ readonly key: string
96
+ readonly name: string
97
+ readonly companyKey: string
98
+ readonly stageSlug: string
99
+ readonly checkSizeCents: number | null
100
+ readonly currency: string | null
101
+ readonly thesisFit: string
102
+ readonly expectedClose: string | null
103
+ readonly summary: string
104
+ readonly tags: readonly string[]
105
+ readonly peopleKeys: readonly string[]
106
+ }
107
+
108
+ export interface FixturePartnership {
109
+ readonly key: string
110
+ readonly name: string
111
+ readonly companyKey: string
112
+ readonly stageSlug: string
113
+ readonly kind: string
114
+ readonly nextTouchpoint: string | null
115
+ readonly goals: string
116
+ readonly successLooksLike: string
117
+ readonly summary: string
118
+ readonly tags: readonly string[]
119
+ readonly peopleKeys: readonly string[]
120
+ }
121
+
122
+ export interface FixtureRole {
123
+ readonly key: string
124
+ readonly title: string
125
+ readonly status: RoleStatus
126
+ }
127
+
128
+ export interface FixtureCandidate {
129
+ readonly key: string
130
+ readonly personKey: string
131
+ readonly roleKey: string
132
+ readonly status: CandidateStatus
133
+ /** Only set when `status` is `in_process`. Other statuses clear it. */
134
+ readonly interviewStage: InterviewStage | null
135
+ readonly referrerPersonKey: string | null
136
+ }
137
+
138
+ export interface Fixture {
139
+ readonly companies: readonly FixtureCompany[]
140
+ readonly people: readonly FixturePerson[]
141
+ readonly positions: readonly FixturePosition[]
142
+ readonly deals: readonly FixtureDeal[]
143
+ readonly plans: readonly FixturePlanItem[]
144
+ readonly notes: readonly FixtureNote[]
145
+ readonly opportunities: readonly FixtureOpportunity[]
146
+ readonly raises: readonly FixtureRaise[]
147
+ readonly partnerships: readonly FixturePartnership[]
148
+ readonly roles: readonly FixtureRole[]
149
+ readonly candidates: readonly FixtureCandidate[]
150
+ }
151
+
152
+ export const SAMPLE_DATA_FIXTURE: Fixture = {
153
+ companies: [
154
+ {
155
+ key: 'northwind',
156
+ name: 'Northwind Traders',
157
+ domain: 'northwind.dev',
158
+ industry: 'Logistics software',
159
+ description: 'Freight forwarding platform for mid-market importers.',
160
+ stage: 'growth',
161
+ sizeBand: '51-200',
162
+ hq: 'Melbourne, AU',
163
+ website: 'https://northwind.dev',
164
+ accountType: 'customer',
165
+ icpFit: 'high',
166
+ techStack: ['Postgres', 'Node', 'React'],
167
+ summary: 'Active customer. Two seats, expanding to five in Q3.',
168
+ tags: ['customer', 'expansion'],
169
+ },
170
+ {
171
+ key: 'globex',
172
+ name: 'Globex Corporation',
173
+ domain: 'globex.example',
174
+ industry: 'Manufacturing',
175
+ description: 'Industrial IoT platform for factory operators.',
176
+ stage: 'enterprise',
177
+ sizeBand: '201+',
178
+ hq: 'Sydney, AU',
179
+ website: 'https://globex.example',
180
+ accountType: 'prospect',
181
+ icpFit: 'medium',
182
+ techStack: ['Java', 'Kafka'],
183
+ summary: 'Qualifying. Legal review is the current gate.',
184
+ tags: ['enterprise'],
185
+ },
186
+ {
187
+ key: 'initech',
188
+ name: 'Initech',
189
+ domain: 'initech.example',
190
+ industry: 'Financial services',
191
+ description: 'Accounts payable automation for SMBs.',
192
+ stage: 'startup',
193
+ sizeBand: '11-50',
194
+ hq: 'Brisbane, AU',
195
+ website: 'https://initech.example',
196
+ accountType: 'prospect',
197
+ icpFit: 'high',
198
+ techStack: ['Python', 'Postgres'],
199
+ summary: 'Inbound from the demo form last week.',
200
+ tags: ['inbound'],
201
+ },
202
+ {
203
+ key: 'hooli',
204
+ name: 'Hooli',
205
+ domain: 'hooli.example',
206
+ industry: 'Consumer platform',
207
+ description: 'Search and discovery for local services.',
208
+ stage: 'growth',
209
+ sizeBand: '51-200',
210
+ hq: 'Perth, AU',
211
+ website: 'https://hooli.example',
212
+ accountType: 'partner',
213
+ icpFit: 'medium',
214
+ techStack: ['Go', 'gRPC'],
215
+ summary: 'Referral partner. Sends two or three inbound leads a month.',
216
+ tags: ['partner'],
217
+ },
218
+ {
219
+ key: 'stark',
220
+ name: 'Stark Industries',
221
+ domain: 'stark.example',
222
+ industry: 'Aerospace',
223
+ description: 'Contract manufacturing for defence primes.',
224
+ stage: 'enterprise',
225
+ sizeBand: '201+',
226
+ hq: 'Adelaide, AU',
227
+ website: 'https://stark.example',
228
+ accountType: 'prospect',
229
+ icpFit: 'low',
230
+ techStack: [],
231
+ summary: 'Not a fit today. Keep warm for a future compliance module.',
232
+ tags: ['not-a-fit'],
233
+ },
234
+ {
235
+ key: 'sequoia',
236
+ name: 'Southern Cross Ventures',
237
+ domain: 'southerncross.example',
238
+ industry: 'Venture capital',
239
+ description: 'Seed-stage investor, one to five million cheque size.',
240
+ stage: 'other',
241
+ sizeBand: '11-50',
242
+ hq: 'Sydney, AU',
243
+ website: 'https://southerncross.example',
244
+ accountType: 'investor',
245
+ icpFit: 'unknown',
246
+ techStack: [],
247
+ summary: 'Warm intro from a portfolio founder in June.',
248
+ tags: ['investor'],
249
+ },
250
+ {
251
+ key: 'sandbox',
252
+ name: 'Sandbox Accelerator',
253
+ domain: 'sandbox.example',
254
+ industry: 'Startup accelerator',
255
+ description: 'Twelve week program with a demo day and alumni network.',
256
+ stage: 'other',
257
+ sizeBand: '11-50',
258
+ hq: 'Melbourne, AU',
259
+ website: 'https://sandbox.example',
260
+ accountType: 'partner',
261
+ icpFit: 'unknown',
262
+ techStack: [],
263
+ summary: 'Alumni of the 2024 cohort. Runs regular founder events.',
264
+ tags: ['accelerator'],
265
+ },
266
+ ],
267
+
268
+ people: [
269
+ {
270
+ key: 'ada',
271
+ name: 'Ada Lovelace',
272
+ email: 'ada@northwind.dev',
273
+ location: 'Melbourne, AU',
274
+ preferredChannel: 'email',
275
+ influence: 'champion',
276
+ relationship: 'strong',
277
+ summary: 'Executive sponsor at Northwind. Signs off on renewals.',
278
+ tags: ['champion'],
279
+ },
280
+ {
281
+ key: 'grace',
282
+ name: 'Grace Hopper',
283
+ email: 'grace@northwind.dev',
284
+ location: 'Melbourne, AU',
285
+ preferredChannel: 'email',
286
+ influence: 'influencer',
287
+ relationship: 'warm',
288
+ summary: 'Operations lead at Northwind. Day-to-day contact.',
289
+ tags: [],
290
+ },
291
+ {
292
+ key: 'tom',
293
+ name: 'Tom Anderson',
294
+ email: 'tom@globex.example',
295
+ location: 'Sydney, AU',
296
+ preferredChannel: 'call',
297
+ influence: 'decision_maker',
298
+ relationship: 'warm',
299
+ summary: 'Head of engineering at Globex. Decision maker on the qualifying deal.',
300
+ tags: ['decision-maker'],
301
+ },
302
+ {
303
+ key: 'peter',
304
+ name: 'Peter Gibbons',
305
+ email: 'peter@initech.example',
306
+ location: 'Brisbane, AU',
307
+ preferredChannel: 'email',
308
+ influence: 'influencer',
309
+ relationship: 'warm',
310
+ summary: 'Product manager at Initech. Filled in the demo form.',
311
+ tags: ['inbound'],
312
+ },
313
+ {
314
+ key: 'gavin',
315
+ name: 'Gavin Belson',
316
+ email: 'gavin@hooli.example',
317
+ location: 'Perth, AU',
318
+ preferredChannel: 'linkedin',
319
+ influence: 'decision_maker',
320
+ relationship: 'warm',
321
+ summary: 'Partnerships lead at Hooli. Introduced Initech last month.',
322
+ tags: ['partner'],
323
+ },
324
+ {
325
+ key: 'roelof',
326
+ name: 'Roelof Nkosi',
327
+ email: 'roelof@southerncross.example',
328
+ location: 'Sydney, AU',
329
+ preferredChannel: 'email',
330
+ influence: 'decision_maker',
331
+ relationship: 'warm',
332
+ summary: 'Partner at Southern Cross. Leading the diligence on our seed round.',
333
+ tags: ['investor'],
334
+ },
335
+ {
336
+ key: 'mei',
337
+ name: 'Mei Zhang',
338
+ email: 'mei@sandbox.example',
339
+ location: 'Melbourne, AU',
340
+ preferredChannel: 'email',
341
+ influence: 'influencer',
342
+ relationship: 'warm',
343
+ summary: 'Program director at Sandbox. Runs the alumni network.',
344
+ tags: ['partner'],
345
+ },
346
+ {
347
+ key: 'charlotte',
348
+ name: 'Charlotte Rivera',
349
+ email: 'charlotte@example.com',
350
+ location: 'Melbourne, AU',
351
+ preferredChannel: 'email',
352
+ influence: 'end_user',
353
+ relationship: 'warm',
354
+ summary: 'Backend engineer applying for the senior role. Strong TypeScript background.',
355
+ tags: ['candidate'],
356
+ },
357
+ {
358
+ key: 'omar',
359
+ name: 'Omar Haddad',
360
+ email: 'omar@example.com',
361
+ location: 'Sydney, AU',
362
+ preferredChannel: 'email',
363
+ influence: 'end_user',
364
+ relationship: 'cold',
365
+ summary: 'Product designer, sourced through the Sandbox alumni list.',
366
+ tags: ['candidate'],
367
+ },
368
+ ],
369
+
370
+ positions: [
371
+ { personKey: 'ada', companyKey: 'northwind', title: 'CEO' },
372
+ { personKey: 'grace', companyKey: 'northwind', title: 'Head of Operations' },
373
+ { personKey: 'tom', companyKey: 'globex', title: 'VP Engineering' },
374
+ { personKey: 'peter', companyKey: 'initech', title: 'Product Manager' },
375
+ { personKey: 'gavin', companyKey: 'hooli', title: 'Head of Partnerships' },
376
+ { personKey: 'roelof', companyKey: 'sequoia', title: 'Partner' },
377
+ { personKey: 'mei', companyKey: 'sandbox', title: 'Program Director' },
378
+ ],
379
+
380
+ deals: [
381
+ {
382
+ key: 'northwind-expansion',
383
+ name: 'Northwind — seat expansion',
384
+ companyKey: 'northwind',
385
+ stageSlug: 'proposal',
386
+ valueCents: 4_800_000,
387
+ currency: 'AUD',
388
+ expectedClose: nextQuarterEnd(),
389
+ whyWin: 'Active customer, executive sponsor, adding three teams.',
390
+ risks: 'Procurement wants a two-year commit at the current per-seat rate.',
391
+ summary: 'Expansion from two to five seats.',
392
+ tags: ['expansion'],
393
+ peopleKeys: ['ada', 'grace'],
394
+ },
395
+ {
396
+ key: 'globex-new',
397
+ name: 'Globex — new logo',
398
+ companyKey: 'globex',
399
+ stageSlug: 'qualifying',
400
+ valueCents: 12_000_000,
401
+ currency: 'AUD',
402
+ expectedClose: nextQuarterEnd(),
403
+ whyWin: 'Replaces a bespoke tool their engineering team already resents.',
404
+ risks: 'Legal redlines around data residency.',
405
+ summary: 'Qualifying. Legal is the gate.',
406
+ tags: ['enterprise'],
407
+ peopleKeys: ['tom'],
408
+ },
409
+ {
410
+ key: 'initech-new',
411
+ name: 'Initech — pilot',
412
+ companyKey: 'initech',
413
+ stageSlug: 'qualifying',
414
+ valueCents: 1_800_000,
415
+ currency: 'AUD',
416
+ expectedClose: null,
417
+ whyWin: 'Inbound. Product manager already sold internally.',
418
+ risks: 'Small team. Budget is not confirmed.',
419
+ summary: 'Inbound from the demo form.',
420
+ tags: ['inbound'],
421
+ peopleKeys: ['peter'],
422
+ },
423
+ ],
424
+
425
+ plans: [
426
+ {
427
+ targetType: 'deal',
428
+ targetDealKey: 'northwind-expansion',
429
+ date: aWeekOut(),
430
+ title: 'Send revised order form',
431
+ status: 'todo',
432
+ },
433
+ {
434
+ targetType: 'deal',
435
+ targetDealKey: 'globex-new',
436
+ date: aWeekOut(),
437
+ title: 'Reply to legal on data residency',
438
+ status: 'todo',
439
+ },
440
+ {
441
+ targetType: 'deal',
442
+ targetDealKey: 'initech-new',
443
+ date: threeDaysOut(),
444
+ title: 'Book a 30-minute discovery call',
445
+ status: 'todo',
446
+ },
447
+ ],
448
+
449
+ notes: [
450
+ {
451
+ targetType: 'company',
452
+ targetKey: 'northwind',
453
+ body: 'Renewal cycle in October. Ada asked for a joint success plan.',
454
+ pinned: true,
455
+ },
456
+ {
457
+ targetType: 'deal',
458
+ targetKey: 'globex-new',
459
+ body: 'Tom prefers a call. Book Wednesday afternoons.',
460
+ pinned: false,
461
+ },
462
+ {
463
+ targetType: 'person',
464
+ targetKey: 'peter',
465
+ body: 'Attended the June webinar. Watched the follow-up demo twice.',
466
+ pinned: false,
467
+ },
468
+ {
469
+ targetType: 'raise',
470
+ targetKey: 'southern-cross-seed',
471
+ body: 'Roelof asked for the updated deck and the last two months of MRR.',
472
+ pinned: true,
473
+ },
474
+ {
475
+ targetType: 'partnership',
476
+ targetKey: 'sandbox-alumni',
477
+ body: 'Mei can slot us into the next alumni dinner in September.',
478
+ pinned: false,
479
+ },
480
+ {
481
+ targetType: 'candidate',
482
+ targetKey: 'charlotte-senior-engineer',
483
+ body: 'Screen call went well. Move to interview loop next week.',
484
+ pinned: false,
485
+ },
486
+ ],
487
+
488
+ opportunities: [
489
+ {
490
+ key: 'yc-w27',
491
+ name: 'Y Combinator W27 application',
492
+ kind: 'Accelerator',
493
+ stageSlug: 'applied',
494
+ companyKey: null,
495
+ expectedClose: nextQuarterEnd(),
496
+ summary: 'Applied October last cycle. Interview invitations go out in six weeks.',
497
+ tags: ['accelerator'],
498
+ },
499
+ {
500
+ key: 'sandbox-cohort',
501
+ name: 'Sandbox Autumn Cohort',
502
+ kind: 'Accelerator',
503
+ stageSlug: 'identified',
504
+ companyKey: 'sandbox',
505
+ expectedClose: null,
506
+ summary: 'Program starts in March. Mei suggested we apply this round.',
507
+ tags: ['accelerator'],
508
+ },
509
+ {
510
+ key: 'innovation-grant',
511
+ name: 'Innovation Australia grant',
512
+ kind: 'Grant',
513
+ stageSlug: 'identified',
514
+ companyKey: null,
515
+ expectedClose: nextQuarterEnd(),
516
+ summary: 'Non-dilutive. Up to $500k. Application window opens next month.',
517
+ tags: ['grant'],
518
+ },
519
+ ],
520
+
521
+ raises: [
522
+ {
523
+ key: 'southern-cross-seed',
524
+ name: 'Southern Cross — Seed',
525
+ companyKey: 'sequoia',
526
+ stageSlug: 'diligence',
527
+ checkSizeCents: 200_000_000,
528
+ currency: 'AUD',
529
+ thesisFit: 'Focus on developer tools and vertical SaaS. Strong fit for Kelpie.',
530
+ expectedClose: nextQuarterEnd(),
531
+ summary: 'Diligence in progress. Reference calls scheduled next week.',
532
+ tags: ['seed'],
533
+ peopleKeys: ['roelof'],
534
+ },
535
+ ],
536
+
537
+ partnerships: [
538
+ {
539
+ key: 'hooli-referral',
540
+ name: 'Hooli — referral partner',
541
+ companyKey: 'hooli',
542
+ stageSlug: 'active',
543
+ kind: 'Referral',
544
+ nextTouchpoint: aWeekOut(),
545
+ goals: 'Two to three inbound leads per month.',
546
+ successLooksLike: 'Steady referrals plus one joint case study by end of quarter.',
547
+ summary: 'Active. Gavin is the day-to-day contact.',
548
+ tags: ['referral'],
549
+ peopleKeys: ['gavin'],
550
+ },
551
+ {
552
+ key: 'sandbox-alumni',
553
+ name: 'Sandbox — alumni network',
554
+ companyKey: 'sandbox',
555
+ stageSlug: 'exploring',
556
+ kind: 'Community',
557
+ nextTouchpoint: aWeekOut(),
558
+ goals: 'Access alumni events and demo day slots.',
559
+ successLooksLike: 'One demo day slot per year plus warm intros into portfolio.',
560
+ summary: 'Exploring. Mei can slot us into the next alumni dinner.',
561
+ tags: ['community'],
562
+ peopleKeys: ['mei'],
563
+ },
564
+ ],
565
+
566
+ roles: [
567
+ { key: 'senior-engineer', title: 'Senior Software Engineer', status: 'open' },
568
+ { key: 'product-designer', title: 'Product Designer', status: 'open' },
569
+ ],
570
+
571
+ candidates: [
572
+ {
573
+ key: 'charlotte-senior-engineer',
574
+ personKey: 'charlotte',
575
+ roleKey: 'senior-engineer',
576
+ status: 'in_process',
577
+ interviewStage: 'screen',
578
+ referrerPersonKey: null,
579
+ },
580
+ {
581
+ key: 'omar-product-designer',
582
+ personKey: 'omar',
583
+ roleKey: 'product-designer',
584
+ status: 'in_process',
585
+ interviewStage: 'sourced',
586
+ referrerPersonKey: 'mei',
587
+ },
588
+ {
589
+ key: 'peter-senior-engineer',
590
+ personKey: 'peter',
591
+ roleKey: 'senior-engineer',
592
+ status: 'nurture',
593
+ interviewStage: null,
594
+ referrerPersonKey: null,
595
+ },
596
+ ],
597
+ }
598
+
599
+ /**
600
+ * The last day of the current calendar quarter, as `YYYY-MM-DD`.
601
+ *
602
+ * A fixture that hardcoded a date would age. This keeps the deal in a
603
+ * plausible future window without asking the caller to keep it fresh.
604
+ */
605
+ function nextQuarterEnd(): string {
606
+ const now = new Date()
607
+ const quarter = Math.floor(now.getUTCMonth() / 3)
608
+ const lastMonth = quarter * 3 + 2
609
+ const year = now.getUTCFullYear()
610
+ const end = new Date(Date.UTC(year, lastMonth + 1, 0))
611
+
612
+ return end.toISOString().slice(0, 10)
613
+ }
614
+
615
+ function aWeekOut(): string {
616
+ const now = new Date()
617
+ now.setUTCDate(now.getUTCDate() + 7)
618
+
619
+ return now.toISOString().slice(0, 10)
620
+ }
621
+
622
+ function threeDaysOut(): string {
623
+ const now = new Date()
624
+ now.setUTCDate(now.getUTCDate() + 3)
625
+
626
+ return now.toISOString().slice(0, 10)
627
+ }
@@ -0,0 +1,50 @@
1
+ import type { KelpieModule } from '../../runtime/module.ts'
2
+ import { mountSampleDataRoutes } from './routes.ts'
3
+ import { createSampleDataService } from './service.ts'
4
+ import { registerSampleDataTools } from './tools.ts'
5
+
6
+ /**
7
+ * Sample data: one-shot fixture install for a new workspace.
8
+ *
9
+ * Owns no tables. Requires the modules whose tables the fixture writes into.
10
+ * A workspace admin invokes it from the setup wizard or from the admin data
11
+ * page; an agent invokes it through the matching MCP tool.
12
+ */
13
+ export function createSampleDataModule(): KelpieModule {
14
+ return {
15
+ id: 'sample-data',
16
+ requires: [
17
+ 'workspace',
18
+ 'pipelines',
19
+ 'companies',
20
+ 'people',
21
+ 'positions',
22
+ 'deals',
23
+ 'plans',
24
+ 'notes',
25
+ 'opportunities',
26
+ 'raises',
27
+ 'partnerships',
28
+ 'hiring',
29
+ ],
30
+
31
+ register(context) {
32
+ const service = createSampleDataService({
33
+ db: context.db,
34
+ transaction: context.transaction,
35
+ createId: context.createId,
36
+ now: context.now,
37
+ })
38
+
39
+ context.routes((router) => {
40
+ mountSampleDataRoutes(router, { db: context.db, now: context.now, service })
41
+ })
42
+
43
+ registerSampleDataTools(context.mcp, service)
44
+
45
+ return Promise.resolve()
46
+ },
47
+ }
48
+ }
49
+
50
+ export type { SampleDataCounts, SampleDataService } from './service.ts'