@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,143 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
CONFIG_DIR="/workspace"
|
|
3
|
+
MARKER="$CONFIG_DIR/.heyagi-customized"
|
|
4
|
+
|
|
5
|
+
# ── Always run: ensure agent-browser dirs exist, clean stale locks ──────────
|
|
6
|
+
mkdir -p "$CONFIG_DIR/.agent-browser" "$CONFIG_DIR/.browser-profile"
|
|
7
|
+
rm -f "$CONFIG_DIR/.browser-profile/SingletonLock" \
|
|
8
|
+
"$CONFIG_DIR/.browser-profile/SingletonCookie" \
|
|
9
|
+
"$CONFIG_DIR/.browser-profile/SingletonSocket" 2>/dev/null
|
|
10
|
+
chown -R abc:abc "$CONFIG_DIR/.agent-browser" "$CONFIG_DIR/.browser-profile"
|
|
11
|
+
|
|
12
|
+
if [ -f "$MARKER" ]; then
|
|
13
|
+
echo "[heyagi] Already customized, skipping."
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
echo "[heyagi] Applying desktop customization..."
|
|
18
|
+
mkdir -p "$CONFIG_DIR/.config/autostart"
|
|
19
|
+
mkdir -p "$CONFIG_DIR/.local/share/konsole"
|
|
20
|
+
|
|
21
|
+
# ── Symlink presentations into Desktop for easy access ─────────────────────
|
|
22
|
+
mkdir -p "$CONFIG_DIR/presentations"
|
|
23
|
+
# Create a Desktop directory for KDE and symlink presentations into it
|
|
24
|
+
mkdir -p "$CONFIG_DIR/Desktop"
|
|
25
|
+
ln -sfn "$CONFIG_DIR/presentations" "$CONFIG_DIR/Desktop/presentations"
|
|
26
|
+
|
|
27
|
+
# ── KDE Global: Breeze Dark ────────────────────────────────────────────────
|
|
28
|
+
cat > "$CONFIG_DIR/.config/kdeglobals" << 'EOF'
|
|
29
|
+
[General]
|
|
30
|
+
ColorScheme=BreezeDark
|
|
31
|
+
Name=Breeze Dark
|
|
32
|
+
widgetStyle=Breeze
|
|
33
|
+
|
|
34
|
+
[Icons]
|
|
35
|
+
Theme=breeze-dark
|
|
36
|
+
|
|
37
|
+
[KDE]
|
|
38
|
+
LookAndFeelPackage=org.kde.breezedark.desktop
|
|
39
|
+
widgetStyle=breeze
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
# ── KWin ────────────────────────────────────────────────────────────────────
|
|
43
|
+
cat > "$CONFIG_DIR/.config/kwinrc" << 'EOF'
|
|
44
|
+
[org.kde.kdecoration2]
|
|
45
|
+
theme=Breeze
|
|
46
|
+
library=org.kde.breeze
|
|
47
|
+
|
|
48
|
+
[Windows]
|
|
49
|
+
Placement=Centered
|
|
50
|
+
|
|
51
|
+
[Desktops]
|
|
52
|
+
Number=1
|
|
53
|
+
Rows=1
|
|
54
|
+
EOF
|
|
55
|
+
|
|
56
|
+
# ── Plasma theme ────────────────────────────────────────────────────────────
|
|
57
|
+
cat > "$CONFIG_DIR/.config/plasmarc" << 'EOF'
|
|
58
|
+
[Theme]
|
|
59
|
+
name=breeze-dark
|
|
60
|
+
EOF
|
|
61
|
+
|
|
62
|
+
# ── Konsole dark profile ───────────────────────────────────────────────────
|
|
63
|
+
cat > "$CONFIG_DIR/.local/share/konsole/HeyAGI.profile" << 'EOF'
|
|
64
|
+
[Appearance]
|
|
65
|
+
ColorScheme=Breeze
|
|
66
|
+
Font=Monospace,11,-1,5,50,0,0,0,0,0
|
|
67
|
+
|
|
68
|
+
[General]
|
|
69
|
+
Name=HeyAGI
|
|
70
|
+
Parent=FALLBACK/
|
|
71
|
+
|
|
72
|
+
[Scrolling]
|
|
73
|
+
HistoryMode=2
|
|
74
|
+
EOF
|
|
75
|
+
|
|
76
|
+
cat > "$CONFIG_DIR/.config/konsolerc" << 'EOF'
|
|
77
|
+
[Desktop Entry]
|
|
78
|
+
DefaultProfile=HeyAGI.profile
|
|
79
|
+
|
|
80
|
+
[MainWindow]
|
|
81
|
+
MenuBar=Disabled
|
|
82
|
+
ToolBarsMovable=Disabled
|
|
83
|
+
EOF
|
|
84
|
+
|
|
85
|
+
# ── Autostart: apply wallpaper + launcher icon after KDE session loads ──────
|
|
86
|
+
cat > "$CONFIG_DIR/.config/autostart/heyagi-desktop.desktop" << 'EOF'
|
|
87
|
+
[Desktop Entry]
|
|
88
|
+
Type=Application
|
|
89
|
+
Name=HeyAGI Desktop Setup
|
|
90
|
+
Exec=/usr/share/wallpapers/heyagi/apply-desktop.sh
|
|
91
|
+
X-KDE-autostart-phase=2
|
|
92
|
+
EOF
|
|
93
|
+
|
|
94
|
+
cat > /usr/share/wallpapers/heyagi/apply-desktop.sh << 'SCRIPT'
|
|
95
|
+
#!/bin/bash
|
|
96
|
+
sleep 5
|
|
97
|
+
|
|
98
|
+
plasma-apply-wallpaperimage /usr/share/wallpapers/heyagi/wallpaper.png
|
|
99
|
+
|
|
100
|
+
PLASMA_RC="$HOME/.config/plasma-org.kde.plasma.desktop-appletsrc"
|
|
101
|
+
ICON_PATH="/usr/share/icons/heyagi/kortix-symbol-white.svg"
|
|
102
|
+
|
|
103
|
+
if [ -f "$PLASMA_RC" ]; then
|
|
104
|
+
# Find the kickoff applet's [Configuration][General] section and inject icon
|
|
105
|
+
# Get the containment/applet IDs for kickoff
|
|
106
|
+
KICKOFF_SECTION=$(grep -B3 "plugin=org.kde.plasma.kickoff" "$PLASMA_RC" | grep "^\[Containments\]" | tail -1)
|
|
107
|
+
|
|
108
|
+
if [ -n "$KICKOFF_SECTION" ]; then
|
|
109
|
+
# Build the [Configuration][General] section name
|
|
110
|
+
GENERAL_SECTION="${KICKOFF_SECTION%]}][Configuration][General]"
|
|
111
|
+
|
|
112
|
+
if grep -q "$(echo "$GENERAL_SECTION" | sed 's/\[/\\[/g; s/\]/\\]/g')" "$PLASMA_RC"; then
|
|
113
|
+
# Section exists -- replace or add icon line
|
|
114
|
+
ESCAPED=$(echo "$GENERAL_SECTION" | sed 's/\[/\\[/g; s/\]/\\]/g')
|
|
115
|
+
if grep -A10 "$ESCAPED" "$PLASMA_RC" | grep -q "^icon="; then
|
|
116
|
+
sed -i "/$ESCAPED/,/^\[/{s|^icon=.*|icon=$ICON_PATH|}" "$PLASMA_RC"
|
|
117
|
+
else
|
|
118
|
+
sed -i "/$ESCAPED/a icon=$ICON_PATH" "$PLASMA_RC"
|
|
119
|
+
fi
|
|
120
|
+
else
|
|
121
|
+
# Section doesn't exist -- create it
|
|
122
|
+
CONF_SECTION="${KICKOFF_SECTION%]}][Configuration]"
|
|
123
|
+
ESCAPED_CONF=$(echo "$CONF_SECTION" | sed 's/\[/\\[/g; s/\]/\\]/g')
|
|
124
|
+
sed -i "/$ESCAPED_CONF/,/^\[/{/^\[.*\]/!b;i\\${GENERAL_SECTION}\nicon=$ICON_PATH
|
|
125
|
+
}" "$PLASMA_RC"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Restart plasmashell to pick up icon change
|
|
130
|
+
kquitapp5 plasmashell 2>/dev/null
|
|
131
|
+
sleep 2
|
|
132
|
+
kstart5 plasmashell 2>/dev/null &
|
|
133
|
+
fi
|
|
134
|
+
SCRIPT
|
|
135
|
+
chmod +x /usr/share/wallpapers/heyagi/apply-desktop.sh
|
|
136
|
+
|
|
137
|
+
# ── Fix ownership ──────────────────────────────────────────────────────────
|
|
138
|
+
# Give abc full ownership of everything under /workspace so opencode and its
|
|
139
|
+
# agents can freely create directories and files (presentations, output, etc.)
|
|
140
|
+
chown -R abc:abc "$CONFIG_DIR" 2>/dev/null
|
|
141
|
+
|
|
142
|
+
touch "$MARKER"
|
|
143
|
+
echo "[heyagi] Customization complete."
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/with-contenv bash
|
|
2
|
+
# Kortix environment setup — runs once on container start (s6 cont-init.d)
|
|
3
|
+
#
|
|
4
|
+
# In cloud/production mode, overrides SDK base URLs to route through
|
|
5
|
+
# the Kortix router proxy. In local mode, does nothing.
|
|
6
|
+
|
|
7
|
+
if [ "$ENV_MODE" = "cloud" ] || [ "$ENV_MODE" = "production" ]; then
|
|
8
|
+
echo "[Kortix] Cloud mode — enabling API proxy routing"
|
|
9
|
+
|
|
10
|
+
if [ -z "$KORTIX_API_URL" ]; then
|
|
11
|
+
echo "[Kortix] WARNING: KORTIX_API_URL is empty — LLM calls will fail until it is set via /env API"
|
|
12
|
+
echo "[Kortix] Services will still start; set KORTIX_API_URL later to enable model routing"
|
|
13
|
+
else
|
|
14
|
+
# Write env overrides that s6 services will inherit via with-contenv
|
|
15
|
+
printf '%s' "${KORTIX_API_URL}/tavily" > /var/run/s6/container_environment/TAVILY_API_URL
|
|
16
|
+
printf '%s' "${KORTIX_API_URL}/serper" > /var/run/s6/container_environment/SERPER_API_URL
|
|
17
|
+
printf '%s' "${KORTIX_API_URL}/firecrawl" > /var/run/s6/container_environment/FIRECRAWL_API_URL
|
|
18
|
+
printf '%s' "${KORTIX_API_URL}/replicate" > /var/run/s6/container_environment/REPLICATE_API_URL
|
|
19
|
+
printf '%s' "${KORTIX_API_URL}/context7" > /var/run/s6/container_environment/CONTEXT7_API_URL
|
|
20
|
+
|
|
21
|
+
echo "[Kortix] SDK URLs routed through ${KORTIX_API_URL}"
|
|
22
|
+
fi
|
|
23
|
+
else
|
|
24
|
+
echo "[Kortix] Local mode — proxy routing disabled"
|
|
25
|
+
fi
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kortix/sandbox-master",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bun run --watch src/index.ts",
|
|
7
|
+
"start": "bun run src/index.ts",
|
|
8
|
+
"test": "bun test",
|
|
9
|
+
"test:unit": "bun test tests/unit/",
|
|
10
|
+
"test:integration": "bun test tests/integration/",
|
|
11
|
+
"test:e2e": "bun test tests/e2e/",
|
|
12
|
+
"test:coverage": "bun test --coverage"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"hono": "^4.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"bun-types": "^1.0.0",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"@types/node": "^20.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const config = {
|
|
2
|
+
// Kortix Master port (main entry point)
|
|
3
|
+
PORT: parseInt(process.env.KORTIX_MASTER_PORT || '8000'),
|
|
4
|
+
|
|
5
|
+
// OpenCode server (proxied)
|
|
6
|
+
OPENCODE_HOST: process.env.OPENCODE_HOST || 'localhost',
|
|
7
|
+
OPENCODE_PORT: parseInt(process.env.OPENCODE_PORT || '4096'),
|
|
8
|
+
OPENCODE_USERNAME: process.env.OPENCODE_SERVER_USERNAME || '',
|
|
9
|
+
OPENCODE_PASSWORD: process.env.OPENCODE_SERVER_PASSWORD || '',
|
|
10
|
+
|
|
11
|
+
// Kortix backend
|
|
12
|
+
KORTIX_API_URL: process.env.KORTIX_API_URL || 'https://api.kortix.ai',
|
|
13
|
+
KORTIX_TOKEN: process.env.KORTIX_TOKEN || '',
|
|
14
|
+
|
|
15
|
+
// Secret storage
|
|
16
|
+
SECRET_FILE_PATH: process.env.SECRET_FILE_PATH || '/app/secrets/.secrets.json',
|
|
17
|
+
SALT_FILE_PATH: process.env.SALT_FILE_PATH || '/app/secrets/.salt',
|
|
18
|
+
|
|
19
|
+
// Sandbox metadata
|
|
20
|
+
SANDBOX_ID: process.env.SANDBOX_ID || '',
|
|
21
|
+
PROJECT_ID: process.env.PROJECT_ID || '',
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { cors } from 'hono/cors'
|
|
3
|
+
import { logger } from 'hono/logger'
|
|
4
|
+
import { proxyToOpenCode } from './services/proxy'
|
|
5
|
+
import { SecretStore } from './services/secret-store'
|
|
6
|
+
import envRouter from './routes/env'
|
|
7
|
+
import proxyRouter from './routes/proxy'
|
|
8
|
+
import updateRouter from './routes/update'
|
|
9
|
+
import { config } from './config'
|
|
10
|
+
|
|
11
|
+
const app = new Hono()
|
|
12
|
+
|
|
13
|
+
// Initialize secret store and load ENV variables
|
|
14
|
+
const secretStore = new SecretStore()
|
|
15
|
+
await secretStore.loadIntoProcessEnv()
|
|
16
|
+
|
|
17
|
+
// Global middleware
|
|
18
|
+
app.use('*', logger())
|
|
19
|
+
app.use('*', cors())
|
|
20
|
+
|
|
21
|
+
// Health check
|
|
22
|
+
app.get('/kortix/health', (c) => c.json({ status: 'ok' }))
|
|
23
|
+
|
|
24
|
+
// Update check — /kortix/update and /kortix/update/status
|
|
25
|
+
app.route('/kortix/update', updateRouter)
|
|
26
|
+
|
|
27
|
+
// ENV management routes
|
|
28
|
+
app.route('/env', envRouter)
|
|
29
|
+
|
|
30
|
+
// Dynamic port proxy — /proxy/:port/* forwards to localhost:{port} inside the sandbox
|
|
31
|
+
app.route('/proxy', proxyRouter)
|
|
32
|
+
|
|
33
|
+
// Proxy all other requests to OpenCode
|
|
34
|
+
app.all('*', async (c) => {
|
|
35
|
+
return proxyToOpenCode(c)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
console.log(`[Kortix Master] Starting on port ${config.PORT}`)
|
|
39
|
+
console.log(`[Kortix Master] Proxying to OpenCode at ${config.OPENCODE_HOST}:${config.OPENCODE_PORT}`)
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
port: config.PORT,
|
|
43
|
+
fetch: app.fetch,
|
|
44
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { SecretStore } from '../services/secret-store'
|
|
3
|
+
|
|
4
|
+
const envRouter = new Hono()
|
|
5
|
+
const secretStore = new SecretStore()
|
|
6
|
+
|
|
7
|
+
// GET /env - list all ENV vars
|
|
8
|
+
envRouter.get('/', async (c) => {
|
|
9
|
+
try {
|
|
10
|
+
const envVars = await secretStore.getAll()
|
|
11
|
+
return c.json(envVars)
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('[ENV API] Error listing environment variables:', error)
|
|
14
|
+
return c.json({ error: 'Failed to list environment variables' }, 500)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// GET /env/:key - get specific ENV var
|
|
19
|
+
envRouter.get('/:key', async (c) => {
|
|
20
|
+
try {
|
|
21
|
+
const key = c.req.param('key')
|
|
22
|
+
const value = await secretStore.get(key)
|
|
23
|
+
if (value === null) {
|
|
24
|
+
return c.json({ error: 'Environment variable not found' }, 404)
|
|
25
|
+
}
|
|
26
|
+
return c.json({ [key]: value })
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('[ENV API] Error getting environment variable:', error)
|
|
29
|
+
return c.json({ error: 'Failed to get environment variable' }, 500)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// POST /env/:key - set ENV var
|
|
34
|
+
envRouter.post('/:key', async (c) => {
|
|
35
|
+
try {
|
|
36
|
+
const key = c.req.param('key')
|
|
37
|
+
const body = await c.req.json()
|
|
38
|
+
|
|
39
|
+
if (!body || typeof body.value !== 'string') {
|
|
40
|
+
return c.json({ error: 'Request body must contain a "value" field with string value' }, 400)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await secretStore.setEnv(key, body.value)
|
|
44
|
+
console.log(`[ENV API] Set environment variable: ${key}`)
|
|
45
|
+
return c.json({ message: 'Environment variable set', key, value: body.value })
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('[ENV API] Error setting environment variable:', error)
|
|
48
|
+
return c.json({ error: 'Failed to set environment variable' }, 500)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// DELETE /env/:key - delete ENV var
|
|
53
|
+
envRouter.delete('/:key', async (c) => {
|
|
54
|
+
try {
|
|
55
|
+
const key = c.req.param('key')
|
|
56
|
+
await secretStore.deleteEnv(key)
|
|
57
|
+
console.log(`[ENV API] Deleted environment variable: ${key}`)
|
|
58
|
+
return c.json({ message: 'Environment variable deleted', key })
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('[ENV API] Error deleting environment variable:', error)
|
|
61
|
+
return c.json({ error: 'Failed to delete environment variable' }, 500)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export default envRouter
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { config } from '../config'
|
|
3
|
+
|
|
4
|
+
const proxyRouter = new Hono()
|
|
5
|
+
|
|
6
|
+
// Blocked ports — prevent proxying to kortix-master itself or other sensitive services
|
|
7
|
+
const BLOCKED_PORTS = new Set([
|
|
8
|
+
config.PORT, // kortix-master itself (default 8000)
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dynamic port proxy: /proxy/:port/*
|
|
13
|
+
*
|
|
14
|
+
* Proxies HTTP requests to any localhost port inside the sandbox container.
|
|
15
|
+
* This enables the frontend to access any service the agent starts
|
|
16
|
+
* (e.g. dev servers on port 3000, 8080, 5173, etc.) without needing
|
|
17
|
+
* those ports individually exposed in docker-compose.
|
|
18
|
+
*
|
|
19
|
+
* In cloud mode, the frontend accesses this via:
|
|
20
|
+
* https://kortix.cloud/{sandboxId}/8000/proxy/{port}/{path}
|
|
21
|
+
*
|
|
22
|
+
* In local mode:
|
|
23
|
+
* http://localhost:8000/proxy/{port}/{path}
|
|
24
|
+
*/
|
|
25
|
+
proxyRouter.all('/:port{[0-9]+}/*', async (c) => {
|
|
26
|
+
const portStr = c.req.param('port')
|
|
27
|
+
const port = parseInt(portStr, 10)
|
|
28
|
+
|
|
29
|
+
// Validate port
|
|
30
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
31
|
+
return c.json({ error: 'Invalid port number', port: portStr }, 400)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (BLOCKED_PORTS.has(port)) {
|
|
35
|
+
return c.json({ error: 'Port is blocked', port }, 403)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Extract the path after /proxy/:port/
|
|
39
|
+
const url = new URL(c.req.url)
|
|
40
|
+
const prefix = `/proxy/${portStr}`
|
|
41
|
+
const remainingPath = url.pathname.slice(prefix.length) || '/'
|
|
42
|
+
const targetUrl = `http://localhost:${port}${remainingPath}${url.search}`
|
|
43
|
+
|
|
44
|
+
// Build headers, stripping Host (it would be wrong for the upstream)
|
|
45
|
+
const headers = new Headers()
|
|
46
|
+
for (const [key, value] of c.req.raw.headers.entries()) {
|
|
47
|
+
const lower = key.toLowerCase()
|
|
48
|
+
if (lower === 'host' || lower === 'authorization') continue
|
|
49
|
+
headers.set(key, value)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Set correct Host for the upstream service
|
|
53
|
+
headers.set('Host', `localhost:${port}`)
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(targetUrl, {
|
|
57
|
+
method: c.req.method,
|
|
58
|
+
headers,
|
|
59
|
+
body: c.req.method !== 'GET' && c.req.method !== 'HEAD'
|
|
60
|
+
? await c.req.raw.clone().arrayBuffer()
|
|
61
|
+
: undefined,
|
|
62
|
+
// @ts-ignore - Bun supports duplex
|
|
63
|
+
duplex: 'half',
|
|
64
|
+
redirect: 'manual',
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Rewrite Location headers so redirects go through the proxy too
|
|
68
|
+
const responseHeaders = new Headers(response.headers)
|
|
69
|
+
const location = responseHeaders.get('location')
|
|
70
|
+
if (location) {
|
|
71
|
+
try {
|
|
72
|
+
const locUrl = new URL(location, `http://localhost:${port}`)
|
|
73
|
+
// Only rewrite if the redirect target is the same localhost port
|
|
74
|
+
if (locUrl.hostname === 'localhost' && parseInt(locUrl.port || '80') === port) {
|
|
75
|
+
responseHeaders.set('location', `${prefix}${locUrl.pathname}${locUrl.search}`)
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// Leave location as-is if we can't parse it
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new Response(response.body, {
|
|
83
|
+
status: response.status,
|
|
84
|
+
statusText: response.statusText,
|
|
85
|
+
headers: responseHeaders,
|
|
86
|
+
})
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`[Kortix Master] Port proxy error (port ${port}):`, error)
|
|
89
|
+
return c.json(
|
|
90
|
+
{
|
|
91
|
+
error: 'Failed to connect to service',
|
|
92
|
+
port,
|
|
93
|
+
details: String(error),
|
|
94
|
+
hint: `No service appears to be running on port ${port} inside the sandbox.`,
|
|
95
|
+
},
|
|
96
|
+
502
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Handle bare /proxy/:port (no trailing path) — redirect to /proxy/:port/
|
|
102
|
+
proxyRouter.all('/:port{[0-9]+}', async (c) => {
|
|
103
|
+
const portStr = c.req.param('port')
|
|
104
|
+
const url = new URL(c.req.url)
|
|
105
|
+
return c.redirect(`/proxy/${portStr}/${url.search}`, 301)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
export default proxyRouter
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { config } from '../config';
|
|
3
|
+
|
|
4
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
interface LocalVersion {
|
|
7
|
+
version: string;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const VERSION_FILE = '/opt/kortix/.version';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Services to restart after update. Order matters:
|
|
17
|
+
* - opencode first (depends on /opt/opencode/)
|
|
18
|
+
* - then other services
|
|
19
|
+
* - kortix-master LAST (deferred — it's us)
|
|
20
|
+
*/
|
|
21
|
+
const SERVICES_TO_RESTART = [
|
|
22
|
+
'opencode-serve',
|
|
23
|
+
'opencode-web',
|
|
24
|
+
'lss-sync',
|
|
25
|
+
'agent-browser-viewer',
|
|
26
|
+
'KORTIX-presentation-viewer',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// ─── State ──────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
let updateInProgress = false;
|
|
32
|
+
|
|
33
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
async function readLocalVersion(): Promise<LocalVersion> {
|
|
36
|
+
try {
|
|
37
|
+
const file = Bun.file(VERSION_FILE);
|
|
38
|
+
if (await file.exists()) {
|
|
39
|
+
return await file.json();
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error('[Update] Failed to read version file:', e);
|
|
43
|
+
}
|
|
44
|
+
return { version: '0.0.0', updatedAt: '' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function fetchLatestVersion(): Promise<string | null> {
|
|
48
|
+
const url = `${config.KORTIX_API_URL}/v1/sandbox/version`;
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(url, {
|
|
51
|
+
headers: { 'Accept': 'application/json' },
|
|
52
|
+
signal: AbortSignal.timeout(10_000),
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
console.error(`[Update] Version fetch failed: ${res.status}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const data = await res.json() as { version: string };
|
|
59
|
+
return data.version;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error('[Update] Failed to fetch version:', e);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function run(cmd: string): Promise<{ ok: boolean; output: string }> {
|
|
67
|
+
try {
|
|
68
|
+
const proc = Bun.spawn(['bash', '-c', cmd], {
|
|
69
|
+
stdout: 'pipe',
|
|
70
|
+
stderr: 'pipe',
|
|
71
|
+
env: { ...process.env, HOME: '/workspace' },
|
|
72
|
+
});
|
|
73
|
+
const [stdout, stderr] = await Promise.all([
|
|
74
|
+
new Response(proc.stdout).text(),
|
|
75
|
+
new Response(proc.stderr).text(),
|
|
76
|
+
]);
|
|
77
|
+
const exitCode = await proc.exited;
|
|
78
|
+
return { ok: exitCode === 0, output: (stdout + '\n' + stderr).trim() };
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return { ok: false, output: String(e) };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function restartService(name: string): Promise<void> {
|
|
85
|
+
await run(`s6-svc -r /var/run/s6/services/${name} 2>/dev/null || s6-svc -r /etc/services.d/${name} 2>/dev/null || true`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function performUpdate(targetVersion: string): Promise<{
|
|
89
|
+
success: boolean;
|
|
90
|
+
output: string;
|
|
91
|
+
}> {
|
|
92
|
+
console.log(`[Update] Installing @kortix/sandbox@${targetVersion}...`);
|
|
93
|
+
|
|
94
|
+
const result = await run(`npm install -g @kortix/sandbox@${targetVersion} 2>&1`);
|
|
95
|
+
|
|
96
|
+
if (!result.ok) {
|
|
97
|
+
console.error('[Update] npm install failed:', result.output);
|
|
98
|
+
return { success: false, output: result.output.slice(0, 1000) };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log('[Update] Install complete, restarting services...');
|
|
102
|
+
|
|
103
|
+
for (const svc of SERVICES_TO_RESTART) {
|
|
104
|
+
console.log(`[Update] Restarting: ${svc}`);
|
|
105
|
+
await restartService(svc);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Self-restart deferred so the HTTP response completes
|
|
109
|
+
console.log('[Update] Scheduling kortix-master restart in 2s...');
|
|
110
|
+
setTimeout(() => restartService('kortix-master'), 2000);
|
|
111
|
+
|
|
112
|
+
return { success: true, output: result.output.slice(0, 1000) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Routes ─────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
const updateRouter = new Hono();
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* GET /kortix/update/status
|
|
121
|
+
*
|
|
122
|
+
* Returns current sandbox version + latest available version.
|
|
123
|
+
* Frontend uses this to decide whether to show "Update available".
|
|
124
|
+
* Read-only — does NOT trigger any update.
|
|
125
|
+
*/
|
|
126
|
+
updateRouter.get('/status', async (c) => {
|
|
127
|
+
const local = await readLocalVersion();
|
|
128
|
+
const latest = await fetchLatestVersion();
|
|
129
|
+
|
|
130
|
+
return c.json({
|
|
131
|
+
currentVersion: local.version,
|
|
132
|
+
latestVersion: latest || 'unknown',
|
|
133
|
+
updateAvailable: latest ? local.version !== latest : false,
|
|
134
|
+
updatedAt: local.updatedAt,
|
|
135
|
+
updateInProgress,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* POST /kortix/update
|
|
141
|
+
*
|
|
142
|
+
* User-triggered update. Fetches latest version, installs the package,
|
|
143
|
+
* restarts services. Only runs when explicitly called.
|
|
144
|
+
*/
|
|
145
|
+
updateRouter.post('/', async (c) => {
|
|
146
|
+
if (updateInProgress) {
|
|
147
|
+
return c.json({ error: 'Update already in progress' }, 409);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
updateInProgress = true;
|
|
151
|
+
try {
|
|
152
|
+
const local = await readLocalVersion();
|
|
153
|
+
const latestVersion = await fetchLatestVersion();
|
|
154
|
+
|
|
155
|
+
if (!latestVersion) {
|
|
156
|
+
return c.json({ error: 'Could not reach version service' }, 502);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (local.version === latestVersion) {
|
|
160
|
+
return c.json({
|
|
161
|
+
upToDate: true,
|
|
162
|
+
currentVersion: local.version,
|
|
163
|
+
latestVersion,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`[Update] User triggered: ${local.version} -> ${latestVersion}`);
|
|
168
|
+
const update = await performUpdate(latestVersion);
|
|
169
|
+
|
|
170
|
+
return c.json({
|
|
171
|
+
success: update.success,
|
|
172
|
+
previousVersion: local.version,
|
|
173
|
+
currentVersion: update.success ? latestVersion : local.version,
|
|
174
|
+
latestVersion,
|
|
175
|
+
output: update.output,
|
|
176
|
+
});
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.error('[Update] Error:', e);
|
|
179
|
+
return c.json({ error: 'Update failed', details: String(e) }, 500);
|
|
180
|
+
} finally {
|
|
181
|
+
updateInProgress = false;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export default updateRouter;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Context } from 'hono'
|
|
2
|
+
import { config } from '../config'
|
|
3
|
+
|
|
4
|
+
export async function proxyToOpenCode(c: Context): Promise<Response> {
|
|
5
|
+
const url = new URL(c.req.url)
|
|
6
|
+
const targetUrl = `http://${config.OPENCODE_HOST}:${config.OPENCODE_PORT}${url.pathname}${url.search}`
|
|
7
|
+
|
|
8
|
+
// Build headers, forwarding most but not Host
|
|
9
|
+
const headers = new Headers()
|
|
10
|
+
for (const [key, value] of c.req.raw.headers.entries()) {
|
|
11
|
+
if (key.toLowerCase() !== 'host') {
|
|
12
|
+
headers.set(key, value)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Add OpenCode basic auth if configured
|
|
17
|
+
if (config.OPENCODE_USERNAME && config.OPENCODE_PASSWORD) {
|
|
18
|
+
const auth = Buffer.from(`${config.OPENCODE_USERNAME}:${config.OPENCODE_PASSWORD}`).toString('base64')
|
|
19
|
+
headers.set('Authorization', `Basic ${auth}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(targetUrl, {
|
|
24
|
+
method: c.req.method,
|
|
25
|
+
headers,
|
|
26
|
+
body: c.req.method !== 'GET' && c.req.method !== 'HEAD'
|
|
27
|
+
? await c.req.raw.clone().arrayBuffer()
|
|
28
|
+
: undefined,
|
|
29
|
+
// @ts-ignore - Bun supports duplex
|
|
30
|
+
duplex: 'half',
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Return proxied response with all headers
|
|
34
|
+
return new Response(response.body, {
|
|
35
|
+
status: response.status,
|
|
36
|
+
statusText: response.statusText,
|
|
37
|
+
headers: response.headers,
|
|
38
|
+
})
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('[Kortix Master] Proxy error:', error)
|
|
41
|
+
return c.json({ error: 'Failed to proxy to OpenCode', details: String(error) }, 502)
|
|
42
|
+
}
|
|
43
|
+
}
|