@skillrecordings/cli 0.1.0 → 0.2.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 (136) hide show
  1. package/bin/skill.mjs +27 -0
  2. package/dist/chunk-2NCCVTEE.js +22342 -0
  3. package/dist/chunk-2NCCVTEE.js.map +1 -0
  4. package/dist/chunk-3E3GYSZR.js +7071 -0
  5. package/dist/chunk-3E3GYSZR.js.map +1 -0
  6. package/dist/chunk-F4EM72IH.js +86 -0
  7. package/dist/chunk-F4EM72IH.js.map +1 -0
  8. package/dist/chunk-FGP7KUQW.js +432 -0
  9. package/dist/chunk-FGP7KUQW.js.map +1 -0
  10. package/dist/chunk-H3D6VCME.js +55 -0
  11. package/dist/chunk-H3D6VCME.js.map +1 -0
  12. package/dist/chunk-HK3PEWFD.js +208 -0
  13. package/dist/chunk-HK3PEWFD.js.map +1 -0
  14. package/dist/chunk-KEV3QKXP.js +4495 -0
  15. package/dist/chunk-KEV3QKXP.js.map +1 -0
  16. package/dist/chunk-MG37YDAK.js +882 -0
  17. package/dist/chunk-MG37YDAK.js.map +1 -0
  18. package/dist/chunk-MLNDSBZ4.js +482 -0
  19. package/dist/chunk-MLNDSBZ4.js.map +1 -0
  20. package/dist/chunk-N2WIV2JV.js +22 -0
  21. package/dist/chunk-N2WIV2JV.js.map +1 -0
  22. package/dist/chunk-PWWRCN5W.js +2067 -0
  23. package/dist/chunk-PWWRCN5W.js.map +1 -0
  24. package/dist/chunk-SKHBM3XP.js +7746 -0
  25. package/dist/chunk-SKHBM3XP.js.map +1 -0
  26. package/dist/chunk-WFANXVQG.js +64 -0
  27. package/dist/chunk-WFANXVQG.js.map +1 -0
  28. package/dist/chunk-WYKL32C3.js +275 -0
  29. package/dist/chunk-WYKL32C3.js.map +1 -0
  30. package/dist/chunk-ZNF7XD2S.js +134 -0
  31. package/dist/chunk-ZNF7XD2S.js.map +1 -0
  32. package/dist/config-AUAIYDSI.js +20 -0
  33. package/dist/config-AUAIYDSI.js.map +1 -0
  34. package/dist/fileFromPath-XN7LXIBI.js +134 -0
  35. package/dist/fileFromPath-XN7LXIBI.js.map +1 -0
  36. package/dist/getMachineId-bsd-KW2E7VK3.js +42 -0
  37. package/dist/getMachineId-bsd-KW2E7VK3.js.map +1 -0
  38. package/dist/getMachineId-darwin-ROXJUJX5.js +42 -0
  39. package/dist/getMachineId-darwin-ROXJUJX5.js.map +1 -0
  40. package/dist/getMachineId-linux-KVZEHQSU.js +34 -0
  41. package/dist/getMachineId-linux-KVZEHQSU.js.map +1 -0
  42. package/dist/getMachineId-unsupported-PPRILPPA.js +25 -0
  43. package/dist/getMachineId-unsupported-PPRILPPA.js.map +1 -0
  44. package/dist/getMachineId-win-IIF36LEJ.js +44 -0
  45. package/dist/getMachineId-win-IIF36LEJ.js.map +1 -0
  46. package/dist/index.js +112703 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/lib-R6DEEJCP.js +7623 -0
  49. package/dist/lib-R6DEEJCP.js.map +1 -0
  50. package/dist/pipeline-IAVVAKTU.js +120 -0
  51. package/dist/pipeline-IAVVAKTU.js.map +1 -0
  52. package/dist/query-NTP5NVXN.js +25 -0
  53. package/dist/query-NTP5NVXN.js.map +1 -0
  54. package/dist/routing-BAEPFB7V.js +390 -0
  55. package/dist/routing-BAEPFB7V.js.map +1 -0
  56. package/dist/stripe-lookup-charge-EPRUMZDL.js +56 -0
  57. package/dist/stripe-lookup-charge-EPRUMZDL.js.map +1 -0
  58. package/dist/stripe-payment-history-SJPKA63N.js +67 -0
  59. package/dist/stripe-payment-history-SJPKA63N.js.map +1 -0
  60. package/dist/stripe-subscription-status-L4Z65GB3.js +58 -0
  61. package/dist/stripe-subscription-status-L4Z65GB3.js.map +1 -0
  62. package/dist/stripe-verify-refund-FZDKCIUQ.js +54 -0
  63. package/dist/stripe-verify-refund-FZDKCIUQ.js.map +1 -0
  64. package/dist/support-memory-WSG7SDKG.js +10 -0
  65. package/dist/support-memory-WSG7SDKG.js.map +1 -0
  66. package/package.json +10 -7
  67. package/.env.encrypted +0 -0
  68. package/CHANGELOG.md +0 -35
  69. package/data/tt-archive-dataset.json +0 -1
  70. package/data/validate-test-dataset.json +0 -97
  71. package/docs/CLI-AUTH.md +0 -504
  72. package/preload.ts +0 -18
  73. package/src/__tests__/init.test.ts +0 -74
  74. package/src/alignment-test.ts +0 -64
  75. package/src/check-apps.ts +0 -16
  76. package/src/commands/auth/decrypt.ts +0 -123
  77. package/src/commands/auth/encrypt.ts +0 -81
  78. package/src/commands/auth/index.ts +0 -50
  79. package/src/commands/auth/keygen.ts +0 -41
  80. package/src/commands/auth/status.ts +0 -164
  81. package/src/commands/axiom/forensic.ts +0 -868
  82. package/src/commands/axiom/index.ts +0 -697
  83. package/src/commands/build-dataset.ts +0 -311
  84. package/src/commands/db-status.ts +0 -47
  85. package/src/commands/deploys.ts +0 -219
  86. package/src/commands/eval-local/compare.ts +0 -171
  87. package/src/commands/eval-local/health.ts +0 -212
  88. package/src/commands/eval-local/index.ts +0 -76
  89. package/src/commands/eval-local/real-tools.ts +0 -416
  90. package/src/commands/eval-local/run.ts +0 -1168
  91. package/src/commands/eval-local/score-production.ts +0 -256
  92. package/src/commands/eval-local/seed.ts +0 -276
  93. package/src/commands/eval-pipeline/index.ts +0 -53
  94. package/src/commands/eval-pipeline/real-tools.ts +0 -492
  95. package/src/commands/eval-pipeline/run.ts +0 -1316
  96. package/src/commands/eval-pipeline/seed.ts +0 -395
  97. package/src/commands/eval-prompt.ts +0 -496
  98. package/src/commands/eval.test.ts +0 -253
  99. package/src/commands/eval.ts +0 -108
  100. package/src/commands/faq-classify.ts +0 -460
  101. package/src/commands/faq-cluster.ts +0 -135
  102. package/src/commands/faq-extract.ts +0 -249
  103. package/src/commands/faq-mine.ts +0 -432
  104. package/src/commands/faq-review.ts +0 -426
  105. package/src/commands/front/index.ts +0 -351
  106. package/src/commands/front/pull-conversations.ts +0 -275
  107. package/src/commands/front/tags.ts +0 -825
  108. package/src/commands/front-cache.ts +0 -1277
  109. package/src/commands/front-stats.ts +0 -75
  110. package/src/commands/health.test.ts +0 -82
  111. package/src/commands/health.ts +0 -362
  112. package/src/commands/init.test.ts +0 -89
  113. package/src/commands/init.ts +0 -106
  114. package/src/commands/inngest/client.ts +0 -294
  115. package/src/commands/inngest/events.ts +0 -296
  116. package/src/commands/inngest/investigate.ts +0 -382
  117. package/src/commands/inngest/runs.ts +0 -149
  118. package/src/commands/inngest/signal.ts +0 -143
  119. package/src/commands/kb-sync.ts +0 -498
  120. package/src/commands/memory/find.ts +0 -135
  121. package/src/commands/memory/get.ts +0 -87
  122. package/src/commands/memory/index.ts +0 -97
  123. package/src/commands/memory/stats.ts +0 -163
  124. package/src/commands/memory/store.ts +0 -49
  125. package/src/commands/memory/vote.ts +0 -159
  126. package/src/commands/pipeline.ts +0 -127
  127. package/src/commands/responses.ts +0 -856
  128. package/src/commands/tools.ts +0 -293
  129. package/src/commands/wizard.ts +0 -319
  130. package/src/index.ts +0 -172
  131. package/src/lib/crypto.ts +0 -56
  132. package/src/lib/env-loader.ts +0 -206
  133. package/src/lib/onepassword.ts +0 -137
  134. package/src/test-agent-local.ts +0 -115
  135. package/tsconfig.json +0 -11
  136. package/vitest.config.ts +0 -10
@@ -1,351 +0,0 @@
1
- /**
2
- * Front CLI commands for debugging and investigation
3
- *
4
- * Provides direct access to Front API for:
5
- * - Fetching messages (body, author, recipients)
6
- * - Fetching conversations with message history
7
- * - Listing and looking up teammates
8
- * - Comparing webhook data vs API data
9
- */
10
-
11
- import { createInstrumentedFrontClient } from '@skillrecordings/core/front/instrumented-client'
12
- import type {
13
- Message as FrontMessage,
14
- MessageList,
15
- } from '@skillrecordings/front-sdk'
16
- import type { Command } from 'commander'
17
- import { registerCacheCommand } from '../front-cache'
18
- import { registerPullCommand } from './pull-conversations'
19
- import { registerTagCommands } from './tags'
20
-
21
- type Message = FrontMessage
22
-
23
- /**
24
- * Get Front API client from environment (instrumented)
25
- */
26
- function getFrontClient() {
27
- const apiToken = process.env.FRONT_API_TOKEN
28
- if (!apiToken) {
29
- throw new Error('FRONT_API_TOKEN environment variable is required')
30
- }
31
- return createInstrumentedFrontClient({ apiToken })
32
- }
33
-
34
- /**
35
- * Get Front SDK client from environment (full typed client)
36
- */
37
- function getFrontSdkClient() {
38
- const apiToken = process.env.FRONT_API_TOKEN
39
- if (!apiToken) {
40
- throw new Error('FRONT_API_TOKEN environment variable is required')
41
- }
42
- return createInstrumentedFrontClient({ apiToken })
43
- }
44
-
45
- /**
46
- * Format timestamp to human-readable
47
- */
48
- function formatTimestamp(ts: number): string {
49
- return new Date(ts * 1000).toLocaleString('en-US', {
50
- month: 'short',
51
- day: 'numeric',
52
- hour: '2-digit',
53
- minute: '2-digit',
54
- })
55
- }
56
-
57
- /**
58
- * Truncate string with ellipsis
59
- */
60
- function truncate(str: string, len: number): string {
61
- if (str.length <= len) return str
62
- return str.slice(0, len - 3) + '...'
63
- }
64
-
65
- /**
66
- * Normalize Front resource ID or URL to ID
67
- */
68
- function normalizeId(idOrUrl: string): string {
69
- return idOrUrl.startsWith('http') ? idOrUrl.split('/').pop()! : idOrUrl
70
- }
71
-
72
- /**
73
- * Command: skill front message <id>
74
- * Fetch full message details from Front API
75
- */
76
- async function getMessage(
77
- id: string,
78
- options: { json?: boolean }
79
- ): Promise<void> {
80
- try {
81
- const front = getFrontClient()
82
- const message = await front.messages.get(normalizeId(id))
83
-
84
- if (options.json) {
85
- console.log(JSON.stringify(message, null, 2))
86
- return
87
- }
88
-
89
- console.log('\n📧 Message Details:')
90
- console.log(` ID: ${message.id}`)
91
- console.log(` Type: ${message.type}`)
92
- console.log(` Subject: ${message.subject || '(none)'}`)
93
- console.log(` Created: ${formatTimestamp(message.created_at)}`)
94
-
95
- if (message.author) {
96
- console.log(` Author: ${message.author.email || message.author.id}`)
97
- }
98
-
99
- console.log('\n📬 Recipients:')
100
- for (const r of message.recipients) {
101
- console.log(` ${r.role}: ${r.handle}`)
102
- }
103
-
104
- console.log('\n📝 Body:')
105
- // Strip HTML and show preview
106
- const textBody =
107
- message.text ||
108
- message.body
109
- .replace(/<[^>]*>/g, ' ')
110
- .replace(/\s+/g, ' ')
111
- .trim()
112
- console.log(
113
- ` Length: ${message.body.length} chars (HTML), ${textBody.length} chars (text)`
114
- )
115
- console.log(` Preview: ${truncate(textBody, 500)}`)
116
-
117
- if (message.attachments && message.attachments.length > 0) {
118
- console.log(`\n📎 Attachments: ${message.attachments.length}`)
119
- for (const a of message.attachments) {
120
- console.log(` - ${a.filename} (${a.content_type})`)
121
- }
122
- }
123
-
124
- console.log('')
125
- } catch (error) {
126
- if (options.json) {
127
- console.error(
128
- JSON.stringify({
129
- error: error instanceof Error ? error.message : 'Unknown error',
130
- })
131
- )
132
- } else {
133
- console.error(
134
- 'Error:',
135
- error instanceof Error ? error.message : 'Unknown error'
136
- )
137
- }
138
- process.exit(1)
139
- }
140
- }
141
-
142
- /**
143
- * Command: skill front conversation <id>
144
- * Fetch conversation details and optionally messages
145
- */
146
- async function getConversation(
147
- id: string,
148
- options: { json?: boolean; messages?: boolean }
149
- ): Promise<void> {
150
- try {
151
- const front = getFrontClient()
152
- const conversation = await front.conversations.get(normalizeId(id))
153
-
154
- // Fetch messages if requested
155
- let messages: Message[] | undefined
156
- if (options.messages) {
157
- const messageList = (await front.conversations.listMessages(
158
- normalizeId(id)
159
- )) as MessageList
160
- messages = messageList._results ?? []
161
- }
162
-
163
- if (options.json) {
164
- console.log(JSON.stringify({ conversation, messages }, null, 2))
165
- return
166
- }
167
-
168
- console.log('\n💬 Conversation Details:')
169
- console.log(` ID: ${conversation.id}`)
170
- console.log(` Subject: ${conversation.subject || '(none)'}`)
171
- console.log(` Status: ${conversation.status}`)
172
- console.log(` Created: ${formatTimestamp(conversation.created_at)}`)
173
-
174
- if (conversation.recipient) {
175
- console.log(` Recipient: ${conversation.recipient.handle}`)
176
- }
177
-
178
- if (conversation.assignee) {
179
- console.log(` Assignee: ${conversation.assignee.email}`)
180
- }
181
-
182
- if (conversation.tags && conversation.tags.length > 0) {
183
- console.log(
184
- ` Tags: ${conversation.tags.map((t: { name: string }) => t.name).join(', ')}`
185
- )
186
- }
187
-
188
- if (options.messages && messages) {
189
- console.log(`\n📨 Messages (${messages.length}):`)
190
- console.log('-'.repeat(80))
191
-
192
- for (const msg of messages) {
193
- const direction = msg.is_inbound ? '← IN' : '→ OUT'
194
- const author = msg.author?.email || 'unknown'
195
- const time = formatTimestamp(msg.created_at)
196
- const textBody =
197
- msg.text ||
198
- msg.body
199
- .replace(/<[^>]*>/g, ' ')
200
- .replace(/\s+/g, ' ')
201
- .trim()
202
-
203
- console.log(`\n[${direction}] ${time} - ${author}`)
204
- console.log(` ${truncate(textBody, 200)}`)
205
- }
206
- } else if (!options.messages) {
207
- console.log('\n (use --messages to see message history)')
208
- }
209
-
210
- console.log('')
211
- } catch (error) {
212
- if (options.json) {
213
- console.error(
214
- JSON.stringify({
215
- error: error instanceof Error ? error.message : 'Unknown error',
216
- })
217
- )
218
- } else {
219
- console.error(
220
- 'Error:',
221
- error instanceof Error ? error.message : 'Unknown error'
222
- )
223
- }
224
- process.exit(1)
225
- }
226
- }
227
-
228
- /**
229
- * Command: skill front teammates
230
- * List all teammates in the workspace
231
- */
232
- async function listTeammates(options: { json?: boolean }): Promise<void> {
233
- try {
234
- const front = getFrontSdkClient()
235
- const result = await front.teammates.list()
236
-
237
- if (options.json) {
238
- console.log(JSON.stringify(result._results, null, 2))
239
- return
240
- }
241
-
242
- console.log('\n👥 Teammates:')
243
- console.log('-'.repeat(60))
244
-
245
- for (const teammate of result._results) {
246
- const available = teammate.is_available ? '✓' : '✗'
247
- console.log(` ${available} ${teammate.id}`)
248
- console.log(` Email: ${teammate.email}`)
249
- if (teammate.first_name || teammate.last_name) {
250
- console.log(
251
- ` Name: ${teammate.first_name || ''} ${teammate.last_name || ''}`.trim()
252
- )
253
- }
254
- if (teammate.username) {
255
- console.log(` Username: ${teammate.username}`)
256
- }
257
- console.log('')
258
- }
259
- } catch (error) {
260
- console.error(
261
- 'Error:',
262
- error instanceof Error ? error.message : 'Unknown error'
263
- )
264
- process.exit(1)
265
- }
266
- }
267
-
268
- /**
269
- * Command: skill front teammate <id>
270
- * Get a specific teammate by ID
271
- */
272
- async function getTeammate(
273
- id: string,
274
- options: { json?: boolean }
275
- ): Promise<void> {
276
- try {
277
- const front = getFrontSdkClient()
278
- const teammate = await front.teammates.get(id)
279
-
280
- if (options.json) {
281
- console.log(JSON.stringify(teammate, null, 2))
282
- return
283
- }
284
-
285
- console.log('\n👤 Teammate Details:')
286
- console.log(` ID: ${teammate.id}`)
287
- console.log(` Email: ${teammate.email}`)
288
- if (teammate.first_name || teammate.last_name) {
289
- console.log(
290
- ` Name: ${teammate.first_name || ''} ${teammate.last_name || ''}`.trim()
291
- )
292
- }
293
- if (teammate.username) {
294
- console.log(` Username: ${teammate.username}`)
295
- }
296
- console.log(` Available: ${teammate.is_available ? 'Yes' : 'No'}`)
297
- console.log('')
298
- } catch (error) {
299
- console.error(
300
- 'Error:',
301
- error instanceof Error ? error.message : 'Unknown error'
302
- )
303
- process.exit(1)
304
- }
305
- }
306
-
307
- /**
308
- * Register Front commands with Commander
309
- */
310
- export function registerFrontCommands(program: Command): void {
311
- const front = program
312
- .command('front')
313
- .description('Front API commands for debugging')
314
-
315
- front
316
- .command('message')
317
- .description('Get message details from Front API')
318
- .argument('<id>', 'Message ID (e.g., msg_xxx)')
319
- .option('--json', 'Output as JSON')
320
- .action(getMessage)
321
-
322
- front
323
- .command('conversation')
324
- .description('Get conversation details from Front API')
325
- .argument('<id>', 'Conversation ID (e.g., cnv_xxx)')
326
- .option('--json', 'Output as JSON')
327
- .option('-m, --messages', 'Include message history')
328
- .action(getConversation)
329
-
330
- front
331
- .command('teammates')
332
- .description('List all teammates in the workspace')
333
- .option('--json', 'Output as JSON')
334
- .action(listTeammates)
335
-
336
- front
337
- .command('teammate')
338
- .description('Get teammate details by ID')
339
- .argument('<id>', 'Teammate ID (e.g., tea_xxx or username)')
340
- .option('--json', 'Output as JSON')
341
- .action(getTeammate)
342
-
343
- // Register pull command for building eval datasets
344
- registerPullCommand(front)
345
-
346
- // Register tag management commands
347
- registerTagCommands(front)
348
-
349
- // Register cache command for DuckDB sync
350
- registerCacheCommand(front)
351
- }
@@ -1,275 +0,0 @@
1
- /**
2
- * Pull conversations from Front for eval dataset
3
- *
4
- * Usage:
5
- * skill front pull --inbox <inbox_id> --limit 100 --output data/front-conversations.json
6
- */
7
-
8
- import { writeFileSync } from 'fs'
9
- import { createInstrumentedFrontClient } from '@skillrecordings/core/front/instrumented-client'
10
- import type { Command } from 'commander'
11
-
12
- interface PullOptions {
13
- inbox?: string
14
- limit?: number
15
- output?: string
16
- filter?: string
17
- json?: boolean
18
- }
19
-
20
- interface FrontConversation {
21
- id: string
22
- subject: string
23
- status: string
24
- created_at: number
25
- last_message_at?: number
26
- tags: Array<{ id: string; name: string }>
27
- recipient?: { handle: string; name?: string }
28
- assignee?: { email: string }
29
- }
30
-
31
- interface FrontMessage {
32
- id: string
33
- type: string
34
- is_inbound: boolean
35
- created_at: number
36
- subject?: string
37
- body?: string
38
- text?: string
39
- author?: { email?: string; name?: string }
40
- }
41
-
42
- interface EvalSample {
43
- id: string
44
- conversationId: string
45
- subject: string
46
- customerEmail: string
47
- status: string
48
- tags: string[]
49
- triggerMessage: {
50
- id: string
51
- subject: string
52
- body: string
53
- timestamp: number
54
- }
55
- conversationHistory: Array<{
56
- direction: 'in' | 'out'
57
- body: string
58
- timestamp: number
59
- author?: string
60
- }>
61
- category: string // inferred from tags/content
62
- }
63
-
64
- export async function pullConversations(options: PullOptions): Promise<void> {
65
- const { inbox, limit = 50, output, filter, json = false } = options
66
-
67
- const frontToken = process.env.FRONT_API_TOKEN
68
- if (!frontToken) {
69
- console.error('Error: FRONT_API_TOKEN environment variable required')
70
- process.exit(1)
71
- }
72
-
73
- const front = createInstrumentedFrontClient({ apiToken: frontToken })
74
-
75
- try {
76
- // If no inbox specified, list available inboxes
77
- if (!inbox) {
78
- console.log('Fetching available inboxes...\n')
79
- const inboxesData = (await front.raw.get('/inboxes')) as {
80
- _results: Array<{ id: string; name: string; address?: string }>
81
- }
82
-
83
- console.log('Available inboxes:')
84
- for (const ib of inboxesData._results || []) {
85
- console.log(` ${ib.id}: ${ib.name} (${ib.address || 'no address'})`)
86
- }
87
- console.log(
88
- '\nUse --inbox <id> to pull conversations from a specific inbox'
89
- )
90
- return
91
- }
92
-
93
- console.log(`Pulling conversations from inbox ${inbox}...`)
94
-
95
- // Get conversations from inbox
96
- let allConversations: FrontConversation[] = []
97
- let nextUrl: string | null = `/inboxes/${inbox}/conversations?limit=50`
98
-
99
- while (nextUrl && allConversations.length < limit) {
100
- const data = (await front.raw.get(nextUrl)) as {
101
- _results: FrontConversation[]
102
- _pagination?: { next?: string }
103
- }
104
-
105
- allConversations = allConversations.concat(data._results || [])
106
- nextUrl = data._pagination?.next || null
107
-
108
- process.stdout.write(
109
- `\r Fetched ${allConversations.length} conversations...`
110
- )
111
- }
112
-
113
- allConversations = allConversations.slice(0, limit)
114
- console.log(`\n Total: ${allConversations.length} conversations`)
115
-
116
- // Filter if specified
117
- if (filter) {
118
- const filterLower = filter.toLowerCase()
119
- allConversations = allConversations.filter((c) => {
120
- const subject = (c.subject || '').toLowerCase()
121
- const tags = c.tags.map((t) => t.name.toLowerCase()).join(' ')
122
- return subject.includes(filterLower) || tags.includes(filterLower)
123
- })
124
- console.log(
125
- ` After filter "${filter}": ${allConversations.length} conversations`
126
- )
127
- }
128
-
129
- // Build eval samples
130
- console.log('\nFetching message details...')
131
- const samples: EvalSample[] = []
132
- let processed = 0
133
-
134
- for (const conv of allConversations) {
135
- processed++
136
- process.stdout.write(
137
- `\r Processing ${processed}/${allConversations.length}...`
138
- )
139
-
140
- try {
141
- // Get messages for this conversation
142
- const messagesData = (await front.raw.get(
143
- `/conversations/${conv.id}/messages`
144
- )) as { _results: FrontMessage[] }
145
- const messages = messagesData._results || []
146
-
147
- // Find the most recent inbound message as trigger
148
- const inboundMessages = messages
149
- .filter((m) => m.is_inbound)
150
- .sort((a, b) => b.created_at - a.created_at)
151
-
152
- const triggerMessage = inboundMessages[0]
153
- if (!triggerMessage) continue // Skip if no inbound messages
154
-
155
- // Extract body text
156
- const bodyText =
157
- triggerMessage.text ||
158
- triggerMessage.body
159
- ?.replace(/<[^>]*>/g, ' ')
160
- .replace(/\s+/g, ' ')
161
- .trim() ||
162
- ''
163
-
164
- // Skip very short messages
165
- if (bodyText.length < 20) continue
166
-
167
- // Build conversation history
168
- const history = messages
169
- .sort((a, b) => a.created_at - b.created_at)
170
- .map((m) => ({
171
- direction: (m.is_inbound ? 'in' : 'out') as 'in' | 'out',
172
- body:
173
- m.text ||
174
- m.body
175
- ?.replace(/<[^>]*>/g, ' ')
176
- .replace(/\s+/g, ' ')
177
- .trim() ||
178
- '',
179
- timestamp: m.created_at,
180
- author: m.author?.email,
181
- }))
182
-
183
- // Infer category from tags/subject
184
- const tagNames = conv.tags.map((t) => t.name.toLowerCase()).join(' ')
185
- const subject = (conv.subject || '').toLowerCase()
186
- let category = 'general'
187
-
188
- if (tagNames.includes('refund') || subject.includes('refund'))
189
- category = 'refund'
190
- else if (
191
- tagNames.includes('access') ||
192
- subject.includes('login') ||
193
- subject.includes('access')
194
- )
195
- category = 'access'
196
- else if (
197
- tagNames.includes('technical') ||
198
- subject.includes('error') ||
199
- subject.includes('bug')
200
- )
201
- category = 'technical'
202
- else if (subject.includes('feedback') || subject.includes('suggestion'))
203
- category = 'feedback'
204
- else if (
205
- subject.includes('partnership') ||
206
- subject.includes('collaborate')
207
- )
208
- category = 'business'
209
-
210
- samples.push({
211
- id: conv.id,
212
- conversationId: conv.id,
213
- subject: conv.subject || '(no subject)',
214
- customerEmail: conv.recipient?.handle || 'unknown',
215
- status: conv.status,
216
- tags: conv.tags.map((t) => t.name),
217
- triggerMessage: {
218
- id: triggerMessage.id,
219
- subject: triggerMessage.subject || conv.subject || '',
220
- body: bodyText,
221
- timestamp: triggerMessage.created_at,
222
- },
223
- conversationHistory: history,
224
- category,
225
- })
226
-
227
- // Rate limit
228
- await new Promise((r) => setTimeout(r, 100))
229
- } catch (err) {
230
- // Skip failed conversations
231
- continue
232
- }
233
- }
234
-
235
- console.log(`\n\nBuilt ${samples.length} eval samples`)
236
-
237
- // Category breakdown
238
- const byCategory: Record<string, number> = {}
239
- for (const s of samples) {
240
- byCategory[s.category] = (byCategory[s.category] || 0) + 1
241
- }
242
- console.log('\nBy category:')
243
- for (const [cat, count] of Object.entries(byCategory).sort(
244
- (a, b) => b[1] - a[1]
245
- )) {
246
- console.log(` ${cat}: ${count}`)
247
- }
248
-
249
- // Output
250
- if (output) {
251
- writeFileSync(output, JSON.stringify(samples, null, 2))
252
- console.log(`\nSaved to ${output}`)
253
- } else if (json) {
254
- console.log(JSON.stringify(samples, null, 2))
255
- }
256
- } catch (error) {
257
- console.error(
258
- '\nError:',
259
- error instanceof Error ? error.message : 'Unknown error'
260
- )
261
- process.exit(1)
262
- }
263
- }
264
-
265
- export function registerPullCommand(parent: Command): void {
266
- parent
267
- .command('pull')
268
- .description('Pull conversations from Front for eval dataset')
269
- .option('-i, --inbox <id>', 'Inbox ID to pull from')
270
- .option('-l, --limit <n>', 'Max conversations to pull', parseInt)
271
- .option('-o, --output <file>', 'Output file path')
272
- .option('-f, --filter <term>', 'Filter by subject/tag containing term')
273
- .option('--json', 'JSON output')
274
- .action(pullConversations)
275
- }