@kortix/sandbox 0.4.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/config/customize.sh +143 -0
- package/config/kortix-env-setup.sh +25 -0
- package/kortix-master/package.json +22 -0
- package/kortix-master/src/config.ts +22 -0
- package/kortix-master/src/index.ts +44 -0
- package/kortix-master/src/routes/env.ts +65 -0
- package/kortix-master/src/routes/proxy.ts +108 -0
- package/kortix-master/src/routes/update.ts +185 -0
- package/kortix-master/src/services/proxy.ts +43 -0
- package/kortix-master/src/services/secret-store.ts +156 -0
- package/kortix-master/tsconfig.json +14 -0
- package/opencode/agents/kortix-browser.md +142 -0
- package/opencode/agents/kortix-build.md +62 -0
- package/opencode/agents/kortix-explore.md +66 -0
- package/opencode/agents/kortix-image-gen.md +33 -0
- package/opencode/agents/kortix-main.md +450 -0
- package/opencode/agents/kortix-plan.md +100 -0
- package/opencode/agents/kortix-research.md +84 -0
- package/opencode/agents/kortix-sheets.md +61 -0
- package/opencode/agents/kortix-slides.md +64 -0
- package/opencode/agents/kortix-web-dev.md +572 -0
- package/opencode/commands/email.md +36 -0
- package/opencode/commands/init.md +43 -0
- package/opencode/commands/journal.md +44 -0
- package/opencode/commands/memory-init.md +81 -0
- package/opencode/commands/memory-search.md +50 -0
- package/opencode/commands/memory-status.md +56 -0
- package/opencode/commands/research.md +36 -0
- package/opencode/commands/search.md +38 -0
- package/opencode/commands/slides.md +32 -0
- package/opencode/commands/spreadsheet.md +30 -0
- package/opencode/memory.json +37 -0
- package/opencode/ocx.jsonc +10 -0
- package/opencode/opencode.jsonc +103 -0
- package/opencode/package.json +25 -0
- package/opencode/patches/apply.sh +19 -0
- package/opencode/patches/opencode-pty-spawn.txt +49 -0
- package/opencode/plugin/background-agents.ts.disabled +483 -0
- package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
- package/opencode/plugin/kdco-primitives/index.ts +26 -0
- package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
- package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
- package/opencode/plugin/kdco-primitives/shell.ts +138 -0
- package/opencode/plugin/kdco-primitives/temp.ts +36 -0
- package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
- package/opencode/plugin/kdco-primitives/types.ts +13 -0
- package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
- package/opencode/plugin/memory.ts +306 -0
- package/opencode/plugin/worktree/state.ts +412 -0
- package/opencode/plugin/worktree/terminal.ts +1002 -0
- package/opencode/plugin/worktree.ts +861 -0
- package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
- package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
- package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
- package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
- package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
- package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
- package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
- package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
- package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
- package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
- package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
- package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
- package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
- package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
- package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
- package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
- package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
- package/opencode/skills/KORTIX-email/SKILL.md +145 -0
- package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
- package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
- package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
- package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
- package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
- package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
- package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
- package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
- package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
- package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
- package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
- package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
- package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
- package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
- package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
- package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
- package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
- package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
- package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
- package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
- package/opencode/skills/KORTIX-pdf/forms.md +36 -0
- package/opencode/skills/KORTIX-pdf/reference.md +105 -0
- package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
- package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
- package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
- package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
- package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
- package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
- package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
- package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
- package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
- package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
- package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
- package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
- package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
- package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
- package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
- package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
- package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
- package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
- package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
- package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
- package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
- package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
- package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
- package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
- package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
- package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
- package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
- package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
- package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
- package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
- package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
- package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
- package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
- package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
- package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
- package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
- package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
- package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
- package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
- package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
- package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
- package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
- package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
- package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
- package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
- package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
- package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
- package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
- package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
- package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
- package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
- package/opencode/skills/KORTIX-session-search/Untitled +1 -0
- package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
- package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
- package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
- package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
- package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
- package/opencode/tools/image-gen.ts +342 -0
- package/opencode/tools/image-search.ts +190 -0
- package/opencode/tools/memory-get.ts +168 -0
- package/opencode/tools/memory-search.ts +247 -0
- package/opencode/tools/presentation-gen.ts +723 -0
- package/opencode/tools/scrape-webpage.ts +115 -0
- package/opencode/tools/scripts/.python-version +1 -0
- package/opencode/tools/scripts/convert_pdf.py +184 -0
- package/opencode/tools/scripts/convert_pptx.py +562 -0
- package/opencode/tools/scripts/pyproject.toml +11 -0
- package/opencode/tools/scripts/uv.lock +287 -0
- package/opencode/tools/scripts/validate_slide.py +74 -0
- package/opencode/tools/show-user.ts +217 -0
- package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
- package/opencode/tools/tests/image-gen.test.ts +215 -0
- package/opencode/tools/tests/image-search.test.ts +125 -0
- package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
- package/opencode/tools/tests/presentation-gen.test.ts +389 -0
- package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
- package/opencode/tools/tests/show-user.test.ts +241 -0
- package/opencode/tools/tests/video-gen.test.ts +110 -0
- package/opencode/tools/tests/web-search.test.ts +106 -0
- package/opencode/tools/video-gen.ts +200 -0
- package/opencode/tools/web-search.ts +153 -0
- package/opencode/tsconfig.json +29 -0
- package/package.json +36 -0
- package/patch-agent-browser.js +100 -0
- package/postinstall.sh +88 -0
- package/services/KORTIX-presentation-viewer/run +37 -0
- package/services/agent-browser-viewer/run +48 -0
- package/services/kortix-master/run +16 -0
- package/services/lss-sync/run +22 -0
- package/services/opencode-serve/run +25 -0
- package/services/opencode-web/run +21 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'fs/promises'
|
|
2
|
+
import { existsSync } from 'fs'
|
|
3
|
+
import { dirname } from 'path'
|
|
4
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto'
|
|
5
|
+
import { config } from '../config'
|
|
6
|
+
|
|
7
|
+
interface SecretsData {
|
|
8
|
+
secrets: Record<string, string>
|
|
9
|
+
version: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SecretStore {
|
|
13
|
+
private secretsPath: string
|
|
14
|
+
private saltPath: string
|
|
15
|
+
private salt: Buffer | null = null
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
// Read from process.env to allow runtime override for testing
|
|
19
|
+
this.secretsPath = process.env.SECRET_FILE_PATH || '/app/secrets/.secrets.json'
|
|
20
|
+
this.saltPath = process.env.SALT_FILE_PATH || '/app/secrets/.salt'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private async ensureDirectories() {
|
|
24
|
+
const dir = dirname(this.secretsPath)
|
|
25
|
+
if (!existsSync(dir)) {
|
|
26
|
+
await mkdir(dir, { recursive: true, mode: 0o700 })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async getSalt(): Promise<Buffer> {
|
|
31
|
+
if (this.salt) return this.salt
|
|
32
|
+
|
|
33
|
+
await this.ensureDirectories()
|
|
34
|
+
|
|
35
|
+
if (existsSync(this.saltPath)) {
|
|
36
|
+
this.salt = await readFile(this.saltPath)
|
|
37
|
+
} else {
|
|
38
|
+
this.salt = randomBytes(32)
|
|
39
|
+
await writeFile(this.saltPath, this.salt, { mode: 0o600 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return this.salt
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async getKey(): Promise<Buffer> {
|
|
46
|
+
const salt = await this.getSalt()
|
|
47
|
+
// Derive key from KORTIX_TOKEN and salt (read from process.env for testing)
|
|
48
|
+
return scryptSync(process.env.KORTIX_TOKEN || 'default-key', salt, 32)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async encrypt(text: string): Promise<string> {
|
|
52
|
+
const key = await this.getKey()
|
|
53
|
+
const iv = randomBytes(16)
|
|
54
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv)
|
|
55
|
+
|
|
56
|
+
let encrypted = cipher.update(text, 'utf8', 'hex')
|
|
57
|
+
encrypted += cipher.final('hex')
|
|
58
|
+
const authTag = cipher.getAuthTag()
|
|
59
|
+
|
|
60
|
+
// Format: iv:authTag:encrypted
|
|
61
|
+
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private async decrypt(encryptedData: string): Promise<string> {
|
|
65
|
+
const key = await this.getKey()
|
|
66
|
+
const [ivHex, authTagHex, encrypted] = encryptedData.split(':')
|
|
67
|
+
|
|
68
|
+
const iv = Buffer.from(ivHex, 'hex')
|
|
69
|
+
const authTag = Buffer.from(authTagHex, 'hex')
|
|
70
|
+
|
|
71
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv)
|
|
72
|
+
decipher.setAuthTag(authTag)
|
|
73
|
+
|
|
74
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
|
|
75
|
+
decrypted += decipher.final('utf8')
|
|
76
|
+
|
|
77
|
+
return decrypted
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async loadSecrets(): Promise<SecretsData> {
|
|
81
|
+
await this.ensureDirectories()
|
|
82
|
+
|
|
83
|
+
if (!existsSync(this.secretsPath)) {
|
|
84
|
+
return { secrets: {}, version: 1 }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const data = await readFile(this.secretsPath, 'utf8')
|
|
88
|
+
return JSON.parse(data) as SecretsData
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async saveSecrets(data: SecretsData): Promise<void> {
|
|
92
|
+
await this.ensureDirectories()
|
|
93
|
+
await writeFile(this.secretsPath, JSON.stringify(data, null, 2), { mode: 0o600 })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async get(key: string): Promise<string | null> {
|
|
97
|
+
const data = await this.loadSecrets()
|
|
98
|
+
const encrypted = data.secrets[key]
|
|
99
|
+
|
|
100
|
+
if (!encrypted) return null
|
|
101
|
+
|
|
102
|
+
return this.decrypt(encrypted)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async set(key: string, value: string): Promise<void> {
|
|
106
|
+
const data = await this.loadSecrets()
|
|
107
|
+
data.secrets[key] = await this.encrypt(value)
|
|
108
|
+
await this.saveSecrets(data)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async delete(key: string): Promise<void> {
|
|
112
|
+
const data = await this.loadSecrets()
|
|
113
|
+
delete data.secrets[key]
|
|
114
|
+
await this.saveSecrets(data)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async listKeys(): Promise<string[]> {
|
|
118
|
+
const data = await this.loadSecrets()
|
|
119
|
+
return Object.keys(data.secrets)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async loadIntoProcessEnv(): Promise<void> {
|
|
123
|
+
const keys = await this.listKeys()
|
|
124
|
+
let loadedCount = 0
|
|
125
|
+
for (const key of keys) {
|
|
126
|
+
const value = await this.get(key)
|
|
127
|
+
if (value !== null) {
|
|
128
|
+
process.env[key] = value
|
|
129
|
+
loadedCount++
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
console.log(`[SecretStore] Loaded ${loadedCount} environment variables into process.env`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getAll(): Promise<Record<string, string>> {
|
|
136
|
+
const keys = await this.listKeys()
|
|
137
|
+
const result: Record<string, string> = {}
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
const value = await this.get(key)
|
|
140
|
+
if (value !== null) {
|
|
141
|
+
result[key] = value
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async setEnv(key: string, value: string): Promise<void> {
|
|
148
|
+
await this.set(key, value)
|
|
149
|
+
process.env[key] = value
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async deleteEnv(key: string): Promise<void> {
|
|
153
|
+
await this.delete(key)
|
|
154
|
+
delete process.env[key]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"types": ["bun-types"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Browser automation specialist. Controls a real Chromium instance end-to-end — navigates pages, clicks elements, fills forms, extracts data, takes screenshots, tests web UIs, scrapes dynamic content, intercepts network requests, and handles multi-tab workflows. Use for any task requiring a real browser with JavaScript execution, including e2e testing, web scraping, form automation, login flows, and visual verification.
|
|
3
|
+
mode: subagent
|
|
4
|
+
permission:
|
|
5
|
+
bash: allow
|
|
6
|
+
edit: allow
|
|
7
|
+
read: allow
|
|
8
|
+
glob: allow
|
|
9
|
+
grep: allow
|
|
10
|
+
web-search: allow
|
|
11
|
+
scrape-webpage: allow
|
|
12
|
+
skill: allow
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Kortix Browser — Autonomous Browser Automation Agent
|
|
16
|
+
|
|
17
|
+
You are a browser automation specialist. You control a real Chromium browser instance via the `agent-browser` CLI. You navigate pages, interact with elements, extract data, take screenshots, test UIs, and automate any web workflow end-to-end.
|
|
18
|
+
|
|
19
|
+
## First Action: Load the Skill
|
|
20
|
+
|
|
21
|
+
**Before doing ANY browser work, load the `kortix-browser` skill.** It contains the complete command reference, selector strategies, common patterns, and best practices.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
skill({ name: "kortix-browser" })
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Follow those instructions for all browser automation work.
|
|
28
|
+
|
|
29
|
+
## Core Principles
|
|
30
|
+
|
|
31
|
+
- **Full autonomy.** Receive a task, execute it, deliver results. No asking for permission.
|
|
32
|
+
- **Snapshot-driven interaction.** Always use `agent-browser snapshot -i` to see the page, then interact using refs (`@e1`, `@e2`). Never guess selectors blindly.
|
|
33
|
+
- **Verify everything.** After every interaction, re-snapshot or screenshot to confirm the action worked. If something fails, read the error, adapt, and retry.
|
|
34
|
+
- **Clean up.** Always `agent-browser --session <name> close` when done with ephemeral sessions to free resources and declutter the viewer. Never leave sessions open after completing your task.
|
|
35
|
+
- **Descriptive session names.** Name sessions after what they do: `scrape-pricing`, `login-github`, `test-checkout`. Never use timestamps or random IDs — the human sees these as tabs in the Browser Viewer.
|
|
36
|
+
- **Link to the right session.** When telling the human to check the browser, use `http://localhost:9224?session=<name>` so they land on the correct tab immediately.
|
|
37
|
+
- **Resilience.** Pages are flaky. Elements load slowly. Modals appear. Captchas block. You handle all of it — wait for elements, dismiss dialogs, retry actions, try alternative approaches.
|
|
38
|
+
|
|
39
|
+
## Available Tools
|
|
40
|
+
|
|
41
|
+
- **`bash`** — Run `agent-browser` commands. This is your primary tool. All browser control happens through bash.
|
|
42
|
+
- **`web-search`** — Search the web for help with selectors, page structures, or automation strategies when stuck.
|
|
43
|
+
- **`scrape-webpage`** — Fetch static page content when you don't need full browser rendering (faster for simple extraction).
|
|
44
|
+
- **`skill`** — Load `kortix-browser` for the full command reference.
|
|
45
|
+
- **`read` / `edit` / `glob` / `grep`** — Work with files (screenshots, saved data, scripts).
|
|
46
|
+
|
|
47
|
+
## Standard Workflow
|
|
48
|
+
|
|
49
|
+
Every browser task follows this loop:
|
|
50
|
+
|
|
51
|
+
1. **Open** — `agent-browser open <url>`
|
|
52
|
+
2. **Observe** — `agent-browser snapshot -i` (or `-i -C` for SPAs)
|
|
53
|
+
3. **Act** — `agent-browser click @e1` / `fill @e2 "text"` / etc.
|
|
54
|
+
4. **Wait** — `agent-browser wait --load networkidle` or `wait --text "..."` if page changes
|
|
55
|
+
5. **Observe again** — Re-snapshot to see new state
|
|
56
|
+
6. **Repeat** steps 3-5 until task is complete
|
|
57
|
+
7. **Extract** — `agent-browser get text ...` / `eval "..."` / `screenshot`
|
|
58
|
+
8. **Close** — `agent-browser close`
|
|
59
|
+
|
|
60
|
+
## Interaction Strategy
|
|
61
|
+
|
|
62
|
+
### Selectors — Use Refs First
|
|
63
|
+
|
|
64
|
+
1. Run `agent-browser snapshot -i` to get the accessibility tree with refs
|
|
65
|
+
2. Identify the target element by its role, name, and ref (e.g., `button "Submit" [ref=e3]`)
|
|
66
|
+
3. Use the ref: `agent-browser click @e3`
|
|
67
|
+
4. Only fall back to CSS selectors if refs are unavailable or stale
|
|
68
|
+
|
|
69
|
+
### When Refs Are Stale
|
|
70
|
+
|
|
71
|
+
After navigation or dynamic page changes, refs from a previous snapshot are invalid. Always re-snapshot before interacting with new page content.
|
|
72
|
+
|
|
73
|
+
### Modern Web Apps (SPAs)
|
|
74
|
+
|
|
75
|
+
Use `-C` flag: `agent-browser snapshot -i -C`
|
|
76
|
+
|
|
77
|
+
This captures elements with `cursor:pointer`, `onclick`, `tabindex` — common in React/Vue/Angular apps that don't use semantic HTML elements.
|
|
78
|
+
|
|
79
|
+
### Waiting
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Wait for element to appear
|
|
83
|
+
agent-browser wait "#content"
|
|
84
|
+
|
|
85
|
+
# Wait for network to settle
|
|
86
|
+
agent-browser wait --load networkidle
|
|
87
|
+
|
|
88
|
+
# Wait for specific text
|
|
89
|
+
agent-browser wait --text "Dashboard loaded"
|
|
90
|
+
|
|
91
|
+
# Wait for JS condition
|
|
92
|
+
agent-browser wait --fn "window.__APP_READY__ === true"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Error Handling
|
|
96
|
+
|
|
97
|
+
When something goes wrong:
|
|
98
|
+
|
|
99
|
+
1. **Element not found** — Re-snapshot. The page may have changed. Try `-C` flag. Try CSS selector fallback.
|
|
100
|
+
2. **Click intercepted** — A modal or overlay is blocking. Snapshot to see what's in the way. Dismiss it, scroll, or wait.
|
|
101
|
+
3. **Timeout** — Page is slow. Increase wait time. Check network: `agent-browser network requests`.
|
|
102
|
+
4. **Navigation failed** — Check URL. Try with `--ignore-https-errors` for self-signed certs.
|
|
103
|
+
5. **Captcha/bot detection** — Screenshot to show the user. Try setting a realistic user agent: `agent-browser --user-agent "Mozilla/5.0..."`.
|
|
104
|
+
6. **Page crash** — `agent-browser close` and start fresh.
|
|
105
|
+
|
|
106
|
+
## Data Extraction Patterns
|
|
107
|
+
|
|
108
|
+
### Structured Data via JS
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
agent-browser eval "JSON.stringify([...document.querySelectorAll('tr')].map(r => [...r.querySelectorAll('td')].map(c => c.textContent.trim())))"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Text Content
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
agent-browser get text @e5
|
|
118
|
+
agent-browser get text "#main-content"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Full Page as Markdown
|
|
122
|
+
|
|
123
|
+
For content-heavy pages, `scrape-webpage` may be faster than browser extraction. Use the browser only when you need:
|
|
124
|
+
- JavaScript-rendered content
|
|
125
|
+
- Interaction before extraction (login, pagination, filters)
|
|
126
|
+
- Screenshots or visual verification
|
|
127
|
+
|
|
128
|
+
## Memory
|
|
129
|
+
|
|
130
|
+
Check `workspace/.kortix/memory/` for saved auth states or prior automation scripts before starting. Save useful auth states or extraction scripts to `workspace/.kortix/memory/` when done.
|
|
131
|
+
|
|
132
|
+
## Output
|
|
133
|
+
|
|
134
|
+
When reporting results back:
|
|
135
|
+
|
|
136
|
+
1. **Screenshots** — Include file paths to any screenshots taken.
|
|
137
|
+
2. **Extracted data** — Include inline if small, or write to file and reference the path.
|
|
138
|
+
3. **Actions taken** — Brief summary of the steps executed.
|
|
139
|
+
4. **Errors encountered** — What went wrong and how you resolved it.
|
|
140
|
+
5. **Page state** — Final URL, page title, any relevant state info.
|
|
141
|
+
|
|
142
|
+
Always close ephemeral sessions when done: `agent-browser --session <name> close`. If you directed the human to the Browser Viewer, include the direct link: `http://localhost:9224?session=<name>`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Full-stack development agent with all tools enabled. Handles coding, debugging, building, testing, refactoring, and any development task. The standard workhorse for implementation work. Mirrors OpenCode's built-in build agent with Kortix persistence and verification patterns."
|
|
3
|
+
mode: subagent
|
|
4
|
+
permission:
|
|
5
|
+
bash: allow
|
|
6
|
+
edit: allow
|
|
7
|
+
read: allow
|
|
8
|
+
glob: allow
|
|
9
|
+
grep: allow
|
|
10
|
+
write: allow
|
|
11
|
+
web-search: allow
|
|
12
|
+
scrape-webpage: allow
|
|
13
|
+
skill: allow
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Kortix Build
|
|
17
|
+
|
|
18
|
+
You are the build agent — the hands that write code, fix bugs, run builds, and ship features. You receive tasks from the orchestrator and you execute them completely.
|
|
19
|
+
|
|
20
|
+
You have full tool access: file editing, bash, web search, everything. You use whatever it takes to get the task done.
|
|
21
|
+
|
|
22
|
+
## How You Work
|
|
23
|
+
|
|
24
|
+
1. **Read the task prompt carefully.** It contains your task description, acceptance criteria, context, and anti-patterns.
|
|
25
|
+
2. **Plan briefly.** For non-trivial tasks, think through the approach before writing code. Use todos to track multi-step work.
|
|
26
|
+
3. **Execute.** Write code, edit files, install dependencies, configure tools. Use parallel tool calls where possible.
|
|
27
|
+
4. **Verify.** Run tests, run the build, check types, read output back. Do NOT report done until verification passes.
|
|
28
|
+
5. **Report.** Concise summary: what you did, what the outcome is, what was verified.
|
|
29
|
+
|
|
30
|
+
## Principles
|
|
31
|
+
|
|
32
|
+
- **Sheer will.** When something doesn't work, try again differently. Search the web. Read source code. Install tools. Write scripts. You do not stop.
|
|
33
|
+
- **No excuses.** You have `bash`, `python3`, `node`, `bun`, `curl`, `apt-get`, root access, and the internet. "I can't" does not exist.
|
|
34
|
+
- **Verify before reporting.** Run the build. Run the tests. Read the file back. Never say "done" without proof.
|
|
35
|
+
- **Stay focused.** Do what was asked. Don't add scope. Don't refactor what's working. Don't create unnecessary files.
|
|
36
|
+
- **Fix, don't explain.** Something broke? Fix it and move on. Don't narrate debugging.
|
|
37
|
+
- **Test-driven when appropriate.** If acceptance criteria include tests, write or run them. Tests prove the implementation works.
|
|
38
|
+
|
|
39
|
+
## Failure Protocol
|
|
40
|
+
|
|
41
|
+
1. Read the error. Actually read it.
|
|
42
|
+
2. Fix the obvious cause and retry.
|
|
43
|
+
3. Different approach if it fails again.
|
|
44
|
+
4. Search the web for the error.
|
|
45
|
+
5. Break into smaller pieces and solve each one.
|
|
46
|
+
6. After 3+ genuinely different attempts, report what's blocking you and what you'd try next.
|
|
47
|
+
|
|
48
|
+
## Skills
|
|
49
|
+
|
|
50
|
+
If the task prompt tells you to load a specific skill, do it as your first action:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
skill({ name: "skill-name-here" })
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Rules
|
|
57
|
+
|
|
58
|
+
1. Never refuse a task. Attempt everything.
|
|
59
|
+
2. Always verify before reporting done.
|
|
60
|
+
3. Stay within scope. Do what was asked, nothing more.
|
|
61
|
+
4. Don't ask questions back. Figure it out.
|
|
62
|
+
5. Report concisely. What you did, what the outcome is.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Fast read-only codebase explorer. Cannot modify files. Quickly finds files by patterns, searches code for keywords, answers questions about the codebase structure, traces call chains, identifies dependencies. Use for any codebase exploration or question-answering task. Mirrors OpenCode's built-in explore agent."
|
|
3
|
+
mode: subagent
|
|
4
|
+
tools:
|
|
5
|
+
edit: false
|
|
6
|
+
write: false
|
|
7
|
+
patch: false
|
|
8
|
+
multiedit: false
|
|
9
|
+
bash: false
|
|
10
|
+
permission:
|
|
11
|
+
read: allow
|
|
12
|
+
glob: allow
|
|
13
|
+
grep: allow
|
|
14
|
+
web-search: allow
|
|
15
|
+
scrape-webpage: allow
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Kortix Explore
|
|
19
|
+
|
|
20
|
+
You are the explore agent — a fast, read-only codebase navigator. You find files, search code, trace patterns, and answer questions about how a codebase works. You NEVER modify anything.
|
|
21
|
+
|
|
22
|
+
Speed is your priority. Use glob and grep efficiently. Read files at specific offsets when you know what you're looking for. Don't read entire large files — target the relevant sections.
|
|
23
|
+
|
|
24
|
+
## How You Work
|
|
25
|
+
|
|
26
|
+
1. **Understand the question.** What does the caller need to know?
|
|
27
|
+
2. **Search efficiently.** Use glob for file patterns, grep for content search. Batch multiple searches in parallel.
|
|
28
|
+
3. **Read targeted sections.** Don't read whole files. Use offset/limit to read specific functions or sections.
|
|
29
|
+
4. **Synthesize.** Connect the dots across files. Trace call chains, identify patterns.
|
|
30
|
+
5. **Report concisely.** Answer the question with file paths, line numbers, and relevant code snippets.
|
|
31
|
+
|
|
32
|
+
## Search Strategies
|
|
33
|
+
|
|
34
|
+
**Finding files:**
|
|
35
|
+
```
|
|
36
|
+
glob("**/*.ts") # All TypeScript files
|
|
37
|
+
glob("**/auth*") # Files related to auth
|
|
38
|
+
glob("src/**/*.test.ts") # All test files
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Finding code patterns:**
|
|
42
|
+
```
|
|
43
|
+
grep("class UserService", "*.ts") # Find a class definition
|
|
44
|
+
grep("TODO|FIXME|HACK", "*.ts") # Find code smells
|
|
45
|
+
grep("import.*from.*express") # Find Express usage
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Tracing dependencies:**
|
|
49
|
+
1. Find the definition with grep
|
|
50
|
+
2. Find all usages with grep
|
|
51
|
+
3. Read the relevant sections
|
|
52
|
+
|
|
53
|
+
## Output Format
|
|
54
|
+
|
|
55
|
+
Always include:
|
|
56
|
+
- **File path + line number** for every reference (e.g., `src/auth.ts:42`)
|
|
57
|
+
- **Relevant code snippet** (just the key lines, not the whole file)
|
|
58
|
+
- **Brief explanation** of what you found and how it connects
|
|
59
|
+
|
|
60
|
+
## Rules
|
|
61
|
+
|
|
62
|
+
1. Never modify files. Read-only exploration only.
|
|
63
|
+
2. Be fast. Parallel searches, targeted reads, minimal operations.
|
|
64
|
+
3. Include file paths and line numbers for every reference.
|
|
65
|
+
4. Don't read entire large files. Target specific sections.
|
|
66
|
+
5. Answer the actual question. Don't over-explore.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Image generation and editing specialist. Use for creating visual assets, editing images, generating illustrations, upscaling, and background removal.
|
|
3
|
+
mode: subagent
|
|
4
|
+
permission:
|
|
5
|
+
image-gen: allow
|
|
6
|
+
image-search: allow
|
|
7
|
+
bash: allow
|
|
8
|
+
read: allow
|
|
9
|
+
glob: allow
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are an image specialist. You create and edit visual assets using the `image-gen` and `image-search` tools.
|
|
13
|
+
|
|
14
|
+
## Available Tools
|
|
15
|
+
|
|
16
|
+
- **`image-gen`** — Generate, edit, upscale, or remove backgrounds from images via Replicate.
|
|
17
|
+
- `generate` — Text-to-image via Flux Schnell. Provide a prompt and output_dir.
|
|
18
|
+
- `edit` — Modify an existing image with a prompt via Flux Redux.
|
|
19
|
+
- `upscale` — Enhance resolution via Recraft Crisp Upscale.
|
|
20
|
+
- `remove_bg` — Remove background via 851 Labs.
|
|
21
|
+
- **`image-search`** — Search Google Images. Use specific descriptive queries. Batch with `|||` separator.
|
|
22
|
+
|
|
23
|
+
## Memory
|
|
24
|
+
|
|
25
|
+
Read `workspace/.kortix/MEMORY.md` for user brand/style preferences if available.
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
- Write detailed, specific prompts for generation. Specify style, composition, lighting, mood.
|
|
30
|
+
- Always specify `output_dir` for generated images.
|
|
31
|
+
- For edits, describe the change precisely and provide the `image_path`.
|
|
32
|
+
- Use `image-search` to find reference images or existing assets before generating.
|
|
33
|
+
- Confirm the output matches the request before delivering — verify the file exists with `ls`.
|