@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.
Files changed (246) hide show
  1. package/config/customize.sh +143 -0
  2. package/config/kortix-env-setup.sh +25 -0
  3. package/kortix-master/package.json +22 -0
  4. package/kortix-master/src/config.ts +22 -0
  5. package/kortix-master/src/index.ts +44 -0
  6. package/kortix-master/src/routes/env.ts +65 -0
  7. package/kortix-master/src/routes/proxy.ts +108 -0
  8. package/kortix-master/src/routes/update.ts +185 -0
  9. package/kortix-master/src/services/proxy.ts +43 -0
  10. package/kortix-master/src/services/secret-store.ts +156 -0
  11. package/kortix-master/tsconfig.json +14 -0
  12. package/opencode/agents/kortix-browser.md +142 -0
  13. package/opencode/agents/kortix-build.md +62 -0
  14. package/opencode/agents/kortix-explore.md +66 -0
  15. package/opencode/agents/kortix-image-gen.md +33 -0
  16. package/opencode/agents/kortix-main.md +450 -0
  17. package/opencode/agents/kortix-plan.md +100 -0
  18. package/opencode/agents/kortix-research.md +84 -0
  19. package/opencode/agents/kortix-sheets.md +61 -0
  20. package/opencode/agents/kortix-slides.md +64 -0
  21. package/opencode/agents/kortix-web-dev.md +572 -0
  22. package/opencode/commands/email.md +36 -0
  23. package/opencode/commands/init.md +43 -0
  24. package/opencode/commands/journal.md +44 -0
  25. package/opencode/commands/memory-init.md +81 -0
  26. package/opencode/commands/memory-search.md +50 -0
  27. package/opencode/commands/memory-status.md +56 -0
  28. package/opencode/commands/research.md +36 -0
  29. package/opencode/commands/search.md +38 -0
  30. package/opencode/commands/slides.md +32 -0
  31. package/opencode/commands/spreadsheet.md +30 -0
  32. package/opencode/memory.json +37 -0
  33. package/opencode/ocx.jsonc +10 -0
  34. package/opencode/opencode.jsonc +103 -0
  35. package/opencode/package.json +25 -0
  36. package/opencode/patches/apply.sh +19 -0
  37. package/opencode/patches/opencode-pty-spawn.txt +49 -0
  38. package/opencode/plugin/background-agents.ts.disabled +483 -0
  39. package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
  40. package/opencode/plugin/kdco-primitives/index.ts +26 -0
  41. package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
  42. package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
  43. package/opencode/plugin/kdco-primitives/shell.ts +138 -0
  44. package/opencode/plugin/kdco-primitives/temp.ts +36 -0
  45. package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
  46. package/opencode/plugin/kdco-primitives/types.ts +13 -0
  47. package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
  48. package/opencode/plugin/memory.ts +306 -0
  49. package/opencode/plugin/worktree/state.ts +412 -0
  50. package/opencode/plugin/worktree/terminal.ts +1002 -0
  51. package/opencode/plugin/worktree.ts +861 -0
  52. package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
  53. package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
  54. package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
  55. package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
  56. package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
  57. package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
  58. package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
  59. package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
  60. package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
  61. package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
  62. package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
  63. package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
  64. package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
  65. package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
  66. package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
  67. package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
  68. package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
  69. package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
  70. package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
  71. package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
  72. package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
  73. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
  74. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
  75. package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
  76. package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
  77. package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
  78. package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
  79. package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
  80. package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
  81. package/opencode/skills/KORTIX-email/SKILL.md +145 -0
  82. package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
  83. package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
  84. package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
  85. package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
  86. package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
  87. package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
  88. package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
  89. package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
  90. package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
  91. package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
  92. package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
  93. package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
  94. package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
  95. package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
  96. package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
  97. package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
  98. package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
  99. package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
  100. package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
  101. package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
  102. package/opencode/skills/KORTIX-pdf/forms.md +36 -0
  103. package/opencode/skills/KORTIX-pdf/reference.md +105 -0
  104. package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
  105. package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
  106. package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
  107. package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
  108. package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
  109. package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
  110. package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
  111. package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  112. package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
  113. package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
  114. package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
  115. package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
  116. package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
  117. package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
  118. package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
  119. package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
  120. package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
  121. package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
  122. package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
  123. package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
  124. package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
  125. package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
  126. package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
  127. package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
  128. package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
  129. package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
  130. package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
  131. package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
  132. package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
  133. package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
  134. package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
  135. package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
  136. package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
  137. package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
  138. package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
  139. package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
  140. package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
  141. package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
  142. package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
  143. package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
  144. package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
  145. package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
  146. package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
  147. package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
  148. package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
  149. package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
  150. package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
  151. package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
  152. package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
  153. package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
  154. package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
  155. package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
  156. package/opencode/skills/KORTIX-session-search/Untitled +1 -0
  157. package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
  158. package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
  159. package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
  160. package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
  161. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
  162. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
  163. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  164. package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
  165. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  166. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  167. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  168. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  169. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  170. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  171. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  172. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  173. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  174. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  175. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  176. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  177. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  178. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  179. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  180. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  181. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  182. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  183. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  184. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  185. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  186. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  187. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  188. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  189. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  190. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  191. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  192. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  193. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  194. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  195. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  196. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  197. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  198. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  199. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  200. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  201. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  202. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  203. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  204. package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
  205. package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
  206. package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
  207. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
  208. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
  209. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
  210. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
  211. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
  212. package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
  213. package/opencode/tools/image-gen.ts +342 -0
  214. package/opencode/tools/image-search.ts +190 -0
  215. package/opencode/tools/memory-get.ts +168 -0
  216. package/opencode/tools/memory-search.ts +247 -0
  217. package/opencode/tools/presentation-gen.ts +723 -0
  218. package/opencode/tools/scrape-webpage.ts +115 -0
  219. package/opencode/tools/scripts/.python-version +1 -0
  220. package/opencode/tools/scripts/convert_pdf.py +184 -0
  221. package/opencode/tools/scripts/convert_pptx.py +562 -0
  222. package/opencode/tools/scripts/pyproject.toml +11 -0
  223. package/opencode/tools/scripts/uv.lock +287 -0
  224. package/opencode/tools/scripts/validate_slide.py +74 -0
  225. package/opencode/tools/show-user.ts +217 -0
  226. package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
  227. package/opencode/tools/tests/image-gen.test.ts +215 -0
  228. package/opencode/tools/tests/image-search.test.ts +125 -0
  229. package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
  230. package/opencode/tools/tests/presentation-gen.test.ts +389 -0
  231. package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
  232. package/opencode/tools/tests/show-user.test.ts +241 -0
  233. package/opencode/tools/tests/video-gen.test.ts +110 -0
  234. package/opencode/tools/tests/web-search.test.ts +106 -0
  235. package/opencode/tools/video-gen.ts +200 -0
  236. package/opencode/tools/web-search.ts +153 -0
  237. package/opencode/tsconfig.json +29 -0
  238. package/package.json +36 -0
  239. package/patch-agent-browser.js +100 -0
  240. package/postinstall.sh +88 -0
  241. package/services/KORTIX-presentation-viewer/run +37 -0
  242. package/services/agent-browser-viewer/run +48 -0
  243. package/services/kortix-master/run +16 -0
  244. package/services/lss-sync/run +22 -0
  245. package/services/opencode-serve/run +25 -0
  246. package/services/opencode-web/run +21 -0
@@ -0,0 +1,110 @@
1
+ import { readFileSync, existsSync, rmSync } from "fs";
2
+ import { resolve } from "path";
3
+
4
+ const envPath = resolve(import.meta.dir, "../../.env");
5
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
6
+ const trimmed = line.trim();
7
+ if (!trimmed || trimmed.startsWith("#")) continue;
8
+ const eq = trimmed.indexOf("=");
9
+ if (eq > 0) process.env[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
10
+ }
11
+
12
+ const OUTPUT_DIR = resolve(import.meta.dir, "test-output-video");
13
+
14
+ async function test(name: string, fn: () => Promise<void>) {
15
+ process.stdout.write(`\n=== ${name} ===\n`);
16
+ try {
17
+ await fn();
18
+ process.stdout.write(`PASS\n`);
19
+ } catch (e) {
20
+ process.stdout.write(`FAIL: ${e}\n`);
21
+ process.exit(1);
22
+ }
23
+ }
24
+
25
+ function assert(cond: boolean, msg: string) {
26
+ if (!cond) throw new Error(msg);
27
+ }
28
+
29
+ const videoGen = (await import("../video-gen.ts")).default;
30
+
31
+ const ctx = { directory: process.cwd(), worktree: process.cwd() } as Parameters<
32
+ typeof videoGen.execute
33
+ >[1];
34
+
35
+ // ── Validation Tests (no API calls) ──
36
+
37
+ await test("video_gen: missing API key", async () => {
38
+ const saved = process.env.REPLICATE_API_TOKEN;
39
+ delete process.env.REPLICATE_API_TOKEN;
40
+ const raw = await videoGen.execute({ prompt: "test" }, ctx);
41
+ process.env.REPLICATE_API_TOKEN = saved;
42
+ assert(
43
+ (raw as string).includes("REPLICATE_API_TOKEN"),
44
+ "should mention missing key",
45
+ );
46
+ });
47
+
48
+ await test("video_gen: missing prompt", async () => {
49
+ const raw = await videoGen.execute({} as any, ctx);
50
+ assert((raw as string).includes("prompt"), "should require prompt");
51
+ });
52
+
53
+ await test("video_gen: nonexistent input image", async () => {
54
+ const raw = await videoGen.execute(
55
+ {
56
+ prompt: "animate this",
57
+ image_path: "/tmp/does-not-exist-99999.png",
58
+ output_dir: OUTPUT_DIR,
59
+ },
60
+ ctx,
61
+ );
62
+ const result = JSON.parse(raw as string);
63
+ assert(result.success === false, "should fail");
64
+ assert(result.error.includes("not found"), "should mention file not found");
65
+ });
66
+
67
+ // ── Live API Test: text-to-video ──
68
+
69
+ await test("video_gen: text-to-video", async () => {
70
+ process.stdout.write(" generating video (this takes 1-3 min)...\n");
71
+ const raw = await videoGen.execute(
72
+ {
73
+ prompt:
74
+ "a slow zoom into a single red rose on a dark background, cinematic",
75
+ duration: 5,
76
+ aspect_ratio: "1:1",
77
+ fps: 24,
78
+ output_dir: OUTPUT_DIR,
79
+ },
80
+ ctx,
81
+ );
82
+ const result = JSON.parse(raw as string);
83
+ assert(result.success === true, `expected success: ${raw}`);
84
+ assert(typeof result.output_path === "string", "should have output_path");
85
+ assert(result.output_path.endsWith(".mp4"), "should be .mp4");
86
+ assert(
87
+ existsSync(result.output_path),
88
+ `file should exist: ${result.output_path}`,
89
+ );
90
+ assert(result.mode === "text-to-video", "should be text-to-video mode");
91
+ assert(result.duration === 5, "duration should be 5");
92
+ assert(
93
+ result.file_size_bytes > 10000,
94
+ `file too small: ${result.file_size_bytes}`,
95
+ );
96
+
97
+ process.stdout.write(
98
+ ` saved: ${result.output_path} (${(result.file_size_bytes / 1024 / 1024).toFixed(1)} MB)\n`,
99
+ );
100
+ });
101
+
102
+ // ── Cleanup (skip with --keep-output) ──
103
+
104
+ if (!process.argv.includes("--keep-output")) {
105
+ try {
106
+ rmSync(OUTPUT_DIR, { recursive: true, force: true });
107
+ } catch {}
108
+ }
109
+
110
+ process.stdout.write("\n\nAll video-gen tests passed!\n");
@@ -0,0 +1,106 @@
1
+ import { readFileSync } from "fs";
2
+ import { resolve } from "path";
3
+
4
+ const envPath = resolve(import.meta.dir, "../../.env");
5
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
6
+ const trimmed = line.trim();
7
+ if (!trimmed || trimmed.startsWith("#")) continue;
8
+ const eq = trimmed.indexOf("=");
9
+ if (eq > 0) process.env[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
10
+ }
11
+
12
+ async function test(name: string, fn: () => Promise<void>) {
13
+ process.stdout.write(`\n=== ${name} ===\n`);
14
+ try {
15
+ await fn();
16
+ process.stdout.write(`PASS\n`);
17
+ } catch (e) {
18
+ process.stdout.write(`FAIL: ${e}\n`);
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ function assert(cond: boolean, msg: string) {
24
+ if (!cond) throw new Error(msg);
25
+ }
26
+
27
+ const webSearch = (await import("../web-search.ts")).default;
28
+
29
+ const ctx = { directory: process.cwd(), worktree: process.cwd() } as Parameters<
30
+ typeof webSearch.execute
31
+ >[1];
32
+
33
+ await test("web_search: single query", async () => {
34
+ const raw = await webSearch.execute(
35
+ { query: "OpenCode AI coding agent" },
36
+ ctx,
37
+ );
38
+ const result = JSON.parse(raw as string);
39
+ assert(
40
+ result.success === true,
41
+ `expected success, got: ${JSON.stringify(result).slice(0, 200)}`,
42
+ );
43
+ assert(Array.isArray(result.results), "results should be array");
44
+ assert(result.results.length > 0, "should have results");
45
+ assert(
46
+ typeof result.results[0].title === "string",
47
+ "result should have title",
48
+ );
49
+ assert(typeof result.results[0].url === "string", "result should have url");
50
+ assert(
51
+ typeof result.results[0].score === "number",
52
+ "result should have score",
53
+ );
54
+ assert(typeof result.answer === "string", "should have answer");
55
+ process.stdout.write(
56
+ ` ${result.results.length} results, answer: ${(result.answer as string).slice(0, 80)}...\n`,
57
+ );
58
+ });
59
+
60
+ await test("web_search: batch query", async () => {
61
+ const raw = await webSearch.execute(
62
+ {
63
+ query: "TypeScript 2025 ||| Bun runtime",
64
+ },
65
+ ctx,
66
+ );
67
+ const result = JSON.parse(raw as string);
68
+ assert(result.batch_mode === true, "should be batch mode");
69
+ assert(
70
+ result.results.length === 2,
71
+ `expected 2 results, got ${result.results.length}`,
72
+ );
73
+ assert(result.results[0].query === "TypeScript 2025", "first query mismatch");
74
+ assert(result.results[1].query === "Bun runtime", "second query mismatch");
75
+ process.stdout.write(
76
+ ` query1: ${result.results[0].results.length} results, query2: ${result.results[1].results.length} results\n`,
77
+ );
78
+ });
79
+
80
+ await test("web_search: topic param", async () => {
81
+ const raw = await webSearch.execute(
82
+ {
83
+ query: "latest AI news",
84
+ topic: "news",
85
+ num_results: 3,
86
+ },
87
+ ctx,
88
+ );
89
+ const result = JSON.parse(raw as string);
90
+ assert(result.success === true, "news search should succeed");
91
+ assert(result.results.length > 0, "should have news results");
92
+ process.stdout.write(` ${result.results.length} news results\n`);
93
+ });
94
+
95
+ await test("web_search: missing API key", async () => {
96
+ const saved = process.env.TAVILY_API_KEY;
97
+ delete process.env.TAVILY_API_KEY;
98
+ const raw = await webSearch.execute({ query: "test" }, ctx);
99
+ process.env.TAVILY_API_KEY = saved;
100
+ assert(
101
+ (raw as string).includes("TAVILY_API_KEY"),
102
+ "should mention missing key",
103
+ );
104
+ });
105
+
106
+ process.stdout.write("\n\nAll web-search tests passed!\n");
@@ -0,0 +1,200 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import Replicate from "replicate";
3
+ import { writeFileSync, mkdirSync, readFileSync, existsSync } from "fs";
4
+ import { resolve, extname, dirname } from "path";
5
+
6
+ const VIDEO_MODEL = "bytedance/seedance-1.5-pro";
7
+
8
+ interface FileOutput {
9
+ url(): string | URL;
10
+ blob(): Promise<Blob>;
11
+ }
12
+
13
+ function ensureDir(filePath: string): void {
14
+ mkdirSync(dirname(filePath), { recursive: true });
15
+ }
16
+
17
+ function slugify(text: string): string {
18
+ return text
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, "-")
21
+ .replace(/^-|-$/g, "")
22
+ .slice(0, 50);
23
+ }
24
+
25
+ function loadImageAsBase64(imagePath: string): string {
26
+ const abs = resolve(imagePath);
27
+ if (!existsSync(abs)) throw new Error(`Image not found: ${abs}`);
28
+ const bytes = readFileSync(abs);
29
+ const ext = extname(abs).toLowerCase();
30
+ const mimeMap: Record<string, string> = {
31
+ ".png": "image/png",
32
+ ".jpg": "image/jpeg",
33
+ ".jpeg": "image/jpeg",
34
+ ".webp": "image/webp",
35
+ };
36
+ const mime = mimeMap[ext] ?? "image/png";
37
+ return `data:${mime};base64,${bytes.toString("base64")}`;
38
+ }
39
+
40
+ async function outputToBytes(
41
+ output: unknown,
42
+ ): Promise<{ bytes: Buffer; url: string }> {
43
+ if (output && typeof output === "object" && "url" in output) {
44
+ const fo = output as FileOutput;
45
+ const blob = await fo.blob();
46
+ const urlVal = fo.url();
47
+ return {
48
+ bytes: Buffer.from(await blob.arrayBuffer()),
49
+ url: typeof urlVal === "string" ? urlVal : urlVal.toString(),
50
+ };
51
+ }
52
+
53
+ if (output && typeof output === "object" && "read" in output) {
54
+ const readable = output as { read(): Uint8Array };
55
+ return { bytes: Buffer.from(readable.read()), url: "" };
56
+ }
57
+
58
+ if (typeof output === "string" && output.startsWith("http")) {
59
+ const res = await fetch(output, { signal: AbortSignal.timeout(120_000) });
60
+ return { bytes: Buffer.from(await res.arrayBuffer()), url: output };
61
+ }
62
+
63
+ throw new Error("Unexpected model output format");
64
+ }
65
+
66
+ function friendlyError(e: unknown): string {
67
+ const msg = e instanceof Error ? e.message : String(e);
68
+ if (msg.includes("moderation") || msg.includes("safety"))
69
+ return "Content was blocked by the safety filter. Try a different prompt.";
70
+ if (msg.includes("429") || msg.includes("Too Many Requests"))
71
+ return "Rate limited (429). Wait a moment and try again.";
72
+ if (msg.includes("timeout"))
73
+ return "The operation timed out. Video generation can take 1-3 minutes — try again.";
74
+ if (msg.includes("invalid") && msg.includes("image"))
75
+ return "The input image format is not supported. Use PNG, JPEG, or WebP.";
76
+ return msg;
77
+ }
78
+
79
+ export default tool({
80
+ description:
81
+ "Generate videos from text prompts or images using ByteDance Seedance 1.5 Pro via Replicate. " +
82
+ "Supports text-to-video and image-to-video. " +
83
+ "Options: duration (2-12s), aspect ratio, fps, camera fixed mode, audio generation. " +
84
+ "Requires REPLICATE_API_TOKEN. You MUST specify output_dir to control where files are saved.",
85
+ args: {
86
+ prompt: tool.schema
87
+ .string()
88
+ .describe(
89
+ "Text prompt describing the video. Be specific about motion, camera angle, and scene.",
90
+ ),
91
+ image_path: tool.schema
92
+ .string()
93
+ .optional()
94
+ .describe(
95
+ "Path to input image for image-to-video. When provided, the video animates from this image. " +
96
+ "Supports PNG, JPEG, WebP.",
97
+ ),
98
+ duration: tool.schema
99
+ .number()
100
+ .optional()
101
+ .describe("Video duration in seconds (5 or 10). Default: 5"),
102
+ aspect_ratio: tool.schema
103
+ .string()
104
+ .optional()
105
+ .describe("Aspect ratio: '16:9' (default), '9:16', '1:1', '4:3', '3:4'"),
106
+ fps: tool.schema
107
+ .number()
108
+ .optional()
109
+ .describe("Frames per second. Default: 24"),
110
+ camera_fixed: tool.schema
111
+ .boolean()
112
+ .optional()
113
+ .describe("Lock camera in place (no camera motion). Default: false"),
114
+ generate_audio: tool.schema
115
+ .boolean()
116
+ .optional()
117
+ .describe("Generate audio track for the video. Default: false"),
118
+ seed: tool.schema
119
+ .number()
120
+ .optional()
121
+ .describe("Random seed for reproducibility"),
122
+ output_dir: tool.schema
123
+ .string()
124
+ .describe(
125
+ "Output directory where the generated video will be saved. The directory will be created if it doesn't exist. Required.",
126
+ ),
127
+ },
128
+ async execute(args, _context) {
129
+ const token = process.env.REPLICATE_API_TOKEN;
130
+ if (!token) return "Error: REPLICATE_API_TOKEN not set.";
131
+
132
+ if (!args.prompt) return "Error: 'prompt' is required.";
133
+ if (!args.output_dir)
134
+ return "Error: 'output_dir' is required. Specify where to save the output.";
135
+
136
+ const replicate = new Replicate({ auth: token });
137
+ const outputDir = resolve(args.output_dir);
138
+
139
+ const rawDuration = args.duration ?? 5;
140
+ const duration = rawDuration <= 7 ? 5 : 10;
141
+ const aspectRatio = args.aspect_ratio ?? "16:9";
142
+ const fps = args.fps ?? 24;
143
+
144
+ const input: Record<string, unknown> = {
145
+ prompt: args.prompt,
146
+ duration,
147
+ aspect_ratio: aspectRatio,
148
+ fps,
149
+ camera_fixed: args.camera_fixed ?? false,
150
+ generate_audio: args.generate_audio ?? false,
151
+ };
152
+
153
+ if (args.seed !== undefined) input.seed = Math.floor(args.seed);
154
+
155
+ if (args.image_path) {
156
+ try {
157
+ input.image = loadImageAsBase64(args.image_path);
158
+ } catch (e) {
159
+ return JSON.stringify(
160
+ { success: false, error: e instanceof Error ? e.message : String(e) },
161
+ null,
162
+ 2,
163
+ );
164
+ }
165
+ }
166
+
167
+ try {
168
+ const output = await replicate.run(VIDEO_MODEL, { input });
169
+ const { bytes, url } = await outputToBytes(output);
170
+
171
+ const filename = `${slugify(args.prompt)}-${Date.now()}.mp4`;
172
+ const outPath = resolve(outputDir, filename);
173
+ ensureDir(outPath);
174
+ writeFileSync(outPath, bytes);
175
+
176
+ return JSON.stringify(
177
+ {
178
+ success: true,
179
+ output_path: outPath,
180
+ replicate_url: url,
181
+ prompt: args.prompt,
182
+ duration,
183
+ aspect_ratio: aspectRatio,
184
+ fps,
185
+ mode: args.image_path ? "image-to-video" : "text-to-video",
186
+ ...(args.image_path && { input_image: args.image_path }),
187
+ file_size_bytes: bytes.length,
188
+ },
189
+ null,
190
+ 2,
191
+ );
192
+ } catch (e) {
193
+ return JSON.stringify(
194
+ { success: false, error: friendlyError(e) },
195
+ null,
196
+ 2,
197
+ );
198
+ }
199
+ },
200
+ });
@@ -0,0 +1,153 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { tavily } from "@tavily/core";
3
+
4
+ interface SearchResult {
5
+ title: string;
6
+ url: string;
7
+ content: string;
8
+ score: number;
9
+ publishedDate?: string;
10
+ rawContent?: string;
11
+ }
12
+
13
+ interface SearchImage {
14
+ url: string;
15
+ description?: string;
16
+ }
17
+
18
+ interface SearchResponse {
19
+ query: string;
20
+ answer?: string;
21
+ results: SearchResult[];
22
+ images?: SearchImage[];
23
+ responseTime?: number;
24
+ }
25
+
26
+ function formatSingle(query: string, response: SearchResponse): string {
27
+ return JSON.stringify(
28
+ {
29
+ query,
30
+ success: response.results.length > 0 || !!response.answer,
31
+ answer: response.answer ?? "",
32
+ results: response.results.map((r) => ({
33
+ title: r.title,
34
+ url: r.url,
35
+ snippet: r.content,
36
+ score: r.score,
37
+ published_date: r.publishedDate ?? "",
38
+ })),
39
+ images: (response.images ?? []).map((img) => ({
40
+ url: img.url,
41
+ description: img.description ?? "",
42
+ })),
43
+ response_time_ms: response.responseTime,
44
+ },
45
+ null,
46
+ 2,
47
+ );
48
+ }
49
+
50
+ export default tool({
51
+ description:
52
+ "Search the web for up-to-date information using Tavily. " +
53
+ "Returns titles, URLs, snippets, relevance scores, images, and a synthesized AI answer. " +
54
+ "Supports batch queries separated by |||. " +
55
+ "Use topic='news' for current events, topic='finance' for financial data. " +
56
+ "After using results, ALWAYS include a Sources section with markdown hyperlinks.",
57
+ args: {
58
+ query: tool.schema
59
+ .string()
60
+ .describe(
61
+ "Search query. For batch, separate with ||| (e.g. 'query one ||| query two')",
62
+ ),
63
+ num_results: tool.schema
64
+ .number()
65
+ .optional()
66
+ .describe("Results per query (1-20). Default: 5"),
67
+ topic: tool.schema
68
+ .string()
69
+ .optional()
70
+ .describe("Search topic: 'general' (default), 'news', or 'finance'"),
71
+ search_depth: tool.schema
72
+ .string()
73
+ .optional()
74
+ .describe(
75
+ "Search depth: 'basic' (faster, cheaper, default) or 'advanced' (slower, more thorough). Use 'basic' for most queries. Reserve 'advanced' for deep research where comprehensiveness matters.",
76
+ ),
77
+ },
78
+ async execute(args, _context) {
79
+ const apiKey = process.env.TAVILY_API_KEY;
80
+ if (!apiKey) return "Error: TAVILY_API_KEY not set.";
81
+
82
+ const client = tavily({ apiKey });
83
+ const maxResults = Math.max(1, Math.min(args.num_results ?? 5, 20));
84
+ const topic = (args.topic as "general" | "news" | "finance") ?? "general";
85
+
86
+ const queries = args.query
87
+ .split("|||")
88
+ .map((q) => q.trim())
89
+ .filter(Boolean);
90
+ if (queries.length === 0) return "Error: empty query.";
91
+
92
+ const searchOne = async (
93
+ q: string,
94
+ ): Promise<{ query: string; data?: SearchResponse; error?: string }> => {
95
+ try {
96
+ const response = (await client.search(q, {
97
+ searchDepth: (args.search_depth as "basic" | "advanced") || "basic",
98
+ topic,
99
+ maxResults,
100
+ includeAnswer: true,
101
+ includeImages: true,
102
+ includeImageDescriptions: true,
103
+ })) as unknown as SearchResponse;
104
+ return { query: q, data: response };
105
+ } catch (e) {
106
+ return { query: q, error: String(e) };
107
+ }
108
+ };
109
+
110
+ const results = await Promise.all(queries.map(searchOne));
111
+
112
+ if (queries.length === 1) {
113
+ const r = results[0]!;
114
+ if (r.error)
115
+ return JSON.stringify(
116
+ { query: r.query, success: false, error: r.error },
117
+ null,
118
+ 2,
119
+ );
120
+ return formatSingle(r.query, r.data!);
121
+ }
122
+
123
+ return JSON.stringify(
124
+ {
125
+ batch_mode: true,
126
+ total_queries: queries.length,
127
+ results: results.map((r) => {
128
+ if (r.error)
129
+ return { query: r.query, success: false, error: r.error };
130
+ const d = r.data!;
131
+ return {
132
+ query: r.query,
133
+ success: d.results.length > 0 || !!d.answer,
134
+ answer: d.answer ?? "",
135
+ results: d.results.map((res) => ({
136
+ title: res.title,
137
+ url: res.url,
138
+ snippet: res.content,
139
+ score: res.score,
140
+ published_date: res.publishedDate ?? "",
141
+ })),
142
+ images: (d.images ?? []).map((img) => ({
143
+ url: img.url,
144
+ description: img.description ?? "",
145
+ })),
146
+ };
147
+ }),
148
+ },
149
+ null,
150
+ 2,
151
+ );
152
+ },
153
+ });
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@kortix/sandbox",
3
+ "version": "0.4.1",
4
+ "description": "Kortix sandbox runtime — kortix-master, opencode config/agents/skills, and dependencies",
5
+ "private": false,
6
+ "scripts": {
7
+ "postinstall": "bash postinstall.sh"
8
+ },
9
+ "files": [
10
+ "kortix-master/src/",
11
+ "kortix-master/package.json",
12
+ "kortix-master/tsconfig.json",
13
+ "opencode/opencode.jsonc",
14
+ "opencode/ocx.jsonc",
15
+ "opencode/package.json",
16
+ "opencode/tsconfig.json",
17
+ "opencode/agents/",
18
+ "opencode/commands/",
19
+ "opencode/tools/",
20
+ "opencode/skills/",
21
+ "opencode/plugin/",
22
+ "opencode/patches/",
23
+ "opencode/memory.json",
24
+ "services/",
25
+ "config/",
26
+ "postinstall.sh",
27
+ "patch-agent-browser.js"
28
+ ],
29
+ "dependencies": {
30
+ "@kortix/opencode-ai": "0.2.0"
31
+ },
32
+ "peerDependencies": {},
33
+ "publishConfig": {
34
+ "access": "public"
35
+ }
36
+ }