@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.
- package/bin/skill.mjs +27 -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
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
|
-
}
|
package/src/lib/env-loader.ts
DELETED
|
@@ -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
|
-
}
|
package/src/lib/onepassword.ts
DELETED
|
@@ -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
|
-
}
|
package/src/test-agent-local.ts
DELETED
|
@@ -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