@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
package/src/index.ts DELETED
@@ -1,172 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { closeDb } from '@skillrecordings/database'
3
- // Note: env is loaded via preload.ts before this file runs
4
- import { Command } from 'commander'
5
- import { registerAuthCommands } from './commands/auth/index'
6
- import { registerAxiomCommands } from './commands/axiom/index'
7
- import { registerDatasetCommands } from './commands/build-dataset'
8
- import { registerDbStatusCommand } from './commands/db-status'
9
- import { registerDeployCommands } from './commands/deploys'
10
- import { runEval } from './commands/eval'
11
- import { registerEvalLocalCommands } from './commands/eval-local/index'
12
- import { registerEvalPipelineCommands } from './commands/eval-pipeline/index'
13
- import { registerEvalPromptCommands } from './commands/eval-prompt'
14
- import { registerFaqClassifyCommands } from './commands/faq-classify'
15
- import { registerFaqClusterCommands } from './commands/faq-cluster'
16
- import { registerFaqExtractCommands } from './commands/faq-extract'
17
- import { registerFaqMineCommands } from './commands/faq-mine'
18
- import { registerFaqReviewCommands } from './commands/faq-review'
19
- import { registerFrontCommands } from './commands/front/index'
20
- import { health } from './commands/health'
21
- import { init } from './commands/init'
22
- import { registerEventsCommands } from './commands/inngest/events'
23
- import { registerRunsCommands } from './commands/inngest/runs'
24
- import { registerSignalCommand } from './commands/inngest/signal'
25
- import { registerKbCommands } from './commands/kb-sync'
26
- import { registerMemoryCommands } from './commands/memory/index'
27
- import { registerPipelineCommands } from './commands/pipeline'
28
- import { registerResponseCommands } from './commands/responses'
29
- import { registerToolsCommands } from './commands/tools'
30
- import { wizard } from './commands/wizard'
31
-
32
- const program = new Command()
33
-
34
- program
35
- .name('skill')
36
- .description('CLI tool for managing app integrations')
37
- .version('0.0.0')
38
-
39
- program
40
- .command('init')
41
- .description('Initialize a new app integration (quick mode)')
42
- .argument(
43
- '[name]',
44
- 'Name of the integration (required in non-interactive mode)'
45
- )
46
- .option('--json', 'Output result as JSON (machine-readable)')
47
- .action(init)
48
-
49
- program
50
- .command('wizard')
51
- .description('Interactive wizard for setting up a new property')
52
- .option('--json', 'Output result as JSON (machine-readable)')
53
- .action(wizard)
54
-
55
- program
56
- .command('health')
57
- .description('Test integration endpoint health')
58
- .argument(
59
- '[slug|url]',
60
- 'App slug (from database) or URL (e.g., https://totaltypescript.com)'
61
- )
62
- .option(
63
- '-s, --secret <secret>',
64
- 'Webhook secret (required for direct URL mode)'
65
- )
66
- .option('-l, --list', 'List all registered apps')
67
- .option('--json', 'Output result as JSON (machine-readable)')
68
- .action(health)
69
-
70
- program
71
- .command('eval')
72
- .description('Run evals against a dataset')
73
- .argument('<type>', 'Eval type (e.g., routing)')
74
- .argument('<dataset>', 'Path to dataset JSON file')
75
- .option('--json', 'Output result as JSON (machine-readable)')
76
- .option(
77
- '--min-precision <number>',
78
- 'Minimum precision threshold (default: 0.92)',
79
- parseFloat
80
- )
81
- .option(
82
- '--min-recall <number>',
83
- 'Minimum recall threshold (default: 0.95)',
84
- parseFloat
85
- )
86
- .option(
87
- '--max-fp-rate <number>',
88
- 'Maximum false positive rate (default: 0.03)',
89
- parseFloat
90
- )
91
- .option(
92
- '--max-fn-rate <number>',
93
- 'Maximum false negative rate (default: 0.02)',
94
- parseFloat
95
- )
96
- .action((type, dataset, options) => {
97
- const gates = {
98
- ...(options.minPrecision !== undefined && {
99
- minPrecision: options.minPrecision,
100
- }),
101
- ...(options.minRecall !== undefined && { minRecall: options.minRecall }),
102
- ...(options.maxFpRate !== undefined && { maxFpRate: options.maxFpRate }),
103
- ...(options.maxFnRate !== undefined && { maxFnRate: options.maxFnRate }),
104
- }
105
- runEval(type, dataset, {
106
- json: options.json,
107
- gates: Object.keys(gates).length > 0 ? gates : undefined,
108
- })
109
- })
110
-
111
- // Register Inngest commands
112
- registerEventsCommands(program)
113
- registerRunsCommands(program)
114
- registerSignalCommand(program)
115
-
116
- // Register Front commands
117
- registerFrontCommands(program)
118
-
119
- // Register Memory commands
120
- registerMemoryCommands(program)
121
-
122
- // Register Auth commands (encrypted secrets for CLI distribution)
123
- registerAuthCommands(program)
124
-
125
- // Register Response commands (for analysis)
126
- registerResponseCommands(program)
127
-
128
- // Register Tools commands (test agent tools)
129
- registerToolsCommands(program)
130
-
131
- // Register Dataset commands (eval dataset building)
132
- registerDatasetCommands(program)
133
- registerEvalPromptCommands(program)
134
-
135
- // Register Axiom commands (log querying)
136
- registerAxiomCommands(program)
137
-
138
- // Register eval-local commands (local eval environment)
139
- registerEvalLocalCommands(program)
140
-
141
- // Register eval-pipeline commands (step-by-step pipeline eval)
142
- registerEvalPipelineCommands(program)
143
-
144
- // Register pipeline commands (new step-based architecture)
145
- registerPipelineCommands(program)
146
-
147
- // Register deploy commands (Vercel status/logs/inspect)
148
- registerDeployCommands(program)
149
-
150
- // Register knowledge base commands (sync/stats)
151
- registerKbCommands(program)
152
-
153
- // Register FAQ mining commands (mine FAQ candidates from conversations)
154
- registerFaqMineCommands(program)
155
- registerDbStatusCommand(program)
156
-
157
- // Register FAQ clustering commands (production clustering from Phase 0)
158
- registerFaqClusterCommands(program)
159
-
160
- // Register FAQ classification commands (LLM topic classification)
161
- registerFaqClassifyCommands(program)
162
-
163
- // Register FAQ extraction commands (extract candidates from clusters)
164
- registerFaqExtractCommands(program)
165
-
166
- // Register FAQ review commands (human curation of FAQ candidates)
167
- registerFaqReviewCommands(program)
168
-
169
- // Parse and cleanup DB connections when done
170
- program.parseAsync().finally(async () => {
171
- await closeDb()
172
- })
package/src/lib/crypto.ts DELETED
@@ -1,56 +0,0 @@
1
- import {
2
- Decrypter,
3
- Encrypter,
4
- generateIdentity,
5
- identityToRecipient,
6
- } from 'age-encryption'
7
-
8
- export interface Keypair {
9
- publicKey: string
10
- privateKey: string
11
- }
12
-
13
- /**
14
- * Generate an age keypair
15
- * @returns Keypair with publicKey (age1...) and privateKey (AGE-SECRET-KEY-1...)
16
- */
17
- export async function generateKeypair(): Promise<Keypair> {
18
- const privateKey = await generateIdentity()
19
- const publicKey = await identityToRecipient(privateKey)
20
- return { publicKey, privateKey }
21
- }
22
-
23
- /**
24
- * Encrypt data with an age public key
25
- * @param data - String or Buffer to encrypt
26
- * @param recipientPublicKey - age public key (age1...)
27
- * @returns Encrypted data as Uint8Array
28
- */
29
- export async function encrypt(
30
- data: string | Buffer,
31
- recipientPublicKey: string
32
- ): Promise<Uint8Array> {
33
- const encrypter = new Encrypter()
34
- encrypter.addRecipient(recipientPublicKey)
35
-
36
- const input = typeof data === 'string' ? data : new TextDecoder().decode(data)
37
- return encrypter.encrypt(input)
38
- }
39
-
40
- /**
41
- * Decrypt data with an age private key
42
- * @param encrypted - Encrypted data as Uint8Array or Buffer
43
- * @param privateKey - age private key (AGE-SECRET-KEY-1...)
44
- * @returns Decrypted data as string
45
- */
46
- export async function decrypt(
47
- encrypted: Uint8Array | Buffer,
48
- privateKey: string
49
- ): Promise<string> {
50
- const decrypter = new Decrypter()
51
- decrypter.addIdentity(privateKey)
52
-
53
- const input =
54
- encrypted instanceof Buffer ? new Uint8Array(encrypted) : encrypted
55
- return decrypter.decrypt(input, 'text')
56
- }
@@ -1,206 +0,0 @@
1
- import { execSync } from 'node:child_process'
2
- import fs from 'node:fs/promises'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { config } from 'dotenv-flow'
6
- import { decrypt } from './crypto.js'
7
-
8
- /** 1Password reference for the age private key */
9
- const OP_AGE_KEY_REF = 'op://Support/skill-cli-age-key/private_key'
10
-
11
- /**
12
- * Load secrets from layered fallback:
13
- * 1. Local .env/.env.local files
14
- * 2. Encrypted .env.encrypted file (auto-fetches key from 1Password if ~/.op-token exists)
15
- * 3. Fail with clear instructions
16
- *
17
- * Injects secrets into process.env
18
- */
19
- export async function loadSecrets(): Promise<void> {
20
- const cliDir = path.resolve(import.meta.dirname, '../..')
21
-
22
- // Layer 1: Check for local .env files
23
- const localEnvExists = await checkLocalEnv(cliDir)
24
- if (localEnvExists) {
25
- config({ path: cliDir, silent: true })
26
- return
27
- }
28
-
29
- // Layer 2: Try encrypted fallback
30
- const encryptedPath = path.join(cliDir, '.env.encrypted')
31
- const encryptedExists = await fileExists(encryptedPath)
32
-
33
- if (encryptedExists) {
34
- // Try to get the private key - either from env or via 1Password
35
- let privateKey = process.env.AGE_SECRET_KEY
36
-
37
- if (!privateKey) {
38
- // Attempt to load from 1Password via ~/.op-token
39
- privateKey = await tryLoadKeyFrom1Password()
40
- }
41
-
42
- if (!privateKey) {
43
- throw new Error(
44
- 'Found .env.encrypted but cannot decrypt.\n\n' +
45
- 'Options:\n' +
46
- '1. Set AGE_SECRET_KEY env var with the private key\n' +
47
- '2. Create ~/.op-token with 1Password service account token\n\n' +
48
- 'See docs/CLI-AUTH.md for setup instructions.'
49
- )
50
- }
51
-
52
- try {
53
- const encryptedData = await fs.readFile(encryptedPath)
54
- const decrypted = await decrypt(encryptedData, privateKey)
55
-
56
- // Parse decrypted env file format (KEY=VALUE lines)
57
- parseAndInjectEnv(decrypted)
58
- return
59
- } catch (error) {
60
- throw new Error(
61
- `Failed to decrypt .env.encrypted: ${error instanceof Error ? error.message : String(error)}\n` +
62
- 'Check that the decryption key matches the encryption key.'
63
- )
64
- }
65
- }
66
-
67
- // Layer 3: Nothing found - fail with instructions
68
- throw new Error(
69
- 'No environment configuration found.\n\n' +
70
- 'Options:\n' +
71
- '1. Create .env.local in packages/cli/ with your secrets\n' +
72
- '2. Set AGE_SECRET_KEY and use encrypted .env.encrypted\n' +
73
- '3. Create ~/.op-token for automatic 1Password-based decryption\n\n' +
74
- 'See docs/CLI-AUTH.md for setup instructions.'
75
- )
76
- }
77
-
78
- /**
79
- * Try to load the age private key from 1Password using ~/.op-token
80
- * Returns the key if successful, undefined otherwise
81
- */
82
- async function tryLoadKeyFrom1Password(): Promise<string | undefined> {
83
- const opTokenPath = path.join(os.homedir(), '.op-token')
84
-
85
- // Check if ~/.op-token exists
86
- if (!(await fileExists(opTokenPath))) {
87
- return undefined
88
- }
89
-
90
- try {
91
- // Read and parse ~/.op-token (format: export VAR="value")
92
- const tokenFileContent = await fs.readFile(opTokenPath, 'utf-8')
93
- const token = parseOpTokenFile(tokenFileContent)
94
-
95
- if (!token) {
96
- return undefined
97
- }
98
-
99
- // Set the token in env for op CLI to use
100
- process.env.OP_SERVICE_ACCOUNT_TOKEN = token
101
-
102
- // Fetch the age private key from 1Password
103
- const privateKey = execSync(`op read "${OP_AGE_KEY_REF}"`, {
104
- encoding: 'utf-8',
105
- stdio: ['ignore', 'pipe', 'pipe'],
106
- env: process.env, // Explicit env pass required for token to be visible
107
- }).trim()
108
-
109
- return privateKey || undefined
110
- } catch {
111
- // Silently fail - caller will show appropriate error
112
- return undefined
113
- }
114
- }
115
-
116
- /**
117
- * Parse ~/.op-token file format: export OP_SERVICE_ACCOUNT_TOKEN="value"
118
- */
119
- function parseOpTokenFile(content: string): string | undefined {
120
- const lines = content.split('\n')
121
-
122
- for (const line of lines) {
123
- const trimmed = line.trim()
124
-
125
- // Match: export OP_SERVICE_ACCOUNT_TOKEN="value" or =value
126
- const match = trimmed.match(
127
- /^export\s+OP_SERVICE_ACCOUNT_TOKEN\s*=\s*["']?([^"'\s]+)["']?/
128
- )
129
- if (match?.[1]) {
130
- return match[1]
131
- }
132
-
133
- // Also support bare assignment: OP_SERVICE_ACCOUNT_TOKEN=value
134
- const bareMatch = trimmed.match(
135
- /^OP_SERVICE_ACCOUNT_TOKEN\s*=\s*["']?([^"'\s]+)["']?/
136
- )
137
- if (bareMatch?.[1]) {
138
- return bareMatch[1]
139
- }
140
- }
141
-
142
- return undefined
143
- }
144
-
145
- /**
146
- * Check if any local .env files exist
147
- */
148
- async function checkLocalEnv(dir: string): Promise<boolean> {
149
- const candidates = ['.env.local', '.env']
150
- for (const file of candidates) {
151
- if (await fileExists(path.join(dir, file))) {
152
- return true
153
- }
154
- }
155
- return false
156
- }
157
-
158
- /**
159
- * Check if a file exists
160
- */
161
- async function fileExists(filePath: string): Promise<boolean> {
162
- try {
163
- await fs.access(filePath)
164
- return true
165
- } catch {
166
- return false
167
- }
168
- }
169
-
170
- /**
171
- * Parse env file format (KEY=VALUE) and inject into process.env
172
- * Handles quoted values, comments, and empty lines
173
- */
174
- function parseAndInjectEnv(content: string): void {
175
- const lines = content.split('\n')
176
-
177
- for (const line of lines) {
178
- const trimmed = line.trim()
179
-
180
- // Skip comments and empty lines
181
- if (!trimmed || trimmed.startsWith('#')) {
182
- continue
183
- }
184
-
185
- const eqIndex = trimmed.indexOf('=')
186
- if (eqIndex === -1) {
187
- continue
188
- }
189
-
190
- const key = trimmed.slice(0, eqIndex).trim()
191
- let value = trimmed.slice(eqIndex + 1).trim()
192
-
193
- // Remove quotes if present
194
- if (
195
- (value.startsWith('"') && value.endsWith('"')) ||
196
- (value.startsWith("'") && value.endsWith("'"))
197
- ) {
198
- value = value.slice(1, -1)
199
- }
200
-
201
- // Only set if not already defined (respect existing env vars)
202
- if (key && process.env[key] === undefined) {
203
- process.env[key] = value
204
- }
205
- }
206
- }
@@ -1,137 +0,0 @@
1
- import { execSync, spawn } from 'node:child_process'
2
-
3
- /**
4
- * 1Password Service Account integration for headless Linux environments
5
- *
6
- * Authenticates using OP_SERVICE_ACCOUNT_TOKEN environment variable.
7
- * Provides functions to read secrets and execute commands with injected secrets.
8
- */
9
-
10
- /**
11
- * Check if the `op` CLI is installed and available
12
- */
13
- export async function isOpAvailable(): Promise<boolean> {
14
- try {
15
- execSync('which op', { stdio: 'ignore' })
16
- return true
17
- } catch {
18
- return false
19
- }
20
- }
21
-
22
- /**
23
- * Check if OP_SERVICE_ACCOUNT_TOKEN is configured
24
- */
25
- export function isServiceAccountConfigured(): boolean {
26
- return Boolean(process.env.OP_SERVICE_ACCOUNT_TOKEN)
27
- }
28
-
29
- /**
30
- * Read a secret from 1Password using a secret reference
31
- *
32
- * @param reference - Secret reference in format: op://vault/item/field
33
- * @returns The secret value
34
- * @throws Error if op CLI is not available, token is not configured, or read fails
35
- *
36
- * @example
37
- * const apiKey = await readSecret('op://Private/Stripe/api_key')
38
- */
39
- export async function readSecret(reference: string): Promise<string> {
40
- if (!(await isOpAvailable())) {
41
- throw new Error(
42
- '1Password CLI not installed. Install from https://developer.1password.com/docs/cli/get-started/'
43
- )
44
- }
45
-
46
- if (!isServiceAccountConfigured()) {
47
- throw new Error(
48
- 'OP_SERVICE_ACCOUNT_TOKEN not set. Configure a service account token: https://developer.1password.com/docs/service-accounts/'
49
- )
50
- }
51
-
52
- if (!reference.startsWith('op://')) {
53
- throw new Error(
54
- `Invalid secret reference format: ${reference}. Expected format: op://vault/item/field`
55
- )
56
- }
57
-
58
- try {
59
- const output = execSync(`op read "${reference}"`, {
60
- encoding: 'utf-8',
61
- stdio: ['ignore', 'pipe', 'pipe'],
62
- })
63
- return output.trim()
64
- } catch (err) {
65
- const message = err instanceof Error ? err.message : 'Unknown error'
66
- throw new Error(`Failed to read secret from 1Password: ${message}`)
67
- }
68
- }
69
-
70
- /**
71
- * Execute a command with secrets injected via `op run`
72
- *
73
- * The command runs with secrets referenced in the environment automatically resolved.
74
- * Use this when you need to run commands that require secrets in their environment.
75
- *
76
- * @param cmd - Command and arguments to execute
77
- * @returns stdout and stderr from the command
78
- * @throws Error if op CLI is not available, token is not configured, or execution fails
79
- *
80
- * @example
81
- * // Environment has STRIPE_KEY="op://Private/Stripe/api_key"
82
- * const { stdout } = await runWithSecrets(['node', 'script.js'])
83
- * // script.js will see STRIPE_KEY with the actual secret value
84
- */
85
- export async function runWithSecrets(
86
- cmd: string[]
87
- ): Promise<{ stdout: string; stderr: string }> {
88
- if (!(await isOpAvailable())) {
89
- throw new Error(
90
- '1Password CLI not installed. Install from https://developer.1password.com/docs/cli/get-started/'
91
- )
92
- }
93
-
94
- if (!isServiceAccountConfigured()) {
95
- throw new Error(
96
- 'OP_SERVICE_ACCOUNT_TOKEN not set. Configure a service account token: https://developer.1password.com/docs/service-accounts/'
97
- )
98
- }
99
-
100
- if (!cmd.length) {
101
- throw new Error('Command array cannot be empty')
102
- }
103
-
104
- return new Promise((resolve, reject) => {
105
- const child = spawn('op', ['run', '--', ...cmd], {
106
- stdio: ['ignore', 'pipe', 'pipe'],
107
- env: process.env,
108
- })
109
-
110
- let stdout = ''
111
- let stderr = ''
112
-
113
- child.stdout?.on('data', (data) => {
114
- stdout += data.toString()
115
- })
116
-
117
- child.stderr?.on('data', (data) => {
118
- stderr += data.toString()
119
- })
120
-
121
- child.on('close', (code) => {
122
- if (code === 0) {
123
- resolve({ stdout, stderr })
124
- } else {
125
- reject(
126
- new Error(
127
- `Command failed with exit code ${code}${stderr ? `: ${stderr}` : ''}`
128
- )
129
- )
130
- }
131
- })
132
-
133
- child.on('error', (err) => {
134
- reject(new Error(`Failed to spawn command: ${err.message}`))
135
- })
136
- })
137
- }
@@ -1,115 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Local test script for support agent
4
- *
5
- * Usage:
6
- * cd packages/cli && bun ../../scripts/test-agent-local.ts
7
- *
8
- * Tests that the agent can access appConfig through experimental_context,
9
- * specifically the instructor_teammate_id for assignToInstructor tool.
10
- */
11
-
12
- import { runSupportAgent } from '@skillrecordings/core/agent'
13
- import { database } from '@skillrecordings/database'
14
- import { IntegrationClient } from '@skillrecordings/sdk/client'
15
-
16
- async function main() {
17
- const appSlug = process.argv[2] || 'total-typescript'
18
- const testMessage =
19
- process.argv[3] ||
20
- 'Hey Matt, just wanted to say your TypeScript course is amazing!'
21
-
22
- console.log('='.repeat(60))
23
- console.log('SUPPORT AGENT LOCAL TEST')
24
- console.log('='.repeat(60))
25
- console.log(`App: ${appSlug}`)
26
- console.log(`Message: ${testMessage}`)
27
- console.log('')
28
-
29
- // Fetch app from database
30
- const app = await database.query.AppsTable.findFirst({
31
- where: (apps, { eq }) => eq(apps.slug, appSlug),
32
- })
33
-
34
- if (!app) {
35
- console.error(`❌ App not found: ${appSlug}`)
36
- process.exit(1)
37
- }
38
-
39
- console.log('App Config:')
40
- console.log(
41
- ` instructor_teammate_id: ${app.instructor_teammate_id || '(not set)'}`
42
- )
43
- console.log(` stripe_account_id: ${app.stripe_account_id || '(not set)'}`)
44
- console.log(` integration_base_url: ${app.integration_base_url}`)
45
- console.log('')
46
-
47
- // Create integration client
48
- const integrationClient = new IntegrationClient({
49
- baseUrl: app.integration_base_url,
50
- webhookSecret: app.webhook_secret,
51
- })
52
-
53
- console.log('Running agent...')
54
- console.log('-'.repeat(60))
55
-
56
- try {
57
- const result = await runSupportAgent({
58
- message: testMessage,
59
- conversationHistory: [],
60
- customerContext: {
61
- email: '[EMAIL]',
62
- },
63
- appId: appSlug,
64
- model: 'anthropic/claude-haiku-4-5',
65
- integrationClient,
66
- appConfig: {
67
- instructor_teammate_id: app.instructor_teammate_id || undefined,
68
- stripeAccountId: app.stripe_account_id || undefined,
69
- },
70
- })
71
-
72
- console.log('')
73
- console.log('RESULT:')
74
- console.log('-'.repeat(60))
75
- console.log(`Response: ${result.response || '(no response)'}`)
76
- console.log(`Requires Approval: ${result.requiresApproval}`)
77
- console.log(`Auto-sent: ${result.autoSent || false}`)
78
- console.log('')
79
- console.log('Tool Calls:')
80
- for (const tc of result.toolCalls) {
81
- console.log(` - ${tc.name}(${JSON.stringify(tc.args)})`)
82
- console.log(` Result: ${JSON.stringify(tc.result)}`)
83
- }
84
-
85
- // Check if assignToInstructor was called and succeeded
86
- const assignCall = result.toolCalls.find(
87
- (tc) => tc.name === 'assignToInstructor'
88
- )
89
- if (assignCall) {
90
- const assignResult = assignCall.result as {
91
- assigned?: boolean
92
- error?: string
93
- }
94
- if (assignResult?.assigned) {
95
- console.log('')
96
- console.log(
97
- '✅ assignToInstructor SUCCEEDED - context was passed correctly!'
98
- )
99
- } else if (assignResult?.error) {
100
- console.log('')
101
- console.log(`❌ assignToInstructor FAILED: ${assignResult.error}`)
102
- console.log(
103
- ' This might indicate experimental_context is not being passed correctly.'
104
- )
105
- }
106
- }
107
- } catch (error) {
108
- console.error('❌ Agent error:', error)
109
- process.exit(1)
110
- }
111
-
112
- process.exit(0)
113
- }
114
-
115
- main()
package/tsconfig.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "@repo/typescript-config/base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "module": "ESNext",
6
- "target": "ESNext",
7
- "moduleResolution": "bundler"
8
- },
9
- "include": ["src"],
10
- "exclude": ["node_modules", "dist"]
11
- }