@skillrecordings/cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skill.mjs +21 -0
- package/dist/chunk-2NCCVTEE.js +22342 -0
- package/dist/chunk-2NCCVTEE.js.map +1 -0
- package/dist/chunk-3E3GYSZR.js +7071 -0
- package/dist/chunk-3E3GYSZR.js.map +1 -0
- package/dist/chunk-F4EM72IH.js +86 -0
- package/dist/chunk-F4EM72IH.js.map +1 -0
- package/dist/chunk-FGP7KUQW.js +432 -0
- package/dist/chunk-FGP7KUQW.js.map +1 -0
- package/dist/chunk-H3D6VCME.js +55 -0
- package/dist/chunk-H3D6VCME.js.map +1 -0
- package/dist/chunk-HK3PEWFD.js +208 -0
- package/dist/chunk-HK3PEWFD.js.map +1 -0
- package/dist/chunk-KEV3QKXP.js +4495 -0
- package/dist/chunk-KEV3QKXP.js.map +1 -0
- package/dist/chunk-MG37YDAK.js +882 -0
- package/dist/chunk-MG37YDAK.js.map +1 -0
- package/dist/chunk-MLNDSBZ4.js +482 -0
- package/dist/chunk-MLNDSBZ4.js.map +1 -0
- package/dist/chunk-N2WIV2JV.js +22 -0
- package/dist/chunk-N2WIV2JV.js.map +1 -0
- package/dist/chunk-PWWRCN5W.js +2067 -0
- package/dist/chunk-PWWRCN5W.js.map +1 -0
- package/dist/chunk-SKHBM3XP.js +7746 -0
- package/dist/chunk-SKHBM3XP.js.map +1 -0
- package/dist/chunk-WFANXVQG.js +64 -0
- package/dist/chunk-WFANXVQG.js.map +1 -0
- package/dist/chunk-WYKL32C3.js +275 -0
- package/dist/chunk-WYKL32C3.js.map +1 -0
- package/dist/chunk-ZNF7XD2S.js +134 -0
- package/dist/chunk-ZNF7XD2S.js.map +1 -0
- package/dist/config-AUAIYDSI.js +20 -0
- package/dist/config-AUAIYDSI.js.map +1 -0
- package/dist/fileFromPath-XN7LXIBI.js +134 -0
- package/dist/fileFromPath-XN7LXIBI.js.map +1 -0
- package/dist/getMachineId-bsd-KW2E7VK3.js +42 -0
- package/dist/getMachineId-bsd-KW2E7VK3.js.map +1 -0
- package/dist/getMachineId-darwin-ROXJUJX5.js +42 -0
- package/dist/getMachineId-darwin-ROXJUJX5.js.map +1 -0
- package/dist/getMachineId-linux-KVZEHQSU.js +34 -0
- package/dist/getMachineId-linux-KVZEHQSU.js.map +1 -0
- package/dist/getMachineId-unsupported-PPRILPPA.js +25 -0
- package/dist/getMachineId-unsupported-PPRILPPA.js.map +1 -0
- package/dist/getMachineId-win-IIF36LEJ.js +44 -0
- package/dist/getMachineId-win-IIF36LEJ.js.map +1 -0
- package/dist/index.js +112703 -0
- package/dist/index.js.map +1 -0
- package/dist/lib-R6DEEJCP.js +7623 -0
- package/dist/lib-R6DEEJCP.js.map +1 -0
- package/dist/pipeline-IAVVAKTU.js +120 -0
- package/dist/pipeline-IAVVAKTU.js.map +1 -0
- package/dist/query-NTP5NVXN.js +25 -0
- package/dist/query-NTP5NVXN.js.map +1 -0
- package/dist/routing-BAEPFB7V.js +390 -0
- package/dist/routing-BAEPFB7V.js.map +1 -0
- package/dist/stripe-lookup-charge-EPRUMZDL.js +56 -0
- package/dist/stripe-lookup-charge-EPRUMZDL.js.map +1 -0
- package/dist/stripe-payment-history-SJPKA63N.js +67 -0
- package/dist/stripe-payment-history-SJPKA63N.js.map +1 -0
- package/dist/stripe-subscription-status-L4Z65GB3.js +58 -0
- package/dist/stripe-subscription-status-L4Z65GB3.js.map +1 -0
- package/dist/stripe-verify-refund-FZDKCIUQ.js +54 -0
- package/dist/stripe-verify-refund-FZDKCIUQ.js.map +1 -0
- package/dist/support-memory-WSG7SDKG.js +10 -0
- package/dist/support-memory-WSG7SDKG.js.map +1 -0
- package/package.json +10 -7
- package/.env.encrypted +0 -0
- package/CHANGELOG.md +0 -35
- package/data/tt-archive-dataset.json +0 -1
- package/data/validate-test-dataset.json +0 -97
- package/docs/CLI-AUTH.md +0 -504
- package/preload.ts +0 -18
- package/src/__tests__/init.test.ts +0 -74
- package/src/alignment-test.ts +0 -64
- package/src/check-apps.ts +0 -16
- package/src/commands/auth/decrypt.ts +0 -123
- package/src/commands/auth/encrypt.ts +0 -81
- package/src/commands/auth/index.ts +0 -50
- package/src/commands/auth/keygen.ts +0 -41
- package/src/commands/auth/status.ts +0 -164
- package/src/commands/axiom/forensic.ts +0 -868
- package/src/commands/axiom/index.ts +0 -697
- package/src/commands/build-dataset.ts +0 -311
- package/src/commands/db-status.ts +0 -47
- package/src/commands/deploys.ts +0 -219
- package/src/commands/eval-local/compare.ts +0 -171
- package/src/commands/eval-local/health.ts +0 -212
- package/src/commands/eval-local/index.ts +0 -76
- package/src/commands/eval-local/real-tools.ts +0 -416
- package/src/commands/eval-local/run.ts +0 -1168
- package/src/commands/eval-local/score-production.ts +0 -256
- package/src/commands/eval-local/seed.ts +0 -276
- package/src/commands/eval-pipeline/index.ts +0 -53
- package/src/commands/eval-pipeline/real-tools.ts +0 -492
- package/src/commands/eval-pipeline/run.ts +0 -1316
- package/src/commands/eval-pipeline/seed.ts +0 -395
- package/src/commands/eval-prompt.ts +0 -496
- package/src/commands/eval.test.ts +0 -253
- package/src/commands/eval.ts +0 -108
- package/src/commands/faq-classify.ts +0 -460
- package/src/commands/faq-cluster.ts +0 -135
- package/src/commands/faq-extract.ts +0 -249
- package/src/commands/faq-mine.ts +0 -432
- package/src/commands/faq-review.ts +0 -426
- package/src/commands/front/index.ts +0 -351
- package/src/commands/front/pull-conversations.ts +0 -275
- package/src/commands/front/tags.ts +0 -825
- package/src/commands/front-cache.ts +0 -1277
- package/src/commands/front-stats.ts +0 -75
- package/src/commands/health.test.ts +0 -82
- package/src/commands/health.ts +0 -362
- package/src/commands/init.test.ts +0 -89
- package/src/commands/init.ts +0 -106
- package/src/commands/inngest/client.ts +0 -294
- package/src/commands/inngest/events.ts +0 -296
- package/src/commands/inngest/investigate.ts +0 -382
- package/src/commands/inngest/runs.ts +0 -149
- package/src/commands/inngest/signal.ts +0 -143
- package/src/commands/kb-sync.ts +0 -498
- package/src/commands/memory/find.ts +0 -135
- package/src/commands/memory/get.ts +0 -87
- package/src/commands/memory/index.ts +0 -97
- package/src/commands/memory/stats.ts +0 -163
- package/src/commands/memory/store.ts +0 -49
- package/src/commands/memory/vote.ts +0 -159
- package/src/commands/pipeline.ts +0 -127
- package/src/commands/responses.ts +0 -856
- package/src/commands/tools.ts +0 -293
- package/src/commands/wizard.ts +0 -319
- package/src/index.ts +0 -172
- package/src/lib/crypto.ts +0 -56
- package/src/lib/env-loader.ts +0 -206
- package/src/lib/onepassword.ts +0 -137
- package/src/test-agent-local.ts +0 -115
- package/tsconfig.json +0 -11
- 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
|
-
}
|