@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,311 +0,0 @@
1
- /**
2
- * Build eval dataset from real agent responses
3
- *
4
- * Pulls trigger messages and agent responses from Front + DB
5
- * to create labeled datasets for eval improvement.
6
- *
7
- * Usage:
8
- * skill dataset build --since 2025-01-01 --output /tmp/dataset.json
9
- * skill dataset build --app total-typescript --labeled-only
10
- */
11
-
12
- import { writeFileSync } from 'fs'
13
- import { createInstrumentedFrontClient } from '@skillrecordings/core/front/instrumented-client'
14
- import {
15
- ActionsTable,
16
- AppsTable,
17
- ConversationsTable,
18
- and,
19
- desc,
20
- eq,
21
- getDb,
22
- gte,
23
- } from '@skillrecordings/database'
24
- import { type Message } from '@skillrecordings/front-sdk'
25
- import type { Command } from 'commander'
26
-
27
- interface EvalDataPoint {
28
- id: string
29
- app: string
30
- conversationId: string
31
- customerEmail: string
32
- triggerMessage: {
33
- subject: string
34
- body: string
35
- timestamp: number
36
- }
37
- agentResponse: {
38
- text: string
39
- category: string
40
- timestamp: string
41
- }
42
- label?: 'good' | 'bad'
43
- labeledBy?: string
44
- conversationHistory?: Array<{
45
- direction: 'in' | 'out'
46
- body: string
47
- timestamp: number
48
- author?: string
49
- }>
50
- }
51
-
52
- /**
53
- * Build eval dataset from responses
54
- */
55
- async function buildDataset(options: {
56
- app?: string
57
- since?: string
58
- output?: string
59
- labeledOnly?: boolean
60
- limit?: number
61
- includeHistory?: boolean
62
- }): Promise<void> {
63
- const db = getDb()
64
- const limit = options.limit || 100
65
-
66
- // Get Front token
67
- const frontToken = process.env.FRONT_API_TOKEN
68
- if (!frontToken) {
69
- console.error('Error: FRONT_API_TOKEN environment variable required')
70
- console.error('Set it or source the env file: source apps/front/.env.local')
71
- process.exit(1)
72
- }
73
-
74
- const front = createInstrumentedFrontClient({ apiToken: frontToken })
75
-
76
- try {
77
- // Build query conditions
78
- const conditions = [eq(ActionsTable.type, 'draft-response')]
79
-
80
- if (options.app) {
81
- const appResults = await db
82
- .select()
83
- .from(AppsTable)
84
- .where(eq(AppsTable.slug, options.app))
85
- .limit(1)
86
-
87
- const foundApp = appResults[0]
88
- if (!foundApp) {
89
- console.error(`App not found: ${options.app}`)
90
- process.exit(1)
91
- }
92
- conditions.push(eq(ActionsTable.app_id, foundApp.id))
93
- }
94
-
95
- if (options.since) {
96
- const sinceDate = new Date(options.since)
97
- conditions.push(gte(ActionsTable.created_at, sinceDate))
98
- }
99
-
100
- // Query actions
101
- const results = await db
102
- .select({
103
- action: ActionsTable,
104
- app: AppsTable,
105
- conversation: ConversationsTable,
106
- })
107
- .from(ActionsTable)
108
- .leftJoin(AppsTable, eq(ActionsTable.app_id, AppsTable.id))
109
- .leftJoin(
110
- ConversationsTable,
111
- eq(
112
- ActionsTable.conversation_id,
113
- ConversationsTable.front_conversation_id
114
- )
115
- )
116
- .where(and(...conditions))
117
- .orderBy(desc(ActionsTable.created_at))
118
- .limit(limit)
119
-
120
- console.log(`Found ${results.length} responses, fetching context...`)
121
-
122
- const dataset: EvalDataPoint[] = []
123
- let processed = 0
124
-
125
- for (const r of results) {
126
- processed++
127
- process.stdout.write(`\rProcessing ${processed}/${results.length}...`)
128
-
129
- const params = r.action.parameters as {
130
- response?: string
131
- category?: string
132
- }
133
-
134
- // Skip if no response
135
- if (!params.response) continue
136
-
137
- // Determine label
138
- let label: 'good' | 'bad' | undefined
139
- let labeledBy: string | undefined
140
-
141
- if (r.action.approved_by) {
142
- label = 'good'
143
- labeledBy = r.action.approved_by
144
- } else if (r.action.rejected_by) {
145
- label = 'bad'
146
- labeledBy = r.action.rejected_by
147
- }
148
-
149
- // Skip unlabeled if labeledOnly
150
- if (options.labeledOnly && !label) continue
151
-
152
- // Fetch conversation from Front
153
- if (!r.action.conversation_id) continue
154
-
155
- let triggerMessage: EvalDataPoint['triggerMessage'] | undefined
156
- let conversationHistory: EvalDataPoint['conversationHistory'] | undefined
157
-
158
- try {
159
- const messageList = (await front.conversations.listMessages(
160
- r.action.conversation_id
161
- )) as { _results?: Message[] }
162
- const messages = messageList._results ?? []
163
-
164
- // Find trigger message (most recent inbound before draft)
165
- const draftTime = r.action.created_at?.getTime() ?? Date.now()
166
- const inboundBefore = messages
167
- .filter((m) => m.is_inbound && m.created_at * 1000 < draftTime)
168
- .sort((a, b) => b.created_at - a.created_at)
169
-
170
- const trigger = inboundBefore[0]
171
- if (trigger) {
172
- triggerMessage = {
173
- subject: trigger.subject ?? '',
174
- body:
175
- trigger.text ??
176
- trigger.body
177
- ?.replace(/<[^>]*>/g, ' ')
178
- .replace(/\s+/g, ' ')
179
- .trim() ??
180
- '',
181
- timestamp: trigger.created_at,
182
- }
183
- }
184
-
185
- // Include history if requested
186
- if (options.includeHistory) {
187
- conversationHistory = messages.map((m) => ({
188
- direction: m.is_inbound ? ('in' as const) : ('out' as const),
189
- body:
190
- m.text ??
191
- m.body
192
- ?.replace(/<[^>]*>/g, ' ')
193
- .replace(/\s+/g, ' ')
194
- .trim() ??
195
- '',
196
- timestamp: m.created_at,
197
- author: m.author?.email,
198
- }))
199
- }
200
- } catch (err) {
201
- // Skip if can't fetch context
202
- continue
203
- }
204
-
205
- // Skip if no trigger message
206
- if (!triggerMessage) continue
207
-
208
- dataset.push({
209
- id: r.action.id,
210
- app: r.app?.slug ?? 'unknown',
211
- conversationId: r.action.conversation_id,
212
- customerEmail: r.conversation?.customer_email ?? 'unknown',
213
- triggerMessage,
214
- agentResponse: {
215
- text: params.response,
216
- category: params.category ?? 'unknown',
217
- timestamp: r.action.created_at?.toISOString() ?? '',
218
- },
219
- label,
220
- labeledBy,
221
- conversationHistory,
222
- })
223
- }
224
-
225
- console.log(`\n\nBuilt dataset with ${dataset.length} eval points`)
226
- console.log(` Labeled: ${dataset.filter((d) => d.label).length}`)
227
- console.log(` Good: ${dataset.filter((d) => d.label === 'good').length}`)
228
- console.log(` Bad: ${dataset.filter((d) => d.label === 'bad').length}`)
229
- console.log(` Unlabeled: ${dataset.filter((d) => !d.label).length}`)
230
-
231
- const outputJson = JSON.stringify(dataset, null, 2)
232
-
233
- if (options.output) {
234
- writeFileSync(options.output, outputJson, 'utf-8')
235
- console.log(`\nSaved to ${options.output}`)
236
- } else {
237
- console.log('\n' + outputJson)
238
- }
239
- } catch (error) {
240
- console.error(
241
- '\nError:',
242
- error instanceof Error ? error.message : 'Unknown error'
243
- )
244
- process.exit(1)
245
- }
246
- }
247
-
248
- /**
249
- * Convert dataset to evalite format
250
- */
251
- async function toEvalite(options: {
252
- input: string
253
- output?: string
254
- }): Promise<void> {
255
- const { readFileSync } = await import('fs')
256
-
257
- const data = JSON.parse(
258
- readFileSync(options.input, 'utf-8')
259
- ) as EvalDataPoint[]
260
-
261
- const evaliteData = data.map((d) => ({
262
- input: d.triggerMessage.body,
263
- output: d.agentResponse.text,
264
- expected: d.label === 'good' ? d.agentResponse.text : '',
265
- metadata: {
266
- id: d.id,
267
- app: d.app,
268
- category: d.agentResponse.category,
269
- label: d.label,
270
- subject: d.triggerMessage.subject,
271
- },
272
- }))
273
-
274
- const outputJson = JSON.stringify(evaliteData, null, 2)
275
-
276
- if (options.output) {
277
- writeFileSync(options.output, outputJson, 'utf-8')
278
- console.log(
279
- `Converted ${evaliteData.length} points to evalite format: ${options.output}`
280
- )
281
- } else {
282
- console.log(outputJson)
283
- }
284
- }
285
-
286
- /**
287
- * Register dataset commands
288
- */
289
- export function registerDatasetCommands(program: Command): void {
290
- const dataset = program
291
- .command('dataset')
292
- .description('Build and manage eval datasets')
293
-
294
- dataset
295
- .command('build')
296
- .description('Build eval dataset from agent responses')
297
- .option('-a, --app <slug>', 'Filter by app slug')
298
- .option('-s, --since <date>', 'Filter responses since date (YYYY-MM-DD)')
299
- .option('-o, --output <file>', 'Output file path')
300
- .option('-l, --limit <n>', 'Max responses to process', parseInt)
301
- .option('--labeled-only', 'Only include labeled responses')
302
- .option('--include-history', 'Include full conversation history')
303
- .action(buildDataset)
304
-
305
- dataset
306
- .command('to-evalite')
307
- .description('Convert dataset to evalite format')
308
- .requiredOption('-i, --input <file>', 'Input dataset JSON file')
309
- .option('-o, --output <file>', 'Output file path')
310
- .action(toEvalite)
311
- }
@@ -1,47 +0,0 @@
1
- import { program } from 'commander'
2
- import {
3
- ConversationsTable,
4
- database,
5
- desc,
6
- sql,
7
- } from '@skillrecordings/database'
8
-
9
- export const registerDbStatusCommand = (prog: typeof program) => {
10
- prog
11
- .command('db-status')
12
- .description('Check database status and conversation counts')
13
- .action(async () => {
14
- try {
15
- // Count by status
16
- const statusCounts = await database
17
- .select({
18
- status: ConversationsTable.status,
19
- count: sql<number>`COUNT(*)`.as('count'),
20
- })
21
- .from(ConversationsTable)
22
- .groupBy(ConversationsTable.status)
23
-
24
- console.log('Conversation counts by status:')
25
- for (const row of statusCounts) {
26
- console.log(` ${row.status}: ${row.count}`)
27
- }
28
-
29
- // Get recent conversations
30
- const recent = await database
31
- .select()
32
- .from(ConversationsTable)
33
- .orderBy(desc(ConversationsTable.updated_at))
34
- .limit(5)
35
-
36
- console.log('\nRecent conversations:')
37
- for (const c of recent) {
38
- console.log(` ${c.front_conversation_id}: ${c.status} (${c.updated_at})`)
39
- }
40
-
41
- process.exit(0)
42
- } catch (error) {
43
- console.error('Error:', error)
44
- process.exit(1)
45
- }
46
- })
47
- }
@@ -1,219 +0,0 @@
1
- import { execSync } from 'child_process'
2
- import { Command } from 'commander'
3
-
4
- const VERCEL_SCOPE = 'skillrecordings'
5
-
6
- // Map of app names to their Vercel project names
7
- const APPS: Record<string, { vercel: string; description: string }> = {
8
- front: {
9
- vercel: 'skill-support-agent-front',
10
- description: 'Front webhook handler (main support pipeline)',
11
- },
12
- slack: {
13
- vercel: 'skill-support-agent-slack',
14
- description: 'Slack interactions (approvals, notifications)',
15
- },
16
- }
17
-
18
- function stripAnsi(str: string): string {
19
- // eslint-disable-next-line no-control-regex
20
- return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '').replace(/\x1B\].*?\x07/g, '')
21
- }
22
-
23
- function runVercel(args: string): string {
24
- try {
25
- // Capture both stdout and stderr (Vercel CLI writes table to stderr)
26
- const raw = execSync(`vercel ${args} --scope ${VERCEL_SCOPE} --yes 2>&1`, {
27
- encoding: 'utf-8',
28
- timeout: 30000,
29
- stdio: ['pipe', 'pipe', 'pipe'],
30
- env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
31
- }).trim()
32
- return stripAnsi(raw)
33
- } catch (err: any) {
34
- const out = err.stdout?.trim() || err.stderr?.trim() || err.message
35
- return stripAnsi(out)
36
- }
37
- }
38
-
39
- function listApps() {
40
- console.log('\nšŸ“¦ Support Platform Apps\n')
41
- for (const [name, app] of Object.entries(APPS)) {
42
- console.log(
43
- ` ${name.padEnd(10)} ${app.vercel.padEnd(35)} ${app.description}`
44
- )
45
- }
46
- console.log()
47
- }
48
-
49
- function resolveApp(
50
- nameOrAll: string | undefined
51
- ): [string, { vercel: string; description: string }][] {
52
- if (!nameOrAll || nameOrAll === 'all') {
53
- return Object.entries(APPS)
54
- }
55
- const app = APPS[nameOrAll]
56
- if (!app) {
57
- console.error(`āŒ Unknown app: ${nameOrAll}`)
58
- console.error(` Available: ${Object.keys(APPS).join(', ')}, all`)
59
- process.exit(1)
60
- }
61
- return [[nameOrAll, app]]
62
- }
63
-
64
- async function deploysStatus(
65
- appName: string | undefined,
66
- options: { limit?: string; json?: boolean }
67
- ) {
68
- const apps = resolveApp(appName)
69
- const limit = parseInt(options.limit || '5')
70
-
71
- for (const [name, app] of apps) {
72
- console.log(`\nšŸš€ ${name} (${app.vercel})`)
73
- console.log(` ${app.description}\n`)
74
-
75
- const output = runVercel(`ls ${app.vercel}`)
76
- const lines = output.split('\n')
77
- // Deploy lines contain https:// URLs
78
- const deployLines = lines.filter((l) => l.includes('https://'))
79
-
80
- if (deployLines.length === 0) {
81
- console.log(' No recent deployments found')
82
- } else {
83
- // Filter production-only for cleaner output, show all if few deploys
84
- const prodLines = deployLines.filter((l) => l.includes('Production'))
85
- const showLines = (
86
- prodLines.length >= limit ? prodLines : deployLines
87
- ).slice(0, limit)
88
-
89
- for (const line of showLines) {
90
- const trimmed = line.trim()
91
- // Extract known tokens from the line
92
- const hasReady = trimmed.includes('Ready')
93
- const hasError = trimmed.includes('Error')
94
- const hasCanceled = trimmed.includes('Canceled')
95
- const isProd = trimmed.includes('Production')
96
- const isPreview = trimmed.includes('Preview')
97
- const prefix = hasError
98
- ? 'āŒ'
99
- : hasCanceled
100
- ? '⚪'
101
- : isProd
102
- ? '🟢'
103
- : 'šŸ”µ'
104
-
105
- // Pull age (first token) and environment
106
- const age = trimmed.split(/\s+/)[0] || ''
107
- const status = hasError
108
- ? 'Error'
109
- : hasCanceled
110
- ? 'Canceled'
111
- : hasReady
112
- ? 'Ready'
113
- : 'Unknown'
114
- const env = isProd ? 'Production' : isPreview ? 'Preview' : ''
115
-
116
- // Pull duration (pattern like "30s", "17s")
117
- const durMatch = trimmed.match(/(\d+s)/)
118
- const duration = durMatch ? durMatch[1] : ''
119
-
120
- console.log(
121
- ` ${prefix} ${age.padEnd(6)} ${status.padEnd(10)} ${env.padEnd(12)} ${duration}`
122
- )
123
- }
124
- }
125
- }
126
- console.log()
127
- }
128
-
129
- async function deploysLogs(appName: string, options: { lines?: string }) {
130
- const apps = resolveApp(appName)
131
- if (apps.length > 1) {
132
- console.error('āŒ Specify a single app for logs (front or slack)')
133
- process.exit(1)
134
- }
135
-
136
- const [name, app] = apps[0]!
137
- console.log(`\nšŸ“‹ Recent logs for ${name} (${app.vercel})\n`)
138
-
139
- // Get latest production deployment URL
140
- const lsOutput = runVercel(`ls ${app.vercel} --limit 5`)
141
- const prodLine = lsOutput
142
- .split('\n')
143
- .find((l) => l.includes('Production') && l.includes('Ready'))
144
- const urlMatch = prodLine?.match(/https:\/\/\S+/)
145
-
146
- if (!urlMatch) {
147
- console.error(' Could not find latest production deployment')
148
- process.exit(1)
149
- }
150
-
151
- const url = urlMatch[0]
152
- console.log(` Deployment: ${url}\n`)
153
-
154
- try {
155
- const logsOutput = execSync(
156
- `vercel logs ${url} --scope ${VERCEL_SCOPE} --output short 2>&1 | tail -${options.lines || '30'}`,
157
- { encoding: 'utf-8', timeout: 30000 }
158
- )
159
- console.log(logsOutput)
160
- } catch (err: any) {
161
- console.log(err.stdout || err.message)
162
- }
163
- }
164
-
165
- async function deploysInspect(appName: string) {
166
- const apps = resolveApp(appName)
167
- if (apps.length > 1) {
168
- console.error('āŒ Specify a single app for inspect (front or slack)')
169
- process.exit(1)
170
- }
171
-
172
- const [name, app] = apps[0]!
173
-
174
- // Get latest production deployment URL
175
- const lsOutput = runVercel(`ls ${app.vercel} --limit 3`)
176
- const prodLine = lsOutput
177
- .split('\n')
178
- .find((l) => l.includes('Production') && l.includes('Ready'))
179
- const urlMatch = prodLine?.match(/https:\/\/\S+/)
180
-
181
- if (!urlMatch) {
182
- console.error(' Could not find latest production deployment')
183
- process.exit(1)
184
- }
185
-
186
- console.log(`\nšŸ” Inspecting ${name} latest production deploy\n`)
187
- const output = runVercel(`inspect ${urlMatch[0]}`)
188
- console.log(output)
189
- }
190
-
191
- export function registerDeployCommands(program: Command) {
192
- const deploys = program
193
- .command('deploys')
194
- .description('Check Vercel deployment status for support platform apps')
195
-
196
- deploys
197
- .command('status')
198
- .description('Show recent deployments for one or all apps')
199
- .argument('[app]', 'App name (front, slack, all)', 'all')
200
- .option('-n, --limit <number>', 'Number of deploys to show', '5')
201
- .option('--json', 'JSON output')
202
- .action(deploysStatus)
203
-
204
- deploys
205
- .command('logs')
206
- .description('Show recent logs for an app')
207
- .argument('<app>', 'App name (front, slack)')
208
- .option('-n, --lines <number>', 'Number of log lines', '30')
209
- .action(deploysLogs)
210
-
211
- deploys
212
- .command('inspect')
213
- .description('Inspect latest production deployment')
214
- .argument('<app>', 'App name (front, slack)')
215
- .action(deploysInspect)
216
-
217
- // Default: `deploys` with no subcommand = status all
218
- deploys.action(() => deploysStatus('all', { limit: '3' }))
219
- }